BFS第一次搜到的点一定是离第一个点最近的点,
每个DFS都一定对应一条搜索树,
#include <iostream>
using namespace std;
const int N = 10;
int n;
int path[N];//path存储路径,到叶节点的时候,就填好路径上的点了,u等于0的时候在第一层,u为1为第二层,u为2在第三层,u等于3的时候是最后一层。所以u==n的时候是最后一层
bool st[N];
void dfs(int u)
{
if (u== n)
{
for (int i = 0; i < n; i ++ ) printf("%d ", path[i]);
puts("");
return;
}
for (int i=1;i<= n; i ++ )
if (!st[i])
{
path[u] = i;
st[i] = true;//true表示用过了,
dfs(u + 1);
st[i] = false;//恢复现场
}
}
int main()
{
cin >> n;
dfs(0);
return 0;
}
每一行都要放一个皇后,并且只能放一个皇后,所以可以从前往后枚举每一行的皇后可以放到哪一个位置上,比如写出n行,即n个空位,如果在第一行的把皇后放到第一列就是1,第二行把皇后放到第三列就是3,即搜索顺序和全排列的搜索顺序是一样的,
剪枝:当前方案不合理,不用继续往下了,直接进行回溯,
第u行所在的对角线和反对角线是对应的直线的截距b,因为y-x可能为负数,所以补上n这个偏移量,反对角线是y+x,
时间复杂度n*n!
#include <iostream>
using namespace std;
const int N = 20;
int n;
char g[N][N];
bool col[N],dg[N], udg[N];//每列,每个主对角线,每个副对角线
void dfs(int u)//当前是枚举第u行的皇后应该放在哪里,
{
if(u== n)
{
for (int i = 0;i< n; i ++ ) puts(g[i]);
puts("");
return;
}
for (int i=0;i<n; i ++ )
if (!col[i] && !dg[u + i] && !udg[n - u + i])//从第一列开始枚举,这一列之前没有放过,并且对角线和反对角线上都没有放过,
{
g[u][i] ='Q';
col[i] = dg[u + i] = udg[n - u + i] = true;//表示这一列以及对角线上已经有皇后了,
dfs(u + 1);
col[i] = dg[u + i] = udg[n - u + i] = false;//恢复现场
g[u][i] ='.';//恢复main函数最开始把每个位置设置为点的状态,
}
}
int main()
{
cin >> n;
for (int i= 0;i< n; i ++ )
for (int j = 0; j< n; j ++ )
g[i][j] ='.';
dfs(0);
return 0;
}
时间复杂度2的n²次方
#include <iostream>
using namespace std;
const int N = 20;
int n;
char g[N][N];
bool row[N],col[N],dg[N], udg[N];//每列,每个主对角线,每个副对角线
void dfs(int x,int y, int s)
{
if (y == n) y=0,x++ ;//如果某一行y到达最后一列出界后会返回下一行的开头,
if (x == n)
{
if (s == n)
{
for (int i = 0;i< n; i ++ ) puts(g[i]);
puts("");
}
return;
}
// 不放皇后
dfs(x,y + 1,s);
// 放皇后
if (!row[x] && !col[y] && !dg[x + y] && !udg[x - y + n])
{
g[x][y] ='Q';
row[x] = col[y] = dg[x + y] = udg[x - y + n] = true;
dfs(x, y+1 ,s + 1);
row[x] = col[y] = dg[x + y] = udg[x - y + n] = false;
g[x][y] ='.';
}
}
int main()
{
cin >> n;
for (int i= 0;i< n; i ++ )
for (int j = 0; j< n; j ++ )
g[i][j] ='.';
dfs(0,0,0);//从左端点开始搜,并且记录一下一共有多少个皇后
return 0;
}
不用写四个判断,可以用向量表示,向上下左右让对应的坐标减就可以,
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef pair<int, int> PII;
const int N = 110;
int n, m;
int g[N][N];//g【】【】数组存的是图
int d[N][N];//d【】【】数组存的是每一个点到起点的距离,
PII q[N *N],Prev[N][N];
int bfs()
{
int hh = 0, tt = 0;//模拟队列,队头队尾
q[0] = {0,0};
memset(d,-1,sizeof d);//把所有距离初始化为-1表示这一点没有走过,
d[0][0] = 0;//表示走过了
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0,1, 0, -1};
while (hh <=tt)//队列不空
{
auto t = q[hh ++ ];//每一次取出队头元素的坐标,尝试往上下左右四个方向的坐标扩展,不用写四个判断,可以用向量来表示,往上就是横坐标减一,纵坐标不变,往右就是横坐标不变,纵坐标加一,往下就是横坐标加一,纵坐标不变,往左就是横坐标不变,纵坐标减一,
for (int i= 0;i<4; i ++ )//每一次枚举一下四个方向
{
int x = t.first + dx[i],y = t.second + dy[i];//x,y表示沿着这个方向走会走到哪一个点,
if (x >= 0 && x< n && y >= 0 && y < m && g[x][y] == 0 && d[x][y] == -1)//g[x][y] == 0表示这个点是空地,因为g如果为-1表示是墙,d[x][y] == -1表示这个点还没有走过,
{
d[x][y] = d[t.first][t.second] + 1;//只有第一次搜到了才会更新它,即从-1的状态变成0,表示这个点是第一次走过
Prev[x][y]=t;//记录前一个点是什么,
q[ ++ tt] = {x,y};//把点加进来
}
}
}
int x = n- 1,y=m-1;
while (x||y)
{
cout << x <<' '<< y << endl;
auto t = Prev[x][y];
x = t.first,y = t.second;
}
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++ )
cin >>g[i][j];
cout << bfs() << endl;
return 0;
}
树是一种特殊的图,无向图是一种特殊的有向图,对于每一条无向边,建两条边就可以了,所以只需要考虑有向图如何存储就可以了,
图的邻接表存储
数和图的存储:邻接表
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010,M = N*2;
int h[N], e[M], ne[M], idx;
//h[]数组存的是n个链表的链表头,e[]存的是所有结点的值,ne[]存的是每个结点的next值是多少
//初始化单链表就是让单链表的头结点指向-1,现在有n个头结点,就让n个头结点指向-1就可以了,
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
int main()
{
memset(h,-1, sizeof h);
}
树和图的遍历,
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010,M = N*2;
int n,m;
int h[N], e[M], ne[M], idx;
//h[]数组存的是n个链表的链表头,e[]存的是所有结点的值,ne[]存的是每个结点的next值是多少
//初始化单链表就是让单链表的头结点指向-1,现在有n个头结点,就让n个头结点指向-1就可以了,
bool st[N];//st[]数组存哪些点已经被遍历过了,只需遍历一次,
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
void dfs(int u)
{
st[u] = true; // 标记一下,已经被搜过了
for (int i = h[u];i != -1; i = ne[i])
{
int j = e[i];//j存一下当前链表里的点对应图里面的点的编号是多少,
if (!st[j]) dfs(j);//如果j没有被搜过就一直搜,搜到底
}
}
int main()
{
memset(h,-1, sizeof h);
dfs(1);
}
树和图的深度优先遍历,因为每一个点都只会被遍历一次,所以树的图的深度优先遍历和宽度优先遍历时间复杂度都是O(n+m)都是和点数以及边数成线性关系的,
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010,M = N*2;
int n;
int h[N], e[M], ne[M], idx;
//h[]数组存的是n个链表的链表头,e[]存的是所有结点的值,ne[]存的是每个结点的next值是多少
//初始化单链表就是让单链表的头结点指向-1,现在有n个头结点,就让n个头结点指向-1就可以了,
bool st[N];//st[]数组存哪些点已经被遍历过了,只需遍历一次,
int ans=N;//记录全局的答案,最小的最大值,
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
//返回以u为根的子树中的点的数量,
int dfs(int u)
{
st[u] = true; // 标记一下,已经被搜过了
int sum = 1,res = 0;//当前这个点算一个点,res记录把这个点删了之后,每一个连通块的最大值,
for (int i = h[u];i != -1; i = ne[i])
{
int j = e[i];//j存一下当前链表里的点对应图里面的点的编号是多少,
if (!st[j])
{
int s = dfs(j);//如果j没有被搜过就一直搜,搜到底,用s表示当前的子树的大小
res = max(res,s);//当前子树也算是一个连通块,所以与res求max
sum += s;//以u的儿子结点为根节点的子树是以u为根节点的子树的一部分,所以sum要加上s,
}
}
res = max(res,n-sum);//结束后,以u为根节点的子树的上面那一部分的连通块也要算,即n-sum,
ans = min(ans,res);//次数ans存的就是把这个点删除后的最大的连通块的点数,和res取min,
return sum;
}
int main()
{
cin>> n;
memset(h,-1, sizeof h);
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;
}
宽度优先遍历
因为图里面边的权值都是1,所以可以用宽搜来求最短距离,
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010;
int n, m;
int h[N], e[N], ne[N], idx;
int d[N], q[N];//d【】是距离,q【】是队列,初始化d数组,
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; //第一个元素的是起点1
memset(d,-1,sizeof d);//-1表示没有被遍历过,
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)//j没有被遍历过
{
d[j] = d[t] + 1;//扩展
q[ ++ tt] = j;//把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;
return 0;
}
拓扑序列是针对有向图来说的,无向图没有拓扑序列,拓扑序列就是给出一个序列后,所有的边都是从前指向后面的,
有环就无法写出拓扑序列了,一个有向无环图一定存在一个拓扑序列,有向无环图也被称为拓扑图
所有入度为0的点都可以排在当前最前面的位置,一个无环图至少存在一个入度为0的点,
一个有向无环图的拓扑序不一定是唯一的,
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010;
int n, m;
int h[N], e[N], ne[N], idx;
int d[N], q[N];//d【】是点的入度,q【】是队列,
void add(int a, int b)
{
e[idx] = b,ne[idx] = h[a], h[a] = idx ++ ;
}
bool topsort()
{
int hh = 0,tt = -1;
for (int i = 1; i <= n; i ++ )
if (!d[i])//把所有入度为0的点加入到队列里,
q[ ++ tt] = i;
while (hh <= tt)//队列不空就取出来队头元素
{
int t = q[hh ++ ];//出队的顺序就是拓扑序,由于出队只是把指针往后移动了一位,前面的顺序都是不变的,因此当我们把所有点都遍历完了之后队列里面现在的队列即q【】里面的次序恰好就是拓扑序,
for (int i = h[t]; i != -1; i = ne[i])//拓展队头元素
{
int j = e[i];//找到出边
d[j] --;//让入度减减
if (d[j] == 0)q[ ++ tt] = j;//如果入度为0,就把j加到队列里
}
}
return tt == n - 1;//最后判断一下是不是所有点都入队了。说明队列里一共进了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);
d[b] ++ ;//插入边,更新入度,一条a指向b的边,则b的入度加一,
}
if (topsort())//如果存在拓扑序就输出出来,不存在就输出-1
{
for (int i = 0; i< n; i ++ ) printf("%d ", q[i]);
puts("");
}
else puts("-1");
return 0;
}
最短路