dfs
#include<iostream> using namespace std; const int N=10; int n; int path[N];//存放每一步的数字 bool vis[N];//表示该数字是否被访问 void dfs(int u) { if(u==n)//u==n表示每一步已经放好了数字,输出 { for(int i=0;i<n;i++)cout<<path[i]<<" "; puts(""); } else{ for(int i=1;i<=n;i++) { if(!vis[i])//判断数字是否被访问 { path[u]=i; vis[i]=true; dfs(u+1); vis[i]=false; } } } } int main() { cin>>n; dfs(0); }
n皇后
#include<iostream> using namespace std; const int N=30; char m[N][N]; bool r[N],l[N],x1[N],x2[N]; void dfs(int u,int n) { if(u==n){ for(int i=0;i<n;i++) { for(int j=0;j<n;j++) cout<<m[i][j]; puts(""); } puts(""); } else{ for(int i=0;i<n;i++) { if(!r[u]&&!l[i]&&!x1[u+i]&&!x2[i-u+n-1]) { m[u][i]='Q'; r[u]=true; l[i]=true; x1[u+i]=true; x2[i-u+n-1]=true; dfs(u+1,n); r[u]=false; m[u][i]='.'; l[i]=false; x1[u+i]=false; x2[i-u+n-1]=false; } } } } int main() { int n; cin>>n; for(int i=0;i<n;i++) for(int j=0;j<n;j++) m[i][j]='.'; dfs(0,n); }
首先思路,依次给每一行放皇后,判断能否放,能放要满足行列对角线无皇后,然后设置对应数组为true,然后放下一行,这个遍历后要回溯,即把之前改变的变回去
bfs
走迷宫
#include<iostream> #include<queue> #include<algorithm> #include<cstring> using namespace std; typedef pair<int,int> PII;//定义打包,这个要学会 int n,m; const int N=110; int a[N][N],d[N][N]; int dfs() { memset(d,-1,sizeof d);//d初始化为-1 d[0][0]=0; queue<PII> q; q.push({0,0}); int dx[4]={0,1,0,-1},dy[4]={1,0,-1,0};//用数组表示偏移 while(q.size()) { PII tmp=q.front(); q.pop(); for(int i=0;i<4;i++) { int x=tmp.first+dx[i],y=tmp.second+dy[i]; if(x>=0&&x<n&&y>=0&&y<m&&d[x][y]==-1&&a[x][y]==0) { d[x][y]=d[tmp.first][tmp.second]+1; q.push({x,y}); } } } return d[n-1][m-1]; } int main() { cin>>n>>m; for(int i=0;i<n;i++) for(int j=0;j<m;j++)scanf("%d",&a[i][j]); cout<<dfs(); }
#include<iostream> #include<queue> #include<algorithm> #include<cstring> #include<unordered_map> using namespace std; string s1,s2; int bfs() { queue<string> q; unordered_map<string,int> d; q.push(s1); int dx[4]={0,1,0,-1},dy[4]={1,0,-1,0}; d[s1]=0; s2="12345678x"; while(q.size()) { auto tmp=q.front(); q.pop(); int sx=tmp.find('x'); if(s2==tmp)return d[s2]; for(int i=0;i<4;i++) { int x=sx/3+dx[i],y=sx%3+dy[i]; if(x>=0&&y>=0&&x<3&&y<3) { int distance=d[tmp]; // cout<<distance<<endl; swap(tmp[sx],tmp[3*x+y]); if(!d.count(tmp)) { q.push(tmp); d[tmp]=distance+1; } swap(tmp[sx],tmp[3*x+y]); } } } return -1; } int main() { char s[2]; for (int i = 0; i < 9; i ++ ) { cin >> s; s1 += *s; } cout << bfs() << endl; return 0; }
关于邻接表的学习
一、首先 弄清楚 几个数组的含义,然后再去 看 邻接表的表示代码,发现容易理解一些,在这里再次回顾下几个数组的含义:
h[N] : 表示 第 i 个节点的 第一条边的 idx
ne[M] : 表示 与 第 idx 条边 同起点 的 下一条边 的 idx
e[M] : 表示 第idx 条边的 终点
N : 节点数量
M:边的数量
i : 节点的下标索引
idx : 边的下标索引
二、然后结合代码模版理解定义
变量初始化定义:
int h[N], e[M], ne[M], idx;
当我们加入一条边的时候:
public static void add(int a,int b){ e[idx] = b; // 记录 加入的边 的终点节点 ne[idx] = h[a]; // h[a] 表示 节点 a 为起点的第一条边的下标,ne[idx] = h[a] 表示把 h[a] 这条边接在了 idx 这条边的后面,其实也就是把 a 节点的整条链表 接在了 idx 这条边 后面;目的就是为了下一步 把 idx 这条边 当成 a 节点的单链表的 第一条边,完成把最新的一条边插入到 链表头的操作; h[a] = idx++; // a节点开头的第一条边置为当前边,idx移动到下一条边 }
树和图的深度优先遍历
//对于每个点k,开一个单链表,存储k所有可以走到的点,h[k]存储这个单链表的头节点 // h[N] : 表示 第 i 个节点的 第一条边的 idx /// ne[M] : 表示 与 第 idx 条边 同起点 的 下一条边 的 idx // e[M] : 表示 第idx 条边的 终点 // int h[N],e[N],ne[N],idx; // N : 节点数量 // M:边的数量 // i : 节点的下标索引 // idx : 边的下标索引 //增加一条边a->b; void add(int a,int b) { e[idx]=b,ne[idx]=h[a],h[a]=idx++; } //初始化 idx=0; memset(h,-1,sizeof h); int dfs(int u){ st[u]=true;//st[u]表示u已经被遍历过 for(int i=h[u];i!=-1;i=ne[i]) { int i=0; int j=e[i]; if(!st[j])dfs(j); } }
#include<iostream> using namespace std; #include<cstring> #include <algorithm> const int N=100010,M=2*N; int e[M],h[N],ne[M],idx;//h表示该顶点的第一条边,e[M]表示该边的终点,ne表示同起点的下一条边 bool st[N]; int ans=N,n; void add(int a,int b) { e[idx]=b,ne[idx]=h[a],h[a]=idx++; } int dfs(int u) { int size=0; int sum=0; st[u]=true; for(int i=h[u];i!=-1;i=ne[i]) { int j=e[i]; if(!st[j]) { int s=dfs(j); size=max(size,s); sum+=s; } } size=max(size,n-sum-1); ans=min(size,ans); return sum+1; } int main() { cin>>n; memset(h, -1, sizeof h); for(int i=0;i<n-1;i++) { int a,b; scanf("%d%d\n",&a,&b); add(a,b); add(b,a); } dfs(1);//顶点定义的是1-n,所以要从1开始dfs; cout<<ans; return 0; }
树和图的广度优先遍历
模板
##bfs queue<int>q; st[1]=true;//表示第一个节点被遍历过 q.push(1);//从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); } } }
#include<iostream> using namespace std; #include<queue> #include<cstring> #include<algorithm> const int N=100010; int h[N],ne[N],e[N],idx,d[N]; void add(int a,int b)//插入 { e[idx]=b; ne[idx]=h[a]; h[a]=idx++; } int main() { int m,n; cin>>n>>m; memset(h,-1,sizeof h);//不初始化下面会超时 memset(d,-1,sizeof d);//初始化,注意位置,一定要先初始化 while(m--) { int a,b; scanf("%d%d\n",&a,&b); add(a,b); } queue<int> q; d[1]=0; q.push(1); while(q.size()) { int a=q.front(); q.pop(); for(int i=h[a];i!=-1;i=ne[i]) { // cout<<"dsdffdfd[a]"; int j=e[i];// if(d[j]==-1){//改变距离,重边和自环无法入队 d[j]=d[a]+1; q.push(j); } } } cout<<d[n]; }
另外加一种我的尝试,目前无法解决自环问题
#include<iostream> #include<cstring> #include<queue> #include<algorithm> using namespace std; const int N=100010,M=N*2; int h[N],ne[M],e[M],idx,d[N]; bool st[N]; int n,m; typedef pair<int,int> PII; void add(int a,int b) { e[idx]=b;ne[idx]=h[a],h[a]=idx++; } int main() { // typedef pair<int,int> PII; // unordered_map<PII,int> mymap; scanf("%d%d",&n,&m); memset(h,-1,sizeof h);//对不起我是SB这里是初始化h,你不初始化永远跳不出去 for(int i=0;i<m;i++) { int a,b; scanf("%d%d",&a,&b); // if(!mymap[{a,b}]) // { add(a,b); // mymap[{a,b}]++; // } } st[1]=true; queue<PII> q; q.push({1,0}); //注意了你这里并不是和你想的一样,首先第一次确实它们都是距离为1,但是第二步呢,每次出队一个边距离就++这时不对的,这并不是距离只能说第几个被遍历的 memset(d, -1, sizeof d); while(q.size()) { PII b=q.front(); int dis=b.second; int a=b.first; q.pop(); for(int i=h[a];i!=-1;i=ne[i]) { int j=e[i]; if(!st[j]) { st[j]=true; q.push({j,dis+1}); d[j]=dis+1; } } } cout<<d[n]; }
拓扑排序
bool topsort() { int hh = 0, tt = -1; // d[i] 存储点i的入度 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; }
dijkstra 算法
int g[N][N];//存储每条边 int dist[N];//存储1号点到每条边的最短距离 bool st[N];//存储每个点的最短路径是否已经确定; //求1号点到n号点的最短路,如果不存在返回-1 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++)//一开始的时候一个点都没有,先确定了1这个点,然后更新1到其他点的距离 , 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]);//第一次循环加入了1这个点,然后开始更新到其他点的距离 st[t]=true; } if(dist[n]=0x3f3f3f3f)return -1; return dist[n]; }
bellman_ford
算法思想遍历所有边,遍历k次找到比较最短路径为k步的最短路径
int n,m;//n表示点数,m表示边数 int dist[N],last[N];//dist[x]存储1到x的最短路径 struct Edge { int a,b,w;//a表示入点,b表示出点,w表示权重 }edges[M]; //求1到n的最短路径,如果无法从1走到n,则返回-1; int bellman_ford() { memset(dist,0x3f,sizeof dist); dist[1]=0; for(int i=0;i<n;i++) { memcpy(last,dist,sizeof dist);//有时候为了避免串联,备份一个上一次的 for(int j=0;j<m;j++) { int a=edges[j].a,b=edges[j].b,w=edges[i].w; if(dist[b]>last[a]+w) dist[b]=last[a]+w; } } if(dist[n]>0x3f3f3f3f/2)return -1; return dist[n]; }
spfa
int n;//总点数 int h[N],w[N],e[N],ne[N],idx;//邻接表相关信息 int dist[N],cnt[N];//dist表示最短距离,cnt表示最短路径上经过点数 bool st[N];//表示判断是否在队列 //存在负环返回true,否则返回false; bool spfa() { //不需要初始化dist[]数组 //原理:如果某条最短路径上有n个点,那么加上自己之后一共有n+1个点,那么一定存在负权回路 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; }
floyd算法
#floyd算法,想象一下以k作为中间节点不断优化 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]);
prim算法
int n; // n表示点数 int g[N][N]; // 邻接矩阵,存储所有边 int dist[N]; // 存储其他点到当前最小生成树的距离 bool st[N]; // 存储每个点是否已经在生成树中 // 如果图不连通,则返回INF(值是0x3f3f3f3f), 否则返回最小生成树的树边权重之和 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; }
Kruskal算法
#Kruskal int n,m;//n为点数,m为边数 int p[N];//并查集的父节点数组 struct Edge//存储边 { int a,b,w; }edges[M]; bool cmp(Edge a,Edge b) { return a.w<b.w; } int find(int x)//并查集查找 { if(p[x]!=x)p[x]=find(p[x]); return p[x]; } int kruskal() { sort(edges,edges+m,cmp); for(int i=1;i<=n;i++);p[i]=i;//并查集初始化 int res=0,cnt=0;//权值和边数 for(int i=0;i<m;i++)//一共m条边 { 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; }
染色法判别二分图
int n;//n表示点数 int h[N],e[M],ne[M],idx;//邻接表存储图 int color[N];//表示每个点的颜色,-1表示未染色,0表示白色,1表示黑色 //参数:u表示当前节点,c表示当前颜色 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; }
匈牙利算法二分图最大匹配
int n1,n2;//n1表示第一个集合中的点数,n2表示第二个集合中的点数 int h[N],e[M],ne[M],idx; int match[N];//存储第二个集合中每个点当前匹配的第一个集合中是哪个点 bool st[N];//表示第二个集合中的每个点是否被遍历过 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]==0||find(match[j])) { match[j]=x; return true; } } } return false; } //求最大匹配数,依次枚举第一个集合中的每个点能否匹配第二个集合中的点 int res=0; for(int i;i<=n1;i++)//也就是最大匹配数 { memset(st,false,sizeof st); if(find(i))res++; } int n1,n2;//n1表示第一个集合中的点数,n2表示第二个集合中的点数 int h[N],e[M],ne[M],idx; int match[N];//存储第二个集合中每个点当前匹配的第一个集合中是哪个点 bool st[N];//表示第二个集合中的每个点是否被遍历过 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]==0||find(match[j])) { match[j]=x; return true; } } } return false; } //求最大匹配数,依次枚举第一个集合中的每个点能否匹配第二个集合中的点 int res=0; for(int i;i<=n1;i++)//也就是最大匹配数 { memset(st,false,sizeof st); if(find(i))res++; }