数据结构小项目—移动迷宫游戏

移动迷宫游戏:迷宫只有两个门,一个入口,一个出口。一个骑士骑马从入口走进迷宫,迷宫中设置有很多墙壁,对前进方向造成障碍。先需要你从迷宫中找到一条最短的通路,将行走路线和行走的最短距离告知骑士。

设计思路:

寻找最短路径这样的问题,最直接的方法就是使用迪杰斯特拉算法和弗洛伊德算法。
迷宫是在二维数组中进行存储的,所以如果使用前面两种算法的话,需要首先将二维数组转化为图的存储形式。

二维数组转化成图

如下图所示,此为 3*3 迷宫:


图 1 移动迷宫.png

提示:S 为入口,E 为出口,# 为墙壁,- 为通路。

迪杰斯特拉算法还是弗洛伊德算法,其处理对象都是有向网或者无向网。迷宫中并不涉及到具体的方向,所以需要将存储迷宫的二维数组转化为无向网。

无向网的存储方式也是用二维数组来实现,将迷宫中所有的顶点看作是图中的顶点,对于上图的迷宫来说,共有 9 个顶点,所以转化为无向网时,需要用 9*9 的一个二维数组来表示。

在转化时,从迷宫的左上角(上图的 S 开始),一行一行的进行转化,对于每个顶点来说,只需要判断其右侧和相邻的下方顶点是否为通路,如果是通路,转化为图中的直接体现就是两顶点之间有线连接。

例如,上图中的 S 其右侧和下方的顶点都是 - ,骑士可以通过,那在图中的表现就是 S 同其右侧顶点和下方顶点之间存储通路,如下图所示:

image.png

对于图 1 中的二维数组,其完全转化为图,如下图所示(每个顶点用其二维数组中的坐标来表示,00 表示第 0 行第 0 列):

image.png

图 1 中的二维数组转化为图的存储表示如下图所示:

image.png

提示:1 表示有通路,0 表示没有通路,# 由于表示墙壁,同其它任何顶点之间都没有通路。


迪杰斯特拉算法实现迷宫

#include <stdio.h>

typedef enum{
    false,true
} bool;
typedef struct {
    char vexs[250];                 //存储图中顶点数据
    int arcs[250][250];            //二维数组,记录顶点之间的关系
    int vexnum;                      //顶点数
    int arcnum;                      //记录图的弧(边)数
}MGraph;

typedef int PathMatrix[250];        //用于存储最短路径中经过的顶点的下标
typedef int ShortPathTable[250];    //用于存储各个最短路径的权值和

//迪杰斯特拉算法,v0表示有向网中起始点所在数组中的下标
void ShortestPath_Dijkstra(MGraph G,int v0,PathMatrix *p,ShortPathTable *D){
    int final[250];//用于存储各顶点是否已经确定最短路径的数组
    int v, i, w;
    //对各数组进行初始化
    for (v=0; v<G.vexnum; v++) {
        final[v]=0;
        (*D)[v]=G.arcs[v0][v];
        (*p)[v]=0;
    }
    //以起点为下标的顶点为起始点,所以不用再判断
    (*D)[v0]=0;
    final[v0]=1;
    int k = 0;
    for (i=0; i<G.vexnum; i++) {
        int min=65535;
        //选择到各顶点权值最小的顶点,即为本次能确定最短路径的顶点
        for (w=0; w<G.vexnum; w++) {
            if (!final[w]) {
                if ((*D)[w]<min) {
                    k=w;
                    min=(*D)[w];
                }
            }
        }
        //设置该顶点的标志位为1,避免下次重复判断
        final[k]=1;
        //对从起点到各顶点的权值进行更新
        for (w=0; w<G.vexnum; w++) {
            if (!final[w]&&(min+G.arcs[k][w]<(*D)[w])) {
                (*D)[w]=min+G.arcs[k][w];
                (*p)[w]=k;//记录各个最短路径上存在的顶点
            }
        }
    }
}
//在将二维数组转化为图的过程中,需要判断当前的点是否越界或者是否为通路
bool canUsed(int i,int j,int n,int m,char a[][110]){
    if (a[i][j]!='#' && i>=0 && i<n && j>=0 && j<m) {
        return true;
    }
    return false;
}
int main(){
    char a[110][110];
    int n,m;
    scanf("%d,%d",&n,&m);
    getchar();
    MGraph G;
    G.vexnum=0;
    G.arcnum=0;
    //记录入口在图的顶点数组中的位置下标
    int start =0;
    //记录出口在图的顶点数组中的位置下标
    int exit=0;
    //初始化记录图的边的二维数组,假设各个边的长度为无穷大,即两顶点之间没有边
    int i ,j;
    for (i=0; i<n*m; i++) {
        for (j=0; j<n*m; j++) {
            G.arcs[i][j]=65535;
        }
    }
    //输入二维数组,同时记录入口和出口的位置
    for (i=0; i<n; i++) {
        for (j=0; j<m; j++) {
            scanf("%c",&a[i][j]);
            G.vexs[i*m+j]=a[i][j];
            G.vexnum++;
            if (a[i]
                [j]=='S') {
                start=i*m+j;
            }else if(a[i][j]=='E'){
                exit=i*m+j;
            }
        }
        getchar();//作用是为了读取缓存区中的换行符(因为迷宫是一行一行输入到内存中的)
    }
    //将二维数组转换为无向图,在转换时,从二维数组的左上角开始,每次判断当前顶点的右侧和下侧是否为通路,这样所有的通路就可以转换为无向图中的边。
    for (i=0; i<n; i++) {
        for (j=0; j<m; j++) {
            //首先判断当前点是否为通路
            if (canUsed(i, j, n, m, a)) {
                if (canUsed(i+1, j, n, m, a)) {
                    //设定两顶点之间的边的权值为 1
                    G.arcs[i*m+j][(i+1)*m+j]=1;
                    G.arcs[(i+1)*m+j][i*m+j]=1;
                    G.arcnum++;
                }
                if (canUsed(i, j+1, n, m, a)) {
                    G.arcs[i*m+j][i*m+j+1]=1;
                    G.arcs[i*m+j+1][i*m+j]=1;
                    G.arcnum++;
                }
            }
        }
    }
    PathMatrix P;
    ShortPathTable D;
    //进行迪杰斯特拉算法
    ShortestPath_Dijkstra(G,start, &P, &D);
    //如果最终记录的权值和还是无穷大,证明,入口和出口之间没有通路
    if (D[exit]==65535) {
        printf("-1");
    }else{
        printf("入口到出口的最短路径长度为:\n");
        printf("%d\n",D[exit]);
        printf("入口到出口的最短路径为(逆序):\n");
        printf("(%d,%d) ",exit/m,exit%m);
        while (P[exit]!=0) {
            printf("(%d,%d) ",P[exit]/m,P[exit]%m);
            exit=P[exit];
        }
        printf("(%d,%d)\n",start/m,start%m);
    }
    return 0;
}

image.png

10.4 广度优先搜索实现迷宫

广度优先搜索也可以查找最短路径,该算法的实现可以直接在二维数组中完成,没有必要转化为图的形式。

image.png

例如拿上图中的迷宫举例,骑士一开始只能选择向右走,当走到坐标为 (2,2) 的位置,骑士有两个选择:向上走或者向下走。

对于广度优先搜索来说,其实现使用的数据存储结构为队列,在搜索的过程中,将每种可选情况都入队,然后一轮一轮的对队列中的可选情况进行尝试,知道尝试出想要的结果为止。

对于此时的骑士来说,结合对广度优先算法的理解,就相当于骑士会分身术,一分为二,一个往上,一个往下,每个人每次只能走一步(你走一步然后我走一步)。

例如假设骑士走下,分身去上,当骑士走到坐标为(3,4)的位置时,又需要选择,要么往右,要么往下,此时骑士又分身,各走各的。但是无论怎么分,所有的骑士都是每次只走一步。

在这种情况下,当只要有一个骑士找到出口时,他所走的路径就绝对是最短路径。

对于广度优先搜索来说,使用的是队列的数据结构,等同于在遍历一棵二叉树时,一层一层的遍历(从上往下,从左往右),可以看做是,对于每种情况,轮流去试探,每次只走一步。

在实际编程实现时,使用广度优先搜索查找最短路径时,只能求得最短路径的长度,如果想要获取最短路径的具体路线,还需要结合其他算法。

在本节,给大家的一个解决思路是:在存储迷宫时,对于每个顶点都分配一个整形变量,在进行广度优先搜索时,骑士和其分身每走一步,该顶点所携带的整形变量的值都是骑士之前所处位置的整形变量+1。

例如,对于下图的迷宫来说,骑士在最终找到出口时的整形变量为:

image.png

提示:从入口开始,初始值假设为 0 ,其右侧通路和下方通路的整形变量的值是0+1=1,最终其出口自身所携带的整形变量值就是最短路径的长度。

通过对“骑士们”所走路线中整形变量的设置,此时我们可以结合回溯法,从入口开始寻找骑士所可能走的所有的最短路径(此时找到的可能不只有一条)。

在使用回溯法时,从入口出发,每次同当前顶点周围查找比自身整形变量值大 1 的顶点,就是骑士所走的路线。如果找不到,回退再找,直到将所有的情况都试探完。

广度优先搜索+回溯法解决迷宫问题代码:

#include <stdio.h>
typedef enum{
    false,true
} bool;

typedef struct {
    int x;
    int y;
    char mess;
    int value;
}check;

bool canUsed(int x,int y,char data,int n,int m){
    if (x>=0 && x<n && y>=0 && y<m && data!='#') {
        return true;
    }
    return false;
}
void createMaze(int n,int m,check a[][110],int *entryx,int *entryy,int *exitx,int *exity){
    int i, j;
    for (i=0; i<n; i++) {
        for (j=0; j<m; j++) {
            scanf("%c",&a[i][j].mess);
            a[i][j].x=i;
            a[i][j].y=j;
            if (a[i][j].mess=='S') {
                *entryx=i;
                *entryy=j;
            }else if(a[i][j].mess=='E'){
                *exitx=i;
                *exity=j;
            }
        }
        getchar();
    }
}
//使用的广度优先搜索的思想,采用队列的数据结构实现
void findRoad(check a[][110],int top,int rear,check queue[],int *value,int entryx,int entryy,int n,int m){
    //首先将入口顶点入队
    check data;
    data.x=entryx;
    data.y=entryy;
    a[entryx][entryy].mess='#';
    data.mess=a[entryx][entryy].mess;
    data.value=0;
    queue[rear]=data;
    bool success=false;
    rear++;
    //队列不满
    while (top!=rear) {
        //逐个出队
        check temp=queue[top];
        a[temp.x][temp.y].value=temp.value;
        top++;
        //对于出队的顶点判断是否是出口,首个判断为出口的顶点,其value值就是最短路径的长度
        if (temp.mess=='E') {
            *value=temp.value;
            printf("%d\n",temp.value);
            success=true;
            break;
        }
        //每次入队,判断其上、下、左、右的顶点是否符合条件,若符合,则入队,同时对其value值赋值为前一个顶点value+1,为了避免重复判断此顶点,对每个入队的顶点,设定其字符为‘#’
        if(canUsed(temp.x-1,temp.y,a[temp.x-1][temp.y].mess,n,m)){
            data.x=temp.x-1;
            data.y=temp.y;
            data.mess=a[temp.x-1][temp.y].mess;
            data.value=temp.value+1;
            queue[rear]=data;
            a[temp.x-1][temp.y].mess='#';
            rear++;
        }
        //右边
        if(canUsed(temp.x,temp.y+1,a[temp.x][temp.y+1].mess,n,m)){
            data.x=temp.x;
            data.y=temp.y+1;
            data.mess=a[temp.x][temp.y+1].mess;
            data.value=temp.value+1;
            queue[rear]=data;
            a[temp.x][temp.y+1].mess='#';
            rear++;
        }
        //下边
        if(canUsed(temp.x+1,temp.y,a[temp.x+1][temp.y].mess,n,m)){
            data.x=temp.x+1;
            data.y=temp.y;
            data.mess=a[temp.x+1][temp.y].mess;
            data.value=temp.value+1;
            queue[rear]=data;
            a[temp.x+1][temp.y].mess='#';
            rear++;
        }
        //左边
        if(canUsed(temp.x,temp.y-1,a[temp.x][temp.y-1].mess,n,m)){
            data.x=temp.x;
            data.y=temp.y-1;
            data.mess=a[temp.x][temp.y-1].mess;
            data.value=temp.value+1;
            queue[rear]=data;
            a[temp.x][temp.y-1].mess='#';
            rear++;
        }
    }
    //如果不成功,证明出口和入口之间没有通路
    if (success==false) {
        printf("-1\n");
    }
}
//用于输出最短路径时回溯过程中的判断
bool judgeValue(int x,int y,int n,int m){
    if (x>=0 && x<n && y>=0 && y<m ){
        return true;
    }
    return false;
}
//在输出时,由于最短路径中从入口开始,一直到出口,所经过的顶点的 value 值逐渐 +1,所以采用回溯法查找所有可能的最短路径
void displayRoad(check a[][110],int entryx,int entryy,int n,int m,int value){
    //设置静态数组,实现栈的作用
    static check stack[1000];
    static int top=-1;//栈的栈顶
    int i;
    //对于每个当前的顶点,首先需要判断其是否符合最基本的要求,由于在前期二维数组中的通路都变成了‘#’,这里采用另一个关键字 ,value的值为主关键字进行搜索
    if (judgeValue(entryx, entryy, n, m)) {
        //回溯思想的实现用的是递归,所以需要设置一个出口,出口就是当查找到顶点的value值为最短路径的顶点数时,表明此时已经搜索在出口位置,此时就可以依次输出栈内存储的各个经过的顶点的坐标
        if (a[entryx][entryy].value==value) {
            for (i=0; i<top; i++) {
                printf("(%d,%d) ",stack[i].x,stack[i].y);
            }
            printf("\n");
            return;
        }
        //从入口出发,判断当前点的上、下、左、右位置上的顶点是否符合要求:1、该顶点的坐标没有超出范围;2该顶点的value值是前一个顶点的value值+1,如果都符合,说明之前判断最短路径时就途径此顶点,将其入栈进行保存
        if (judgeValue(entryx+1, entryy, n, m) && a[entryx+1][entryy].value==a[entryx][entryy].value+1) {
            top++;
            stack[top]=a[entryx+1][entryy];
            displayRoad(a, entryx+1, entryy, n, m,value);
            //当运行至此,又两种情况:途径此顶点最终找到出口,并将最终结果输出,此时应将该顶点弹栈;该顶点的路径不是正确的,应弹栈。两种情况都应弹栈。
            top--;
        }
        if (judgeValue(entryx-1, entryy, n, m) && a[entryx-1][entryy].value==a[entryx][entryy].value+1) {
            top++;
            stack[top]=a[entryx-1][entryy];
            displayRoad(a, entryx-1, entryy, n, m,value);
            top--;
        }
        if (judgeValue(entryx, entryy+1, n, m) && a[entryx][entryy+1].value==a[entryx][entryy].value+1) {
            top++;
            stack[top]=a[entryx][entryy+1];
            displayRoad(a, entryx, entryy+1, n, m,value);
            top--;
        }
        if (judgeValue(entryx, entryy-1, n, m) && a[entryx][entryy-1].value==a[entryx][entryy].value+1) {
            top++;
            stack[top]=a[entryx][entryy-1];
            displayRoad(a, entryx, entryy-1, n, m,value);
            top--;
        }
    }
}
int main(int argc, const char * argv[]) {
    check a[110][110];
    check queue[100000];
    int top=0,rear=0;
    int n,m;
    int entryx = 0,entryy=0,exitx=0,exity=0;
    scanf("%d,%d",&n,&m);
    getchar();
    //创建迷宫,并找到入口和出口的位置坐标
    createMaze(n,m,a,&entryx,&entryy,&exitx,&exity);
    //在迷宫中查找从入口到出口的最短路径,若有,输出最短路径的长度;反之,输出-1
    int value;
    findRoad(a,top,rear,queue,&value,entryx,entryy,n,m);
    //输出所有的最短路径
    displayRoad(a, entryx, entryy, n, m, value);
    return 0;
}

image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,372评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,368评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,415评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,157评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,171评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,125评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,028评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,887评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,310评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,533评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,690评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,411评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,004评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,812评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,693评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,577评论 2 353

推荐阅读更多精彩内容

  • 一些概念 数据结构就是研究数据的逻辑结构和物理结构以及它们之间相互关系,并对这种结构定义相应的运算,而且确保经过这...
    Winterfell_Z阅读 5,774评论 0 13
  • 现实生活中有很大一类问题可以用简洁明了的图论语言来描述,可以转化为图论问题。 相关定义 图可以表示为G=(V, E...
    芥丶未央阅读 1,702评论 0 7
  • 1. 图的定义和基本术语 线性结构中,元素仅有线性关系,每个元素只有一个直接前驱和直接后继;树形结构中,数据元素(...
    yinxmm阅读 5,450评论 0 3
  • 目录 基本概念及问题图的三种表示方式现实应用图的遍历最短路径 - Dijkstra算法拓扑排序最小生成树 基本概念...
    萌妈码码阅读 1,241评论 0 2
  • https://zh.visualgo.net/graphds 浅谈图形结构https://zh.visualgo...
    狼之独步阅读 4,148评论 0 0