数据结构初阶--栈和队列(讲解+类模板实现)

news2024/11/26 15:49:58

栈的概念和结构

栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)加粗样式的原则。
入栈:从栈顶放入数据的操作。
出栈:从栈顶取出元素的操作。

栈的实现

栈用链表和顺序表两种数据结构都可以实现,我们要确定选择哪一种更优,我们来分析一下。
链表栈:如果选择单链表的话,我们应该选择头当栈顶,尾当栈底,不然的话,每次存取数据都要遍历一遍链表。所以选双链表会比较好一点。
数组栈:访问栈顶的时间复杂度为O(1),相比链表栈比较优。
所以下面我们用顺序表来实现栈的这种数据结构。
结构如下:初始化栈的大小为5

#define InitSize  5
template <class DateType>
class Stack
{
public:
private:
	DateType* data;//栈空间指针
	int size;//栈容量
	int top;//栈顶,栈中元素个数
};	

栈的接口

栈要实现的接口有以下几个:

Stack();//初始化栈,初始化的大小是5
Stack(const Stack& stack);//拷贝构造函数
Stack& operator=(const Stack& stack);//等号运算符的重载
~Stack();//销毁栈
bool ifFull();//判断栈是否满了
bool isEmpty();//判断栈是否为空
void Push(const DateType& val);//入栈
bool Pop(DateType &item);//出栈,并获取出栈元素
void ExpandStack();//扩容
void PrintStack();//打印

栈的初始化

初始化栈就是把结构体中的成员都初始化一下,方便后续的扩容等操作。
具体实现如下:

//初始化栈,初始化的大小是5
Stack()
{
	data = new DateType[InitSize];
	if (data == NULL)
	{
		cout << "内存分配失败" << endl;
		exit(-1);
	}
	size = InitSize;
	top = 0;
}

拷贝构造

//拷贝构造函数
Stack(const Stack& stack)
{
	this->data = new DateType[stack.size];
	if (data == NULL)
	{
		cout << "内存分配失败" << endl;
		exit(-1);
	}
	for (int i = 0; i < stack.size; i++)
	{
		this->data[i] = stack.data[i];
	}
	this->size = stack.size;
	this->top = stack.top;
}

判断栈满

//判断栈是否满了
bool ifFull()
{
	if (top == size)
	{
		return true;
	}
	return false;
}

扩容

//扩容
void ExpandStack()
{
	this->size = this->size == 0 ? 4 : 2 * this->size;
	DateType* tmp = new DateType[this->size];
	if (tmp == NULL)
	{
		cout << "内存分配失败" << endl;
		exit(-1);
	}
	for (int i = 0; i < top; i++)
	{
		tmp[i] = data[i];
	}
	delete[] data;
	data = tmp;
}

判断栈空

//判断栈是否为空
bool isEmpty()
{
	if (top == 0)
	{
		return true;
	}
	return false;
}

入栈

压栈就是在栈顶插入元素,其中是肯定要考虑到扩容的问题,当this->top == this->size时,就要考虑到扩容了,扩容也是像之前顺序表那样每次扩一倍,这样可以一定程度地减少扩容次数,但同时是会带来一定的空间消耗的。

//入栈
void Push(const DateType& val)
{
	if (ifFull())
	{
		ExpandStack();
	}
	data[top++] = val;
}

出栈

出栈就是在栈顶pop掉一个元素,也就是top-1指向的位置,只需要将top进行一个减1的操作即可。
与此同时,我们要确保此次栈不为空,所以要进行判断栈空的操作,防止程序崩溃。

//出栈,并获取出栈元素
bool Pop(DateType &item)
{
	if (isEmpty())
	{
		cout << "栈为空,无法出栈" << endl;
		return false;
	}
	item = data[--top];
	return true;
}

赋值运算符重载

运算符重载的注意事项在前面的博客我已经介绍过了,尤其是赋值运算符,感兴趣的小伙伴可以去看看,这里要注意几点

  • 返回的是*this,对象本身
  • 不要自己给自己赋值
  • 要防止内存泄漏
  • 防止浅拷贝的发生
//等号运算符的重载
Stack& operator=(const Stack& stack)
{
	//防止自赋值
	if (this == &stack)
	{
		return *this;
	}
	//防止内存泄漏
	if (data != NULL)
	{
		delete[] data;
	}
	//防止浅拷贝
	this->data = new DateType[stack.size];
	if (data == NULL)
	{
		cout << "内存分配失败" << endl;
		exit(-1);
	}
	for (int i = 0; i < stack.top; i++)
	{
		this->data[i] = stack.data[i];
	}
	this->size = stack.size;
	this->top = stack.top;
	return *this;
}

打印

//打印
void PrintStack()
{
	for (int i = 0; i < top; i++)
	{
		cout << data[i] << "  ";
	}
	cout << endl;
}

整体代码以及测试

#define _CRT_SECURE_NO_WARNINGS
#include<iostream> //引入头文件
#include<string>//C++中的字符串
using namespace std; //标准命名空间
#define InitSize  5
/*
栈,利用顺序表实现
*/
template <class DateType>
class Stack
{
public:
	//初始化栈,初始化的大小是5
	Stack()
	{
		data = new DateType[InitSize];
		if (data == NULL)
		{
			cout << "内存分配失败" << endl;
			exit(-1);
		}
		size = InitSize;
		top = 0;
	}
	//拷贝构造函数
	Stack(const Stack& stack)
	{
		this->data = new DateType[stack.size];
		if (data == NULL)
		{
			cout << "内存分配失败" << endl;
			exit(-1);
		}
		for (int i = 0; i < stack.size; i++)
		{
			this->data[i] = stack.data[i];

		}
		this->size = stack.size;
		this->top = stack.top;
	}
	//等号运算符的重载
	Stack& operator=(const Stack& stack)
	{
		//防止自赋值
		if (this == &stack)
		{
			return *this;
		}
		//防止内存泄漏
		if (data != NULL)
		{
			delete[] data;
		}
		//防止浅拷贝
		this->data = new DateType[stack.size];
		if (data == NULL)
		{
			cout << "内存分配失败" << endl;
			exit(-1);
		}
		for (int i = 0; i < stack.top; i++)
		{
			this->data[i] = stack.data[i];

		}
		this->size = stack.size;
		this->top = stack.top;
		return *this;
	}
	//销毁栈
	~Stack()
	{
		if (data != NULL)
		{
			delete[] data;
			data = NULL;
		}
	}
	//判断栈是否满了
	bool ifFull()
	{
		if (top == size)
		{
			return true;
		}
		return false;
	}
	//判断栈是否为空
	bool isEmpty()
	{
		if (top == 0)
		{
			return true;
		}
		return false;
	}
	//入栈
	void Push(const DateType& val)
	{
		if (ifFull())
		{
			ExpandStack();
		}
		data[top++] = val;
	}
	//出栈,并获取出栈元素
	bool Pop(DateType &item)
	{
		if (isEmpty())
		{
			cout << "栈为空,无法出栈" << endl;
			return false;
		}
		item = data[--top];
		return true;
	}
	//扩容
	void ExpandStack()
	{
		this->size = this->size == 0 ? 4 : 2 * this->size;
		DateType* tmp = new DateType[this->size];
		if (tmp == NULL)
		{
			cout << "内存分配失败" << endl;
			exit(-1);
		}
		for (int i = 0; i < top; i++)
		{
			tmp[i] = data[i];
		}
		delete[] data;
		data = tmp;
	}
	//打印
	void PrintStack()
	{
		for (int i = 0; i < top; i++)
		{
			cout << data[i] << "  ";
		}
		cout << endl;
	}
private:
	DateType* data;//栈空间指针
	int size;//栈容量
	int top;//栈顶,栈中元素个数
};
int main()
{
	Stack<int> stack;
	stack.Push(1);
	stack.Push(2);
	stack.Push(3);
	stack.Push(4);
	stack.Push(5);
	stack.PrintStack();
	cout << "-------------------------" << endl;
	int b = 0;
	stack.Pop(b);
	cout << b << endl;
	stack.Pop(b);
	cout << b << endl;
	stack.PrintStack();
	cout << "-------------------------" << endl;
	Stack<int> stack2(stack);
	stack2.PrintStack();
	cout << "-------------------------" << endl;
	Stack<int> stack3;
	stack3 = stack2;
	stack3.PrintStack();
	cout << "-------------------------" << endl;
	system("pause");
	return EXIT_SUCCESS;
}

队列

队列的概念和结构

队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out) 入队列:进行插入操作的一端称为队尾 出队列:进行删除操作的一端称为队头。

队列的结构,我们选取单链表来实现,秩序进行头删和为插的不足即可。如果选数组,那么每一次删头我们都要挪动一遍数据,这种方式不优,所以我们还是选取用单链表来实现。
定义的结构如下:

template<class DateType>
//链队的结点类型
struct Node
{
	DateType data;
	Node<DateType> *next;
	Node(Node<DateType>* p = NULL)
	{
		next = p;
	}
	//构造函数
	Node(DateType val, Node<DateType>* p = NULL)
	{
		data = val;
		next = p;
	}
};
template <class DateType>
class Queue
{
public:
private:
	//声明,也是定义,只不过定义的是指针类型,保存的应该是地址,未初始化
	//队头指针
	Node<DateType>* front;
	//队尾指针
	Node<DateType>* rear;
};

队列的实现

队列的接口

Queue();//构造函数,初始化队列
~Queue();//析构函数
bool QueuePush(const DateType& val);//队尾入队
bool QueuePop(DateType& val);//对头出队列
bool getFront(DateType& val) const;//获取对头元素的值
bool getRear(DateType& val);//获取队尾元素的值
void MakeEmpty();//将队列清空
bool isEmpty() const;//判断队列是否为NULL
int getSize() const;//返回队列元素的个数
void PrintQueue();//输出队列元素

队列的初始化

初始化很简单,只要将头指针和尾指针都置空。

//构造函数
Queue()
{
	front = NULL;
	rear = NULL;
}

判断队列是否为空

//判断队列是否为NULL
bool isEmpty() const
{
	if (front == NULL)
	{
		return true;
	}
	else
	{
		return false;
	}
}

入队

出队就是进行单链表尾删的操作,要考虑链表为空时不能进行删除,还要注意的是只有一个节点进行删除是要改变尾指针的指向。

//队尾入队列
bool QueuePush(const DateType& val)
{
	if (front == NULL)
	{
		//如果队列为空,直接用指针开辟结点
		front = rear = new Node<DateType>(val);
		if (front == NULL)
		{
			cout << "内存分配失败" << endl;
			return false;
		}
	}
	else
	{
		Node<DateType>* p = new Node<DateType>(val);
		rear->next = p;
		if (rear->next == NULL)
		{
			cout << "内存分配失败" << endl;
			return false;
		}
		//更新尾结点
		rear = rear->next;
	}
	return true;
}

出队

出队就是进行单链表尾删的操作,要考虑链表为空时不能进行删除,还要注意的是只有一个节点进行删除是要改变尾指针的指向。

bool QueuePop(DateType& val)
{
	//如果队列为空,不允许出列
	if (isEmpty())
	{
		return false;
	}
	else
	{
		Node<DateType>* p = front;
		val = front->data;
		front = front->next;
		delete p;
		return true;
	}
}

获取队头元素和队尾元素

首先要确保链表不为空,对头就是返回头节点的大小,队尾就是返回尾节点的大小。
具体实现如下:

//获取对头元素的值
bool getFront(DateType& val) const
{
	if (isEmpty())
	{
		return false;
	}
	val = front->data;
	return true;
}
//获取队尾元素的值
bool getRear(DateType& val) {
	if (isEmpty())
	{
		return false;
	}
	val = rear->data;
	return true;
}

获取队列元素个数

遍历一遍链表,同时进行计数操作,最后返回计数结果即可。

//返回队列元素的个数
int getSize() const
{
	//函数返回队列元素的个数
	Node<DateType>* p = front;
	int count = 0;
	while (p != NULL)
	{
		++count;
		p = p->next;
	}
	return count;
}

队列的销毁

为了防止内存泄漏,动态内存申请的空间一定要我们自己手动释放,养成一个良好的习惯。所以要将链表的空间逐个释放。

//将队列清空
void MakeEmpty()
{
	//置空队列,释放链表中所有的结点
	Node<DateType>* current;
	if (front != NULL)
	{
		current = front;
		front = front->next;
		delete current;
	}
}

打印队列

//输出队列元素
void PrintQueue()
{
	Node<DateType>* p = front;
	while (p != NULL)
	{
		cout << p->data << "  ";
		p = p->next;
	}
	cout << endl;
}

整体代码以及测试

#define _CRT_SECURE_NO_WARNINGS
#include<iostream> //引入头文件
#include<string>//C++中的字符串
using namespace std; //标准命名空间
/*
队列,单链表实现
*/
template<class DateType>
//链队的结点类型
struct Node
{
	DateType data;
	Node<DateType> *next;
	Node(Node<DateType>* p = NULL)
	{
		next = p;
	}
	//构造函数
	Node(DateType val, Node<DateType>* p = NULL)
	{
		data = val;
		next = p;
	}
};
template <class DateType>
class Queue
{
public:
	//构造函数
	Queue()
	{
		front = NULL;
		rear = NULL;
	}
	//析构函数
	~Queue()
	{
		MakeEmpty();
	}
	//队尾入队列
	bool QueuePush(const DateType& val)
	{
		if (front == NULL)
		{
			//如果队列为空,直接用指针开辟结点
			front = rear = new Node<DateType>(val);
			if (front == NULL)
			{
				cout << "内存分配失败" << endl;
				return false;
			}
		}
		else
		{
			Node<DateType>* p = new Node<DateType>(val);
			rear->next = p;
			if (rear->next == NULL)
			{
				cout << "内存分配失败" << endl;
				return false;
			}
			//更新尾结点
			rear = rear->next;
		}
		return true;
	}
	//对头出队列
	bool QueuePop(DateType& val)
	{
		//如果队列为空,不允许出列
		if (isEmpty())
		{
			return false;
		}
		else
		{
			Node<DateType>* p = front;
			val = front->data;
			front = front->next;
			delete p;
			return true;

		}
	}
	//获取对头元素的值
	bool getFront(DateType& val) const
	{
		if (isEmpty())
		{
			return false;
		}
		val = front->data;
		return true;
	}
	//获取队尾元素的值
	bool getRear(DateType& val) {
		if (isEmpty())
		{
			return false;
		}
		val = rear->data;
		return true;
	}
	//将队列清空
	void MakeEmpty()
	{
		//置空队列,释放链表中所有的结点
		Node<DateType>* current;
		if (front != NULL)
		{
			current = front;
			front = front->next;
			delete current;
		}
	}
	//判断队列是否为NULL
	bool isEmpty() const
	{
		if (front == NULL)
		{
			return true;
		}
		else
		{
			return false;
		}
	}
	//返回队列元素的个数
	int getSize() const
	{
		//函数返回队列元素的个数
		Node<DateType>* p = front;
		int count = 0;
		while (p != NULL)
		{
			++count;
			p = p->next;
		}
		return count;
	}
	//输出队列元素
	void PrintQueue()
	{
		Node<DateType>* p = front;
		while (p != NULL)
		{
			cout << p->data << "  ";
			p = p->next;
		}
		cout << endl;
	}
private:
	//声明,也是定义,只不过定义的是指针类型,保存的应该是地址,未初始化
	//队头指针
	Node<DateType>* front;
	//队尾指针
	Node<DateType>* rear;
};
int main()
{
	Queue<int> que;
	que.QueuePush(1);
	que.QueuePush(2);
	que.QueuePush(3);
	que.QueuePush(4);
	que.PrintQueue();
	cout << "----------------------" << endl;
	int b = 0;
	que.QueuePop(b);
	cout << b << endl;
	que.QueuePop(b);
	cout << b << endl;
	que.PrintQueue();
	cout << "----------------------" << endl;
	int c = 0;
	que.getFront(c);
	cout << c << endl;
	que.PrintQueue();
	cout << que.getSize() << endl;
	cout << "----------------------" << endl;
	system("pause");
	return EXIT_SUCCESS;
}

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

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

相关文章

debug - 用Procmon记录目标程序启动后的操作

文章目录debug - 用Procmon记录目标程序启动后的操作概述笔记备注ENDdebug - 用Procmon记录目标程序启动后的操作 概述 想看看 D:\Cadence\SPB_17.4\tools\bin\Capture.exe 开始页中的recent projects 从哪里读的. 想用Procmon记录Capture.exe启动后的动作, 再记录成文本日志…

【Spring】一文带你吃透AOP面向切面编程技术(上篇)

个人主页&#xff1a; 几分醉意的CSDN博客_传送门 文章目录&#x1f496;AOP概念✨AOP作用✨AOP术语✨什么时候需要用AOP&#x1f496;Aspectj框架介绍✨Aspectj的5个通知注解✨Aspectj切入点表达式✨前置通知Before&#x1f496;投票传送门&#xff08;欢迎伙伴们投票&#xf…

Nginx加载Lua脚本lua_shared_dict缓存

1、介绍 lua_shared_dict缓存是nginx为lua提供的一个多进程共享空间&#xff0c;为了避免多进程修改造成脏数据&#xff0c;lua_shared_dict修改数据是用锁来实现的。这样就会有qps访问瓶颈变小的问题。这是性能缺点。 2、使用 1&#xff09;首先在nginx.conf里申请一块共享…

数据分享|PYTHON用决策树分类预测糖尿病和可视化实例

全文下载链接&#xff1a;http://tecdat.cn/?p23848在本文中&#xff0c;决策树是对例子进行分类的一种简单表示。它是一种有监督的机器学习技术&#xff0c;数据根据某个参数被连续分割。决策树分析可以帮助解决分类和回归问题&#xff08;点击文末“阅读原文”获取完整代码数…

大一学生WEB前端静态网页——唯品会1页 包含hover效果

⛵ 源码获取 文末联系 ✈ Web前端开发技术 描述 网页设计题材&#xff0c;DIVCSS 布局制作,HTMLCSS网页设计期末课程大作业 | 在线商城购物 | 水果商城 | 商城系统建设 | 多平台移动商城 | H5微商城购物商城项目 | HTML期末大学生网页设计作业&#xff0c;Web大学生网页 HTML&a…

SpringCloud:使用Nacos作为配置中心

目录 一、nacos配置中心简介 二、nacos配置实时更新及同一个微服务不同环境的差异化配置 准备工作 针对商品微服务实现实时更新&#xff08;以商品微服务为例&#xff09; 三、nacos同一个微服务不同环境的共享配置 同一个微服务修改配置才能访问不同环境 四、nacos不同微…

【JavaEE】MyBatis

文章目录1.MyBatis介绍2.MyBatis快速入门3.Mapper代理开发4.MyBatis核心配置文件5.配置文件完成增删改查5.1 查询5.2 添加/修改5.3 删除6.MyBatis参数传递7.注解完成增删改查1.MyBatis介绍 1.什么是MyBatis? MyBatis是一款优秀的 持久层框架&#xff0c;用于简化JDBC开发MyBat…

STC 51单片机46——看门狗测试

#include <reg52.h> sfr WDT_CONTR 0xE1; //声明WDT_CONTR void delay(void){ //改变延时长度&#xff0c;可以观测是否触发看门狗 unsigned char i,j,k; for(i0;i<255;i) for(j0;j<255;j) for(k0;k<255;k); } void…

图神经网络

前言 图与图的表示 图是由一些点和一些线构成的&#xff0c;能表示一些实体之间的关系&#xff0c;图中的点就是实体&#xff0c;线就是实体间的关系。如下图&#xff0c;v就是顶点&#xff0c;e是边&#xff0c;u是整张图。attrinbutes是信息的意思&#xff0c;每个点、每条…

MFC界面控件BCGControlBar v33.3 - 升级Ribbon Bar自定义功能

BCGControlBar库拥有500多个经过全面设计、测试和充分记录的MFC扩展类。 我们的组件可以轻松地集成到您的应用程序中&#xff0c;并为您节省数百个开发和调试时间。 该版本包含了增强的Ribbon自定义、新的日期/时间数字指示器、带有文本对齐的组控件、多行支持以及其他一些新功…

第二证券|12月A股投资方向来了!这些板块已先涨为敬

日前&#xff0c;我国银河、信达证券、中泰证券、安全证券等多家券商连续发布12月A股月度出资组合。全体上券商对后市持活跃情绪&#xff0c;以为当时商场处于震动磨底装备区间&#xff0c;商场动摇并不影响“暖冬行情”的延续&#xff0c;一些活跃的券商以为后市有望走出季度级…

R语言rcurl抓取问财财经搜索网页股票数据

问财财经搜索是同花顺旗下的服务之一,主要针对上市公司的公告、研报、即时新闻等提供搜索及参考资料。相对于其他股票软件来说&#xff0c;一个强大之处在于用自然语言就可以按你指定的条件进行筛选。而大部分现有的行情软件支持的都不是很好&#xff0c;写起来就费尽心思&…

Nginx加载Lua脚本链接mysql

1、nginx加载lua脚本方法可参我的这篇文章 Nginx安装Openresty加载Lua代码_IT东东歌的博客-CSDN博客 2、测试代码 官网 https://github.com/openresty/lua-resty-mysql local mysql require "resty.mysql" local db, err mysql:new() if not db then ngx.sa…

Django 第四章 模版系统详解(ORM数据模型-使用mysql数据库增删改查)

djiango模版系统&#xff1a; 用于自动渲染一个文本文件&#xff0c;一般用于HTML页面&#xff0c;模版引擎渲染的最终HTML内容返回给客户端浏览器 模版系统分成两部分 静态部分&#xff1a; 例如html css .js 动态部分 djiango 模版语言&#xff0c;类似于jinja语法变量定义&…

SpringCloud 组件Gateway服务网关【全局过滤器】

目录 1&#xff0c;全局过滤器 1.1&#xff1a;全局过滤器作用 1.2&#xff1a;自定义全局过滤器 1.3&#xff1a;过滤器执行顺序、 2&#xff1a;跨域问题 2.1&#xff1a;什么是跨域问题 2.2&#xff1a;示例跨域问题 2.3&#xff1a;解决跨域问题 1&#xff0c;全局…

python将CSV文件(excel文件)按固定行数拆分成小文件

最近接到一个需求&#xff0c;就是把非常大的CSV文件&#xff0c;电脑根本打不开&#xff08;或者打开也不能完全展现所有的数据&#xff09;&#xff0c;以每 80万(不够80万行的也独自成为一个单独的文件) 行进行拆分成一个小文件&#xff0c;各位小伙伴在日常工作中有没有遇到…

seata分布式事务1.4版本TM注册全局事务之源码分析(五)

今天我们分析seata分布式事务1.4版本TM注册到全流程的源码&#xff0c;这也是事务执行的核心开始&#xff1a; 首先分为客户端TM和服务端TC&#xff0c;业务发起肯定在TM端&#xff0c;接受在TC端。 整体类图&#xff1a; 一、业务入口TM端&#xff1a; 1、GlobalTransactio…

将本地文件上传到gitee和GitHub,以及Github加速访问

&#xff08;1&#xff09;我全程使用网络上合法加速软件&#xff0c;网易UU加速器是网易自主研发极速引擎&#xff0c;属于合法软件。我们进行加速行为也只是针对于Github这一个网站。 &#xff08;2&#xff09;GitHub是一个面向开源及私有软件项目的托管平台&#xff0c;纯技…

Win,M1Mac上安装jupyter的MATLAB支持插件的方法

tags: MATLAB Win Mac Tips 写在前面 11月的最后一天了, 总结一下支持MATLAB的一个jupyter的插件, 有了这个你就可以在jupyter notebook或者jupyter lab上面使用MATLAB语句了, 还是很不错的, 虽然我安装了好久… 下面来说一下我在我的电脑以及朋友的电脑(Win11)上面安装这个…

安科瑞安全用电监测,智慧用电装置,导轨式安装带无线通讯功能

安科瑞 王晶淼/司红霞 前言 随着电气化的迅猛发展和用电普及程度的日益提高,电已经成为人类生存和发展必不可少的能源之一。然而,由于种种原因,由电气引发的火灾和爆炸事故也直呈现上升趋势。电气设备的绝缘大量使用塑料、橡胶、绝缘漆、稀释剂等易燃物品,在电气设备运行中,由…