例1:城市最短路径问题
1. 题目:
已知若干个城市的地图,,求从一个初始到另一个城市的路径,要求该路径经过的城市最少
2. 算法设计:
- 我们可以使用广度优先遍历的思想来解决这个问题
- 图的广度优先搜索类似于树的层次遍历,逐层搜索正好可以尽快找到一个结点与另一个结点相对而言最直接的距离
- 程序实现
#include<stdio.h>
#define N 8//N表示有8个结点,进行宏定义再之后的修改时比较方便
//建立一个存储图的结点信息的邻接矩阵
int a[N][N] = {{0,1,1,1,0,1,0,0},
{1,0,0,0,0,1,0,0},
{1,0,0,1,1,0,0,0},
{1,0,1,0,0,0,1,0},
{0,0,1,0,0,0,1,1},
{1,1,0,0,0,0,0,1},
{0,0,0,1,1,0,0,1},
{0,0,0,0,1,1,1,0} };
//创建结构体
struct {
int city;
int pre;
}sq[N];//使其充当我们这个题的队列
//创建visit数组
int visit[N];char data[N] ;
int s, r;//用它们来表示队首s和队尾r
void Out() {
printf("%c ->", data[sq[r].city]);
while (sq[r].pre != -1) {
r = sq[r].pre;
printf("%c ->", data[sq[r].city]);
}
return;
}
void search() {
//让队列中的头指针指向0和尾指针指向1
s = -1; r = 0;
//先将起始结点A入队
sq[0].city = 0; sq[0].pre = -1; visit[0] = 1;
//广度优先遍历
while (s != r) {
//队首元素出栈
s += 1;
//拓展结点
for (int i = 0; i < N; i++) {
//队首s指的一定是接下来数据的前一个结点
if (a[sq[s].city][i] == 1 && visit[i] == 0) {
r += 1;
sq[r].city = i;
//仔细观察s表示的位置在其在a中所表示的位置是一一对应的,因为在队中走的是s 和 r,
//所以才有以下的写法,s所表示的位置一定是其子结点的父节点,
//因为当s变的时候,一定是将a[s]的子节点都遍历完了,该遍历下一个结点了,才会精选s+1的操作
sq[r].pre = s;
visit[i] = 1;
if (sq[r].city == 7) {
Out();
return;
}
}
}
}
printf("Non solution\n");
return;
}
void main() {
//初始化visit数组和初始化结点数组
for (int i = 0; i < N; i++) {
visit[i] = 0;//0表示没有被访问
data[i] = 65 + i;
}
search();//搜索函数来实现用广度优先遍历
return;
}
例2:走迷宫问题
1. 题目:
迷宫式许多个小方格构成的矩形,在每个方格中有的是墙(1表示墙),有的是路(0表示路)。走迷宫就是从一个小方格沿上下左右四个方向到临近的方格,当然不可以穿墙。设迷宫的入口是在左上角(1,1),出口是右下角(8,8)。根据给定的迷宫,找出一条从出口到入口的路径。
2. 算法分析:
-
该题的思路可以和例1的思路完全一样,进行广度优先遍历,搜索所有可以到达的方格入队,再扩展队首的方格,知道搜索到出口时算法结束
-
数据的存储结构讨论
-
我们知道对于图的存储可以有 零阶矩阵, 链表 法,但是对于这个题来说,好像不太现实
就拿零阶矩阵来讲:零阶矩阵的实现逻辑是将每个结点相对于其他各个结点的关系,用二维数组给存储下来,如果我们要在这个题中实现零阶矩阵,需要一个 64 * 64的二维表
为什么呢?因为在该题中 ,每个方格就代表一个结点,而每个结点的上下左右表示其即 邻接结点,而且我们还得将其之间的关系存储甚至是手打实现该零阶矩阵,想想就要死了!果断抛弃该方法
链表可想而知…
-
其实我们就可以利用题目所给的表来存储,搜索方格的过程是有规律的,对迷宫任意一个A(X,Y)点都有4个搜索方向
向上 A(X-1,Y) ,向下A(X+1,Y),向左A(X,Y-1) ,向右A(X,Y+1)
且保证它不越出该二维表,并且不穿墙便可
为了构造循环体 我们可以使用数组 fx[] = { -1 , 1 , 0 , 0} fy[] = {0 ,0 , -1, 1}模拟向下左右搜索时下标的变化过程
-
-
对存储图的二维表 a的设计,就是抄题呀!
但是但是我们如何区分哪些点我们走过,那个点我们没有走过呢?
那就得设置一个表示数组了,相当于例1中的visit数组,那我们就起名visit吧!
打错特错,(好吧!我承认我刚开始也没有想出来),和例1不同的是,我们不用设置visit数组来记录方格的访问情况,而是利用迷宫原有的存储空间a 进行记录
设置1 表示这个点是个墙;设置0表示这个点还没有遍历;设置-1表示这个点已经遍历过了
-
程序实现
#include<stdio.h>
#define N 8//N表示迷宫矩阵的行或列
int a[N][N] = { {0,0,0,0,0,0,0,0},
{0,1,1,1,1,0,1,0},
{0,0,0,0,1,0,1,0},
{0,1,0,0,0,0,1,0},
{0,1,0,1,1,0,1,0},
{0,1,0,0,0,0,1,1},
{0,1,0,0,1,0,0,0},
{0,1,1,1,1,1,1,0} };
int fx[4] = { -1,1,0,0 }; int fy[4] = { 0,0,-1,1 };
int s, r;//s白哦是对首,r表示队尾
int i, j;
struct {
int x;
int y;
int pre;
}sq[N*N];//定义队列
int check(int i, int j) {
//书上的方法
/*int flag = 1;
if (i < 0 || i > 7 || j < 0 || j > 7) {
flag = 0;
}
if (a[i][j] == 1 || a[i][j] == -1) {
flag = 0;
}
return flag;*/
//我的方法
//但是输出的顺序是用终点->起点的顺序,我们可以建立一个数组来存放这些值,
//也可以将起点变换为重点,通过着算法再进行求解,就是要该以下算法中的条件
/*
if (sq[r].x == 0 && sq[r].y == 0) {
Out();
return;
}
以及我所注释的一部分
*/
if (i <= 7 && i >= 0 && j <= 7 && j >= 0) {
if (a[i][j] == 0) {
return 1;
}
else {
return 0;
}
}
return 0;
}
void Out() {
printf("(%d,%d) -> ", sq[r].x, sq[r].y);
while (sq[r].pre != -1) {
r = sq[r].pre;
printf("(%d,%d)->", sq[r].x, sq[r].y);
}
return;
}
void search() {
//同样的套路走起
//1. 设置队首,队尾的值
s = -1; r = 0;
//2. 将起点入队列;-1表示遍历过了
sq[0].pre = -1;sq[0].x = 0; sq[0].y = 0;
a[0][0] = -1;
/*
* //第一次看的时候可以跳过这个被注释的程序
sq[0].pre = -1; sq[0].x = 7; sq[0].y = 7;
a[7][7] = -1;*/
//3. 广度优先遍历
while (s != r) {
//1. 队列的首元素出队
s += 1;
//2. 将其邻接结点入队(但是由于不是邻接矩阵存储的格式,所有这里有所不同)
for (int k = 0; k < 4; k++) {
i = sq[s].x + fx[k];
j = sq[s].y + fy[k];
if (check(i,j) == 1) {//确保其不越界,不翻墙
//入队
r += 1;
sq[r].pre = s; sq[r].x = i; sq[r].y = j;
a[i][j] = -1;
if (sq[r].x == 7 && sq[r].y == 7) {
Out();
return;
}
}
}
}
printf("Non solution!");
return;
}
void main() {
search();
return;
}