【算法学习】DFS与BFS


一。深度优先搜索

深度优先搜索是一种枚举所有完整路径以遍历所有情况的搜索方法。

类似树的先根遍历。简单来说,就是一直往深处走,直到找到解或走不下去了为止。

可以使用栈来实现,用栈保存未被检测的结点,结点按照深度优先的次序被访问并依次被压入栈中,并以相反的次序出站进行新的检测。

也可以使用递归实现,相对代码简洁,但使用递归的时候系统会调用系统栈来存放递归中每一层的状态,亦是用到了栈。



举个栗子:

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

分析:

扫描二维码关注公众号,回复: 2505618 查看本文章

岔路口——每件物品选或不选两种选择

死胡同——选择的物品重量总和不能超过V

代码实现:

#include <cstdio>
const int maxn = 30;
int n ,V, maxValue = 0;   //物品件数n,背包容量V, 最大价值maxValue
int w[maxn], c[maxn];     //w[i]为每件物品重量,c[i]为每件物品价值

void DFS(int index, int sumW, int sumC){
	if(index == n){   //已经完成对n件物品的选择(死胡同) 
		if(sumW <= V && sumC > maxValue){
			maxValue = sumC;
		}
		return; 
	}
	DFS(index+1, sumW, sumC);  //不选第index件物品
	DFS(index+1, sumW+w[index], sumC+c[index]);  //选第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);     //初始时为第0件物品,当前总重量和总价值均为0
	printf("%d\n", maxValue);
	return 0; 
} 


上面代码总是把n件物品的选择全部确定后才去更新最大价值。对DFS进行优化,先判断进入第index件物品后能否满足容量不超过V的要求,只有当条件满足时才更新最大价值以及进入这条岔路。

如此通过题目条件的限制来节省DFS计算量的方法称为剪纸。

void DFS(int index, int sumW, int sumC){
	if(index == n){   //已经完成对n件物品的选择(死胡同) 
		return; 
	}
	DFS(index+1, sumW, sumC);  //不选第index件物品
	if(sumW + w[index] <= V){   //加入第index件物品后未超过容量V,才继续 
		if(sumC + c[index] > ans){
			ans = sumC + c[index];  //更新最大价值maxValue 
		}
		DFS(index+1, sumW+w[index], sumC+c[index]);  //选第index件物品
	} 
} 

再举一个栗子,理解一下DFS函数该怎么写:

给定N个整数(可能有负数),从中选择K个数,使得这K个数之和恰好等于一个给定的整数X:如果有多种方案的话,选择他们中元素平方和最大的一个。数据保证这样的方案唯一。

int n, k, x, maxSumSqu = -1, A[maxn];  //序列A中n个数选k个数使其和为x,最大平方和为maxSumSqu
vector<int> temp, ans;                 //temp存放临时方案,ans存放平方和最大的方案

// 当前处理index号整数,当前已选整数个数为nowK 
// 当前已选整数之和为sum,当前已选整数平方和为sumSqu 
void DFS(int index, int nowK, int sum, int sumSqu){
	if(nowK == k && sum == x){
		if(sumSqu > maxSumSqu){   //找到k个数的和为x 
			maxSumSqu = sumSqu;   //更新最大平方和 
			ans = temp;           //更新最优方案 
		}
		return; 
	}
	// 已经处理完n个数,或者超过k个数,或者和超过x,返回
	if(index == n || nowK > k || sum > x) return;
	// 选index号数 
	temp.push_back(A[index]);
	DFS(index+1, nowK + 1, sum + A[index], sumSqu + A[index] * A[index]);
	temp.pop_back();
	// 不选index号数 
	DFS(index+1, nowK, sum, sumSqu);
} 



二。广度优先搜索

BFS一般用队列保存未被检测的结点,且总是按照层次的顺序进行遍历。

类似树的按层次遍历的过程。


以上模板注释:

(1)定义队列q,并将起点s入队

(2)写一个while循环,循环条件是队列q非空

(3)在while循环中,先取出队首元素top,然后访问它(访问可以是任何事情,例如将其输出)。访问完后将其出队。

(4)将top的下一层结点中所有未曾入队的结点入队,并标记他们的层号为now的层号+1,同时设置这些入队的结点已入过队。

(5)返回(2)继续循环


举个栗子:

给出一个m*m的矩阵,矩阵中的元素为0或1。我们称位置(x, y)与其上下左右四个位置(x, y+1)、(x, y-1)、(x+1, y)、(x-1, y)是相邻的。如果矩阵中有若干个1是相邻的(不必两两相邻),那么我们称这些1构成了一个“块”。求给定的矩阵中“块”的个数。


例如上面的6*7的矩阵中,“块”的个数为4。

思路:

枚举每一个位置的元素,如果为0则跳过;如果为1,则使用BFS查询与该位置相邻的四个位置(前提是不出界),判断他们是否为1(如果某个相邻的位置为1,则同样去查询与该位置相邻的四个位置,直到整个“1”块访问完毕)。

而防止走回头路,一般可以设置一个bool型数组inq(即in queue的简写)来记录每个位置是否在BFS中已入过队。

一个小技巧:对当前位置(x, y)来说,由于与其相邻的四个位置分别为(x, y+1)、(x, y-1)、(x+1, y)、(x-1, y),那么可以设置两个增量数组,来表示四个方向(0, 1)、(0, -1)、(1, 0)、(-1, 0)。


这样我们可以使用for循环来枚举四个方向,以确定与当前坐标(nowX, nowY)相邻的四个位置,如下:


代码实现:

#include <cstdio>
#include <queue>
using namespace std;

const int maxn = 100;
struct node{
	int x, y;  //位置(x, y) 
}Node;

int n, m;
int matrix[maxn][maxn];          //01矩阵
bool inq[maxn][maxn] = {false};  //记录位置(x, y)是否已入过队
int X[4] = {0, 0, 1, -1};
int Y[4] = {1, -1, 0, 0};

bool judge(int x, int y){       //判断坐标(x, y)是否需要访问 
	//越界返回false
	if(x>=n || x<0 || y>=m || y<0) return false;
	//当前位置为0,或(x,y)已入过队,返回false
	if(matrix[x][y] == 0 || inq[x][y] == true)  return false;
	//以上都不满足,返回true
	return true; 
} 
//BFS函数访问位置(x,y)所在的块,将该块中所有"1"的inq都设置为true
void BFS(int x, int y){
	queue<node> Q;   //定义队列 
	Node.x = x;      //当前结点坐标(x,y) 
	Node.y = y;
	Q.push(Node);    //将结点Node入队 
	inq[x][y] = true;
	while(!Q.empty()){
		node top = Q.front();  //取出队首元素
		Q.pop();               //队首元素出队
		for(int i = 0; i<4; i++){   //循环四次,得到四个相邻位置 
			int newX = top.x + X[i];
			int newY = top.y + Y[i];
			if(judge(newX, newY)){  //如果新位置(newX, newY)需要访问
				Node.x = newX, Node.y = newY;   //设置Node的坐标为(newX,mewY)
				Q.push(Node);                   //结点Node加入队列 
				inq[newX][newY] = true;         //设置位置(newX, newY)已入过队列 
			} 
		} 
	} 
} 
int main(){
	scanf("%d%d", &n, &m);
	for(int x = 0; x<n; x++){
		for(int y = 0; y<m; y++){
			scanf("%d", &matrix[x][y]);  //读入01矩阵 
		}
	}
	int ans = 0;   //存放块数
	for(int x = 0; x<n; x++){     //枚举每一个位置 
		for(int y = 0; y<m; y++){
			if(matrix[x][y] == 1 && inq[x][y] == false){
				ans ++;
				BFS(x, y);  //访问整个块,将该块所有"1"的inq都标记为true 
			} 
		}
	} 
	printf("%d\n", ans);
	return 0;
} 






进一寸有一寸的欢喜。借此学习下算法。

算了算还有8天考ccf,考多少分无所谓啦。


猜你喜欢

转载自blog.csdn.net/u014322206/article/details/78630085