问题1: 什么是搜索?

搜索,是一个动态的,收集信息,分析信息,保存信息的循环过程。在循环的过程中,我们根据已知的信息,对探索方向进行调整。根据选择探索方向的策略,我们将搜索大致划分为“广度优先搜索”(Breadth-First Search,简称BFS)和“深度优先搜索”(Depth-First Search,简称DFS),而本文主要介绍关于广度优先搜索(BFS)的相关知识和刷题总结。

问题2:什么是广度优先搜索?

广度优先搜索,是搜索的一种策略,我们从某一个位置出发,仅向当前所能到达的位置迈进,之后我们再从这些可以到达的位置出发,继续向下一个能到达的位置迈进。这样“由一个点向周围一圈一圈地扩散搜索”的过程,宛如一颗石头砸入一片平静的湖水,激起一圈一圈向外扩散的波浪一般,波浪所及之处,就是我们到达之处,因此得名“广度”。生活当中,我们也经常能“应用”广度优先搜索。比如我们想要找到某个人,必定先询问我们周围的人是否有认识ta的人存在,然后周围的人再继续问他们周围的人是否有认识ta的人存在…这样从身边开始一圈一圈向外扩散地寻找答案,正是广度优先搜索。

更为书面地讲,广度优先搜索的过程,就是判断哪些位置可以迈进,并记录下来作为下次出发的候选位置,并在此过程中判断是否找到了答案的过程。

问题3:为什么需要广度优先搜索?

我们来想象一个场景:我们想找一个人,且除了问朋友没有别的手段(排除查户口,查社交账号,查监控摄像头等手段),如何在最短的时间内找到ta呢?答案是:我先问一圈自己的朋友,看看有没有认识这个人的,如果没有,那就拜托我的朋友们再去问问他们的朋友,并且我会告诉我的朋友我已经问过哪些人了,这样我的朋友们就不会再彼此询问浪费时间了。人多力量大,几轮询问下来,很快我们就能知道结果,要么是找到了这个人,要么就是大家都不认识这个人。这样的搜索方式,肯定比我一个接一个朋友的“击鼓传花”式的寻找要快的多。而这,就是广度优先搜索的优势,它能从一个点开始以最快的速度向外扩散,且到达的区域会越来越广,获得的信息量也越来越多,就更容易找到问题的答案。

书面地讲,广度优先搜索解决的是“无权图寻找中最短路径”问题。所谓无权图,即边与边之间没有长短的区别,只包含两个点之间是否相连的信息的图。因为搜索策略的特性,只有我们到达了距离起点为d的位置,才能到达距离起点为d+1的位置,而d+1一定是距离d最近的位置。根据“两点之间,线段最短”,所有距离最短的线段组合起来,就是某两点之间的最短路径,所以BFS很适合解决一些“最值/极值”问题,比如:最小距离,最大距离,最少操作数等。

问题4:如何实现广度优先搜索?

我们再回想上面说的“最少询问圈数内找到人”的过程(所谓圈数,就是间接询问的次数,我们询问自己身边的朋友统一算一圈,朋友问他们身边的朋友算第二圈,以此类推):

  • 首先,我们要问一圈自己的朋友,因此,我们需要一个“笔记本”来记录我们都问过谁了;
  • 其次,我们需要让朋友们去问他们的朋友,如果我们把自己的朋友和他们的朋友都记在同一个“笔记本”里,难免会忘记谁是我们直接询问的,谁是朋友帮我们询问的。当然了,以人类的智慧,这种事情还不至于记不住,大不了在名字旁边写个标记也行,但计算机毕竟没有智力,只能处理我们给它的信息,因此,我们需要想办法把我们自己询问的,和朋友询问的,以及之后朋友的朋友询问的…区分开。

对于第一个需求,我们可以使用一个哈希表或者数组,来记录谁被问过,谁没被问过。

对于第二个需求,我们可以用一种数据结构来实现——队列。

现实生活中的队列就是一群人排成一排,然后最前头的人优先办理业务,结束后离开,下一个最前头的人接着办业务。而算法中的队列也是如此,它只能从末尾添加数据,从队首取出数据。

我们将自己的朋友加入队列,并记录一个人数size,我们每次都从队列的最前端拉出一个人询问ta知不知道我们要找的人,此时队列里少了一个人,因此size减1。如果被我们拉出来的这个人知道我们要找的人在哪儿,那就停止搜索;如果ta不知道,那我们就让ta去询问ta的朋友们,并把ta的朋友们加入队列的末尾(注意,这里我们会告诉ta,我们已经问过谁了,所以如果ta的朋友中那些已经被问过的人将不被加入队列)。一直重复这个过程,直至size变成0。当size变成0,意味着我们直接询问的朋友们已经都检查光了,剩下的都是朋友帮忙询问的,因此我们询问的圈数+1。循环搜索,直到我们找到那个要找的人,或者没有更多的可以询问的朋友为止。需要注意的是,在我们将某个人加入队列时,一定要立刻把ta的名字记录到笔记中,避免其他人也是ta的好友,导致将ta反复加入队列。

伪代码实现:

unordered_set<int> hash; //用来记录谁问过谁没问过的笔记本(哈希表)
queue<int> q; //用来保存接下来需要询问的人的队列
q.push(me); //将我自己添加到队列中,因为需要从我开始向身边询问
hash.insert(me); //将我自己添加到笔记本中,证明我自己已经问过了,让朋友的朋友们不要跑来问我
int round = 0; //圈数
while(!q.empty()){ //当队列中还有人可以询问的时候,继续询问int size = q.size(); //当前这一轮需要询问的人数while(size--){auto person = q.front(); //从队列开头拉出一个人q.pop(); //队列少了一个人if(person==target) return round; //如果这个人就是我们要找的目标,则返回圈数for(auto& friend: person.friends){ //如果不是,则拜托他询问他的朋友们if(hash.find(friend)!=hash.end()) continue; //如果朋友的朋友已经被问过了,则跳过hash.insert(friend); //如果没有被问过,就加入笔记本,这一点非常重要!!!q.push(friend); //同时将那个人加入队列中}}round++; //一轮询问结束,圈数+1
}
return -1;//-1表示我们最终未能找到那个人

5. 广度优先搜索与树

树,是一种数据结构,它包含了一个根节点,以及一些子节点。树,也是一种特殊的图,它是无权无环图。因此,在搜索树的节点时,我们可以使用广度优先搜索来实现搜索过程。因为搜索的过程是一层一层地将节点加入队列,因此在树中的广度优先搜索又被称为“逐层遍历”或“层序遍历”。

代码实现 ( LeetCode102. 二叉树的层序遍历 ):

class Solution {
public: vector<vector<int>> levelOrder(TreeNode* root) { if(!root) return {}; //节点为空,则直接返回queue<TreeNode*> q; //保存树节点的队列vector<vector<int>> ans; //答案数组q.push(root); //将根节点加入队列while(!q.empty()){ //当队列中还有节点未访问时继续循环int size = q.size(); //当前层的节点数vector<int> temp; //用于保存当前层节点数据的数组while(size--){ TreeNode* current = q.front(); //取出队列最前面的节点temp.push_back(current->val); //将数据加入temp数组//如果这个节点存在左子节点,则将左子节点加入队列if(current->left) q.push(current->left); //如果这个节点存在右子节点,则将右子节点加入队列if(current->right) q.push(current->right); q.pop(); } ans.push_back(temp); //将当前层的数据加入到答案数组中} return ans;
}
};

6. 无权图的广度优先搜索

二维数组就是一个最常见的无权图,我们可以利用广度优先搜索在二维数组构成的无权图中进行搜索。为了实现这个过程,除了广度优先搜索中需要用到的队列和记录是否访问过的数组外,我们还需要明确三个要素:起点,方向,终点:

  • 起点,决定了我们开始扩散的位置
  • 方向,决定了我们扩散的方向和幅度
  • 终点,决定了我们什么时候停止搜索

代码实现 ( LeetCode529. 扫雷游戏 ):

class  Solution {public:int  dx[8] = {0,0,1,-1,1,1,-1,-1}; //X轴的移动幅度int  dy[8] = {1,-1,0,0,1,-1,1,-1}; //Y轴的移动幅度vector<vector<int>> visit; //用于标记哪些点已经访问了的数组vector<vector<char>> updateBoard(vector<vector<char>>&  board, vector<int>&  click) {int m = board.size(); //棋盘的X轴方向长度int n = board[0].size(); //棋盘的Y轴方向长度visit.resize(m,vector<int>(n));queue<pair<int,int>> q; //保存需要访问节点的坐标的队列q.emplace(click[0],click[1]); //将起点加入队列visit[click[0]][click[1]]=1; //将起点标记为已访问while(!q.empty()){ //当还有未检查的点存在于队列时,继续循环int size = q.size(); //当前一轮队列的大小while(size--){auto [x,y] = q.front(); //取队列最前面的元素q.pop();if(board[x][y]=='M'){ //如果这个元素是地雷,则导致爆炸,游戏结束board[x][y]='X';return board;}int count=0; //如果不是地雷,则开始统计周围是否存在地雷for(int i=0;i<8;i++){ //检查当前位置的周围八个方向int nx = x+dx[i];int ny = y+dy[i];if(nx>=0&&nx<m&&ny>=0&&ny<n&&!visit[nx][ny]){ //在可访问范围内+尚未被访问if(board[nx][ny]=='M') count++; //如果是雷,则计数+1}}if(count==0){ //如果计数为零,说明周围没有雷board[x][y]='B'; //按照游戏规则标记for(int i=0;i<8;i++){ //并将周围8个方向的位置加入队列,准备检查int nx = x+dx[i];int ny = y+dy[i];if(nx>=0&&nx<m&&ny>=0&&ny<n&&!visit[nx][ny]){if(board[nx][ny]=='E'){q.emplace(nx,ny);visit[nx][ny]=1;}}}}else  board[x][y] = '0'+count; //如果周围有雷,则按照规则标记}}return board; //最终返回结果
}
};

7. 抽象无权图中的广度优先搜索遍历

当我们遇到一些“最值/极值”问题,又不是用二维数组表示的时候,我们可以尝试将问题转化为求无权图中的最短路径问题。这么说感觉很抽象,但也确实没有办法…我们用下面的两个例子来说明。

例子1: LeetCode279. 完全平方数

题目中,给定一个正整数n,找到若干个完全平方数(比如1,4,9,16,。。。)使得他们的和等于n。求使总和达到n的最少完全平方数个数。比如,n=12,它可以由4,4,4构成,也可以是1,1,1,9构成,但因为4,4,4的完全平方数个数是最少的,因此答案为3。

常见的做法是动态规划,从1开始枚举数字i,然后枚举不超过数字i的完全平方数j,计算和为i的最少完全平方数个数,最后一步一步推到n。在这里,我们提供另一套思路,使用广度优先搜索解决这个问题:

此问题可以抽象成,起点为n,终点为0,需要求从n到0的最短路径(最少的完全平方数个数)的无权图最短路径问题。起点有了,终点有了,那么方向呢?

对于n到0之间的任何一个数字i(节点),它都有不超过 i \sqrt{i} i ​个完全平方数可以选择。为了找到最少,我们需要将所有选择都试一遍,因此我们枚举不超过i的完全平方数j,并用i减去j,将剩下的部分保存到队列中,进行之后的搜索(注意,加入队列之前,需要先检查剩余的部分是否已经在队列中,如果已经在队列中则直接跳过,不需要重复加入队列)。而这些操作都统一成“一轮操作”,当到达0时,我们返回操作的轮数,就是到达n的最少完全平方数的个数。

代码实现:

class Solution {public:int numSquares(int n) {queue<int> q; //用于保存计算结果的队列q.push(n); //将起点放入队列int ans = 0; //轮数初始化为0vector<int> visit(n+1); //标记数组visit[n]=1; //标记n为已访问while(!q.empty()){int size = q.size();while(size--){auto p = q.front(); //从队列最前端取出一个数字q.pop();if(p==0) return ans; //如果是0,说明我们已经找到最短路径,直接返回for(int i=1;i*i<=p;i++){ //枚举不大于p的完全平方数if(visit[p-i*i]) continue; //如果已经在队列或已访问,直接跳过q.push(p-i*i); //将剩余部分加入队列visit[p-i*i]=1; //并标记}}ans++; //没有找到0,操作轮数+1}return ans;}
};

例子2:LeetCode365. 水壶问题

有两个容量分别为 x升 和 y升 的水壶以及无限多的水。请判断能否通过使用这两个水壶,从而可以得到恰好 z升 的水?可以执行的操作有:将一个水壶装满;将一个水壶清空;将一个水壶的水倒入另一个水壶,直至装满或者倒空。

问题分析:首先,当z等于0或者两个壶全装满正好等于z的时候,显然是可以得到恰好z升水的(z=0 or x+y=z)。然后,当两个壶全都装满也无法达到z升水时,显然是不可能得到恰好z升水的(x+y<z)。

当0<z<x+y时,我们可以开始探索,起点为0,终点为z,求从0到z的是否存在路径(广度优先搜索不仅能解决“极值问题”,也可以解决“存在问题”)。

方向:假设当前的总水量为total,我们可以考虑一下几种操作:

  1. 将x壶灌满,那么只要total+x不超过总容量上限x+y,且之前总水量没有到达过total+x,我们就可以将total+x加入队列
  2. 同理,我们也可以将y壶灌满,满足不超上限+之前没有到达过total+y,就可以将total+y加入队列
  3. 将x壶清空,那么只要total-x不会低于0,且之前总水量没有达到过total-x,就可以将total-x加入队列
  4. 同理,我们也可以将y壶清空,只要保证不低于0且之前没有到达过total-y,既可以将total-y加入队列

也许你会问,那将一个壶里得水倒入另一个壶的操作呢?其实,这个操作是毫无意义的。

  1. 因为任何时刻,两个壶的状态只有双满,双空,一满一空,不存在两个同时都不满的情况,这是由题目给的操作造成的。
  2. 如果对一个不满的桶加水:若另一个桶为空,那么相当于灌满这个不满的桶;如果另一个桶为满,那么相当于将两个桶都灌满
  3. 如果将一个不满的桶的水倒掉:若另一个桶为空,那么相当于回到初始状态;如果另一个桶为满,相当于开始的时候就把另一个桶灌满…

代码实现:

class Solution {public:bool canMeasureWater(int x, int y, int z) {if(z==0||x+y==z) return true;if(z<0||x+y<z) return false;queue<int> q;vector<int> visit(x+y+1,0);q.push(0);while(!q.empty()){int total = q.front();q.pop();if(total+x<=x+y&&!visit[total+x]) q.push(total+x),visit[total+x]=1;if(total+y<=x+y&&!visit[total+y]) q.push(total+y),visit[total+y]=1;if(total-x>=0&&!visit[total-x]) q.push(total-x),visit[total-x]=1;if(total-y>=0&&!visit[total-y]) q.push(total-y),visit[total-y]=1;if(visit[z]) return true;}return false;}
};

8. 拓扑排序与广度优先搜索

拓扑排序(Topological Sorting)是一种应用在“有向无环图”上,给出节点输出先后顺序的算法。拓扑忽略了节点的大小关系,而是给出节点的先后顺序。拓扑排序存在的条件为:

  1. 每个节点输出且仅输出一次;
  2. 在有向无环图中,如果存在一条从u到v的路径,那么再拓扑排序的结果中,u必须在v之前(未必相邻,但必须保证先后)

拓扑排序可以用来检测有向图是否存在环,以及得到节点的拓扑排序。经典的应用是:课程安排和任务安排。比如,在学习某个课程A之前,我们要求学生必须先掌握另外一些课程B和C的知识。那么拓扑序就是BCA或者CBA,表示先学习B和C,再学习A。假如存在环,比如学习A必须学习B,学习B之前必须学习C,学习C之前必须学习A,那么就不存在拓扑序,因为环的存在,无法确定谁在谁的前面。

而拓扑排序是可以通过广度优先搜索来实现的,步骤如下:

  1. 首先需要构建图:包括但不限于使用哈希表,邻接表,邻接矩阵,保存每个节点的连接情况
  2. 构建图的过程中,我们需要记录每个节点的入度(和出度,入度最常用,出度不是很常用)。所谓入度,就是指向自己的边的条数,而出度就是从自己指向别的节点的边的边数
  3. 检查每个节点的入度,当某个节点入度为0时,说明它没有前驱节点,因此可以作为我们探索的起点,将它加入队列
  4. 依照广度优先搜索对队列进行处理,每次从队列中取出一个点,这个点一定是满足拓扑序的,记录到答案中,同时将与它连接的所有的点的入度都减一,因为我们把这个点加入答案后,要将这个点删除,所以需要消除它对其他点的影响。如果有节点在入度减一后入度变成了0,说明它也可以作为下一轮搜索的起点,加入队列
  5. 搜索完毕后,检查答案中节点的数量与全图节点数量是否一致,如果一致,说明拓扑序完成;如果不一致,说明存在环

例子:LeetCode310. 最小高度树

题目大意:有一个有n个节点的树,可以任选其中一个节点称为根,求使得树的高度最小的根的标签列表。

问题分析:一个朴素的想法就是,对每个点都遍历一次全树,记录最低高度,然后将达到最低高度的节点都加入答案中。然而节点量为 2 × 1 0 4 2 \times 10^{4} 2×104个,时间复杂度为O(N^2)的算法显然会超时。因此必须要想别的办法

思路:虽然暴力不可取,但是在上面的尝试中,我们会发现一个规律,那就是越是靠近中间的节点,构成的树的高度越低,因此,我们可以反向拓扑,从所有入度为1的节点(也就是边缘节点)开始向内拓扑,并保存每一层的节点到数组中,当遍历结束后,返回最后一层的节点,就是答案

class Solution {public:vector<int> findMinHeightTrees(int n, vector<vector<int>>& edges) {vector<vector<int>> adj(n);vector<int> outdegrees(n);for(auto& e: edges){adj[e[0]].push_back(e[1]);adj[e[1]].push_back(e[0]);outdegrees[e[0]]++;outdegrees[e[1]]++;}queue<int> q;for(int i=0;i<n;i++){if(outdegrees[i]<=1) q.push(i);}vector<int> ans;while(q.size()!=n){int size = q.size();n-=size;while(size--){auto p = q.front();q.pop();for(auto& v: adj[p]){if(outdegrees[v]>1){outdegrees[v]--;if(outdegrees[v]==1){q.push(v);}}}}}while(!q.empty()) ans.push_back(q.front()), q.pop();return ans;}
};

9. 双向BFS

双向BFS可以应用于这样的特殊场景:无向图,且有明确的搜索终点。朴素的BFS是从起点出发,逐层遍历,最后到达终点,而双向BFS则是从起点和终点同时出发,交替逐层遍历。下图中,蓝色部分为单向BFS探索的区域范围,绿色为双向BFS探索的区域范围,显然比蓝色面积小,因此双向BFS的效率比单向BFS更高。

例子:LeetCode127. 单词接龙

题目大意:给定一个起始单词和一个终点单词,以及一本字典,每次可以修改单词的一个位置的字符,但修改后得到的单词也必须是字典中的单词。求最少需要操作几次才能从起始单词变成终点单词。

思路:

单向BFS,我们以起始单词为起点,每次修改它的一个位置,然后检查是否在字典内,如果存在,且之前没有加入过队列,则加入队列,否则继续尝试修改成别的字符或者别的位置。

双向BFS,我们将起始单词和终点单词分别加入两个不同的队列,然后交替做单向BFS,检查修改后的单词是否在字典中以及是否在对方的笔记里出现过,如果出现过,那么就找到了起点到终点的最短路径,返回路径长度即可

代码实现

class Solution {public:int ladderLength(string beginWord, string endWord, vector<string>& wordList) {unordered_set<string> hash;for(auto& w: wordList){hash.insert(w);}if(!hash.count(endWord)) return 0;queue<string> start;queue<string> end;start.push(beginWord);end.push(endWord);unordered_set<string> seen_start;unordered_set<string> seen_end;seen_start.insert(beginWord);seen_end.insert(endWord);int step=0;while(!start.empty()&&!end.empty()){int size = start.size();while(size--){auto p = start.front();start.pop();int n = p.size();for(int i=0;i<n;i++){char ch = p[i];for(int j=0;j<26;j++){if(j==ch-'a') continue;p[i] = 'a'+j;// cout<<p<<endl;if(hash.count(p)){if(seen_end.count(p)) return step+2;if(!seen_start.count(p)){seen_start.insert(p);start.push(p);}}}p[i]=ch;}}step++;size=end.size();while(size--){auto p = end.front();end.pop();int n = p.size();for(int i=0;i<n;i++){char ch = p[i];for(int j=0;j<26;j++){if(j==ch-'a') continue;p[i] = 'a'+j;// cout<<p<<endl;if(hash.count(p)){if(seen_start.count(p)) return step+2;if(!seen_end.count(p)){seen_end.insert(p);end.push(p);}}}p[i]=ch;}}step++;}return 0;}
};

10. 多源BFS

其实我们在拓扑排序中已经接触过多源BFS了,即起点不止一个的单向BFS。在此不再赘述

从零开始的广度优先搜索(BFS)相关推荐

  1. 一文搞定深度优先搜索(DFS)与广度优先搜索(BFS)【含完整源码】

    写在前面:博主是一位普普通通的19届双非软工在读生,平时最大的爱好就是听听歌,逛逛B站.博主很喜欢的一句话花开堪折直须折,莫待无花空折枝:博主的理解是头一次为人,就应该做自己想做的事,做自己不后悔的事 ...

  2. 广度优先搜索(BFS)——抓住那头牛(POJ 4001)

    本文将以(POJ 4001)抓住那头牛 为例,讲解经典算法广度优先搜索(BFS)的STL写法 在实际写算法中,怎么能不使用更快.更方便.更准确.更高效的C++ STL模板呢 相信很多人都了解过广度优先 ...

  3. 【蓝桥杯】历届试题 青蛙跳杯子(广度优先搜索bfs)(C++)

    [蓝桥杯]历届试题 青蛙跳杯子 问题描述 思路分析 代码实现 问题描述 题目链接:青蛙跳杯子 资源限制 时间限制:1.0s 内存限制:256.0MB 问题描述 X星球的流行宠物是青蛙,一般有两种颜色: ...

  4. 算法简介:层层递进----广度优先搜索(BFS)

    算法简介:层层递进----广度优先搜索(BFS) 算法简介 算法简介 BFS算法思想: 首先以一个未被访问过的顶点作为起始顶点,访问其所有相邻的顶点,然后对每个相邻的顶点,再访问它们相邻的未被访问过的 ...

  5. 广度优先搜索BFS进阶(一):多源BFS、优先队列BFS、双端队列BFS

    一.多源BFS 在上一篇博客:广度优先搜索BFS基础中,我们接触到的BFS均是单起点(单源)的,但是对于某一些问题,其有多个起点,此类问题我们称为多源BFS问题.先思考下面一道例题: 1.腐烂的橘子 ...

  6. 算法复习|广度优先搜索BFS

    广度优先搜索BFS 文章目录 广度优先搜索BFS HDU-1253 胜利大逃亡 HDU-1241 Oil Deposits 算法思想 从初始状态S开始,利用一定的规则,生成所有下一层的状态,依次入队 ...

  7. matlab bfs函数,matlab练习程序(广度优先搜索BFS、深度优先搜索DFS)

    如此经典的算法竟一直没有单独的实现过,真是遗憾啊. 广度优先搜索在过去实现的二值图像连通区域标记和prim最小生成树算法时已经无意识的用到了,深度优先搜索倒是没用过. 这次单独的将两个算法实现出来,因 ...

  8. 广度优先搜索 BFS算法

    广度优先搜索算法(Breadth-First-Search,BFS),又称作宽度优先搜索.BFS算法是从根节点开始,沿着树的宽度遍历树的节点.如果所有节点均被访问,则算法中止. 算法思想 1.首先将根 ...

  9. 广度优先搜索(BFS)

    广度优先 Description: 阿狸被困在迷宫,snoopy要去救他,snoopy可以向上.下.左.右四个方向行走,每走一步(格)就要喝掉一瓶益力多.现在给它一个迷宫地图请问:snoopy最少需要 ...

最新文章

  1. java代码二进制转为十六进制_Java 中二进制转换成十六进制的两种实现方法
  2. 终于搞明白gluPerspective和gluLookAt的关系了
  3. sql 查询 定义变量
  4. 在IDEA中使用MyBatis Generator逆向工程生成代码
  5. brk(), sbrk() 用法详解【转】
  6. html中隐式转换成数字,关于 JS 类型隐式转换的完整总结
  7. SeDuMi教程(2)_线性规划的两种求解器的对比
  8. Unreal角色技术指南
  9. 关于AE大数据点文件读取生成SHP文件时使用IFeatureBuffer快速提高读取效率
  10. Hadoop 环境准备
  11. 赞一个 kindle电子书有最新的计算机图书可买了【Docker技术入门与实战】
  12. 品优购案例之横向列表伪元素的应用
  13. C语言基础专题 - 数组(编辑中)
  14. 常见计算机基础笔试题总结quickstart
  15. 【语音处理】基于matlab GUI录音信号时域频域分析(带面板)【含Matlab源码 064期】
  16. 计算机房加湿机,数据中心加湿系统计算及方法探讨【新规范加湿方式对比及计算分析】...
  17. 网易云下载的音频.ncm格式如何转换成MP3格式
  18. orcadcapture安装_电路原理图设计软件(OrCAD Capture CIS 中文版) 16.6 免费安装版
  19. ceph最低配置和硬件推荐
  20. 网线水晶头接法详细图文教程

热门文章

  1. 移动端web页面开发
  2. 数字信号处理基础----傅里叶级数
  3. BST+AVL+SB
  4. You辉编程_JavaScript高级程序
  5. 最短路径Floyd算法图解与C++实现
  6. kf真空接口_一种测试仪器真空接口转标准kf真空接口的转接头的制作方法
  7. keil添加stc单片机型号和头文件
  8. 微信删除好友怎么加回来啊?
  9. 如何用Jmeter提取和引用Token
  10. windows 10同时安装 JDK1.8 与 JDK1.6 版本贴换,可解决问题版