混元算法 -------------深(广)度优先搜索(D(B)FS)

本文介绍了深度优先遍历(DFS)和广度优先遍历(BFS)两种图遍历算法,强调了它们在解决寻路问题和迷宫问题时的不同优势。通过实例展示了如何使用DFS解决最短路径问题,并提供了C++代码实现。同时,文章还探讨了BFS在解决迷宫问题中的应用,同样附带了相应的C++代码。
摘要由CSDN通过智能技术生成

对于深度优先算法(dfs)或广度优先算法(bfs)一般在大学的数据结构里面都学过,老师也都讲过,但是为了我自己加深印象,还是写一遍吧

【深度优先遍历】

我们一般都是深度优先和广度优先一起学的,一般都是运用在图的遍历,它们各有优缺点,对于大部分情况而言,深度优先的空间会优于广度优先,但广度优先的时间复杂度就优于深度优先。

深度优先遍历就是一条路走到黑不行了就回退上一步看还有没有其他的路一直这样下去直到遍历所有连通子图的节点,然后看还有没有未被访问的节点。而广度优先遍历是按层来遍历的,意思是先遍历第一层节点(也就是第一个节点),然后遍历第二层(从上一层节点可以直接一步到达的),然后遍历第三次(从第二层节点可以直接一步到达的)但是中间要判重,一般遵守先被访问的节点的子节点先被访问。他们都需要将访问过的节点标记避免重复访问。

我们先来着重看一下深度优先遍历,如图1:
图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;
}

好了,就说这些了,有什么更好的或者有哪不对的欢迎指出来哦

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 这个错误的意思是 JavaScript 堆内存不足。这意味着你的程序尝试使用更多内存,但是电脑上可用的内存不足以满足需求。 这种情况通常发生在你的程序中存在内存泄露(memory leak)或者你的程序使用了过多的内存。 解决方法可能包括: - 寻找并修复内存泄露 - 优化你的程序,减少内存使用 - 尝试使用更大的内存限制来运行你的程序(例如,使用 `node --max-old-space-size=4096 script.js` 运行你的程序) ### 回答2: "FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory" 是一个错误信息,意思是 JavaScript 堆内存不足导致分配失败。 这个错误通常在运行大型 JavaScript 应用程序或使用大量内存的操作时出现。JavaScript 使用堆内存来存储变量、对象和函数,并且在执行时需要动态地分配和释放内存。当堆内存不足时,JavaScript 引擎无法分配新的内存,导致出现该错误。 解决这个问题的方法有以下几种: 1. 增加 JavaScript 的堆内存限制:可以通过命令行参数 `--max-old-space-size` 来增加 Node.js 或 Chrome 等应用程序的堆内存限制。例如,`node --max-old-space-size=4096 app.js` 将堆内存限制增加到 4 GB。需要根据具体情况适当调整堆内存限制。 2. 优化代码和内存使用:检查代码中是否存在不必要的变量或未释放的内存。通过合理使用变量和及时释放不再使用的内存,可以减少对堆内存的需求。 3. 减少单次内存请求:如果代码中有大量的内存请求,可以将其拆分为多个较小的请求。减少每次请求的内存量可以减轻对堆内存的负担。 4. 使用流式处理数据:对于处理大量数据的情况,可以考虑使用流式处理方式,将数据分批次读取和处理,而不是一次性加载到内存中。 5. 更新 JavaScript 引擎或应用程序版本:有时,该错误可能是由于 JavaScript 引擎或应用程序本身的 bug 导致的。在这种情况下,升级到最新的版本可能会修复问题。 总之,当出现"FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory" 错误时,我们可以通过增加堆内存限制、优化代码和内存使用、减少单次内存请求、采用流式处理数据或升级软件等方式来解决问题。 ### 回答3: "FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory" 是一个常见的 JavaScript 错误,表示 JavaScript 堆内存耗尽导致的致命错误。 这个错误通常发生在运行大型程序或处理大量数据时,JavaScript 堆内存无法满足程序的需求。JavaScript 堆内存是用于存储变量和对象的地方,当程序需要分配更多内存而无法找到足够的空间时,就会引发这个错误。 解决这个问题有几种方法: 1. 增加可用内存:通过增加 Node.js 进程的内存限制,可以尝试解决这个问题。使用命令 `node --max-old-space-size=4096 script.js` 可以将可用内存增加到 4GB,可以根据需求调整内存大小。 2. 优化代码:检查代码中是否存在内存泄漏或无限循环等问题。释放不再使用的变量和对象,减少内存占用。 3. 分批处理数据:如果程序处理的数据量过大,可以考虑将数据分批处理,避免一次性加载大量数据导致内存不足。 4. 使用流式处理:对于需要处理大型数据集的情况,可以使用流式处理,逐个读取和处理数据,避免一次性加载全部数据。 5. 使用更高效的算法和数据结构:评估代码中的算法和数据结构,寻找更高效的替代方案,减少内存占用。 总结:解决 "FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory" 错误的关键是增加可用内存、优化代码和分批处理数据,以及使用更高效的算法和数据结构。根据具体情况选择适合的解决方法,以确保程序能够正常运行。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值