搜索与图论

图的遍历


深度优先遍历(DFS)

深度优先搜索,从初始节点出发,按预定的顺序扩展到下一个节点,然后从下一节点出发继续扩展新的节点,不断递归执行这个过程,直到某个节点不能再扩展下一个节点为止。

举例题目:n-皇后问题
#include<stdio.h>
#include<string.h>
#include<stdbool.h>
#define N 20

int n;
char g[N][N];
bool col[N], dg[N], udg[N];//col[N]用于判断这一列是否存在Q dg[N和udg[N]用于判断对角线是否存在Q 

void dfs(int u) {
	int i;
	if(u == n) {
		for(i = 0; i < n; i++) {
			printf("%s", g[i]);
			printf("\n");
		}
		printf("\n");
		return;
	}
	
	for(i = 0; i < n; i++) {
		if(!col[i] && !dg[u + i] && !udg[n - u + i]) {//对角线参照二维坐标系的函数 y = x + b和 y = -x + b
			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] = '.'; 
		}
	}
} 
int main() {
	scanf("%d", &n);
	int i, j;
	for(i = 0; i < n; i++) {
		for(j = 0; j < n; j++) {
			g[i][j] = '.';
		}
	}
	
	dfs(0);
	
	return 0;
}

广度优先遍历(BFS)
举例题目:走迷宫
  • 一层一层往外搜索

  • 框架:

    1. 把初始值放入队列
    2. while循环队列 每次把队头拿出来 拓展队头
  • 代码:

#include<stdio.h>
#include<string.h>
#define N 110

typedef struct PII {//数组模拟队列 
	int x;
	int y;
}PII;
PII q[N * N];
int head, tail;

int n, m;
int g[N][N], s[N][N];

int bfs() {
	memset(s, -1, sizeof(s));
	s[1][1] = 0;//表示该点已经走过了 
	q[++tail].x = 1;//记录走到的点x坐标 
	q[tail].y = 1;//记录走到的点y坐标 
	
	int vx[4] = {-1, 0, 1, 0}, vy[4] = {0, 1, 0, -1};//上下左右 (x, y) 
	
	while(head <= tail) {//队列不为空 
		PII h = q[head++];//每次取出队头元素  尝试向上下左右扩展 
		int i;
		for(i = 0; i < 4; i++) {
			int x = h.x + vx[i], y = h.y + vy[i];//表示走到的点的坐标 
			if(x >= 1 && x <= n && y >= 1 && y <= m && g[x][y] == 0 && s[x][y] == -1) {//点满足走的条件且在边界内 
				s[x][y] = s[h.x][h.y] + 1;
				q[++tail].x = x, q[tail].y = y;//tail只用加一次 
			}
		}
	}
	return s[n][m]; 
}

int main() {
	scanf("%d%d", &n, &m);
	int i,j;
	
	for(i = 1; i <= n; i++) {
		for(j = 1; j <= m; j++) {
			scanf("%d", &g[i][j]);//遍历输入每一个点是否可以通过 0表示可以通过 1不可以通过 
		}
	}
	printf("%d", bfs());
	
	return 0;
}

图的应用

Dijkstra求最短路

1.初始化距离:只有起点到起点的距离是0,其余都为+∞(未确定)

2.接下来n次迭代(循环) (s:当前已确定的最短距离的点,最开始是原点)

  1. 找到不在s中的距离最近的点为t
  2. 把t加到s中
  3. 用t更新其他点的距离(从t出去的所有边,dis[x] >? dis[t])
  • 时间复杂度:O(n^2)

  • 稠密图用邻接矩阵 稀疏图用邻接表 这是稠密图

  • 没有有向图无向图的区别,无向图是一种特殊的有向图

3.代码:

#include<stdio.h>
#include<string.h>
#define N 510

int g[N][N];//表示i->j的长度 
int d[N];//表示1号到i号的当前最短距离 
char st[N];//表示i号点的最短距离是否被确定
int n;

int dijkstra() {
	memset(d, 0x3f, sizeof d);
	d[1] = 0;
	int i, j;
	for(i = 1; i < n; i++) {
		int t = 0;
		for(j = 1; j <= n; j++) {
			if(!st[j] && (!t || d[j] < d[t])) {//找打一个未被确定最短路径的点  并且该点距离最短  我终于知道啦!st[j] == 0的时候,!st[j]就是1,一个反转的意思 
				t = j;
			}
		}
		st[t] = 1;//确定该点为最短路径的点 
		
		for(j = 1; j <= n; j++) {//找以t为起点的连接的各个点的最短路径 
			if(d[t] + g[t][j] < d[j]) {
				d[j] = d[t] + g[t][j];
			}
		}
	}
	
	if(d[n] == 0x3f3f3f3f) {//0x3f3f3f3f表示无穷大 
		return -1;
	} else {
		return d[n];
	}
}

int main() {
	memset(g, 0x3f, sizeof g);//g表示要填充的内存块  0x3f(作为无穷大使用)表示要被设置的值  第二个g表示要被设置该值的字符数  
	int m;
	scanf("%d%d", &n, &m);//n表示点的个数 m表示边的个数 
	while(m--) {
		int a, b, w;
		scanf("%d%d%d", &a, &b, &w);//a b表示点 w表示权重 
		if(w < g[a][b]) {
			g[a][b] = w;//连线上的值的初始化 
		}
	} 
	
	printf("%d", dijkstra());
	return 0;
} 

Prim
  1. 把所有距离初始化为正无穷

  2. 进行n次迭代 (s:当前已经在连通块的点)

    1. 找到不在集合当中的距离最近的点t(最开始随便一个,接下来是找距离最小的点)
    2. 用t更新其他点到集合的距离
    3. st[t] = true
  3. 代码:

#include<stdio.h>
#include<string.h>
#include<stdbool.h>
#define min(a, b) a < b ? a : b
#define N 510
#define INF 0x3f3f3f3f

int n, m;
int g[N][N];
int d[N];
bool st[N]; 

int prim() {
	memset(d, 0x3f, sizeof d);
	int i, j;
	
	int res = 0;
	for(i = 0; i < n; i++) {
		int t = -1;
		for(j = 1; j <= n; j++) {
			if(!st[j] && (t == -1 || d[t] > d[j])) {//!st[j]用于判断这个点是否被使用  后面的语句用于找从t开始的距离它最小的点 
				t = j;
			}
		}
		
		if(i && d[t] == INF) {//通过前面的判断 如果仍然为INF 说明后面没有路径与之相连 
			return INF;
		}
		
		if(i) {//if(i)等价于if(i != 0) 用于排除第一个点 
			res += d[t];
		}
		st[t] = true;
		
		for(j = 1; j <= n; j++) {
			d[j] = min(d[j], g[t][j]);//找出下一次迭代中以t为起点,各个点到他的距离 
		}
	}	
	
	return res;
}

int main() {
	scanf("%d%d", &n, &m);
	
	memset(g, 0x3f, sizeof g);
	
	while(m--) {
		int a, b, w;
		scanf("%d%d%d", &a, &b, &w);
		g[a][b] = g[b][a] = min(g[a][b], w);
	}
	
	int t = prim();
	if(t == INF) {
		printf("impossible");
	} else {
		printf("%d\n", t);
	}
	return 0;
}

拓扑序列
  • 针对有向图,无向图没有拓扑序列

  • 若一个由图中所有点构成的序列 A 满足:对于图中的每条边 (x,y),xA 中都出现在 y 之前,则称 A 是该图的一个拓扑序列

    (从前指向后

  • 有环不存在拓扑序列

  • 有向无环图(拓扑图)一定存在一个拓扑序列 一定至少存在一个入度为0的点

  • 入度为0的点:没有任何一条边指向它—>排在当前最前面的位置

  • 步骤:

    1. 把所有入度为0的点入队

    2. 宽搜:while(队列不空){

      ​ 取出队头

      ​ 枚举t的所有出边t->j

      ​ 删掉t->j j的入度减一 d[j]–

      ​ if(d[j] == 0) 让j入队

      ​ }


(持续更新)
心得体会:图的应用非常多,只要熟悉了计算思路,就可以搞清楚怎么实现这个功能,但是就是写不出来,说明其实最考验的,还是编程能力,因此还是要多敲代码。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值