对于深度优先算法(dfs)或广度优先算法(bfs)一般在大学的数据结构里面都学过,老师也都讲过,但是为了我自己加深印象,还是写一遍吧
【深度优先遍历】
我们一般都是深度优先和广度优先一起学的,一般都是运用在图的遍历,它们各有优缺点,对于大部分情况而言,深度优先的空间会优于广度优先,但广度优先的时间复杂度就优于深度优先。
深度优先遍历就是一条路走到黑不行了就回退上一步看还有没有其他的路一直这样下去直到遍历所有连通子图的节点,然后看还有没有未被访问的节点。而广度优先遍历是按层来遍历的,意思是先遍历第一层节点(也就是第一个节点),然后遍历第二层(从上一层节点可以直接一步到达的),然后遍历第三次(从第二层节点可以直接一步到达的)但是中间要判重,一般遵守先被访问的节点的子节点先被访问。他们都需要将访问过的节点标记避免重复访问。
我们先来着重看一下深度优先遍历,如图1:
对于这个图用深度优先遍历的话就是:
第1步:访问A。
第2步:访问B(A的邻接点)。 在第1步访问A之后,接下来应该访问的是A的邻接点,即"B,D,F"中的一个。但在本文的实现中,顶点ABCDEFGH是按照顺序存储,B在"D和F"的前面,因此,先访问B。
第3步:访问G(B的邻接点)。 和B相连只有"G"(A已经访问过了)
第4步:访问E(G的邻接点)。 在第3步访问了B的邻接点G之后,接下来应该访问G的邻接点,即"E和H"中一个(B已经被访问过,就不算在内)。而由于E在H之前,先访问E。
第5步:访问C(E的邻接点)。 和E相连只有"C"(G已经访问过了)。
第6步:访问D(C的邻接点)。
第7步:访问H。因为D没有未被访问的邻接点;因此,一直回溯到访问G的另一个邻接点H。
第8步:访问(H的邻接点)F。
因此访问顺序是:A -> B -> G -> E -> C -> D -> H -> F
另外,利用深度优先遍历寻找最优解问题是,一般最好配上剪支,就是我走到中间的那个地方发现继续走下去没前途,没啥意义,或者已经发现这条路不可能是最优解了(前面有比这更好的)那么也没有必要继续走这条路下去,因为深度优先搜索本来大部分都是递归,时间复杂度就比较高,所以要尽量优化。不然好多问题若在比赛或在Leetbook等上面提交时都会超时。
也没啥好说的了,下来直接看一道例题:
【寻路问题】
问题就是我要从起点到某个地方,每条路有路费,有长度,并且我只有那么多钱,问我最短可以只走多少,前提是钱要够。
现在输入三个数K(一共有多少钱),N(要到达的终点),R(多少条路),接下来R行输入每条路的起点,终点,路费,长度,然后输出最短路径。进过分析,我们只要把每条路的信息存起来,二维数组的行就代表起点,然后这一列就代表可以到达下一个地方的信息。这样就可以把整个图的信息存起来G[s][i](G[s]就代表以s为起点的那些边,G[s][i]就代表其中一个边的信息)。
然后我们就深度优先遍历,我还是比较喜欢用递归来实现,这样感觉代码好写些,如果不用递归的话那么就要用到栈的先进先出的特点来存储信息,那样写其实更好些,不用一直调用函数关键是不会爆栈(函数的调用都是在栈里面执行的),代码跑的也会快些,这个我还是用递归吧,要注意剪枝的问题,不然的话提交会超时,就这我们会发现有两条地方可以剪,第一个就是到达某个地方发现它的长度已经超过原来记录的最小到达目的地的长度了,那么再走下去肯定也没有意义,还有就是我们发现到达某个地方发现原来来过且花的钱一样但是总路径更长,那么这个肯定也没有走下去的意义了,那么这个就要开辟一个二维数组来存储这个信息了,相当于用空间换时间。可能还有其他的剪枝思路,那就慢慢思考吧。上代码:
#include<iostream>
using namespace std;
#include<algorithm>
#include<vector>
#include<cstring>
struct Road
{
int goal,cost,len;
};
int K,N,R;
vector< vector<Road> > G(1001);
int dp[111][10001];
int MinLen = 65535;
int TotalLen = 0;
int TotalCost = 0;
bool visted[1001];
void DFS(int s)
{
if(s == N)
{
MinLen = min(MinLen,TotalLen);
return;
}
for(int i = 0;i < int(G[s].size());i++)
{
Road r = G[s][i];
if((TotalCost + r.cost) >= K) continue;
if(!visted[r.goal])
{
TotalLen += r.len;
if(TotalLen >= MinLen) continue;//第一次剪枝,现在这条路走的长度已经超过原来记录的最小路径了
TotalCost += r.cost;
if(TotalLen>=dp[r.goal][TotalCost]) continue; //花费一样的钱到达这个地方走的路已经超过上次来这个地方的路线了
dp[r.goal][TotalCost] = TotalLen;
visted[r.goal] = true;
DFS(r.goal);
visted[r.goal] = false;
TotalLen -= r.len;
TotalCost -= r.cost;
}
}
}
int main()
{
cin>>K>>N>>R;
for(int i = 0;i<R;i++)
{
int s;
Road r;
cin>>s>>r.goal>>r.len>>r.cost;
if(s != r.goal) G[s].push_back(r);
}
visted[1] = true;
memset(dp, 0x3f, sizeof dp);
DFS(1);
if(MinLen<65535) cout<<MinLen<<endl;
else cout<<"-1"<<endl;
return 0;
}
我以前还有用DFS做的题,大家可以看看。
城堡问题是我以前用深度优先做的一道题。
【广度优先遍历】
在我看来,广度优先搜索更适合那种求最短路径的问题(每两个节点之间的长度相等的那种),对于这样的问题广度就比深度的优势要大些,深度我喜欢用递归来实现,但是广度我好像不喜欢用递归了,可能是个人习惯吧。
广度优先搜索(BFS)也是用在图论问题上的一种算法,和深度一样。广度是按层访问,我们直接看一道广度优先遍历的最经典的问题:
【迷宫问题】(找了半天还是决定用蓝桥杯里面的有道原题吧!)
下图给出了一个迷宫的平面图,其中标记为1 的为障碍,标记为0 的为可
以通行的地方。
010000
000100
001001
110000
迷宫的入口为左上角,出口为右下角,在迷宫中,只能从一个位置走到这
个它的上、下、左、右四个方向之一。
对于上面的迷宫,从入口开始,可以按DRRURRDDDR 的顺序通过迷宫,
一共10 步。其中D、U、L、R 分别表示向下、向上、向左、向右走。
对于下面这个更复杂的迷宫(30 行50 列),请找出一种通过迷宫的方式,
其使用的步数最少,在步数最少的前提下,请找出字典序最小的一个作为答案。
请注意在字典序中D<L<R<U。
难受,当时在写这个的时候原本以为最多10分钟就写完了,结果写到中间测试数据的时候最后忘了改过来,害得我最后一直运行的结果不对,调试了好久,哭……,这就是程序员掉头发的原因吗?另外在网上看到这道题好多用EXCEL做出来的……,果然大佬就是多啊,但是这样容易出错,不过对于写代码需要很久或者根本就不会的来说还是可以的。Excel解决迷宫
这道题就是典型的用广度优先遍历的经典例子,其实不难,不用递归的话就要用队列来确保这种遍历的顺序,就是首节点先进队列,然后出队列,再把这个节点的下一层按照一定的顺序又放到队列(这道题人家明确要求按照字典的顺序D<L<R<U),这里要注意判重和一些边界条件,然后直到队列为空或者中间出队列时达到终点结束。其实不难,特别是对于用C++来写的码农来说,可以直接用STL中的queue模板,自带出队进队都不用自己写,直接上代码:
#include<iostream>
using namespace std;
#include<queue>
struct Node
{
int x = 0, y = 0;
string road;
};
char Group[32][52];
bool visted[32][52];
queue<Node> Q;
int dir[4][2] = { {1,0},{0,-1},{0,1},{-1,0} };
char dirs[4] = { 'D','L','R','U' };
bool Isroad(int x, int y)
{
if (x < 0 || x>29)
return false;
if (y < 0 || y>49)
return false;
if (visted[x][y])
return false;
if(Group[x][y] == '1')
return false;
return true;
}
void BFS(int i, int j)
{
Node q;
q.x = i; q.y = j;
Q.push(q);
visted[i][j] = true;
while (!Q.empty())
{
Node end = Q.front();
Q.pop();
if (end.x == 29 && end.y == 49)
{
cout << end.road << endl;
return;
}
for (int k = 0; k < 4; k++)
{
Node next;
int n = end.x + dir[k][0];
int m = end.y + dir[k][1];
if (Isroad(n,m))
{
next.x = n; next.y = m; next.road = end.road+dirs[k];
Q.push(next);
visted[n][m] = true;
}
}
}
}
int main()
{
for (int i = 0; i < 30; i++)
{
for (int j = 0; j < 50; j++)
{
cin >> Group[i][j];
}
}
BFS(0, 0);
return 0;
}
好了,就说这些了,有什么更好的或者有哪不对的欢迎指出来哦