2024/1/30 dfs与bfs

1 篇文章 0 订阅

想要了解dfs与bfs,就得了解队列和栈。

一、栈与队列

1.栈

栈说白了就是先入后出。把栈类比为一个容器。只有一个口,所以如果我们想要取出最底层也就是最先放入的元素,只能最后取出它。

栈基础操作有如下几种:

  1. push 放入
  2. pop 拿出
  3. empty 是否为空
  4. size 栈的大小
  5. top 获取栈顶元素

下面将用两种方式实现:

  1. 用数组或者链表模拟栈
    #include <iostream>
    using namespace std;
    
    const int N = 100;
    
    int stk[N], top = 0;
    
    int main()
    {
    
    	//使用数组模拟栈
    	int x;
    	cin >> x;
    	//push放入栈中
    	stk[++top] = x;
    	//输出栈顶元素
    	cout << stk[top] << endl;
    	//pop拿出(不管top元素删不删,top--就行)
    	top--;
    	// 常用的就是,获取栈顶元素的同时,弹出栈顶元素
    	int u = stk[top--];
    
    
    	return 0;
    }

    以上是普通静态数组。但是考虑到现实生活由于入栈的元素可能会源源不断地增加,因此我们可以使用动态数组或者链表,这样就无须自行处理数组扩容问题。这里贴出hello算法里对这个的网址。https://www.hello-algo.com/chapter_stack_and_queue/stack/#1icon-default.png?t=N7T8https://www.hello-algo.com/chapter_stack_and_queue/stack/#1

  2. stl库写栈

首先就是 #include <stack>

定义:stack<数据类型> stk;

使用:

  • stk.push(x);
  • stk.pop();
  • stk.top();
  • stk.empty();//true表示空
  • stk.size();//返回正整数
#include <iostream>
#include <stack>
using namespace std;


int main()
{
	stack<int>stk;
	int x;
	cin >> x;
	//放入栈中
	stk.push(x);
	//输出栈顶
	cout << stk.top() << endl;
	//弹出栈顶
	stk.pop();
	return 0;
}

2.队列

队列则是:“先入先出”。「队列 queue」是一种遵循先入先出规则的线性数据结构。顾名思义,队列模拟了排队现象,即新来的人不断加入队列尾部,而位于队列头部的人逐个离开。

队列基础操作有如下几种:

  1. push 放入(从队尾)
  2. pop 拿出(从队首)
  3. empty 是否为空
  4. size 栈的大小
  5. front
#include <iostream>
using namespace std;

const int N = 100;

int q[N], front = 0, tail = 0;

int main()
{

	//使用数组模拟队列
	int x;
	cin >> x;
	//push 放入队列中
	q[++tail] = x;
	//pop 把元素移除(只能从队首)
	front++;
	//size 队列长度
	int size = tail - front + 1;
	//empty 看size是否为0
	//front 
	return q[front];


	return 0;
}
/* 初始化队列 */
queue<int> queue;

/* 元素入队 */
queue.push(1);
queue.push(3);
queue.push(2);
queue.push(5);
queue.push(4);

/* 访问队首元素 */
int front = queue.front();

/* 元素出队 */
queue.pop();

/* 获取队列的长度 */
int size = queue.size();

/* 判断队列是否为空 */
bool empty = queue.empty();

额外:优先队列,双向队列

http://t.csdnimg.cn/KpFjNicon-default.png?t=N7T8http://t.csdnimg.cn/KpFjN5.2   队列 - Hello 算法 (hello-algo.com)icon-default.png?t=N7T8https://www.hello-algo.com/chapter_stack_and_queue/queue/#5215.3   双向队列 - Hello 算法 (hello-algo.com)icon-default.png?t=N7T8https://www.hello-algo.com/chapter_stack_and_queue/deque/

/* 初始化双向队列 */
deque<int> deque;

/* 元素入队 */
deque.push_back(2);   // 添加至队尾
deque.push_back(5);
deque.push_back(4);
deque.push_front(3);  // 添加至队首
deque.push_front(1);

/* 访问元素 */
int front = deque.front(); // 队首元素
int back = deque.back();   // 队尾元素

/* 元素出队 */
deque.pop_front();  // 队首元素出队
deque.pop_back();   // 队尾元素出队

/* 获取双向队列的长度 */
int size = deque.size();

/* 判断双向队列是否为空 */
bool empty = deque.empty();

双向队列兼具栈与队列的逻辑,因此它可以实现这两者的所有应用场景,同时提供更高的自由度

我们知道,软件的“撤销”功能通常使用栈来实现:系统将每次更改操作 push 到栈中,然后通过 pop 实现撤销。然而,考虑到系统资源的限制,软件通常会限制撤销的步数(例如仅允许保存 50 步)。当栈的长度超过 50 时,软件需要在栈底(队首)执行删除操作。但栈无法实现该功能,此时就需要使用双向队列来替代栈。请注意,“撤销”的核心逻辑仍然遵循栈的先入后出原则,只是双向队列能够更加灵活地实现一些额外逻辑。

二、DFS深度优先搜索-----栈

 1.指数型搜索

模板:

void dfs(int step){
    判断边界
    尝试每一种可能 for(i=1;i<=n;i++){
        继续下一步 dfs(step+1)
        }
    返回
}

#include <iostream>

using namespace std;

const int N = 100;

int n;//输入的节点数  
int st[N] = {}; //决策数组 0 表示没有搜索过, 1表示选择 , 2表示没有选择

// 对于每一个点,都需要有一次决策,无论是1还是2 
// 搜索是把所有的情况都考虑 

 // u表示对u进行抉择,已经抉择了u个数
void dfs(int u) {
	// 递归出口,当所有节点都决策完毕时输出结果  
	if (u > n){
		for (int i = 0; i < n; i++) {
			// 只有当第i个节点被选择时才输出 
			if (st[u] = 1){
				cout << i << " ";
			}
		}

	}
	// 选择第u个节点(标记为1)并继续搜索下一个节点
	st[u] = 1;
	dfs(u + 1);

	// 恢复第u个节点的状态(标记为0)并选择不选择第u个节点(标记为2)并继续搜索下一个节点
	st[u] = 0;
	st[u] = 2;
	dfs(u + 1);
	// 继续恢复第u个节点的状态(标记为0)以备下次决策使用  
	st[u] = 0;
}
int main()
{
	cin >> n;

	dfs(1);
	return 0;
}


Python Tutor code visualizer: Visualize code in Python, JavaScript, C, C++, and Javaicon-default.png?t=N7T8https://pythontutor.com/render.html#code=%23include%20%3Ciostream%3E%0A%0Ausing%20namespace%20std%3B%0A%0Aconst%20int%20N%20%3D%20100%3B%0A%0Aint%20n%20,%20st%5BN%5D%20%3D%20%7B%7D%3B%20//%200%20%E8%A1%A8%E7%A4%BA%E6%B2%A1%E6%9C%89%E6%90%9C%E7%B4%A2%E8%BF%87%EF%BC%8C%201%E8%A1%A8%E7%A4%BA%E9%80%89%E6%8B%A9%20%EF%BC%8C%202%E8%A1%A8%E7%A4%BA%E6%B2%A1%E6%9C%89%E9%80%89%E6%8B%A9%0A%0A//%20%E5%AF%B9%E4%BA%8E%E6%AF%8F%E4%B8%80%E4%B8%AA%E7%82%B9%EF%BC%8C%E9%83%BD%E9%9C%80%E8%A6%81%E6%9C%89%E4%B8%80%E6%AC%A1%E5%86%B3%E7%AD%96%EF%BC%8C%E6%97%A0%E8%AE%BA%E6%98%AF1%E8%BF%98%E6%98%AF2%20%0A//%20%E6%90%9C%E7%B4%A2%E6%98%AF%E6%8A%8A%E6%89%80%E6%9C%89%E7%9A%84%E6%83%85%E5%86%B5%E9%83%BD%E8%80%83%E8%99%91%20%0A%0A//%20u%E8%A1%A8%E7%A4%BA%E5%AF%B9u%E8%BF%9B%E8%A1%8C%E6%8A%89%E6%8B%A9%EF%BC%8C%E5%B7%B2%E7%BB%8F%E6%8A%89%E6%8B%A9%E4%BA%86u%E4%B8%AA%E6%95%B0%20%0Avoid%20dfs%28%20int%20u%20%29%20%7B%0A%20%20%20%20%0A%20%20%20%20//%20%E9%80%92%E5%BD%92%E5%87%BA%E5%8F%A3%20%EF%BC%8C%20%E6%AF%8F%E4%B8%80%E4%B8%AA%E7%82%B9%E9%83%BD%E6%8A%89%E6%8B%A9%E8%BF%87%E4%BA%86%20%0A%20%20%20%20if%28u%20%3E%20n%20%29%7B%0A%20%20%20%20%20%20%20%20for%28int%20i%20%3D%201%3B%20i%20%3C%3D%20n%20%3B%20i%2B%2B%20%29%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20//%20%E6%88%91%E4%BB%AC%E5%8F%AA%E6%9C%89%E5%9C%A8%E9%80%89%E6%8B%A9%E7%AC%ACi%E4%B8%AA%E7%82%B9%E7%9A%84%E6%97%B6%E5%80%99%E8%BE%93%E5%87%BA%20%0A%20%20%20%20%20%20%20%20%20%20%20%20if%28st%5Bu%5D%20%3D%3D%201%20%29%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cout%20%3C%3C%20i%20%3C%3C%20%22%20%22%20%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20return%20%3B%0A%20%20%20%20%7D%0A%20%20%20%20%0A%20%20%20%20%0A%20%20%20%20//%20%E9%80%89%E6%8B%A9%0A%20%20%20%20//%20st%5Bu%5D%20%3D%200%3B%20%E8%BF%99%E5%8F%A5%E8%AF%9D%E9%80%9A%E5%B8%B8%E7%9C%81%E7%95%A5%20%0A%20%20%20%20st%5Bu%5D%20%3D%201%3B%0A%20%20%20%20dfs%28u%20%2B%201%29%3B%0A%20%20%20%20%0A%20%20%20%20//%20%E6%81%A2%E5%A4%8D%E7%8E%B0%E5%9C%BA%EF%BC%8C%E6%8E%A7%E5%88%B6%E5%8F%98%E9%87%8F%20%EF%BC%9A%20%E5%8D%95%E4%B8%80%E5%8F%98%E9%87%8F%E5%8E%9F%E5%88%99%20%0A%20%20%20%20st%5Bu%5D%20%3D%200%3B%20%0A%20%20%20%20//%20%E4%B8%8D%E9%80%89%E6%8B%A9%20%0A%20%20%20%20st%5Bu%5D%20%3D%202%3B%0A%20%20%20%20dfs%28u%20%2B%201%29%3B%0A%20%20%20%20%0A%20%20%20%20//%20%E7%BB%A7%E7%BB%AD%E6%81%A2%E5%A4%8D%E7%8E%B0%E5%9C%BA%0A%20%20%20%20st%5Bu%5D%20%3D%200%3B%20%0A%7D%0A%0Aint%20main%28%29%7B%0A%20%20n%3D3%3B%0A%20%20%20%20dfs%281%29%3B%0A%20%20%20%20%0A%20%20%20%20return%200%3B%0A%7D&cppShowMemAddrs=true&cumulative=false&curInstr=38&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=cpp_g%2B%2B9.3.0&rawInputLstJSON=%5B%5D&textReferences=false

例题1 P2036 Perket

#include <iostream>  
#include <algorithm>  
#include <cstdio>  
#include <cmath>  
using namespace std;  
  
// 全局变量声明  
int n; // n代表数字的个数  
int mindif = 999999999; // mindif用于存储最小差值,初始化为一个非常大的数  
int s[10], b[10]; // s数组和b数组分别用于存储输入的数字和它们对应的值  
  
// perket函数是一个递归函数,用于计算所有可能的组合  
// u代表当前处理的数字的索引  
// sp代表当前选择的s数组中数字的乘积  
// bs代表当前选择的b数组中数字的和  
void perket(int u, int sp, int bs) {  
    // 递归终止条件:当u大于n时,表示所有数字都已经处理完毕  
    if (u > n) {  
        // 如果bs为0,表示没有选择任何b数组中的数字,直接返回  
        if (!bs) {  
            return;  
        }  
        // 更新最小差值  
        mindif = min(mindif, abs(sp - bs));  
        return;  
    }  
      
    // 递归调用1:不选择当前数字  
    perket(u + 1, sp, bs);  
      
    // 递归调用2:选择当前数字  
    perket(u + 1, sp * s[u], bs + b[u]);  
}  
  
int main() {  
    // 输入数字的个数  
    cin >> n;  
      
    // 输入每个数字及其对应的值  
    for (int i = 1; i <= n; i++) {  
        cin >> s[i] >> b[i];  
    }  
      
    // 调用perket函数开始计算  
    perket(1, 1, 0);  
      
    // 输出最小差值  
    cout << mindif << endl;  
      
    return 0;  
}

Python Tutor code visualizer: Visualize code in Python, JavaScript, C, C++, and Javaicon-default.png?t=N7T8https://pythontutor.com/render.html#code=%23include%20%3Ciostream%3E%20%20%0A%23include%20%3Calgorithm%3E%20%20%0A%23include%20%3Ccstdio%3E%20%20%0A%23include%20%3Ccmath%3E%20%20%0Ausing%20namespace%20std%3B%20%20%0A%20%20%0A//%20%E5%85%A8%E5%B1%80%E5%8F%98%E9%87%8F%E5%A3%B0%E6%98%8E%20%20%0Aint%20n%3B%20//%20n%E4%BB%A3%E8%A1%A8%E6%95%B0%E5%AD%97%E7%9A%84%E4%B8%AA%E6%95%B0%20%20%0Aint%20mindif%20%3D%20999999999%3B%20//%20mindif%E7%94%A8%E4%BA%8E%E5%AD%98%E5%82%A8%E6%9C%80%E5%B0%8F%E5%B7%AE%E5%80%BC%EF%BC%8C%E5%88%9D%E5%A7%8B%E5%8C%96%E4%B8%BA%E4%B8%80%E4%B8%AA%E9%9D%9E%E5%B8%B8%E5%A4%A7%E7%9A%84%E6%95%B0%20%20%0Aint%20s%5B10%5D,%20b%5B10%5D%3B%20//%20s%E6%95%B0%E7%BB%84%E5%92%8Cb%E6%95%B0%E7%BB%84%E5%88%86%E5%88%AB%E7%94%A8%E4%BA%8E%E5%AD%98%E5%82%A8%E8%BE%93%E5%85%A5%E7%9A%84%E6%95%B0%E5%AD%97%E5%92%8C%E5%AE%83%E4%BB%AC%E5%AF%B9%E5%BA%94%E7%9A%84%E5%80%BC%20%20%0A%20%20%0A//%20perket%E5%87%BD%E6%95%B0%E6%98%AF%E4%B8%80%E4%B8%AA%E9%80%92%E5%BD%92%E5%87%BD%E6%95%B0%EF%BC%8C%E7%94%A8%E4%BA%8E%E8%AE%A1%E7%AE%97%E6%89%80%E6%9C%89%E5%8F%AF%E8%83%BD%E7%9A%84%E7%BB%84%E5%90%88%20%20%0A//%20u%E4%BB%A3%E8%A1%A8%E5%BD%93%E5%89%8D%E5%A4%84%E7%90%86%E7%9A%84%E6%95%B0%E5%AD%97%E7%9A%84%E7%B4%A2%E5%BC%95%20%20%0A//%20sp%E4%BB%A3%E8%A1%A8%E5%BD%93%E5%89%8D%E9%80%89%E6%8B%A9%E7%9A%84s%E6%95%B0%E7%BB%84%E4%B8%AD%E6%95%B0%E5%AD%97%E7%9A%84%E4%B9%98%E7%A7%AF%20%20%0A//%20bs%E4%BB%A3%E8%A1%A8%E5%BD%93%E5%89%8D%E9%80%89%E6%8B%A9%E7%9A%84b%E6%95%B0%E7%BB%84%E4%B8%AD%E6%95%B0%E5%AD%97%E7%9A%84%E5%92%8C%20%20%0Avoid%20perket%28int%20u,%20int%20sp,%20int%20bs%29%20%7B%20%20%0A%20%20%20%20//%20%E9%80%92%E5%BD%92%E7%BB%88%E6%AD%A2%E6%9D%A1%E4%BB%B6%EF%BC%9A%E5%BD%93u%E5%A4%A7%E4%BA%8En%E6%97%B6%EF%BC%8C%E8%A1%A8%E7%A4%BA%E6%89%80%E6%9C%89%E6%95%B0%E5%AD%97%E9%83%BD%E5%B7%B2%E7%BB%8F%E5%A4%84%E7%90%86%E5%AE%8C%E6%AF%95%20%20%0A%20%20%20%20if%20%28u%20%3E%20n%29%20%7B%20%20%0A%20%20%20%20%20%20%20%20//%20%E5%A6%82%E6%9E%9Cbs%E4%B8%BA0%EF%BC%8C%E8%A1%A8%E7%A4%BA%E6%B2%A1%E6%9C%89%E9%80%89%E6%8B%A9%E4%BB%BB%E4%BD%95b%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E6%95%B0%E5%AD%97%EF%BC%8C%E7%9B%B4%E6%8E%A5%E8%BF%94%E5%9B%9E%20%20%0A%20%20%20%20%20%20%20%20if%20%28!bs%29%20%7B%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20return%3B%20%20%0A%20%20%20%20%20%20%20%20%7D%20%20%0A%20%20%20%20%20%20%20%20//%20%E6%9B%B4%E6%96%B0%E6%9C%80%E5%B0%8F%E5%B7%AE%E5%80%BC%20%20%0A%20%20%20%20%20%20%20%20mindif%20%3D%20min%28mindif,%20abs%28sp%20-%20bs%29%29%3B%20%20%0A%20%20%20%20%20%20%20%20return%3B%20%20%0A%20%20%20%20%7D%20%20%0A%20%20%20%20%20%20%0A%20%20%20%20//%20%E9%80%92%E5%BD%92%E8%B0%83%E7%94%A81%EF%BC%9A%E4%B8%8D%E9%80%89%E6%8B%A9%E5%BD%93%E5%89%8D%E6%95%B0%E5%AD%97%20%20%0A%20%20%20%20perket%28u%20%2B%201,%20sp,%20bs%29%3B%20%20%0A%20%20%20%20%20%20%0A%20%20%20%20//%20%E9%80%92%E5%BD%92%E8%B0%83%E7%94%A82%EF%BC%9A%E9%80%89%E6%8B%A9%E5%BD%93%E5%89%8D%E6%95%B0%E5%AD%97%20%20%0A%20%20%20%20perket%28u%20%2B%201,%20sp%20*%20s%5Bu%5D,%20bs%20%2B%20b%5Bu%5D%29%3B%20%20%0A%7D%20%20%0A%20%20%0Aint%20main%28%29%20%7B%20%20%0A%20%20%20%20//%20%E8%BE%93%E5%85%A5%E6%95%B0%E5%AD%97%E7%9A%84%E4%B8%AA%E6%95%B0%20%20%0A%20%20%20%20n%3D4%3B%0A%20%20%20%20%20%20%0A%20%20%20%20//%20%E8%BE%93%E5%85%A5%E6%AF%8F%E4%B8%AA%E6%95%B0%E5%AD%97%E5%8F%8A%E5%85%B6%E5%AF%B9%E5%BA%94%E7%9A%84%E5%80%BC%20%20%0A%20%20%20%20s%5B0%5D%3D1,s%5B1%5D%3D2,s%5B2%5D%3D3,s%5B3%5D%3D4%3B%0A%20%20%20%20b%5B0%5D%3D7,b%5B1%5D%3D6,b%5B2%5D%3D8,b%5B3%5D%3D9%3B%0A%20%20%20%20%20%20%0A%20%20%20%20//%20%E8%B0%83%E7%94%A8perket%E5%87%BD%E6%95%B0%E5%BC%80%E5%A7%8B%E8%AE%A1%E7%AE%97%20%20%0A%20%20%20%20perket%281,%201,%200%29%3B%20%20%0A%20%20%20%20%20%20%0A%20%20%20%20//%20%E8%BE%93%E5%87%BA%E6%9C%80%E5%B0%8F%E5%B7%AE%E5%80%BC%20%20%0A%20%20%20%20cout%20%3C%3C%20mindif%20%3C%3C%20endl%3B%20%20%0A%20%20%20%20%20%20%0A%20%20%20%20return%200%3B%20%20%0A%7D&cppShowMemAddrs=true&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=cpp_g%2B%2B9.3.0&rawInputLstJSON=%5B%5D&textReferences=false

在给定的代码中,每次递归的两种选择(选择当前数字和不选择当前数字)可能会产生重复的结果。这意味着,递归函数perket可能会计算出重复的组合。

为了解决这个问题,可以在递归过程中添加一些逻辑来跳过已经计算过的组合,或者使用其他方法来避免重复计算。例如,可以维护一个集合或哈希集合来跟踪已经计算过的组合,并在递归函数中进行检查。

如果不采取措施避免重复计算,则可能会导致递归函数的时间复杂度增加,因为重复计算相同的组合会导致更多的递归调用。在处理大量数据时,这可能会导致性能问题。

2.组合型搜索

例题2 全排列

【题意】
先给一个正整数 ( 1 < = n < = 10 ),输出所有全排列。
什么是全排列,例如n=3,输出所有组合,并且按字典序输出:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
每个全排列一行,相邻两个数用空格隔开(最后一个数后面没有空格)
【输入格式】
一行一个整数n。
【输出格式】
输出1~n的所有全排列。
【样例输入】
3
【样例输出】
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1

#include <iostream>   
#include <algorithm>  
#include <cstdio>        
#include <cmath>         
using namespace std;     
  
int n, ans[10], used[10];   // 定义全局变量,n表示要选取的数字个数,ans数组用于存放结果,used数组用于标记数字是否被使用过  
  
void dfs(int cnt){          // 定义深度优先搜索函数,cnt表示当前已经选取的数字个数  
    if (cnt > n) {           // 如果已经选取的数字个数大于n,说明已经选取了所有数字,输出结果并返回  
        for (int i = 1; i <= n; i++) { // 遍历所有数字并输出  
            cout << ans[i] << " ";  
        }  
        cout << endl;  
        return;  
    }  
    for (int i = 1; i <= n; i++){ // 枚举所有数字  
        if (used[i] == 0) {      // 如果数字i还没有被使用过  
            ans[cnt] = i;        // 将数字i放入当前位置  
            used[i] = 1;          // 标记数字i为已使用  
            dfs(cnt + 1);        // 递归搜索下一个位置  
               
            ans[cnt] = 0;         // 撤销当前位置的选择,即清空当前位置  
            used[i] = 0;           // 撤销对数字i的选择,即将其标记为未使用  
        }  
    }  
}  
  
int main() {                  // 主函数  
    cin >> n;                  // 输入要选取的数字个数n  
    dfs(1);                     // 从第一个位置开始搜索  
    return 0;                   // 程序正常结束  
}

这段代码中的回溯是通过以下步骤实现的:

1. 在dfs函数中,首先检查是否已经选取了所有数字(即`cnt`是否大于`n`)。如果是,则输出当前结果并返回。
2. 然后,函数使用一个循环来枚举所有数字。如果某个数字还没有被使用过(即`used[i]`等于0),则进行以下操作:

  •     将该数字放入当前位置(即`ans[cnt] = i`)。
  •     标记该数字为已使用(即`used[i] = 1`)。
  •     递归调用`dfs(cnt + 1)`来搜索下一个位置。

3. 在递归调用返回后,执行回溯操作:

  •     撤销当前位置的选择,即将`ans[cnt]`重置为0,表示该位置还没有被选择。
  •     撤销对数字`i`的选择,即将`used[i]`重置为0,表示数字`i`现在可以再次被选择。

4. 重复上述过程,直到所有可能的排列都被探索完为止。

通过这种方式,代码能够撤销之前的决策,尝试其他可能的路径,从而全面地探索所有可能的解空间。

还有几个题,以后补补(太赶了qaq

三、BFS广度优先搜索-----队列

bfs个人感觉就是,同时往每个方向都试试,最先得到的就是最好的。

它是一种盲目搜寻法,目的是系统地展开并检查图中的所有节点,以找寻结果。并不考虑结果的可能位置,彻底地搜索整张图,直到找到结果为止。宽度优先搜索是一种对图进行搜索的算法。假设我们一开始位于某个顶点(即起点),此 时并不知道图的整体结构,而我们的目的是从起点开始顺着边搜索,直到到达指定顶点(即终 点)。在此过程中每走到一个顶点,就会判断一次它是否为终点。广度优先搜索会优先从离起点 近的顶点开始搜索。

b站有个视频非常清晰,这里贴下链接

【广度优先搜索BFS遍历-动画演示过程,希望对大家有所帮助~】

https://www.bilibili.com/video/BV1bP4y1K7Tr?vd_source=7ccfb0770f378b7710213a50fbacfc17icon-default.png?t=N7T8https://www.bilibili.com/video/BV1bP4y1K7Tr?vd_source=7ccfb0770f378b7710213a50fbacfc17

这个大佬写的也很清晰:

http://t.csdnimg.cn/sw4pZicon-default.png?t=N7T8http://t.csdnimg.cn/sw4pZ

int BFS(Node start, Node target) {
    入队(初始状态);
    visited[初始状态] = true;
    while(!空队) {
        for() { // 状态转换
            Node node = 队首元素;
 
            对node的处理,生成新node状态;
 
            if (visited[node] == true)
                continue;
            if (node == target) {
                输出答案;
                return 0;
            }
            v[node] = true;
            入队(node);
        }
    出队();
    }
    
}

 例题3 P1135 奇怪的电梯

P1135 奇怪的电梯 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P1135

#include <iostream>  // 引入输入输出流    
#include <cstring>  // 引入C风格的字符串处理函数库,用于memset  
#include <queue>  // 引入队列容器  
using namespace std;  
  
const int N = 201;  // 定义常量N为201,表示图的最大节点数  
  
int n, a, b;  // n为图的节点数,a为起点,b为终点  
int k[N], vis[N], ans[N];  // k数组存储每个节点的关联值,vis数组标记节点是否被访问过,ans数组存储从起点到每个节点的最短路径长度  
  
// BFS函数,从节点a开始搜索  
void bfs(int a) {  
    queue<int> Q;  // 定义一个队列Q  
    Q.push(a);  // 将起点a加入队列  
    ans[a] = 0;  // 设置起点a到自身的距离为0  
  
    // 当队列不为空时继续循环  
    while (!Q.empty()) {  
        int u = Q.front();  // 取出队首元素  
        Q.pop();  // 弹出队首元素  
  
        // 如果当前节点是目标节点b,则结束搜索  
        if (u == b) {  
            break;  
        }  
        // 如果当前节点已经被访问过,则跳过  
        if (vis[u] == 1) {  
            continue;  
        }  
  
        vis[u] = 1;  // 标记当前节点为已访问  
  
        // 计算通过当前节点的关联值k[u]可以到达的两个节点v1和v2  
        int v1 = u + k[u];  
        // 如果v1是一个有效的节点且未被访问过,则将其加入队列,并更新ans[v1]  
        if (v1 >= 1 && v1 <= n && vis[v1] == 0) {  
            Q.push(v1);  
            ans[v1] = min(ans[v1], ans[u] + 1);  
        }  
  
        int v2 = u - k[u];  
        // 如果v2是一个有效的节点且未被访问过,则将其加入队列,并更新ans[v2]  
        if (v2 >= 1 && v2 <= n && vis[v2] == 0) {  
            Q.push(v2);  
            ans[v2] = min(ans[v2], ans[u] + 1);  
        }  
    }  
}  
  
int main() {  
    cin >> n >> a >> b;  // 输入节点数n,起点a和终点b  
  
    // 初始化ans数组为一个很大的数(表示无穷大)  
    memset(ans, 0x3f, sizeof(ans));  
  
    // 输入每个节点的关联值k[i]  
    for (int i = 1; i <= n; i++) {  
        cin >> k[i];  
    }  
  
    bfs(a);  // 从起点a开始BFS搜索  
  
    // 如果从a到b没有路径,则输出-1,否则输出最短路径长度  
    if (ans[b] == 1e9) {  
        cout << -1 << endl;  
    } else {  
        cout << ans[b] << endl;  
    }  
    return 0;  
}

四、BFS与DFS区别

bfs 遍历节点是先进先出,dfs遍历节点是先进后出;
bfs是按层次访问的,dfs 是按照一个路径一直访问到底,当前节点没有未访问的邻居节点时,然后回溯到上一个节点,不断的尝试,直到访问到目标节点或所有节点都已访问。
bfs 适用于求源点与目标节点距离最近的情况,例如:求最短路径。dfs 更适合于求解一个任意符合方案中的一个或者遍历所有情况,例如:全排列、拓扑排序、求到达某一点的任意一条路径。
————————————————

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                        
原文链接:https://blog.csdn.net/aliyonghang/article/details/128724989

五、练习题

1.DFS

P1036 [NOIP2002 普及组] 选数

P1036 [NOIP2002 普及组] 选数 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P1036

#include <iostream>

using namespace std;

int n, k, x[22], ans=0;

bool prime(int a) {
	for (int i = 2; i * i <= a; i++) {
		if (a % i == 0) {
			return 0;
		}
	}
	return 1;
}

void dfs(int u,int nums,int sum) {
	if (nums == k) {
		if (prime(sum)) {
			ans++;
		}
		return;
	}
	else if (u <= n) {
		dfs(u + 1, nums, sum);
		dfs(u + 1, nums + 1, sum + x[u]);
	}
}

int main() {
	cin >> n >> k;
	for (int i = 1; i <= n; i++) {
		cin >> x[i];
	}
	dfs(1, 0, 0);

	cout << ans << endl;

	return 0;

}

一个大佬的做法:

P1036 [NOIP2002 普及组] 选数 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)icon-default.png?t=N7T8https://www.luogu.com.cn/problem/solution/P1036

 #include <iostream>    
#include <stack>    
#include <cstring>    
#include <cmath>     
#include <algorithm>     
using namespace std;

 
int k, n, x[21];  // k表示需要选择的数字个数,n表示数组x的长度,x是存储输入数字的数组  
long long count1; // 用于统计满足条件的方案数  

// 判断一个数是否为质数  
bool prime(int a) {
	for (int i = 2; i * i <= a; i++) 
		if (a % i == 0)
			return false; 
	return true;  
}

// 深度优先搜索函数,用于遍历所有可能的组合  
void dfs(int u, int sum, int startx) {
	if (u == k) { // 如果已经选择了k个数  
		if (prime(sum)) { // 判断这k个数的和是否为质数  
			count1++; // 如果是,则方案数加1  
		}
		return; // 结束递归  
	}
	for (int i = startx; i < n; i++) // 从startx开始遍历数组x  
		dfs(u + 1, sum + x[i], i + 1); // 递归调用,选择下一个数,并更新当前和与起始位置  
	return;
}

int main() {
	cin >> n >> k;  
	for (int i = 0; i < n; i++) {
		cin >> x[i];   
	}
	dfs(0, 0, 0); // 从第0个数开始,当前和为0,起始位置为0,开始递归搜索  
	cout << count1 << endl; 
	return 0;  
}

这里这一段值得单独提出来说说

for (int i = startx; i < n; i++) // 从startx开始遍历数组x  
		dfs(u + 1, sum + x[i], i + 1); // 递归调用,选择下一个数,并更新当前和与起始位置

大佬这里用到了不降原则,简单来说,就是让startx作为一个升序排列,防止算重。 

就是当前的初始值
也就是最小的,符合不降原则的参数
这样一来,就可以去重了!

P1219 [USACO1.5] 八皇后 Checker Challenge

P1219 [USACO1.5] 八皇后 Checker Challenge - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P1219

这道题我们分别为左斜线(/)右斜线(\ ) 单独建立数组。对于左斜线上的所有点来说,row+col的数字是一样的;而对于右斜线上的所有点来说,同一斜线上,row-col的差是一样的,但是row-col的值可能存在负值。那么就需要加上一个偏移量。把数组向右平移几个单位(在这里平移n就够)。因为右斜线上所有数都向右平移了n,所以总的数组中的关系不变。

同理我们看左斜线上的点,我们发现它们的row+col是个定值。 因为这是个加法,不会出现负数的情况,所以我们不需要在末尾加上n。

#include <iostream>

using namespace std;

const int N = 15;

int n, row[N], col[N], dui1[N + N + 100], dui2[N + N + 100];

int ans = 0;

void deal() {
	ans++;
	if (ans <= 3) {
		for (int i = 1; i <= n; i++) {
			cout << row[i] << " ";
		}
		cout << endl;
	}
}
void dfs(int h) {
// 递归出口
// 1-n行都放完了
	if (h > n) {
		deal();
		return;
	}
	for (int j = 1; j <= n; j++) {
		if (col[j] == 1 || dui1[h - j + 100] == 1 || dui2[h + j] == 1) {
			continue;
		}

		col[j] = 1;
		dui1[h - j + 100] = 1;
		dui2[h + j] = 1;
		row[h] = j;
		dfs(h + 1);

		col[j] = 0;
		dui1[h - j + 100] = 0;
		dui2[h + j] = 0;
		row[h] = 0;
	}
}
int main() {

	cin >> n;
	dfs(1);
	cout << ans << endl;
	return 0;

}

P1644 跳马问题

P1644 跳马问题 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P1644

 

#include<iostream>
#include<cmath>
#include<stdio.h>
using namespace std;

int n, m;
int total = 0;

void dfs(int x, int y) {
	if (y<0||x > m || y > n) {
		return;
	}
	if (x == m && y == n) {
		total++;
	}
	else
	{
		dfs(x + 1, y + 2);
		dfs(x + 2, y + 1);
		dfs(x + 1, y - 2);
		dfs(x + 2, y - 1);
	}
}
int main()
{
	cin >> n >> m;
	dfs(0, 0);

	cout << total << endl;
	return 0;
}

 

P1605 迷宫

P1605 迷宫 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P1605

#include<iostream>   
using namespace std;  
 
const int N = 10;  
  

int n, m, t;  // n和m分别表示迷宫的行数和列数,t表示障碍物的数量  
int map[N][N], vis[N][N], flag[N][N];  // map表示迷宫,vis表示已访问过的位置,flag表示障碍物位置  
int sx, sy, fx, fy, ans = 0;  // sx和sy表示起点,fx和fy表示终点,ans表示从起点到终点的路径数量  
int direction[4][2] = { {1,0},{-1,0},{0,1},{0,-1} };  // 定义四个方向的偏移量  
  
// DFS函数,用于搜索从(x, y)到终点的所有路径  
void dfs(int x, int y) {  
	// 如果当前位置是终点,则路径数量加1  
	if (x == fx && y == fy) {  
		ans++;  
		return;  
	}  
	// 遍历四个方向  
	for (int i = 0; i < 4; i++) {  
		// 计算新位置  
		int new_x = x + direction[i][0];  
		int new_y = y + direction[i][1];  
  
		// 判断新位置是否合法(在迷宫范围内,未被访问过,不是障碍物)  
		if (new_x<1 || new_x>n || new_y < 1 || new_y > m || vis[new_x][new_y] == 1 || flag[new_x][new_y] == 1)   
			continue;  
  
		// 标记新位置为已访问  
		vis[new_x][new_y] = 1;  
		// 递归调用DFS函数  
		dfs(new_x, new_y);  
  
		// 回溯,将新位置标记为未访问  
		vis[new_x][new_y] = 0;  
	}  
}  
  
int main()  
{  
	// 输入迷宫大小、起点和终点坐标  
	cin >> n >> m >> t;  
	cin >> sx >> sy >> fx >> fy;  
	  
	// 输入障碍物位置,并在flag数组中标记  
	for (int i = 1; i <= t; i++) {  
		int x, y;  
		cin >> x >> y;  
		flag[x][y] = 1;  
	}  
	  
	// 标记起点为已访问  
	vis[sx][sy] = 1;  
	// 从起点开始DFS搜索  
	dfs(sx, sy);  
  
	// 输出从起点到终点的路径数量  
	cout << ans << endl;  
	return 0;  
}

2.BFS 

P1605 迷宫

P1605 迷宫 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P1605

P1443 马的遍历

P1443 马的遍历 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P1443

以后可能陆陆续续会更新一点这几篇内容,先记录一下

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值