算法十:DFS深度优先搜索(DFS子序列问题、迷宫问题、全排列)

本文深入探讨深度优先搜索算法,包括其原理、DFS在背包问题、子序列问题及迷宫问题中的应用,并提供优化版本的代码示例。理解DFS如何选择路径,以及在实际问题中的灵活运用和性能提升技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

深度优先搜索概述

深度优先搜索和广度优先搜索一样,都是对图进行搜索的算法,目的也都是从起点开始搜索直到到达指定顶点(终点)。深度优先搜索会沿着一条路径不断往下搜索直到不能再继续为止,然后再折返,开始搜索下一条候补路径。

DFS思想

A为起点,G为终点。一开始我们在起点A上。
在这里插入图片描述
将可以从A直达的三个顶点B、C、D设为下一步的候补顶点。
在这里插入图片描述
从候补顶点中选出一个顶点。优先选择最新成为候补的点,如果几个顶点同时成为候补,那么可以从中随意选择一个。
在这里插入图片描述
注:此处,候补顶点是用“后入先出”(LIFO)的方式来管理的,因此可以使用“栈”这个数据结构。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
到达终点G,搜索结束。
在这里插入图片描述
深度优先搜索的特征为沿着一条路径不断往下,进行深度搜索。虽然广度优先搜索和深度优先搜索在搜索顺序上有很大的差异,但是在操作步骤上却只有一点不同,那就是选择哪一个候补顶点作为下一个顶点的基准不同。

广度优先搜索选择的是最早成为候补的顶点,因为顶点离起点越近就越早成为候补,所以会从离起点近的地方开始按顺序搜索;而深度优先搜索选择的则是最新成为候补的顶点,所以会一路往下,沿着新发现的路径不断深入搜索。

背包问题

题目描述

现有n件物品,重量分别为w[0],w[1],…w[n-1],价值为c[i],现需要选出若干件物品放入一个容量为V的背包中,使得在选入背包的物品总重量不超过容量V的前提下,让背包中物品的价值之和最大,求最大价值(1 <= n <= 20)

思路:

这就相当于迷宫问题,当物品总重量超过V,则到达死胡同,而岔道口就是选或不选这个物品

示例代码如下:

#include<cstdio>

const int maxn = 30;

int n, V, maxValue = 0;		//物品件数n,背包容量V 
int w[maxn], c[maxn];		//w[i]每件物品的重量,c[i]为每件物品的价值 

void DFS(int index, int sumW, int sumC)
{
	if(index == n)
	{
		if(sumW <= V && sumC > maxValue)
		{
			maxValue = sumC;		//不超过背包容量时更新最大价值maxValue 
		}
		return;
	}
	
	DFS(index + 1, sumW, sumC);
	DFS(index + 1, sumW + w[index], sumC + c[index]);
} 

int main()
{
	scanf("%d%d", &n, &V);
	for(int i = 0; i < n; i++)
	{
		scanf("%d", &w[i]);
	}
	for(int i = 0; i < n; i++)
	{
		scanf("%d", &c[i]);
	}
	
	DFS(0, 0, 0);
	printf("%d\n", maxValue);
	return 0;
}

样例运行结果:
在这里插入图片描述
优化版代码:

#include<cstdio>

const int maxn = 30;

int n, V, maxValue = 0;		//物品件数n,背包容量V 
int ans = 0;
int w[maxn], c[maxn];		//w[i]每件物品的重量,c[i]为每件物品的价值 

void DFS(int index, int sumW, int sumC)
{
	if(index == n)
	{
		if(sumW <= V && sumC > maxValue)
		{
			maxValue = sumC;		//不超过背包容量时更新最大价值maxValue 
			ans = maxValue;
		}
		return;
	}
	
	DFS(index + 1, sumW, sumC);
	
	if(sumW + w[index] <= V)
	{
		if(sumC + c[index] > ans)
		{
			ans = sumC + c[index];
		}
		DFS(index + 1, sumW + w[index], sumC + c[index]);
	 } 
	
} 

int main()
{
	scanf("%d%d", &n, &V);
	for(int i = 0; i < n; i++)
	{
		scanf("%d", &w[i]);
	}
	for(int i = 0; i < n; i++)
	{
		scanf("%d", &c[i]);
	}
	
	DFS(0, 0, 0);
	printf("%d\n", maxValue);
	return 0;
}

上面优化的代码在于,只有当加入的第index件物品后背包未超重,才能进入岔路,否则就不走该岔路。这样做可以节省计算量

DFS子序列问题

题目描述

给定N个整数(可能有负数),从中选择K个数,使得这K个数之和恰好等于一个给定的整数X;如果有多种方案,选择它们中元素平方和最大的一个。数据保证这样的方案唯一。例如,从4个整数(2,3,3,4}中选择2个数,使它们的和为6,显然有两种方案{2,4}与{3,3},其中平方和最大的方案为{2,4}。

思路:
使用深度搜索遍历来实现,一般每次只有两种选择:
1、将当前数字放入已选序列
2、不将当前数字放入已选序列

注:若将当前数字放入已选序列,则当要执行不放入序列的时候,需要将当前数字去掉

详细C++代码如下:

#include<iostream>
#include<vector>
using namespace std;

const int maxn = 50;

int n, k, x, maxSumSqu = -1, A[maxn];

vector<int> temp, ans;		//存放最优方案的数组ans 

//sum是整数之和,sumSqu是整数平方和
//nowK是已选整数的个数 
//index是当前的序列号 
void DFS(int index, int nowK, int sum, int sumSqu)
{
	if(nowK == k && sum == x)
	{
		if(sumSqu > maxSumSqu)
		{
			maxSumSqu = sumSqu;
			ans = temp;
		}
		return;
	}
	
	if(index == n || nowK > k || sum > x)
		return;
		
	temp.push_back(A[index]);
	DFS(index + 1, nowK + 1, sum + A[index], sumSqu + A[index] * A[index]);
	temp.pop_back();
	
	DFS(index + 1, nowK, sum, sumSqu);
}

int main()
{
	int num;
	cin >> num;
	for(int i = 0; i < num; i++)
	{
		cin >> A[i];
	}
	cout << "please input 限定选择的K个数中的K, 要求数字的和x:";
	cin >> k >> x;
	n = num;
	
	DFS(0, 0, 0, 0); 
	int sum = 0;
	cout << "ans:";
	for(int i = 0; i < ans.size(); i++)
	{
		cout << ans[i] << " ";
		sum += ans[i];
	}
	
	cout << "\nsum:" << sum << endl;
	cout << "maxSumSqu:" << maxSumSqu << endl;
	return 0;
}

样例运行结果如下:
在这里插入图片描述

变式题

题目描述

假设N个整数中的每一个都可以被选择多次,那么选择K个数,使得K个数之和恰好为X。

思路:

这个问题只需要对上面的代码进行少量的修改即可。由于每个整数可以被选择多次,因此当选择了index号数时,不应当直接进入index + 1号进行处理,应当继续选择index号数,知道某个时刻决定不再选择index号数,就会通过"不选index号数"这条分支进入处理,即
DFS(index + 1, nowK, sum, sumSqu);

需要修改的代码为

DFS(index + 1, nowK + 1, sum + A[index], sumSqu + A[index] * A[index]);

改成

DFS(index, nowK + 1, sum + A[index], sumSqu + A[index] * A[index]);

完整C++代码如下:

#include<iostream>
#include<vector>
using namespace std;

const int maxn = 50;

int n, k, x, maxSumSqu = -1, A[maxn];

vector<int> temp, ans;		//存放最优方案的数组ans 

//sum是整数之和,sumSqu是整数平方和
//nowK是已选整数的个数 
//index是当前的序列号 
void DFS(int index, int nowK, int sum, int sumSqu)
{
	if(nowK == k && sum == x)
	{
		if(sumSqu > maxSumSqu)
		{
			maxSumSqu = sumSqu;
			ans = temp;
		}
		return;
	}
	
	if(index == n || nowK > k || sum > x)
		return;
		
	temp.push_back(A[index]);
	DFS(index, nowK + 1, sum + A[index], sumSqu + A[index] * A[index]);
	temp.pop_back();
	
	DFS(index + 1, nowK, sum, sumSqu);
}

int main()
{
	int num;
	cin >> num;
	for(int i = 0; i < num; i++)
	{
		cin >> A[i];
	}
	cout << "please input 限定选择的K个数中的K, 要求数字的和x:";
	cin >> k >> x;
	n = num;
	
	DFS(0, 0, 0, 0); 
	int sum = 0;
	cout << "ans:";
	for(int i = 0; i < ans.size(); i++)
	{
		cout << ans[i] << " ";
		sum += ans[i];
	}
	
	cout << "\nsum:" << sum << endl;
	cout << "maxSumSqu:" << maxSumSqu << endl;
	return 0;
}

样例运行结果如下:
在这里插入图片描述

迷宫问题

题目描述

在这里插入图片描述

解题思路

dfs函数中有三个关键参数:横坐标、纵坐标、到目前为止所走的步数
在这里插入图片描述
方向数组
在这里插入图片描述
在这里插入图片描述

C代码:

#include<stdio.h>

int n, m, p, q, min = 99999999;

int a[51][51], book[51][51];

void dfs(int x, int y, int step)
{
	/*右   下   左   上 
	*/ 
	int next[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
	
	int tx, ty, k;
	if(x == p && y == q)
	{
		if(step < min)
		{
			min = step;
		} 
	} 
	
	for(k = 0; k <= 3; k++)
	{
		tx = x + next[k][1];
		ty = y + next[k][0];
		
		if(tx < 1 || tx > n || ty < 1 || ty > m)
		{
			continue;
		}
		if(a[tx][ty] == 0 && book[tx][ty] == 0)
		{
			book[tx][ty] = 1;
			dfs(tx, ty, step + 1);
			book[tx][ty] = 0;
		}
	}
} 

int main()
{
	int i, j, startx, starty;
	
	scanf("%d %d", &n, &m);
	
	for(i = 1; i <= n; i++)
	{
		for(j = 1; j <= m; j++)
		{
			scanf("%d", &a[i][j]);
		}
	}
	scanf("%d %d %d %d", &startx, &starty, &p, &q);
	
	book[startx][startx] = 1;
	dfs(startx, starty, 0);
	
	printf("%d", min);
	getchar();
	return 0;
}

完整C++代码如下:

#include<bits/stdc++.h>
using namespace std;

//n, m是迷宫的行列数,p,q是目标位置 
int n, m, p, q, minn = 999999;
int a[51][51], book[51][51];

void dfs(int x, int y, int step)
{
	//方向数组 
	int next[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
	
	int tx, ty, k;
	
	//判断是否达到目标位置 
	if(x == p && y == q)
	{
		if(step < minn)
		{
			minn = step;
		}
		return;
	}
	
	//枚举4种走法 
	for(k = 0; k <= 3; k++)
	{
		tx = x + next[k][0];
		ty = y + next[k][1];
		//判断是否越界 
		if(tx < 1 || tx > n || ty < 1 || ty > n)
		{
			continue;
		}
		
		if(a[tx][ty] == 0 && book[tx][ty] == 0)
		{
			book[tx][ty] = 1;
			dfs(tx, ty, step + 1);
			book[tx][ty] = 0;
		}
	}
}

int main()
{
	int i, j, startx, starty;
	
	cin >> n >> m;
	for(i = 1; i <= n; i++)
	{
		for(j = 1; j <= m; j++)
		{
			cin >> a[i][j];
		}
	} 
	//输入起点和终点 
	cin >> startx >> starty >> p >> q;
	book[startx][starty] = 1;
	dfs(startx, starty, 0);
	
	cout << minn << endl;	
	return 0;
}

样例运行结果:
在这里插入图片描述

完善版C++代码

#include<bits/stdc++.h>
using namespace std;

int m, n, p, q, minn = 99999999;
//a数组记录原始地图情况,book数组记录路径是否已被走过 
int a[100][100], book[100][100];
//二维数组不要用memset(book, 0, sizeof(book));

void dfs(int x, int y, int step)
{
	//方向数组(x, y)   右   下   左   上 
	int next[4][2] = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}};
	
	int tx, ty, k; 
	
	if(x == p && y == q)
	{		
		//更新最小步数 
		if(step < minn)
		{
			minn = step;
		}
	}
	for(k = 0; k <= 3; k++)
	{
		tx = x + next[k][0];
		ty = y + next[k][1];
		
		//m表示行,n表示列 
		if(tx < 1 || tx > n || ty < 1 || ty > m)
		{
			continue;
		}
		//判断该点是否为障碍物或者已经走过了 
		if(a[tx][ty] == 0 && book[tx][ty] == 0)
		{
			book[tx][ty] = 1;
			dfs(tx, ty, step + 1);
			book[tx][ty] = 0;
		}
	}
	
}

int main()
{
	cin >> m >> n;
	for(int i = 1; i <= m; i++)
	{
		for(int j = 1; j <= n; j++)
		{
			cin >>a[i][j];
		}
	}
	
	int startx, starty;
	book[startx][starty] = 1;
	cin >> startx >> starty >> p >> q;
	dfs(startx, starty, 0); 
	cout << minn << endl;
	return 0; 
} 

样例运行结果:
在这里插入图片描述

全排列问题

题目描述

在这里插入图片描述

解题思路:

dfs的演化过程:

首先建立第step个盒子的处理函数
在这里插入图片描述
(注释里很详细了,这里就不做过多说明了)
在这里插入图片描述
完整C++代码如下:

#include<iostream>
using namespace std;

int n;
int a[10];
int book[10];

void dfs(int step)
{
	int i;
	if(step == n + 1)
	{
		for(i = 1; i <= n; i++)
		{
			cout << a[i] << " ";
		}
		cout << "\n";
		return;
	}
	
	for(i = 1; i <= n; i++)
	{
		if(book[i] == 0)
		{
			a[step] = i;
			book[i] = 1;
			dfs(step + 1);
			book[i] = 0;
		}
	}
}

int main()
{
	cin >> n;
	dfs(1);
	return 0;
} 

C代码如下:

#include<stdio.h>

int a[10], book[10], n;

void dfs(int step)
{
	int i;
	if(step == n + 1)
	{
		for(i = 1; i <= n; i++)
		{
			printf("%d ", a[i]);
		}
		printf("\n");
		return;
	}
	
	for(i = 1; i <= n; i++)
	{
		if(book[i] == 0)		//表示扑克牌在手上
		{
			a[step] = i;
			book[i] = 1;		//标记扑克牌不在手上 
			
			dfs(step + 1);
			book[i] = 0; 
		}
	}
	
} 

int main()
{
	scanf("%d", &n);
	dfs(1);
	return 0;
}


运行结果:
在这里插入图片描述

之后我会持续更新,如果喜欢我的文章,请记得一键三连哦,点赞关注收藏,你的每一个赞每一份关注每一次收藏都将是我前进路上的无限动力 !!!↖(▔▽▔)↗感谢支持!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值