顺序栈的实现----数据结构

news2024/12/25 9:04:16

栈的概念

对于栈(Stack),后进先出(Last In First Out,LIFO),栈也是一种线性表,只不过是一种操作受限的线性表,只能在一端操作,也就是不允许在中间进行查找、插入、删除等操作。

栈的图:

进出的一端称为栈顶(top),另一端称为栈底(base),栈可以顺序存储,也可以链式存储,这里讲的是栈的顺序存储方式。

栈也可以比喻成一个乒乓球桶,桶底是封口的,桶顶是打开的,桶的横截面积恰好为一个乒乓球投影的面积,也就是说你只能从最后一个乒乓球后放入乒乓球(后面放进去的乒乓球会压着前面的乒乓球),只能拿走最后一个乒乓球(不能拿走被压着的乒乓球)。

栈的算法实现

栈的数据定义

#define MAX_SIZE 120    //栈的最大容量
typedef int DateElem;
typedef struct _Stack
{
	DateElem* top;     //栈顶指针
	DateElem* base;    //栈底指针,指向栈的开始
}Stack;

初始化栈

bool initStack(Stack& s)
{
	s.base = new DateElem[MAX_SIZE];
	if (!s.base) return false;        //空间分配失败

	s.top = s.base;        //为空栈,栈中无任何元素
	return true;
}

销毁栈

销毁与初始化一一对应,初始化时申请了内存,销毁时就需要释放。

void destoryStack(Stack& s)
{
	if (s.base != NULL) //栈空间有效
	{
		delete s.base;
		s.base = s.top = NULL;
	}

}

入栈

bool pushStack(Stack& s, DateElem e)
{
	if (!s.base || (s.top - s.base) >= MAX_SIZE) return false; //栈没建立 或者 栈空间满了

	*(s.top++) = e; //因为最开始 s->top = 0 
	return true;
}

出栈

bool popStack(Stack& s, DateElem& e) //用 e 返回被删除的元素的指
{
	if (!s.base || s.base == s.top) return false;

	e = *(--s.top); //先将栈顶元素赋值给e,栈顶指针再移动,因为栈顶指针都是指向栈顶元素的后一个位置,除了栈为空时,栈顶指针为 0
	return true;
}

获取栈顶元素

bool getTop(Stack& s,DateElem &e)
{
	if (s.top > s.base) //栈不为空
	{
		e = *(s.top - 1); //e 返回栈顶元素的值
		return true;
	}
	else
	{
		return false;
	}
}

判断栈是否为空

bool IsEmpty(Stack& s)
{
	if (s.base == s.top)
	{	
		return true;
	}
	else
	{
		return false;
	}
}

获取栈中元素的个数

int getLength(Stack& s)
{
    return (int)(s.top - s.base);
}

栈的应用

迷宫问题

在给定区域内(二维数组)告诉你起点,找到一条到出口的移动路线。

迷宫求解问题

对于走出一个迷宫,我们只需要将所有的路都走一遍就可以走出迷宫(需要避免走重复的路,对走过的路做好标记),在这个过程中,如果遇到岔路,就选择其中一条路前进,如果碰到死胡同就返回,回退到上个岔路,选择别的路,如果这个岔路也无路可走了,继续回退到上个岔路选择一条路……直到找到出口,或者是无路可走。(我们把一个坐标的位置看作一个岔路,可以向上、下,左、右走

找迷宫的通路使用到的是回溯法,穷举法的改进,回溯的过程需要用到栈。(我最开始想到的是栈的递归)

回溯法:对一个包括有很多个结点,每个结点有若千个搜索分支的问题,把原问题分解为若千个子问题求解的算法;当搜索到某个结点发现无法再继续搜索下去时,就让搜索过程回溯(回退)到该节点的前一一个结点,继续搜索该节点外的其他尚未搜索的分支;如果发现该结点无法再搜索下去,就让搜索过程回溯到这个结点的前一结点继续这样的搜索过程;这样的搜索过程--直进行到搜索到问题的解或者搜索完了全部可搜索分支没有解存在为止。

代码实现 

其中栈存储的数据为,而不是之前的 int 类型了:

typedef struct _Position
{
	int x;
	int y;
}position;

typedef position DateElem;

然后代码我就没有发关于顺序栈的实现了。

具体思想:

、在当前位置,分别判断左、上、右、下四个方向的位置是否为路。

、选择一条路,然后这条路就是当前位置了。(每当某个位置变为当前位置就要标记一下,入口也要,防止重复走),判断当前位置是否为终点,不为的话,执行步骤一。

、如果在当前位置,左、上、右、下四个方向的位置都走过了或者是墙,就回退到上一个位置(存储在栈中),执行步骤一、二。

、重复步骤一、二、三,直到找到出口或者回退到入口。

#include <iostream>
#include "顺序栈.h"

using namespace std;

#define ROW 6
#define COL 6

typedef struct _Maze //迷宫的结构体
{
	int map[ROW][COL];
}Maze;

void initMaze(Maze* maze, int map[ROW][COL]) //将传入的地图数据来初始化迷宫
{
	for (int i = 0; i < ROW; i++)
	{
		for (int j = 0; j < COL; j++)
		{
			maze->map[i][j] = map[i][j];
		}
	}
}
void printMaze(Maze* maze) //打印整个迷宫
{
	for (int i = 0; i < ROW; i++)
	{
		for (int j = 0; j < COL; j++)
		{
			cout << maze->map[i][j] << " ";
		}
		cout << endl;
	}
}

int IsValidEnter(position* enter,Maze *maze) //判断入口是否有效
{

	if (!enter || !maze) return -1; //合法性检查

	if ((enter->x == 0 || enter->x == ROW-1 ) ||
		(enter->y == 0 || enter->y == COL-1) &&
		maze->map[enter->x][enter->y] == 1) //在地图的边界上并且是通路
	{
		return 1;
	}

	else
	{
		return 0;
	}
}
int IsValidExit(position *cur, Maze* maze,position *enter) //判断出口是否有效
{
	if (!cur || !maze || !enter) return -1; //合法性检查

	if (((cur->x == 0 || cur->x == ROW - 1) ||
		(cur->y == 0 || cur->y == COL - 1)) &&
		(enter->x != cur->x && enter->y != cur->y)
		) //在地图边界上,并且不是入口,那就是出口
	{
		return 1;
	}
	else
	{
		return 0;
	}

}
int IsNextPass(position *next, Maze* maze) //判断下一步的位置是否有效
{
	if (!next || !maze) return -1; //合法性检查

	if ((next->x >= 0 && next->x < ROW) &&
		(next->y >= 0 && next->y < COL) &&
		maze->map[next->x][next->y] == 1) //在地图里,并且是路(没走过),等于2、3、4……就是走过的路
	{
		return 1;
	}
	else
	{
		return 0;
	}
}
int PassMaze(position *enter,Maze *maze,Stack *s)
{
	if (!maze || IsValidEnter(enter,maze) == 0) return 0; //迷宫为空或者入口无效

	position cur = { enter->x,enter->y }; //当前人所在的位置
	position next; // 保存下一步的位置
	pushStack(*s, cur); //入口入栈
	maze->map[enter->x][enter->y] = 2; // 走过的地方要改变他的值,防止重复走
	while (!IsEmpty(*s))
	{
		getTop(*s,cur); //得到当前所在的位置
		if (IsValidExit(&cur, maze, enter)) //是出口,就可以结束了
		{
			return 1;
		}

		next = cur;
		next.y--;    //尝试向左走一步,看看能不能继续走
		if (IsNextPass(&next, maze) == 1) 
		{
			pushStack(*s, next); //下一步入栈
			maze->map[next.x][next.y] = maze->map[cur.x][cur.y] + 1; //下一步的值为当前位置的值+1
			continue;
		}
		next = cur;
		next.x--; //尝试向上走一步,看看能不能继续走
		if (IsNextPass(&next, maze) == 1)
		{
			pushStack(*s, next);
			maze->map[next.x][next.y] = maze->map[cur.x][cur.y] + 1;
			continue;
		}
		next = cur;
		next.y++; //尝试向右走一步,看看能不能继续走
		if (IsNextPass(&next, maze) == 1)
		{
			pushStack(*s, next);
			maze->map[next.x][next.y] = maze->map[cur.x][cur.y] + 1;
			continue;
		}
		next = cur;
		next.x++; //尝试向下走一步,看看能不能继续走
		if (IsNextPass(&next, maze) == 1)
		{
			pushStack(*s, next); 
			maze->map[next.x][next.y] = maze->map[cur.x][cur.y] + 1; 
			continue;
		}
		//走到这里了,说明当前位置的四个方向都走不通,进行回溯(到上个结点),看看上个结点未被遍历的方向能否走通
		position temp;
		popStack(*s, temp);
	}

	return false;
}
int main(void)
{
	int map[ROW][COL] = {
		0,0,1,0,0,0,
		0,0,1,1,0,0,
		0,0,1,0,0,0,
		0,1,1,1,1,0,
		0,0,1,0,1,0,
		0,0,0,0,1,0
	}; // 二维数组代表迷宫,1代表路,0代表墙

	Maze maze;		//迷宫的拷贝
	initMaze(&maze, map);		//初始化maze迷宫
	//printMaze(&maze);		//打印迷宫,测试

	position enter = { 0,2 };		//创建了迷宫入口并初始化
	Stack s;		//栈用来保存已走过的路,便于回溯
	initStack(s);		//初始化栈

	if (PassMaze(&enter, &maze, &s))
	{
		cout << "恭喜你,找到了出口" << endl;
	}
	else
	{
		cout << "无路可走了" << endl;
	}
	
	printMaze(&maze); //打印迷宫
	return 0;
}

代码很多,但是结构清楚。以下是代码的执行效果,能清楚知道移动的轨迹。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

javaEE -4(11000字详解多线程)

一&#xff1a;常见的锁策略 1.1 乐观锁 vs 悲观锁 乐观锁和悲观锁是并发控制的两种不同策略&#xff0c;用于处理多个线程同时访问共享资源的情况。它们的主要区别在于对并发冲突的处理方式。 悲观锁是一种较保守的并发控制策略&#xff0c;它假设在整个事务过程中会发生冲…

什么是云迁移?迁移到云的基本指南

企业可以执行多种类型的云迁移。一种常见的模型是将数据和应用从本地数据中心传输到公共云。然而&#xff0c;云迁移还可能需要将数据和应用从一个云平台或提供商迁移到另一个云平台或提供商;这种模式称为云到云(C2C)迁移。第三种类型的迁移是反向云迁移、云遣返或云退出&#…

如何提高webpack的构建速度?

一、背景 随着我们的项目涉及到页面越来越多&#xff0c;功能和业务代码也会随着越多&#xff0c;相应的 webpack 的构建时间也会越来越久 构建时间与我们日常开发效率密切相关&#xff0c;当我们本地开发启动 devServer 或者 build 的时候&#xff0c;如果时间过长&#xff…

Linux下的IMX6ULL——环境搭建与软件安装(一)

前言&#xff1a; 从今天开始我们就要用到IMX6ULL这块开发板进行linux的学习了&#xff0c;对于初学者&#xff0c;下载好资料、搭建好开发环境后&#xff0c;按照下面顺序学习即可&#xff1a; 先应用&#xff0c;再驱动&#xff0c;最后做项目。应用、驱动、项目这三个慢慢走…

酷开科技丨大屏购物买买买,酷开系统助力网购模式再升级

随着技术的发展和家庭场景智能化的趋势&#xff0c;消费者对品质和体验的需求不断提高。在这一背景下&#xff0c;OTT大屏营销生态得到了快速发展&#xff0c;大屏的购物功能进一步被释放&#xff0c;已经具备更强的“转化”能力。电视的智能化、交互简单化、账号同步化等特性使…

如何利用验证链技术减少大型语言模型中的幻觉

一、前言 随着大型语言模型在自然语言处理领域取得了惊人的进步。相信深度使用过大模型产品的朋友都会发现一个问题&#xff0c;就是有时候在上下文内容比较多&#xff0c;对话比较长&#xff0c;或者是模型本身知识不了解的情况下与GPT模型对话&#xff0c;模型反馈出来的结果…

Linux基础指令(二)

目录 前言 1. which 2. alias 3. cat 3.1 echo指令 3.2 输出重定向 3.3 cat指令 3.4 * cat其他用法 4. more指令 5. less指令 6.head指令 7. tail 指令 8. 时间相关的指令 9. cal指令 总结 前言 Linux环境下的指令本质是一组可执行的命令&#xff0c;它们被编写成可…

商家在哪里可以制作小程序抽奖活动

在当今数字化时代&#xff0c;小程序已经成为一种普及且高效的营销工具。对于商家而言&#xff0c;利用小程序开展抽奖活动是一种极具吸引力的营销策略。本文将详细介绍商家如何创建小程序抽奖活动&#xff0c;并阐述其带来的营销效果。 一、明确活动目的与方案 在开始制作小程…

图纸管理制度《一》

在不断进步的时代&#xff0c;我们都跟制度有着直接或间接的联系&#xff0c;制度一般指要求大家共同遵守的办事规程或行动准则&#xff0c;也指在一定历史条件下形成的法令、礼俗等规范或一定的规格。那么什么样的制度才是有效的呢? 彩虹图纸管理软件_图纸管理系统_图纸文档管…

浅析 C# Console 控制台为什么也会卡死

一&#xff1a;背景 1. 讲故事 在分析旅程中&#xff0c;总会有几例控制台的意外卡死导致的生产事故&#xff0c;有经验的朋友都知道&#xff0c;控制台卡死一般是动了 快速编辑窗口 的缘故&#xff0c;截图如下&#xff1a; 虽然知道缘由&#xff0c;但一直没有时间探究底层…

【C++】不是用new生成的对象调用析构函数

2023年10月23日&#xff0c;周一上午 #include <iostream>class Book{ private:int price; public:~Book(){std::cout<<"调用析构函数"<<std::endl; } };int main(){Book b1;b1.~Book(); } 从运行结果可以看出&#xff1a; 手动调用b1.~Book()时&…

众和策略:题材股什么意思?

题材股是股票商场上的一个术语&#xff0c;许多刚接触股票出资的人可能对它不太熟悉。那么&#xff0c;题材股什么意思呢&#xff1f;在本文中&#xff0c;咱们将从多个角度剖析这个问题&#xff0c;帮忙读者更好地了解。 一、什么是题材股 题材股是指某个工作或主题的股票集结…

redis缓存基本使用和缓存问题解决

一、缓存 1、缓存使用 为了系统性能的提升&#xff0c;我们一般都会将部分数据放入缓存中&#xff0c;加速访问。而 db 承担数据落盘工作。 哪些数据适合放入缓存&#xff1f; &#xff08;1&#xff09;即时性、数据一致性要求不高的 &#xff08;2&#xff09;访问量大且更…

面试官:说说Loader和Plugin的区别?

一、区别 前面两节我们有提到Loader与Plugin对应的概念&#xff0c;先来回顾下 loader 是文件加载器&#xff0c;能够加载资源文件&#xff0c;并对这些文件进行一些处理&#xff0c;诸如编译、压缩等&#xff0c;最终一起打包到指定的文件中plugin 赋予了 webpack 各种灵活的…

抛硬币有连续x次正面的期望

首先来看期望的基本公式 其中x是抛x次&#xff0c;p是在抛第x次后出现连续正面的概率 先来看出现1次正面的期望 按照公式来算是 抛1次 1/2的概率 抛2次 1/4的概率 抛3次 1/8的概率... 我们可以写一个程序试一下 signed main() {ios_base::sync_with_stdio(0); cin.tie(0…

企业需要拓展出海业务?CRM为您保驾护航(下)

2022年企业似乎格外艰难&#xff1a;线上流量看似见顶&#xff0c;线下受疫情影响严重&#xff0c;展会推迟&#xff0c;出差受阻&#xff0c;增长乏力沦为常态。为了寻求增长&#xff0c;一批又一批企业将目光看向海外&#xff0c;那里尚有流量红利和增长空间等待挖掘。CRM客户…

解码Java的垃圾回收:掌握GC机制,提升代码的稳定性与可伸缩性

1. GC是什么 JVM垃圾收集(Java Garbage Collection ) GC采用分代收集算法&#xff1a; 次数上频繁收集Young区次数上较少收集Old区基本不动Perm区 2. GC 算法总体概述 JVM在进行GC时&#xff0c;并非每次都对上面三个内存区域一起回收的&#xff0c;大部分时候回收的都是指…

【milkv】internal codec -- mic

文章目录 一、硬件二、dts三、配置引脚board_initpin 四、makefile五、config六、添加tinyalsa七、测试结果7.1 查看录制device信息7.2 录制音频7.3 pc端获取录制好的设备7.4 使用audacity查看波形 一、硬件 i2s0 —— adc i2s1 —— pdm i2s3 —— dace spk_en存疑 i2s2 ——…

Git常用的命令有哪些?

一、前言 git 的操作可以通过命令的形式如执行&#xff0c;日常使用就如下图6个命令即可 实际上&#xff0c;如果想要熟练使用&#xff0c;超过60多个命令需要了解&#xff0c;下面则介绍下常见的的git 命令 二、有哪些 配置 Git 自带一个 git config 的工具来帮助设置控制…

【Python爬虫】安装requests库解决报错问题

requests 确保pip的安装命令行下安装出现的问题以及解决办法换镜像源安装验证安装为什么使用requests库呢 废话不多说了&#xff0c;直接进入正题 确保pip的安装 首先要想安装requests库&#xff0c;第一点就是要确保pip已经安装。这个pip在Python高级版本中已经默认安装了。…