数据结构与算法:PART 2 搜索与图论 查找中静态与动态差异仅在于是否可以动态增删元素
动态查找在几何计算上应用广泛
搜索种类: 无信息搜索:
1.DFS:递归结束条件的选择+状态标记+递归后的恢复(也可以用栈来搭建出来 ) 2.BFS:模拟队列 q[N], d[N] 使用d数组标记状态 3.搜索:解空间的搜索往往需要dfs+剪枝,bfs用来找最短路 4.树和图的存储:邻接表 h[N], e[N], ne[N], idx 5.树和图的遍历:遍历不用像搜索解空间一样递归后恢复,只用遍历一次即可
有信息搜索:
A搜索算法
建立搜索模型:
DFS 优势:可以获取子树的大小 核心:顺序 842. 排列数字 - AcWing题库
DFS人称暴力搜索,对应的是一个多叉树的形式,DFS的搜索顺序和前序遍历其实一样 ,但是实际上存储结构只会存一条路径,回溯的时候就会消失,不需要真正建立树,但是一定要注意恢复现场
![深度优先遍历.png](D:\typora note\55289_0cd4222d73-深度优先遍历.png)
举例:全排列做法,使用回溯(还可以使用剪枝)
全排列做法: 时间复杂度为 O(n*n!)。
空间复杂度为 O(n)。
算法:
用 path 数组保存排列,当排列的长度为 n 时,是一种方案,输出。\
用 state 数组表示数字是否用过。当 state[i] 为 1 时:i 已经被用过,state[i] 为 0 时,i 没有被用过。
dfs(i) 表示的含义是:在 path[i] 处填写数字,然后递归的在下一个位置填写数字。
回溯:第 i 个位置填写某个数字的所有情况都遍历后, 第 i 个位置填写下一个数字。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include <iostream> using namespace std;const int N = 10 ;int path[N];int state[N];int n;void dfs (int u) { if (u > n) { for (int i = 1 ; i <= n; i++) cout << path[i] << " " ; cout << endl; } for (int i = 1 ; i <= n; i++) { if (!state[i]) { path[u] = i; state[i] = 1 ; dfs (u + 1 ); state[i] = 0 ; } } } int main () { cin >> n; dfs (1 ); }
DFS另一种应用:皇后位置 c++直接完整版本代码:(LeetCode 版本)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 class Solution {private :vector<vector<string>> result; void backtracking (int n, int row, vector<string>& chessboard) { if (row == n) { result.push_back (chessboard); return ; } for (int col = 0 ; col < n; col++) { if (isValid (row, col, chessboard, n)) { chessboard[row][col] = 'Q' ; backtracking (n, row + 1 , chessboard); chessboard[row][col] = '.' ; } } } bool isValid (int row, int col, vector<string>& chessboard, int n) { int count = 0 ; for (int i = 0 ; i < row; i++) { if (chessboard[i][col] == 'Q' ) { return false ; } } for (int i = row - 1 , j = col - 1 ; i >=0 && j >= 0 ; i--, j--) { if (chessboard[i][j] == 'Q' ) { return false ; } } for (int i = row - 1 , j = col + 1 ; i >= 0 && j < n; i--, j++) { if (chessboard[i][j] == 'Q' ) { return false ; } } return true ; } public : vector<vector<string>> solveNQueens (int n) { result.clear (); std::vector<std::string> chessboard (n, std::string(n, '.' )) ; backtracking (n, 0 , chessboard); return result; } };
c++数组版本皇后问题:老子自己写的!(双手叉腰)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 #include <iostream> #include <vector> using namespace std;const int maxn=9 ;int solu[maxn]={0 };int qn[maxn]={0 };bool isValid (int posj,int posi) { if (posi==0 )return true ; for (int i=0 ;i<posi;i++) { if (qn[i]==posj)return false ; } for (int i=posi-1 ;i>=0 ;i--) { if (posi-i==posj-qn[i])return false ; } for (int i=posi-1 ;i>=0 ;i--) { if (posi-i==qn[i]-posj)return false ; } return true ; } void backtracing (int n,int row) { if (row==n){ cout<<"bottom reached\n" ; cout<<"solution:\n" ; for (int i=0 ;i<n;i++) { cout<<"row " <<i+1 <<" equals to " <<qn[i]+1 <<" \n" ; } cout<<"go find other solution\n\n" ; return ; } else { for (int i=0 ;i<n;i++) { qn[row]=i; if (isValid (qn[row],row)) { backtracing (n,row+1 ); } } } } int main () { int n; cin>>n; backtracing (n,0 ); }
BFS(使用二维数组) 原因:宽搜的性质:辐射性向外搜索(想想一层一层向外扩张的信号)
844. 走迷宫 - AcWing题库
注意:
dp问题和最短路问题其实是互通的
边的权重都是1的时候才可以使用BFS求最短路,一般都用最短路
dp问题不能用最短路算法(最短路时间一般都高)
第一次搜到的点才是最短的点,之后的都不是
不同于深搜,宽搜一般有一个框架
广搜的框架
初始状态放到队列中
写一个while循环,只要队列不空,在循环内每一次拿出队头,扩展队头
往上下左右扩展的技巧:使用向量来表示
代码(如该道题)
void bfs(int a, int b): 广度优遍历函数。输入的是起点坐标。
queue q;:用来存储每一步走到的点。
while(!q.empty())循环:循环依次取出同一步数能走到的点,再往前走一步。
int dx[4] = {0, 1, 0, -1}, dy[4] = {-1, 0, 1, 0};:一个点往下一步走得时候,可以往上下左右四方向走。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #include <cstring> #include <iostream> #include <queue> using namespace std;typedef pair<int , int > PII;const int N = 110 ;int g[N][N];int f[N][N];int n, m;void bfs (int a, int b) { queue<PII> q; q.push ({a, b}); while (!q.empty ()) { PII start = q.front (); q.pop (); g[start.first][start.second] = 1 ; int dx[4 ] = {0 , 1 , 0 , -1 }, dy[4 ] = {-1 , 0 , 1 , 0 }; for (int i = 0 ; i < 4 ; i++) { int x = start.first + dx[i], y = start.second + dy[i]; if (g[x][y] == 0 ) { g[x][y] = 1 ; f[x][y] = f[start.first][start.second] + 1 ; q.push ({x, y}); } } } cout << f[n][m]; } int main () { memset (g, 1 , sizeof (g)); cin >> n >>m; for (int i = 1 ; i <= n; i++) { for (int j = 1 ; j <= m; j++) { cin >> g[i][j]; } } bfs (1 ,1 ); }
树和图的DFS,BFS 树和图的DFS,BFS就是特殊的搜索(因为有那个环)
树和图的遍历时间复杂度都是o(n+m)
树与图的存储选择 稠密图:邻接矩阵
稀疏图:邻接表
无向图实际上就是有向图进行一个对称
有向图的存储方式
邻接矩阵: 同BFS,开二维数组存就完事了
优化:使用vector向量
1 2 3 4 5 6 7 8 9 int V,num; cin>>V>>num; vector<vector<int >> graph (V); vector<vector<int >> price (V); for (int i=0 ;i<V;i++) { graph[i].resize (V); price[i].resize (V); }
邻接表: 使用数组模拟链表(还是注意idx的作用类似于分配器),有多少个点开多少个单链表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 #include <cstring> int h[N], e[N], ne[N], idx;void add (int a, int b) { e[idx] = b; ne[idx] = h[a]; h[a] = idx ; idx++; } void add_to_tail (int x) { e[idx]=x; if (head==-1 { ne[idx]=head; head=idx; idx++; return ; } int i=head; if (head!=-1 )while (ne[i]!=-1 )i=ne[i]; ne[idx]=-1 ; ne[i]=idx; idx++; } void dele (int a,int b){ if (e[h[a]]==b)h[a]=ne[h[a]]; else for (int i=h[a];ne[i]!=-1 ;i=ne[i]) { if (e[ne[i]]==b) { ne[i]=ne[ne[i]]; } } } bool find (int a,int b){ for (int i=h[a],i!=-1 ;i=ne[i]) { if (e[i]==b)return true ; } return false ; } idx = 0 ; memset (h, -1 , sizeof h);
另一种选择:使用vector+list来构建(很方便)
树图DFS 模板: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 这个模板同样是使用邻接表来存储的 bool st[N];int dfs (int u) { st[u] = true ; 这里是要对DFS进行的操作部分 for (int i = h[u]; i != -1 ; i = ne[i]) { int j = e[i]; if (!st[j]) dfs (j); } }
846. 树的重心 - AcWing题库
树的重心可能不唯一,但是树是唯一的
该题解法:遍历删掉所有点之后连通块的最大值,之后在一堆最大值里面对比寻找出来最小的就行
发现:获取了子树的大小也就获得了剩余树的大小(即全部节点数量减去子树的size就好)
本题的本质是树的dfs, 每次dfs可以确定以u为重心的最大连通块的节点数,并且更新一下ans。
也就是说,dfs并不直接返回答案,而是在每次更新中迭代一次答案。
这样的套路会经常用到,在 树的dfs 题目中
答案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 #include <iostream> #include <algorithm> #include <cstring> using namespace std;const int N = 1e5 + 10 ; const int M = 2 * N; int h[N]; int e[M]; int ne[M]; int idx; int n; int ans = N; bool st[N]; void add (int a, int b) { e[idx] = b, ne[idx] = h[a], h[a] = idx++; } int dfs (int u) { int res = 0 ; st[u] = true ; int sum = 1 ; for (int i = h[u]; i != -1 ; i = ne[i]) { int j = e[i]; if (!st[j]) { int s = dfs (j); res = max (res, s); sum += s; } } res = max (res, n - sum); ans = min (res, ans); return sum; } int main () { memset (h, -1 , sizeof h); cin >> n; for (int i = 0 ; i < n - 1 ; i++) { int a, b; cin >> a >> b; add (a, b), add (b, a); } dfs (1 ); cout << ans << endl; return 0 ; }
树图BFS 思想:从1开始不断扩展一层节点,然后不断向下辐射
自闭环在dp问题中及逆行讨论
模板:
初始状态放到队列中
写一个while循环,只要队列不空,在循环内每一次拿出队头,扩展队头的所有邻点
只考虑第一次遍历,只要没有被遍历过,将x入队
更新路径距离
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 int ne[N],e[N],h[N];queue<int > q; bool st[N];st[1 ] = true ; q.push (1 ); while (q.size ()){ int t = q.front (); q.pop (); for (int i = h[t]; i != -1 ; i = ne[i]) { int j = e[i]; if (!st[j]) { 该干嘛干嘛,进行需要的操作 st[j] = true ; q.push (j); } } }
847. 图中点的层次 - AcWing题库
该题答案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 #include <cstring> #include <iostream> using namespace std;const int N=1e5 +10 ;int h[N], e[N], idx, ne[N];int d[N]; int n, m; int q[N]; void add (int a, int b) { e[idx]=b,ne[idx]=h[a],h[a]=idx++; } int bfs () { int hh=0 ,tt=0 ; q[0 ]=1 ; memset (d,-1 ,sizeof d); d[1 ]=0 ; while (hh<=tt) { int t=q[hh++]; for (int i=h[t];i!=-1 ;i=ne[i]) { int j=e[i]; if (d[j]==-1 ) { d[j]=d[t]+1 ; q[++tt] = j; } } } return d[n]; } int main () { cin>>n>>m; memset (h,-1 ,sizeof h); for (int i=0 ;i<m;i++) { int a,b; cin>>a>>b; add (a,b); } cout<<bfs ()<<endl; }
一致代价BFS:改进版本BFS
拓扑排序:BFS的一种应用(AOV网,流程能否正确执行) 拓扑、dfs、并查集都可以图中判断有没有环, floyd可以找最小环
仅针对有向图,无向图没得拓扑 定义:每条边都是起点在终点的前面,即所有的边都是从前指向后面的
这种算一个拓扑
这种就不算了(有一个环,只要有环就一定木大 )
可以证明:有向无环一定可以整成一个拓扑图
如何进行一个求 一个有向图有入度和出度,由所有都是从前指向后,则所有入度为0的点都可以排在当前最前面的位置
一个无环图一定至少存在一个入度为0的点
有向无环的top序不唯一
思路:
所有入度为0的点进行入队
进行一个BFS,每一次出队一个队头,然后找出所有出队列这个点发出的边,删除边,同时边的另一侧的点的入度 -1。
如果d[j]==0,说明j之前的都排序好了,入队j就完事了
如果所有点都进过队列,则可以拓扑排序,输出所有顶点。否则输出-1,代表不可以进行拓扑排序。
模板: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 bool topsort () { int hh = 0 , tt = -1 ; for (int i = 1 ; i <= n; i ++ ) if (!d[i]) q[ ++ tt] = i; while (hh <= tt) { int t = q[hh ++ ]; for (int i = h[t]; i != -1 ; i = ne[i]) { int j = e[i]; if (-- d[j] == 0 ) q[ ++ tt] = j; } } return tt == n - 1 ; }
848. 有向图的拓扑序列 - AcWing题库
AcWing 848. $\color{green}{拓扑排序–思路介绍+图解模拟+详细代码注释 }$ - AcWing
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 #include <iostream> #include <cstring> #include <algorithm> using namespace std;const int N = 100010 ;int e[N], ne[N], idx;int h[N];int q[N], hh = 0 , tt = -1 ;int n, m;int d[N];void add (int a, int b) { e[idx] = b, ne[idx] = h[a], h[a] = idx++; } void topsort () { for (int i = 1 ; i <= n; i++){ if (d[i] == 0 ) q[++tt] = i; } while (tt >= hh){ int a = q[hh++]; for (int i = h[a]; i != -1 ; i = ne[i]){ int b = e[i]; d[b]--; if (d[b] == 0 ) q[++tt] = b; } } if (tt == n - 1 ){ for (int i = 0 ; i < n; i++){ cout << q[i] << " " ; } } else cout << -1 ; } int main () { cin >> n >> m; memset (h, -1 , sizeof h); while (m -- ){ int a, b; cin >> a >> b; d[b]++; add (a, b); } topsort (); return 0 ; }
动态查找:二叉搜索树 构建树:每个左孩子都比双亲小,每个右孩子都比双亲大
插入节点: 删除节点 叶子节点 直接删除
节点p仅有一棵子树: • 直接用p的左子树(或右子树)取代p的位置而成为f的一棵子树。即原来p是f的左子树,则p的子树成为f的左子树;原来p是f的右子树,则p的子树成为f的右子树
即类似链表的删除操作(懂得都懂)
节点p既有左子树又有右子树: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 #include <iostream> #include <string> using namespace std;const int M=1024 ;int tree[M]={0 };int char_to_int (char a) { return (int )(a-'0' ); } void add_tree (int tree[],string a,int str_pos,int tree_pos,int judge) { int length=a.size (); if (str_pos>length)return ; int num=char_to_int (a[str_pos]); if (tree[tree_pos]==-1 ) { tree[tree_pos]=num; judge=0 ; return ; } else if (num>=tree[tree_pos])tree_pos=2 *tree_pos+1 ; else tree_pos=2 *tree_pos; add_tree (tree,a,str_pos,tree_pos,judge); } int main () { int n; cin>>n; string ans; cin>>ans; int total=ans.size (); for (int i=1 ;i<=M;i++) { tree[i]=-1 ; } tree[1 ]=char_to_int (ans[0 ]); for (int i=1 ;i<total;i++) { add_tree (tree,ans,i,1 ,1 ); } cout<<endl; int str_com[M]; string str_a; for (int time=0 ;time<n;time++) { if (time==6 )break ; for (int i=1 ;i<=M;i++) { str_com[i]=-1 ; } cin>>str_a; str_com[1 ]=char_to_int (str_a[0 ]); for (int i=1 ;i<total;i++) { add_tree (str_com,str_a,i,1 ,1 ); } int judge=1 ; for (int i=1 ;i<=M;i++) { if (str_com[i]!=tree[i]) { cout<<"NO\n" ; judge=0 ; break ; } } if (judge==1 )cout<<"YES\n" ; } int time; cin>>time; return 0 ; }
动态查找:kd树(图形相关的东西会涉及,很重要的一种数据结构)🙏 空间划分树:
二维树:最近邻查找
实际就使用了剪枝(即切除不可能的子树)
最短路问题 朴素Dijkstra算法(和堆有关)(面试也有) 书的作者写的算法通常都很长
与bf算法最大区别:所有边只松弛一次(bf每条边多次)(这里很有可能数据结构考)
堆优化:Dijkstra
应用场景:(什么时候使用该算法)
计算机网络中网络传输问题(参考ele实验室路由器)与协议
迷宫问题(往下看图形化篇章)
核心思想:贪心
迪杰斯特拉算法适用于求正权有向图中,源点到其余各个节点的最短路径。注意:图中可以有环,但不能有负权边。
思路: s:当前已经确定最短距离的点
初始化距离(只有起点的距离是确定的)
开始循环迭代n次,找到不在s中的距离最近的点
用 t来更新其它所有点的距离(就能更新起点到每个点最短距离)
模板:(朴素版本的Dijkstra)复杂度:n^2 849. Dijkstra求最短路 I - AcWing题库
AcWing 849. Dijkstra求最短路 I:图解 详细代码(图解) - AcWing
注意这里读入都是从1开始
dist注意初始化为+∞
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 int g[N][N]; int dist[N]; bool st[N]; const int INF=0x3f ;int dijkstra () { memset (dist, 0x3f , sizeof dist); dist[1 ] = 0 ; for (int i = 0 ; i < n - 1 ; i ++ ) { int t = -1 ; for (int j = 1 ; j <= n; j ++ ) { if (!st[j] && (t == -1 || dist[t] > dist[j])) {t = j;} } 想想dijkstra的步骤,每一次都是先找到所有里面最近的点 for (int j = 1 ; j <= n; j ++ ) {使用一开始寻找到的点逐个遍历所有其它可以到达的点进行比较并更新 dist[j] = min (dist[j], dist[t] + g[t][j]); } st[t] = true ; } if (dist[n] == 0x3f3f3f3f ) return -1 ; return dist[n]; } for (int i=1 ;i<=n;i++) { for (int j=1 ;j<=n;j++) { int tem; cin>>tem; if (tem==0 )g[i][j]=INF; else g[i][j]=tem; } }
对于该题有重边和自环,因此需要考虑一下处理
多重边仅保留路径最短的那一条
自环肯定不会被纳入最短路,可以不考虑
修改(任意一个点到其它所有点) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #include <iostream> #include <cstring> using namespace std;const int INF=0x3f3f3f3f ;const int N=60 ;int n; int g[N][N]; int dist[N]; bool st[N]; void dijkstra (int s) { s=s+1 ; memset (dist, 0x3f3f3f3f , sizeof dist); dist[s] = 0 ; for (int i = 0 ; i < n - 1 ; i ++ ) { int t = -1 ; for (int j = 1 ; j <= n; j ++ ) if (!st[j] && (t == -1 || dist[t] > dist[j])) t = j; for (int j = 1 ; j <= n; j ++ ) dist[j] = min (dist[j], dist[t] + g[t][j]); st[t] = true ; } for (int i=1 ;i<=n;i++) { if (i!=s&&dist[i]!=0x3f3f3f3f )cout<<dist[i]<<' ' ; else if (i!=s&&dist[i]==0x3f3f3f3f )cout<<-1 <<' ' ; } } int main () { int s; cin>>n>>s; for (int i=1 ;i<=n;i++) { for (int j=1 ;j<=n;j++) { int tem; cin>>tem; if (tem==0 )g[i][j]=INF; else g[i][j]=tem; } } dijkstra (s); }
题目模板 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 #include <iostream> #include <cstring> #include <algorithm> using namespace std;const int N = 510 , M = 100010 ;const int inf = 0x3f3f3f3f ;int n, m;int g[N][N], dist[N];bool vis[N];int dijkstra () { memset (vis, 0 , sizeof vis); memset (dist, 0x3f , sizeof dist); dist[1 ] = 0 ; for (int i = 1 ; i <= n; i ++ ) { int t = -1 ; for (int j = 1 ; j <= n; j ++ ) { if (!vis[j] && (t == -1 || dist[j] < dist[t])) { t = j; } } vis[t] = 1 ; for (int j = 1 ; j <= n; j ++ ) { if (dist[j] > g[t][j] + dist[t]) { dist[j] = g[t][j] + dist[t]; } } } if (dist[n] * 2 > inf) { return -1 ; } return dist[n]; } int main () { cin >> n >> m; memset (g, 0x3f , sizeof g); while (m -- ) { int in, out, weight; cin >> in >> out >> weight; g[in][out] = min (g[in][out], weight); } cout << dijkstra () << "\n" ; return 0 ; }
模板:(优化版本的Dijkstra) 实际有点像广搜的
如果是一个稀疏图,但是还要用遍历矩阵(n2复杂度)容易时间爆掉
使用小根堆优化
也就是可以优化第一步,可以将第一步时间变成o1,第三步就要修改成mlogn,第二步不变
即通过堆来存储,就可以减少计算量
堆的实现方式:
手写堆(好处在于方便维护)
优先队列(c++的stl库已经预置好了),但是不支持修改任意一个元素 ,解决思路:每一次改变就插入一个新的数(好处方便操作,坏处会多了操作数,时间复杂度变成mlogm,但是方便,)
因此直接使用c++内置的优先队列就ok
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 typedef pair<int , int > PII;int n; int h[N], w[N], e[N], ne[N], idx; int dist[N]; bool st[N]; int dijkstra () { memset (dist, 0x3f , sizeof dist); dist[1 ] = 0 ; priority_queue<PII, vector<PII>, greater<PII>> heap; heap.push ({0 , 1 }); while (heap.size ()) { auto t = heap.top (); heap.pop (); int ver = t.second, distance = t.first; if (st[ver]) continue ; st[ver] = true ; for (int i = h[ver]; i != -1 ; i = ne[i]) { int j = e[i]; if (dist[j] > distance + w[i]) { dist[j] = distance + w[i]; heap.push ({dist[j], j}); } } } if (dist[n] == 0x3f3f3f3f ) return -1 ; return dist[n]; }
全部:
有关优先队列库的函数:
(110条消息) 【C++】优先队列详细讲解(原理+STL库调用)_半路杀出来的小黑同学的博客-CSDN博客_c++ 优先队列遍历
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 #include <cstring> #include <iostream> #include <algorithm> #include <queue> using namespace std;typedef pair<int , int > PII;const int N = 1e6 + 10 ;int n, m;int h[N], w[N], e[N], ne[N], idx;int dist[N];bool st[N];void add (int a, int b, int c) { e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ; } int dijkstra () { memset (dist, 0x3f , sizeof dist); dist[1 ] = 0 ; priority_queue<PII, vector<PII>, greater<PII>> heap; heap.push ({0 , 1 }); while (heap.size ()) { auto t = heap.top (); heap.pop (); int ver = t.second, distance = t.first; if (st[ver]) continue ; st[ver] = true ; for (int i = h[ver]; i != -1 ; i = ne[i]) { int j = e[i]; if (dist[j] > dist[ver] + w[i]) { dist[j] = dist[ver] + w[i]; heap.push ({dist[j], j}); } } } if (dist[n] == 0x3f3f3f3f ) return -1 ; return dist[n]; } int main () { scanf ("%d%d" , &n, &m); memset (h, -1 , sizeof h); while (m -- ) { int a, b, c; scanf ("%d%d%d" , &a, &b, &c); add (a, b, c); } cout << dijkstra () << endl; return 0 ; }
Bellman-Ford算法(bf算法) 复杂度通常为o(VE)
会多次访问同一条边
解决问题:
可能有负边回路,Dijkstra如果判断的图中有负会导致距离成正无穷
部分只能使用bf解决(如果有边数限制,就只能用bf )
很牛逼:可以用任意存储方式,也可以用结构体
应用:比如坐火车不能无限坐火车
思路:
两重循环,第二次循环所有边
n次迭代,更新思路有点类似dijistra
可以判断是否存在负回路 ,即是可以用来找负环(如果限制了走的边的个数就可以用来找最短路)的,但时间较高,一般使用spfa寻找负环
模板: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 int n, m; int dist[N]; struct Edge { int a, b, w; }edges[M]; int bellman_ford () { memset (dist, 0x3f , sizeof dist); dist[1 ] = 0 ; for (int i = 0 ; i < n; i ++ ) { for (int j = 0 ; j < m; j ++ ) { int a = edges[j].a, b = edges[j].b, w = edges[j].w; if (dist[b] > dist[a] + w) dist[b] = dist[a] + w; } } if (dist[n] > 0x3f3f3f3f / 2 ) return -1 ; return dist[n]; }
AcWing 853. 有边数限制的最短路 - AcWing
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #include <iostream> #include <cstring> using namespace std;const int N = 510 , M = 10010 ;struct Edge { int a; int b; int w; } e[M]; int dist[N];int back[N];int n, m, k;int bellman_ford () { memset (dist, 0x3f , sizeof dist); dist[1 ] = 0 ; for (int i = 0 ; i < k; i++) { memcpy (back, dist, sizeof dist); for (int j = 0 ; j < m; j++) { int a = e[j].a, b = e[j].b, w = e[j].w; dist[b] = min (dist[b], back[a] + w); } } if (dist[n] > 0x3f3f3f3f / 2 ) return -1 ; else return dist[n]; } int main () { scanf ("%d%d%d" , &n, &m, &k); for (int i = 0 ; i < m; i++) { int a, b, w; scanf ("%d%d%d" , &a, &b, &w); e[i] = {a, b, w}; } int res = bellman_ford (); if (res == -1 ) puts ("impossible" ); else cout << res; return 0 ; }
spfa(队列优化版本的Bellman-Ford) 处理稀疏图,使用邻接表进行存储
可以视为各方面都更加优秀的bf,就是对于bf进行一个优化
AcWing 851. spfa求最短路—图解–$\color{red}{海绵宝宝来喽}$ - AcWing
其实正权图也可以用spfa去整
bf缺点:每一次都要迭代所有边进行更新(每一次都要全部扫描)
优化:使用广搜 进行优化
思路:
起点放到队列里面去
只要队列不空,就是广搜的迭代操作
即我更新过谁,我再用谁去更新别人,我后面的人才会更小
图中一定不能有负环(但是可以有负权边),不然不能用
时间复杂度 平均情况下 O(m),最坏情况下 O(nm), n 表示点数,m 表示边数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 int n; int h[N], w[N], e[N], ne[N], idx; int dist[N]; bool st[N]; int spfa () { memset (dist, 0x3f , sizeof dist); dist[1 ] = 0 ; queue<int > q; q.push (1 ); st[1 ] = true ; while (q.size ()) { auto t = q.front (); q.pop (); st[t] = false ; for (int i = h[t]; i != -1 ; i = ne[i]) { int j = e[i]; if (dist[j] > dist[t] + w[i]) { dist[j] = dist[t] + w[i]; if (!st[j]) { q.push (j); st[j] = true ; } } } } if (dist[n] == 0x3f3f3f3f ) return -1 ; return dist[n]; }
851. spfa求最短路 - AcWing题库
spfa判断是否存在负环 时间复杂度是 O(nm), n 表示点数,m 表示边数
原理:使用dist数组表示1号数组到其它数组的距离,使用cnt表示当前最短路的边数
模板: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 int n; int h[N], w[N], e[N], ne[N], idx; int dist[N], cnt[N]; bool st[N]; bool spfa () { queue<int > q; for (int i = 1 ; i <= n; i ++ ) { q.push (i); st[i] = true ; } while (q.size ()) { auto t = q.front (); q.pop (); st[t] = false ; for (int i = h[t]; i != -1 ; i = ne[i]) { int j = e[i]; if (dist[j] > dist[t] + w[i]) { dist[j] = dist[t] + w[i]; cnt[j] = cnt[t] + 1 ; if (cnt[j] >= n) return true ; if (!st[j]) { q.push (j); st[j] = true ; } } } } return false ; }
FLOYID算法(Dijkstra进一步输出全部) 很简单,针对所有路
原理:动态规划
d[k,i,j],从i点出发,经过k个点,最终到达j点
那么有d[k,i,j]=d[k-1,i,k]+d[k-1,k,j]//右边各经过了k-1个点
可以优化,去掉一维,即d[i,j]=d[i,k]=d[k,j]
应用: 可以对有负边进行操作(但是不能有负回路)
FLOYID可以求出所有点之间的最短路径(很暴力很牛逼很简洁很费时)
使用邻接矩阵存储所有边
模板 时间:n^3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 for (int i = 1 ; i <= n; i ++ ) for (int j = 1 ; j <= n; j ++ ) if (i == j) d[i][j] = 0 ; else d[i][j] = INF; void floyd () { for (int k = 1 ; k <= n; k ++ ) for (int i = 1 ; i <= n; i ++ ) for (int j = 1 ; j <= n; j ++ ) d[i][j] = min (d[i][j], d[i][k] + d[k][j]); }
对于求floyid,类似spfa判断无穷情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <iostream> using namespace std;const int N = 210 , M = 2e+10 , INF = 1e9 ;int n, m, k, x, y, z;int d[N][N];void floyd () { for (int k = 1 ; k <= n; k++) for (int i = 1 ; i <= n; i++) for (int j = 1 ; j <= n; j++) d[i][j] = min (d[i][j], d[i][k] + d[k][j]); } int main () { cin >> n >> m >> k; for (int i = 1 ; i <= n; i++) for (int j = 1 ; j <= n; j++) if (i == j) d[i][j] = 0 ; else d[i][j] = INF; while (m--) { cin >> x >> y >> z; d[x][y] = min (d[x][y], z); } floyd (); while (k--) { cin >> x >> y; if (d[x][y] > INF/2 ) puts ("impossible" ); else cout << d[x][y] << endl; } return 0 ; }
prim最小生成树 非常非常像dijkstra
处理步骤:
首先全部初始化为+无穷
开始循环迭代,用t更新到集合的距离 (dijkstra这里是t更新到起点的距离)
st[t]=true(真的真的很像dijkstra)
点到集合的距离指点到集合的所有路径中选择最短的那一条
注意一点 ,这个二维数组要下标是1到n
一次循环只会更新一个最近点
每次更新最近点之后就会在该点可触及范围内更新与其它点的距离
朴素prim模板 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 int n; int g[N][N]; int dist[N]; bool st[N]; const int INF=0x3f int prim (){ memset (dist, 0x3f , sizeof dist); int res = 0 ; for (int i = 0 ; i < n; i ++ ) { int t = -1 ; for (int j = 1 ; j <= n; j ++ ) { if (!st[j] && (t == -1 || dist[t] > dist[j])) { t = j; } } if (i && dist[t] == INF) return INF; if (i) res += dist[t]; st[t] = true ; for (int j = 1 ; j <= n; j ++ ) dist[j] = min (dist[j], g[t][j]); } return res; }
858. Prim算法求最小生成树 - AcWing题库
AcWing 858. Prim算法求最小生成树:图解+详细代码注释(带上了保存路径) - AcWing
AcWing 858. $\Huge\color{MediumTurquoise}{prim 与dijkstra的区别}$ - AcWing
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 #include <iostream> #include <cstring> #include <algorithm> using namespace std;const int N = 510 ;int g[N][N];int dt[N];int st[N];int pre[N];int n, m;void prim () { memset (dt,0x3f , sizeof (dt)); int res= 0 ; dt[1 ] = 0 ; for (int i = 0 ; i < n; i++) { int t = -1 ; for (int j = 1 ; j <= n; j++) { if (!st[j] && (t == -1 || dt[j] < dt[t])) t = j; } if (dt[t] == 0x3f3f3f3f ) { cout << "impossible" ; return ; } st[t] = 1 ; res += dt[t]; for (int i = 1 ; i <= n; i++) { if (dt[i] > g[t][i] && !st[i]) { dt[i] = g[t][i]; pre[i] = t; } } } cout << res; } void getPath () { for (int i = n; i > 1 ; i--) { cout << i <<" " << pre[i] << " " << endl; } } int main () { memset (g, 0x3f , sizeof (g)); cin >> n >> m; while (m --) { int a, b, w; cin >> a >> b >> w; g[a][b] = g[b][a] = min (g[a][b],w); } prim (); return 0 ; }
与Dijkstra类似,Prim算法也可以用堆优化,优先队列代替堆,优化的Prim算法时间复杂度O(mlogn)。适用于稀疏图,但是稀疏图的时候求最小生成树,Kruskal 算法更加实用。
克鲁斯卡尔最小生成树 时间是mlogm,思路也简单(不用考虑prim的)
核心:删除,判断
优点:
稀疏图尽管使用kruskal就ok
kruskal也可以处理负权边的哦
kruskal同bf算法,对于存储方式不挑剔,能存就行,可以用结构体
将所有边按照权重从小到大进行排序(这里用原有的排序就行)
枚举每条边ab(权重是c),如果ab不连通(即在 ),就将这条边加入到集合里面
只要边数是节点数-1就ok辽
这里建议参考并查集例题,837题
837. 连通块中点的数量 - AcWing题库
kruskal是使用一个并查集去维护的,堆用于去做dijkstra
AcWing 859. Kruskal算法求最小生成树—$\color{red}{海绵宝宝来喽}$ - AcWing
模板 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 #include <algorithm> int n, m; int p[N]; struct Edge { int a, b, w; bool operator < (const Edge &W)const { return w < W.w; } }edges[M]; int find (int x) { if (p[x] != x) p[x] = find (p[x]); return p[x]; } int kruskal () { sort (edges, edges + m); for (int i = 1 ; i <= n; i ++ ) p[i] = i; int res = 0 , cnt = 0 ; for (int i = 0 ; i < m; i ++ ) { int a = edges[i].a, b = edges[i].b, w = edges[i].w; a = find (a), b = find (b); if (a != b) { p[a] = b; res += w; cnt ++ ; } } if (cnt < n - 1 ) return INF; return res; }
题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 #include <iostream> #include <cstring> #include <algorithm> using namespace std;const int N = 100010 ;int p[N];struct E { int a; int b; int w; bool operator < (const E& rhs){ return this ->w < rhs.w; } }edg[N * 2 ]; int res = 0 ;int n, m;int cnt = 0 ;int find (int a) { if (p[a] != a) p[a] = find (p[a]); return p[a]; } void klskr () { for (int i = 1 ; i <= m; i++) { int pa = find (edg[i].a); int pb = find (edg[i].b); if (pa != pb){ res += edg[i].w; p[pa] = pb; cnt ++; } } } int main () { cin >> n >> m; for (int i = 1 ; i <= n; i++) p[i] = i; for (int i = 1 ; i <= m; i++){ int a, b , c; cin >> a >> b >>c; edg[i] = {a, b, c}; } sort (edg + 1 , edg + m + 1 ); klskr (); if (cnt < n - 1 ) { cout<< "impossible" ; return 0 ; } cout << res; return 0 ; }
二分图 染色法判定: 使用性质:一个图是二分图当且仅当图中不含奇数环/奇圈
奇数环:环,而且当中边的数量是奇数
时间复杂度是 O(n+m), n 表示点数,m 表示边数
思路:一条边的两个端点一定属于不同的集合 ,一个联通块中只要一个点的颜色确定,其它点的颜色都确定(白色只能与黑色相邻,黑色只能与白色相邻),由于不存在奇数环,染色过程中一定没有矛盾
因此需要将整个图遍历一遍,由于图没有权,负边,因此可以用dfs,bfs来弄,模板这里使用dfs
因此,如果有矛盾,那肯定就不是了呗
则判定两个关键函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 int n; int h[N], e[M], ne[M], idx; int color[N]; bool dfs (int u, int c) { color[u] = c; for (int i = h[u]; i != -1 ; i = ne[i]) { int j = e[i]; if (color[j] == -1 ) { if (!dfs (j, !c)) return false ; } else if (color[j] == c) return false ; } return true ; } bool check () { memset (color, -1 , sizeof color); bool flag = true ; for (int i = 1 ; i <= n; i ++ ) if (color[i] == -1 ) if (!dfs (i, 0 )) { flag = false ; break ; } return flag; }
随便整一道例题康康:
AcWing 860. 染色法判定二分图—$\color{green}{详细代码注释+图解}$ - AcWing
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 #include <iostream> #include <cstring> #include <algorithm> using namespace std;const int N = 100010 * 2 ;int e[N], ne[N], idx;int h[N];int color[N];int n, m;void add (int a, int b) { e[idx] = b, ne[idx]= h[a], h[a] = idx++; } bool dfs (int u, int c) { color[u] = c; for (int i = h[u]; i!= -1 ; i = ne[i]) { int b = e[i]; if (!color[b]) { if (!dfs (b, 3 - c)) return false ; } else if (color[b] && color[b] != 3 - c) { return false ; } } return true ; } int main () { memset (h, -1 , sizeof h); cin >> n >> m; for (int i = 1 ; i <= m; i++) { int a, b; cin >> a >> b; add (a, b), add (b, a); } for (int i = 1 ; i <= n; i++) { if (!color[i]) { if (!dfs (i, 1 )) { cout << "No" << endl; return 0 ; } } } cout << "Yes" << endl; return 0 ; }
匈牙利算法 作用:给定一个二分图,求它的最大匹配 (可以在一个较短的时间内匹配)
把它想象成月老匹配对象算法
一些概念:
二分图的匹配:给定一个二分图 G,在 G 的一个子图 M 中,M 的边集 {E}中的任意两条边都不依附于同一个顶点,则称 M 是一个匹配。
二分图的最大匹配:所有匹配中包含边数最多的一组匹配被称为二分图的最大匹配,其边数即为最大匹配数
基本思路:
对于每个男生从前往后看(果然开始当月老看了),如果女生处于单身状态则可以匹配
再看下一个,某个男生匹配某个女生,如果该女生已经被匹配,这个男生也不会善罢甘休(什么黄毛剧情),如果之前匹配的男生a可以更换女生(越来越奇怪了),男生b会与这个女生匹配(他甚至专门用了绿色绝了)
所以很多时候做错是没事的,错过是真的会后悔
时间复杂度(n*m)(最坏情况 ),实际上时间是会很少的
注意:如果对左边进行遍历,那么边就要存储左边指向右边的
模板: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 int n1, n2; int h[N], e[M], ne[M], idx; int match[N]; bool st[N]; bool find (int x) { for (int i = h[x]; i != -1 ; i = ne[i]) { int j = e[i]; if (!st[j]) { st[j] = true ; if (match[j] == 0 || find (match[j])) { match[j] = x; return true ; } } } return false ; } int res = 0 ;for (int i = 1 ; i <= n1; i ++ ){ memset (st, false , sizeof st); if (find (i)) res ++ ; }
AcWing 861. 二分图的最大匹配 - AcWing
AcWing 861. 二分图最大匹配 - 如果你看了别的题解,仍然对递归写法的st数组心存疑虑,看看这里【21.12.7更新】 - AcWing
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 #include <iostream> #include <cstring> using namespace std;const int N = 510 , M = 100010 ;int n1,n2,m;int h[N],ne[M],e[M],idx;bool st[N];int match[N];void add (int a , int b) { e[idx] = b, ne[idx] = h[a], h[a] = idx++; } void init () { memset (h,-1 ,sizeof h); } int find (int x) { for (int i = h[x] ; i != -1 ;i = ne[i]) { int j = e[i]; if (!st[j]) { st[j] = true ; if (!match[j]||find (match[j])) { match[j] = x; return true ; } } } return false ; } int main () { init (); cin>>n1>>n2>>m; while (m--) { int a,b; cin>>a>>b; add (a,b); } int res = 0 ; for (int i = 1 ; i <= n1 ;i ++) { memset (st,false ,sizeof st); if (find (i)) res++; } cout<<res<<endl; }
关键路径(AOE网,拓扑plus版本) 基础背景知识: AOE网:AOV网基础上加上每个节点(工序)所消耗的时间
AOE网要建立再活动之间制约关系没有矛盾的基础之上,再来分析完成整个工程需要多少时间,或者为了缩短完成工程所需时间,应当加快哪些活动
关键路径:从源点到汇点具有最大长度的路径:
关键活动:关键路径上的活动(实际上就可以理解成多种活动并行执行,有些耗时最长的活动来衡量整体工程的执行时间)->修改关键活动才会对整个工期长度进行实际减少
核心:寻找关键活动 方法:找到所有活动的最早开始时间和最晚开始时间,并且进行比较,如果相等意味着此活动是关键活动,活动间的路径是关键路径
贪心 与最短路关系: dp问题可以视为特殊的最短路问题,即最短路包含dp,是一个没有环存在的最短路,dp实际是深搜,保证可以到达终点,但不保证是最短路
暴力搜索 回溯法 回溯法效率:低,但比for强
解决问题:for嵌套无法解决时候 理论基础:来源于递归的回溯(隐藏在递归的下面),与二叉树遍历,深度优先搜索混在一起进行,二叉树在返回的过程中也用到了回溯
可解决问题:传统for暴力搜索方式无法解决
组合问题:(如一串数字询问可以组合的方式数)组合强调没有顺序
排列问题:排列强调元素顺序
切割问题:(字符串,加上某种限定条件,询问切割方式)
子集问题:找出所有子集
棋盘问题:(典中典皇后,数独)
小总结,用于解决组合和排列
ADT:可以抽象为一个树形结构 ,可以抽象为一棵n叉树,树的宽度是集合的大小 ,即每个节点处理的集合的大小 ->这里通常使用for循环来处理。树的深度(纵方向)也就是递归的深度 ,
回溯模板 1 2 3 4 5 6 7 8 9 10 11 12 13 void backtracking (参数) { if (终止条件) { 存放结果; return ; } for (选择:本层集合中元素(树中节点的数量就是集合的大小)) { 处理节点; backtracking (路径,选择列表); 回溯, 撤销处理结果 } }
棋盘典中点:皇后位置 c++直接完整版本代码:(LeetCode 版本)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 class Solution {private :vector<vector<string>> result; void backtracking (int n, int row, vector<string>& chessboard) { if (row == n) { result.push_back (chessboard); return ; } for (int col = 0 ; col < n; col++) { if (isValid (row, col, chessboard, n)) { chessboard[row][col] = 'Q' ; backtracking (n, row + 1 , chessboard); chessboard[row][col] = '.' ; } } } bool isValid (int row, int col, vector<string>& chessboard, int n) { int count = 0 ; for (int i = 0 ; i < row; i++) { if (chessboard[i][col] == 'Q' ) { return false ; } } for (int i = row - 1 , j = col - 1 ; i >=0 && j >= 0 ; i--, j--) { if (chessboard[i][j] == 'Q' ) { return false ; } } for (int i = row - 1 , j = col + 1 ; i >= 0 && j < n; i--, j++) { if (chessboard[i][j] == 'Q' ) { return false ; } } return true ; } public : vector<vector<string>> solveNQueens (int n) { result.clear (); std::vector<std::string> chessboard (n, std::string(n, '.' )) ; backtracking (n, 0 , chessboard); return result; } };
c++数组版本皇后问题:老子自己写的!(双手叉腰)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 #include <iostream> #include <vector> using namespace std;const int maxn=9 ;int solu[maxn]={0 };int qn[maxn]={0 };bool isValid (int posj,int posi) { if (posi==0 )return true ; for (int i=0 ;i<posi;i++) { if (qn[i]==posj)return false ; } for (int i=posi-1 ;i>=0 ;i--) { if (posi-i==posj-qn[i])return false ; } for (int i=posi-1 ;i>=0 ;i--) { if (posi-i==qn[i]-posj)return false ; } return true ; } void backtracing (int n,int row) { if (row==n){ cout<<"bottom reached\n" ; cout<<"solution:\n" ; for (int i=0 ;i<n;i++) { cout<<"row " <<i+1 <<" equals to " <<qn[i]+1 <<" \n" ; } cout<<"go find other solution\n\n" ; return ; } else { for (int i=0 ;i<n;i++) { qn[row]=i; if (isValid (qn[row],row)) { backtracing (n,row+1 ); } } } } int main () { int n; cin>>n; backtracing (n,0 ); }
图形化数据结构学习: 栈:非递归汉诺塔 难点:不能使用递归(如果使用递归在画图之前会全部更新完,最后只会给出结果),改成非递归方式
本质: 将递归结构在循环中实现
老师解法:
参考博客:(99条消息) 汉诺塔的非递归实现(堆栈)_.别拖至春天.的博客-CSDN博客_汉诺塔的非递归实现
![屏幕截图 2022-10-12 155526](D:\typora note\屏幕截图 2022-10-12 155526.png)
代码以及注释:
ops是空栈,用于保存指令,并且在n==1的时候才进行弹出操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 if (!ops.empty ()) { op current = ops.top (); ops.pop (); if (current.n == 1 ) { int d = stacks[current.start]->top (); stacks[current.start]->pop (); stacks[current.end]->push (d); } else { ops.push (op (current.n - 1 , current.via, current.start, current.end)); ops.push (op (1 , current.start, current.via, current.end)); ops.push (op (current.n - 1 , current.start, current.end, current.via)); } }
南大关于非递归的应用(对于C语言作为状态机的分析讲解 )
队列:迷宫生成及解决 重点解决问题:回溯法
顺便要学习的算法:迪杰斯特拉解法(可以等一等再看看)