数据结构-栈和队列的应用

目录

  • 前言
  • 一、栈的应用(迷宫问题)
    • 1.1 问题描述
    • 1.2 算法选择
    • 1.3 算法精化
    • 1.4 算法实现
    • 1.5 问题结果
  • 二、队列的应用(农夫过河问题)
    • 2.1 问题描述
    • 2.2 算法选择
    • 2.3 算法精化
    • 2.4 算法实现
    • 2.5 问题结果
  • 总结

前言

本篇文章使用两个例子说明栈和队列的应用,
对于迷宫问题,使用栈实现深度优先策略解决迷宫问题;
对于农夫过河问题,使用队列实现广度优先策略解决农夫过河问题。

一、栈的应用(迷宫问题)

1.1 问题描述

从入口进入迷宫,如何可以尽快找到迷宫的出口?这是一个十分有趣的经典游戏。
迷宫可用如图1.1所示的方块表示,其中每个元素或为通道(以空白方块表示),或为墙(以带阴影的方块表示)。迷宫问题要求的是:从入口到出口的一个以空白方块构成的(无环路径)。
在这里插入图片描述

图1.1 迷宫的图形表示

1.2 算法选择

求解迷宫问题的简单方法是:从入口出发,沿某一方向进行探索,若能走通,则继续往前走;否则沿原路返回,换一方向再进行探索,直到找到了一个出口,或者所有可能的探索都以失败而告终。这类探索方法统称为回溯法,也可以称为深度优先探索方法。实现深度优先探索的工具是栈。
其算法的基本框架如下:

mazeFrame(void)
{创建一个(保存探索过程)空栈;把入口位置压入栈中;while(栈不为空){取栈顶位置并设置为当前位置;while(当前位置存在试探的可能性){取下一试探位置;if(下一位置是出口)打印栈中保存的探索过程然后返回;if(下一位置是通道)把下一位置进栈并且设置为当前位置;}}	
}

1.3 算法精化

在求解程序中,迷宫可用二维数组maze[m][n]表示,其中数组中元素是0表示通道,1表示墙。
入口坐标为(1,1)
出口坐标为(6,9)
在这里插入图片描述

图1.2 迷宫的二维数组表示

为了细化前面的框架,还要考虑试探方向的表示
假设某时刻所在迷宫的位置坐标为(i,j),相邻的四个位置分别标E、S、W、N表示东、南、西、北方向;
为了简化算法,可以建立一个数组direction,这个数组给出了相对于位置(i,j)的4个方向上,i和j的增量值
使用一个变量elem_d表示试探方向,
0-表示方向E
1-表示方向S
2-表示方向W
3-表示方向N
则某个方向的试探位置i和j的变化值为

new_i = i + direction[elem_d][0] (新的行坐标)
new_j = j + direction[elem_d][1] (新的列坐标)例如,当位置坐标为(3,4)时
elem_d = 0 表示试探方向为E
则试探坐标为
new_i = i + direction[0][0] = 3 + 0 = 3
new_j = j + direction[0][1] = 4 + 1  = 5
则新坐标为(3,5)

在这里插入图片描述

图1.3 迷宫中方向的表示

另外,为了避免走到已经试探过的位置(包括现在探索路径上的和曾经在探索路径上的位置),凡是已经探索过的位置都应该做上标记。根据约定,值为1表示墙,0表示通道,那么,凡是探索过的位置可以赋予一个既非0又非1的值,假设取值为2(这样做可以节省空间,缺点是破坏了maze数组的状态;否则要设置一个与迷宫那样大小的数组来保存标记)。一旦将某一位置(i,j)纳入到当前路径中,就将maze[i][j]设置为2。为了记录探索路径中当前位置以及在该位置上的试探方向,算法设置了一个栈,栈中元素包括三项,分别记录当前位置的行坐标、列坐标和以及在该位置上的试探方向。

//栈数据元素类型
struct ElemType {int x;			//行坐标int y;			//列坐标int direction;	//试探方向
};

1.4 算法实现

经过上述设计,求迷宫中一条路径上的算法可以从入口开始,对每个当前位置都从E方向(elem_d = 0)开始试探,若不能通过,则顺时针依次试探S方向、W方向和N方向。当选定一个可以前进的位置,要把当前所在的位置纳入探索路径中,并将当前所在位置以及试探方向记录下来,以便走不通时可以顺序原路一步步回退,每退一步以后接着试探在该位置上的其他未试探过的方向,如此循环,直到找到出口。

/*
int(*maze)[11] 数组指针类型,表示传入迷宫数组
int(*direction)[2] 数组指针类型,表示传入direction数组
int entrance_x  入口行坐标
int entrance_y  入口列坐标
int eixt_x		出口行坐标
int exit_y 		出口纵坐标
*/
void mazePath(int (*maze)[11], int(*direction)[2], int entrance_x, int entrance_y, int exit_x, int exit_y)
{int elem_x = 0;int elem_y = 0;int elem_d = 0;int newDirection_x = 0;int newDirection_y = 0;//初始化栈SeqStack stack = { 0 };initSeqStack(&stack);//初始化入口坐标元素SElemType element = { 0 };maze[entrance_x][entrance_y] = 2; //从入口开始标记element.x = entrance_x;			 element.y = entrance_y;element.direction = -1;		     //未试探任何方向if (!pushSeqStack(&stack, element))	 //将入口点进栈{destroySeqStack(&stack);return;}while (!isEmptySeqStack(&stack))   //走不通时,一步步回退{if (!popSeqStack(&stack, &element))  //获取栈顶元素并出栈{destroySeqStack(&stack);return;}elem_x = element.x;elem_y = element.y;elem_d = element.direction+1;while (elem_d <= 3)  //一次试探一个方向{newDirection_x = elem_x + direction[elem_d][0];newDirection_y = elem_y + direction[elem_d][1];if (newDirection_x == exit_x && newDirection_y == exit_y && maze[newDirection_x][newDirection_y] == 0)//走到出口{element.x = elem_x;element.y = elem_y;element.direction = elem_d;if (!pushSeqStack(&stack, element)){destroySeqStack(&stack);return;}element.x = newDirection_x;element.y = newDirection_y;element.direction = 4;if (!pushSeqStack(&stack, element)){destroySeqStack(&stack);return;}printf("The revers path is:\n"); //打印路径while (!isEmptySeqStack(&stack)){if (!popSeqStack(&stack, &element)){destroySeqStack(&stack);return;}printf("The node is: (%d %d)\n", element.x, element.y);}//销毁栈destroySeqStack(&stack);return;}if (maze[newDirection_x][newDirection_y] == 0) //走到没走过的点{maze[newDirection_x][newDirection_y] = 2;   //标记走过的点element.x = elem_x;element.y = elem_y;element.direction = elem_d;if (!pushSeqStack(&stack, element))  //进栈{destroySeqStack(&stack);return;}elem_x = newDirection_x;  //下一点转换成当前点elem_y = newDirection_y;elem_d = -1;}elem_d++;}}printf("The path has not been found!\n");//销毁栈destroySeqStack(&stack);
}

注意: 顺序栈的代码已经省略

1.5 问题结果

二、队列的应用(农夫过河问题)

2.1 问题描述

一个农夫带着一只狼、一只羊和一颗白菜,身处河的南岸。农夫要把这些东西全部运到河的北岸。问题是农夫只有一条小船,船小到只能容下农夫和一件物品,当然,船只有农夫能撑。另外,因为狼能吃羊,而羊能吃白菜,所以,农夫不能留下羊和狼或者羊和白菜单独在河一边,自己离开。好在狼属于食肉动物,它不吃白菜。请问农夫该采取什么方案,才能将所有的东西安全运过河呢?

2.2 算法选择

求解这个问题的最简单的方法是逐步进行试探。每一步都在前一步选择基础上搜素下一步的所有可能的状态。用计算机实现上述系统搜索过程,可以采用两种不同的策略,一种是广度优先搜索(breadth first);另一种是深度优先搜索(depth first)。实现广度优先搜索的工具是队列;实现深度优先搜索的工具是栈。本节讨论队列的应用,所以重点介绍广度优先搜索策略。
广度优先搜索的思想就是,在搜索过程中,总是首先搜索下面一步的所有可能情况,然后再进一步考虑更后面的各种情况。要实现广度优先搜索,一般都采用队列作为辅助结构,把每一步所有可能达到的状态都列举出来,放在这个队列中,然后顺序取出来分别进行处理,在处理中又再把下一步的情况全部放在队列里。由于队列的操作原则是先进先出,所以,只有在前一步的所有情况都处理完后,才能开始后面一步各情况的处理。
以遍历二叉树为例,使用广度优先搜索策略,进行层序遍历。
在这里插入图片描述

图2.1 广度优先遍历二叉树

2.3 算法精化

要模拟农夫过河问题,首先需要选择一个对问题中每个角色的位置进行描述的方法。一个很方便的方法是用4位二进制数顺序分别表示农夫、狼、白菜和羊的位置。例如,用0表示农夫或某物品在河的南岸,1表示在河的北岸。因此,整数5(其二进制表示为0101)表示农夫和白菜在河的南岸,而狼和羊在北岸。这时,农夫不在,因此狼会把羊吃掉,所以是一种不安全的状态。问题的初始状态是整数0(其二进制为0000);而问题的终结状态是整数15(其二进制表示为1111)。
在这里插入图片描述

图2.2 二进制位置表示图

用整数location表示用上述方法描述的状态,可以用下面的4个函数从上述状态得到每个角色所在位置的代码。函数返回值为真(1)表示农夫或物品的位置在河的北岸,否则在南岸。

//农夫过河问题
//个体判断函数
/*	四位二进制数分别表示农夫,狼,白菜,羊的位置0 表示在南岸  1 表示在北岸初始状态 location = 0000(二进制) 表示农夫,狼,白菜,羊都位于河的南岸使用按位 & 操作,取出每个位置的信息,例如 location & 0x08 取出农夫的位置信息
*///取出农夫位置信息
int farmer(int location)
{return (0 != (location & 0x08));
}//取出狼位置信息
int wolf(int location)
{return (0 != (location & 0x04));
}//取出白菜位置信息
int cabbage(int location)
{return (0 != (location & 0x02));
}//取出羊位置信息
int goat(int location)
{return (0 != (location & 0x01));
}

此外,还应该分析问题中的所有角色构成的状态,确定其中那些状态是安全的,哪些是不安全的。因为,单独留下白菜和羊或单独留下狼和羊在某一岸是不安全的,所以安全状态的判断可以使用下面的函数实现。

//安全状态的判断函数
/*不能单独留下狼和羊,例如 location = 0101 是一种不安全的状态不能单独留下白菜和羊,例如 location = 1100 是一种不安全的转态安全返回 1不安全返回 0
*/int safe(int location)
{//判断羊和白菜是否单独if ((goat(location) == cabbage(location)) && (farmer(location) != goat(location))){return 0;}//判断狼和羊是否单独if ((wolf(location) == goat(location)) && (farmer(location) != goat(location))){return 0;}return 1;   //其他状态安全
}

2.4 算法实现

完成了上面的准备工作后,现在的问题变成:从初始状态二进制0000出发,寻找一种全部由安全状态构成的、能够实现的状态变迁序列(序列中的每状态都可以从前一状态通过农夫带东西划船过河到达),到达最终状态二进制1111.为避免不必要的重复,在序列中不应该出现重复的状态。
根据广度优先搜索的思想,算法中需要使用一个整数队列moveTo,把搜索过程中每一步所有可能到达的状态都保存起来。队列中的每个元素表示可以安全到达的中间状态。
另外,使用一个整数数组rooute记录已被访问过的各个状态,以及已被发现的能够到达这些状态的前驱状态。由于在这个问题中需要列举的所有状态(二进制0000~1111)一共16种,所以route数组只需要使用16个元素。route的每个元素初始化为-1,每当在队列中加入一个新的状态时,就把route中以该状态作下标的元素的值改为达到这个状态的前一转态的下标值。所以数组的第i个元素不仅记录了状态i是否已经被访问过,同时对于以及被访问过的状态,还保存了这个状态的前驱状态下标。算法结束后,可以利用route数组元素的值生成一个正确的路径。
在这里插入图片描述

图2.3 route数组

代码实现如下:

//农夫问题求解/*从初始状态二进制0000出发,寻找一种全部由安全状态构成、能够实现的状态变迁序列(序列中的每个状态都可以从前一状态通过农夫带东西划船过河到达)到达最终的状态1111。为避免不必要的重复,在序列中不应该出现重复的状态整数队列moveTo:把搜索过程中每一步所有可能到达的状态都保存起来。队列中的每个元素表示可以安全到达的中间状态整数数组route:用于记录已被访问过的各个状态,以及已被发现的能够到达这些状态的前驱状态由于在这个问题中需要列举的状态(二进制0000~1111)一共16种,则数组长度大小为16每个数组的元素值初始化为-1每当在队列中加入一个新的状态时,就把route中以该状态做下标的元素的值改为达到这个状态的下标值数组的第i个元素不仅记录状态i是否已被访问过,同时对于已被访问过的状态,还保存了这个状态的前驱状态算法结束后,可以利用route数组元素的值生成一个正确的状态路径
*/void farmerProblem()
{int movers, location, newlocation;int route[16];				//用于记录已考虑的状态路径struct SeqQueue moveTo;		//用于记录可以安全到达的中间状态initSeqQueue(&moveTo);		//初始化队列enSeqQueue(&moveTo, 0x00);	//初始状态二进制0000入队for (int i = 0; i < 16; i++)//初始化数组route{route[i] = -1;}route[0] = 0;while (!isEmptySeqQueue(&moveTo) && (route[15] == -1)){getQElemSeqQueue(&moveTo, &location);	//取出队头状态为当前状态QElemType e;deSeqQueue(&moveTo, &e);//循环值依次表示羊、白菜、狼、农夫的移动情况//movers的值用二进制表示依次为0001(羊)、0010(白菜)、0100(狼)、1000(农夫)for (movers = 1; movers <= 8; movers <<= 1){if ((0 != (location & 0x08)) == (0 != (location & movers))) //农夫与移动的物品在同一岸{// 0x08 | movers 得到的结果表明农夫或物品应该在船上,物品无法单独过河// location ^ (0x08 | movers) 把坐船过河的农夫与物品的状态翻转newlocation = location ^ (0x08 | movers);			//计算新状态if (safe(newlocation) && route[newlocation] == -1)	//新状态安全且未处理{route[newlocation] = location;					//记录旧状态且作为新状态的前驱enSeqQueue(&moveTo, newlocation);				//将新状态入队}}}}if (route[15] != -1){printf("The reverse path is :\n");for (location = 15; location >= 0; location = route[location]){printf("The location is : %d\n", location);if (location == 0){exit(0);}}}else{printf("No solution\n");}
}

注意:这里实现队列的代码已省略,感兴趣可查看文章:https://blog.csdn.net/pyc68/article/details/145093486?spm=1001.2014.3001.5501

算法开始时,把初始状态为0(表明人和物都在南岸)放入队列中。while循环时,只要队列不为空便取出队头元素,for循环用于列举所有可以移动的角色(包括农夫本身,用movers表示),for循环的增量表达式里使用了左移位操作,循环值依次为1、2、4、8,分别表示羊、白菜、狼和农夫的移动情况。由于在每一次移动时,农夫都必须改变状态,所以只有农夫与被移动的东西在同一岸时,农夫才可以将其带走。当然,农夫可以什么都不带,单独过河。将变量movers与二进制0x08进行按位异或运算,所得结果为1,表明相应的农夫或物品应该在船上,再将原有状态与这个结果进行一次按位异或运算,得到移动后的一个新状态。最后检验新状态是否安全,是否未被处理过。如果成立,就确认这个状态转换可行(把这个状态放入队列中,并且修改route数组的值)

2.5 问题结果

图2.4标出了送入队列的各个状态(位置)和广度优先搜索的顺序编号。
通过图2.4得出一条从0000到达1111的路径:
0000-1001:农夫把羊从南岸带到北岸
1001-0001:农夫独自从北岸回到南岸
0001-1011:农夫把白菜从南岸带到北岸
1011-0010:农夫把羊从北岸带回南岸
0010-1110:农夫把狼从南岸带到北岸
1110-0110:农夫独自从北岸回到南岸
0110-1111:农夫把羊从南岸带到北岸

在这里插入图片描述

图2.4 广度优先搜索的结果和顺序图

总结

迷宫问题完整代码: https://gitee.com/PYSpring/data-structure/tree/master/maze_code
农夫过河问题完整代码:https://gitee.com/PYSpring/data-structure/tree/master/queue_code

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/18905.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SkyWalking 10.1.0 实战:从零构建全链路监控,解锁微服务性能优化新境界

文章目录 前言一、集成SkyWalking二、SkyWalking使用三、SkyWalking性能剖析四、SkyWalking 告警推送4.1 配置告警规则4.2 配置告警通知地址4.3 下发告警信息4.4 测试告警4.5 慢SQL查询 总结 前言 在传统监控系统中&#xff0c;我们通过进程监控和日志分析来发现系统问题&…

AIGC图生视频保姆级教程

一、AI文生图高阶技巧 推荐工具 ▸ MidJourney&#xff08;艺术感最强&#xff09; ▸ DALLE 3&#xff08;与ChatGPT深度联动&#xff09; ▸ Leonardo.ai&#xff08;精细化参数控制&#xff09; 核心策略 提示词架构&#xff1a; [主体描述][环境氛围][镜头语言][风格参数…

springboot整合mybatis-plus【详细版】

目录 一&#xff0c;简介 1. 什么是mybatis-plus2.mybatis-plus特点 二&#xff0c;搭建基本环境 1. 导入基本依赖&#xff1a;2. 编写配置文件3. 创建实体类4. 编写controller层5. 编写service接口6. 编写service层7. 编写mapper层 三&#xff0c;基本知识介绍 1. 基本注解 T…

利用亚马逊云科技RDS for SQL Server配置向量数据存储

生成式人工智能&#xff08;AI&#xff09;正迎来又一个快速发展期&#xff0c;引起了开发者们的广泛关注。将生成式能力集成到商业服务和解决方案中变得非常重要。当前的生成式AI解决方案是机器学习和深度学习模型逐步进化迭代的结果。从深度学习到生成式AI的质变飞跃主要是由…

c++ 多线程知识汇总

一、std::thread std::thread 是 C11 引入的标准库中的线程类&#xff0c;用于创建和管理线程 1. 带参数的构造函数 template <class F, class... Args> std::thread::thread(F&& f, Args&&... args);F&& f&#xff1a;线程要执行的函数&…

H5接入支付宝手机网站支付并实现

小程序文档 - 支付宝文档中心 1.登录 支付宝开放平台 创建 网页/移动应用 2.填写创建应用信息 3.配置开发设置 4.网页/移动应用&#xff1a;需要手动上线。提交审核后&#xff0c;预计 1 个工作日的审核时间。详细步骤可点击查看 上线应用 。应用上线后&#xff0c;还需要完成…

字节二面:DNS是什么?是什么原理?

写在前面 最近有个同学后台私信让我出一个DNS的工作原理&#xff0c;面试的时候居然问到了&#xff0c;所以就简单聊聊DNS的工作原理吧&#xff01; 1. DNS 的核心作用 DNS&#xff08;域名系统&#xff0c;Domain Name System&#xff09;是互联网中用于将人类可读的域名转…

【Unity3D】Jenkins Pipeline流水线自动构建Apk

目录 一、准备阶段 二、创建Pipeline流水线项目 三、注意事项 四、扩展 1、Pipeline添加SVN更新项目Stage阶段 一、准备阶段 1、安装tomcat 10.0.5 Index of apache-local/tomcat/tomcat-10 2、安装jdk 17 Java Archive Downloads - Java SE 17.0.13 and later 3、…

【数据结构】(9) 优先级队列(堆)

一、优先级队列 优先级队列不同于队列&#xff0c;队列是先进先出&#xff0c;优先级队列是优先级最高的先出。一般有两种操作&#xff1a;返回最高优先级对象&#xff0c;添加一个新对象。 二、堆 2.1、什么是堆 堆也是一种数据结构&#xff0c;是一棵完全二叉树&#xff0c…

2025.2.15

web [HNCTF 2022 Week1]Interesting_include&#xff1a; 直接打开 PHP代码片段包含两部分&#xff1a;一个主脚本和一个潜在的被包含文件。主脚本负责处理GET请求&#xff0c;特别是filter参数&#xff0c;而被包含文件&#xff08;假设为./flag.php&#xff09;似乎包含了我…

CentOS 7.8 安装MongoDB 7教程

文章目录 CentOS 7.8 安装MongoDB 7教程一、准备工作1. 系统更新2. 权限 二、添加MongoDB软件源1. 创建MongoDB的yum源文件2. 添加以下内容3. 保存并退出编辑器 三、安装MongoDB1. 更新yum缓存2. 安装MongoDB 四、启动MongoDB服务1. 启动MongoDB2. 设置MongoDB开机自启动 五、配…

ElasticSearch基础和使用

ElasticSearch基础 1 初识ES相关组件 &#xff08;1&#xff09;Elasticsearch是一款非常强大的开源搜索引擎&#xff0c;可以帮助我们从海量数据中快速找到需要的内容。Elasticsearch结合kibana、Logstash、Beats组件 也就是elastic stack&#xff08;ELK&#xff09; 广泛应…

[C++]多态详解

目录 一、多态的概念 二、静态的多态 三、动态的多态 3.1多态的定义 3.2虚函数 四、虚函数的重写&#xff08;覆盖&#xff09; 4.1虚函数 4.2三同 4.3两种特殊情况 &#xff08;1&#xff09;协变 &#xff08;2&#xff09;析构函数的重写 五、C11中的final和over…

【git-hub项目:YOLOs-CPP】本地实现01:项目构建

目录 写在前面 项目介绍 最新发布说明 Segmentation示例 功能特点 依赖项 安装 克隆代码仓库 配置 构建项目 写在前面 前面刚刚实现的系列文章: 【Windows/C++/yolo开发部署01】 【Windows/C++/yolo开发部署02】 【Windows/C++/yolo开发部署03】 【Windows/C++/yolo…

在WPS中通过JavaScript宏(JSA)调用本地DeepSeek API优化文档教程

既然我们已经在本地部署了DeepSeek,肯定希望能够利用本地的模型对自己软件开发、办公文档进行优化使用,接下来就先在WPS中通过JavaScript宏(JSA)调用本地DeepSeek API优化文档的教程奉上。 前提: (1)已经部署好了DeepSeek,可以看我的文章:个人windows电脑上安装DeepSe…

安装 Docker Desktop 修改默认安装目录到指定目录

Docker Desktop安装目录设置 Docker Desktop 默认安装位置 &#xff08;C:\Program Files\Docker\Docker) 是这个 &#xff0c;导致系统盘占用过大&#xff0c;大概2G ; 那么如何安装到其他磁盘呢&#xff1f; 根据docker desktop 官网 Docker Desktop install 我们可以看到&a…

DeepSeek官方发布R1模型推荐设置

今年以来&#xff0c;DeepSeek便在AI领域独占鳌头&#xff0c;热度一骑绝尘。其官方App更是创造了惊人纪录&#xff0c;成为史上最快突破3000万日活的应用&#xff0c;这一成绩无疑彰显了它在大众中的超高人气与强大吸引力。一时间&#xff0c;各大AI及云服务厂商纷纷投身其中&…

常见的IP地址分配方式有几种:深入剖析与适用场景‌

在数字互联的世界里&#xff0c;IP地址如同网络世界的“门牌号”&#xff0c;是设备间通信的基础。随着网络技术的飞速发展&#xff0c;IP地址的分配方式也日趋多样化&#xff0c;以适应不同规模、不同需求的网络环境。本文将深入探讨当前主流的几种IP地址分配方式&#xff0c;…

NLP 八股 DAY1:BERT

BERT全称&#xff1a;Pre-training of deep bidirectional transformers for language understanding&#xff0c;即深度双向Transformer。 模型训练时的两个任务是预测句⼦中被掩盖的词以及判断输⼊的两个句⼦是不是上下句。在预训练 好的BERT模型后⾯根据特定任务加上相应的⽹…

Flutter_学习记录_动画的简单了解

用AnimationController简单实现如下的效果图&#xff1a; 1. 只用AnimationController实现简单动画 1.1 完整代码案例 import package:flutter/material.dart;class AnimationDemo extends StatefulWidget {const AnimationDemo({super.key});overrideState<AnimationDe…