题目:
从开始位置(start)找出到出口(end)的通路,并从start到end输出;输出路径的长度。
采用下图迷宫
可以将节点进行编号,从1开始编号,编号次序从上到下,从左到右;保存每条边,根据两个节点之间是否有边,来决定是否能够走通;
将边保存在文件中,用俩个端点来表示一条边;
每个节点的分支数不一样,要求根据分支数决定遍历的方向数,不能用恒定的遍历方向数。
补充:找到从start到end的一条最短的路径;当迷宫有多个出口应该如何找到最近的出口;
下图:起点2,终点17
注意编号是特定的,要修改的话就必须修改邻接表
如标题所述如果你要用这种方法就必须做好心理准备,这种操作虽然高效,内存利用率更高,但这实在不好操作,好了,话不多说,让我们看看要用什么知识吧~
-
邻接表
邻接表可以看作两部分,一部分结构体数组,另一部分是链表,具体结构是邻接表每一项可以看作一个结点与相邻节点的组成链表如图所示:
解释一下,因为上图是无向图,所以如果1,2节点有路径,用邻接表就可以表示为1->2,2->1,最后一个节点用^表示。
下面代码是创建邻接表的过程,解释一下:
比如4->5->3->^这条路径,首先4是首顶点,我将它与其他首顶点一起存入数组AdjList中,并把AdjList的数据类型设置为顶点类型,再创建一个次点表,最后创建一个以首顶点为头,一层一层连接次点的邻接链表,这就相当于每一条路径都有对应的链表,至此邻接表创建完成。
图例:
/*创建邻接表*/
typedef struct Node { //次点表
int adjvex;
struct Node* next;
} EdgeNode;
typedef struct vertex { //首顶点表
int vertex;
EdgeNode* edgenext;
} VertexNode;
typedef VertexNode AdjList[poi];//首顶点表数组
typedef struct {
AdjList adjlist;
int n, e;//n为顶点数,e为边数
} AdjMatrix; //建立邻接表结构体
void CreateAdja(AdjMatrix *G) { //建立邻接表G
EdgeNode *edge_node;//用来表示边链表
printf("Please input the number of vertexs and edges(there is space in the middle):");
scanf("%d%d", &G->n, &G->e);//输入顶点和边的数量
/*读取文件*/
printf("Reading file,please wait......\n");
FILE *fp;
fp = fopen("demo.txt", "r");
if (fp == NULL)//判断文件是否为空
printf("Fail to open the file, because it is null or its address is invalid!\n");
if (fp != NULL) {//如果文件不为空
for (int k = 0; k < G->n; ++k) { //建立顶点表
G->adjlist[k].vertex = k + 1;
G->adjlist[k].edgenext = NULL;
}
int i = 0, j = 0;
while (fscanf(fp, "%d", &i) != EOF) { //读取顶点和边节点
fscanf(fp, "%d", &j);
edge_node = (EdgeNode*)malloc(sizeof(struct Node));
edge_node->adjvex = j;//边表赋值
edge_node->next = G->adjlist[i - 1].edgenext;
G->adjlist[i - 1].edgenext = edge_node;
edge_node = (EdgeNode*)malloc(sizeof(struct Node));
edge_node->adjvex = i;//双向连接,因为是无向表,所以要双向建表
edge_node->next = G->adjlist[j - 1].edgenext;
G->adjlist[j - 1].edgenext = edge_node;
}
printf("Congratulations, adjacency is successfully created!\n");
//无向邻接表成功建立
}
}
- 广度优先搜索
广度优先搜索可以看作剥洋葱,一层层的寻找最短路径,这里广度优先就不展开解释,具体说明思路:
首先我们需要一个容器(这里我使用的是队列),这个容器用来装下所有节点,如下:
从起点2开始,与它相邻的是3,所以把3写入2的邻格,与3相邻的是1,2,4,6,7,8,因为2已经在容器内,所以我们依次填入1,4,6,7,8(填入顺序无影响)。
重复操作,直到终点进入容器,此时我们可以用通过记录每条路径,找到从终点到起点的路径(这里注意因为使用容器的缘故,我们找到的路径是逆序的),而且因为广度优先搜索的原因,这是最短路径之一。
最后我使用栈,将顶点逆序输出得到从起点到终点的一条最短路径。
/*创建队列*/
typedef struct Queue {
int vertex_number ;//顶点
int number ;//边点
} queue;
queue q[poi + 1];//全局变量队列数组
/*创建栈*/
typedef struct Stack {
int top;
int path[poi + 1];//路径数组
} stack;
void stack_push(stack* s, int i) {
s->path[s->top++] = i;
}//压栈
int stack_pop(stack* s) {
int j = s->path[--s->top];
return j;
}//出栈
int main() {
AdjMatrix *demo = (AdjMatrix*)malloc(sizeof(AdjMatrix));//分配邻接表空间
CreateAdja(demo);//创建邻接表
int start, end;
int flag = 0;
stack* sta = (stack*)malloc(sizeof(struct Stack));//给栈分配空间
sta->top = 0;//栈置空
printf("Please input start and end, there is space in the middle: ");
scanf("%d%d", &start, &end);
int book[poi + 1] = {0}; //标记数组用来标记是否进入队列,没进队列设为0,进入队列设为1
int i = start, j = 0, count = 0;//i为起始点,j表示遍历的动态点,count用来控制队列数组的移动
int path_length[poi + 1] = {0};//存储路径长度的数组
/*使用队列模拟广度优先搜索找到路径*/
while (demo->adjlist[start-1].edgenext!=NULL && demo->adjlist[end-1].edgenext!=NULL && count < demo->n) {//首先判断输入的起始点和结束点有邻边
if (book[demo->adjlist[i - 1].vertex] == 0) {//遍历顶点
q[j].vertex_number = demo->adjlist[i - 1].vertex;
q[j].number = i;
if (q[j].number != start) {
path_length[q[j].number] = path_length[q[j].vertex_number] + 1;
}
book[i] = 1;//标记该点已遍历过
j++;
}
EdgeNode* demo1 = demo->adjlist[i - 1].edgenext;//让节点指向边节点
while (demo1) { //遍历边结点
if (book[demo1->adjvex] == 0) { //判断是否已经入队
q[j].vertex_number = demo->adjlist[i - 1].vertex;
q[j].number = demo1->adjvex;
path_length[q[j].number] = path_length[q[j].vertex_number] + 1;
book[demo1->adjvex] = 1;//标记该点已遍历过
if (q[j].number == end) {//找到出口,结束遍历
flag = 1;
break;
}
j++;
}
demo1 = demo1 -> next;//边链表遍历
}
if (flag == 1) //找到出口,标志变为1
break;
i = q[count++].number;
}
/*用栈逆序输出路径*/
if (flag == 1) {
printf("Successfully , you have found a path:\n");
int t = j ;
for (int m = 0 ; m <= path_length[q[j].number] ; ++m) {//循环结束条件为出口到起点距离遍历完成
stack_push(sta, q[t].number);//把经过的点压栈(这里是逆序,所以刚开始为出口顶点)
for (int i = 0 ; i < j ; ++i)
if (q[i].number == q[t].vertex_number)//寻找出口节点的顶点,并定位顶点,再找顶点的顶点,迭代寻找,找到起点
t = i;
}
while (sta->top > 1) {//输出路径
printf("%d->", stack_pop(sta));
}
printf("%d\n", stack_pop(sta));
printf("Length of the path is:");//打印路径长度
printf(" %d", path_length[q[j].number]);
}
else//如果标志flag!=1表示未找到通路
printf("There is no path to connect two vertexs!");
free(demo);//释放邻接表内存
free(sta);//释放逆序栈内存
return 0;
}
兄弟们,完整代码可以去我的代码仓复制粘贴(dogge):
https://gitcode.net/flag_wkx/bupt
创作不易,谢谢点赞