ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

数据结构:栈与队列奇妙之旅(图文带你欣赏栈和队列的魅力)

2021-05-04 09:05:58  阅读:166  来源: 互联网

标签:sta 队列 top datatype que 数据结构 pointer 图文


定义: 栈(Stack)是限制在表的一端进行插入和删除运算的线性表,通常称插入、删除的这一端为栈顶(Top),另一端为栈底(Bottom)。当表中没有元素时称为空栈。
例: 假设栈S=(a1,a2,a3,…an),则a1称为栈底元素,an为栈顶元素。栈中元素按a1,a2,a3,…an的次序进栈,退栈的第一个元素应为栈顶元素。换句话说,栈的修改是按后进先出的原则进行的。因此,栈称为后进先出表(LIFO)。

例: 一叠书或一叠盘子。
栈的抽象数据类型的定义如下:
在这里插入图片描述

顺序栈

由于栈是运算受限的线性表,因此线性表的存储结构对栈也适应。 栈的顺序存储结构简称为顺序栈。因此,可用数组来实现顺序栈。因为栈底位置是固定不变的,所以可以将栈底位置设置在数组的两端的任何一个端点;栈顶位置是随着进栈和退栈操作而变化的,故需用一个整型变量top来指示当前栈顶的位置,通常称top为栈顶指针。因此,顺序栈的类型定义只需将顺序表的类型定义中的长度属性改为top即可。顺序栈的类型定义如下:

typedef int datatype;
#define MyStacksize 10
typedef struct mystack
{
	datatype data[MyStacksize] = { 0 };
	int top;
}MyStack;

初始状态:在这里插入图片描述

当栈满时再做进栈运算必定产生空间溢出,简称“上溢”;当栈空时再做退栈运算也将产生溢出,简称“下溢”。上溢是一种出错状态,应该设法避免之;下溢则可能是正常现象,因为栈在程序中使用时,其初态或终态都是空栈,所以下溢常常用来作为程序控制转移的条件。

完整代码

include <stdio.h>
typedef int datatype;
#define MyStacksize 10
typedef struct mystack
{
	datatype data[MyStacksize] = { 0 };
	int top;
}MyStack;

void initstack(MyStack* sta)//创建空栈
{
	sta->top = -1;
}

bool stackempty(MyStack* sta)//判断栈空
{
	return sta->top == -1;
}

bool stackfull(MyStack* sta)//判断栈满
{
	return sta->top == MyStacksize - 1;
}

void push(MyStack* sta,datatype element)//压栈
{
	if (stackfull(sta)) {
		printf("栈满,压栈失败!!!");
		return;
	}
	else {
		sta->top++;
		sta->data[sta->top] = element;
	}
}

datatype pop(MyStack* sta)//出栈
{
	if (stackempty(sta)) {
		printf("栈空,出栈失败!!!");
		return NULL;
	}
	else {
		datatype element;
		element = sta->data[sta->top];	//取得栈顶元素
		sta->data[sta->top] = 0;		//删除栈顶元素
		sta->top--;						//更新栈顶指针
		return element;
	}
}

datatype stacktop(MyStack* sta)//取栈顶元素
{
	if (stackempty(sta)) {
		printf("栈空,取出失败!!!");
		return NULL;
	}
	else {
		return sta->data[sta->top];
	}
}

int main()//主函数只给出初始化和部分测试代码,可自己在主函数添加具体测试调用函数
{
	MyStack* sta=(MyStack*)malloc(sizeof(MyStack));
	initstack(sta);				//创建空栈
	printf("%d\n", sta->top);

	if (stackempty(sta))		//判断栈是否为空
		printf("栈空!!!\n");
	else
		printf("栈不空!!!\n");

	push(sta, 10);				//压栈
	printf("%d\n", stacktop(sta));

	if (stackfull(sta))			//判断栈满
		printf("栈满!!!\n");
	else
		printf("栈未满!!!\n");

	printf("%d\n",pop(sta));	//出栈

	for (int i = 0; i < 10; i++) {//连续压栈
		push(sta, i);
	}
	printf("%d\n", pop(sta));
	return 0;
}

链栈

定义: 栈的链式存储结构称为链栈,它的运算是受限的单链表,插入和删除操作仅限制在表头位置上进行。由于只能在链表头部进行操作,故链表没有必要像单链表那样附加头结点。栈顶指针就是链表的头指针

链栈存储单元:

typedef int datatype;
typedef struct mystack
{
	datatype data;//栈顶指针存放栈长,其它存放元素数据
	mystack* next;
}MyStack;

例:在这里插入图片描述

链栈特点:

  • 链式栈无栈满问题,空间可扩充
  • 插入与删除仅在栈顶处执行
  • 链式栈的栈顶在链头
  • 适合于多栈操作

压栈算法:
其实就是上一篇文章所讲的单链表的头插法,不断更新栈顶指针指向的节点。

void push(MyStack* top, datatype element)//压栈
{
	MyStack* sta = (MyStack*)malloc(sizeof(MyStack));//为新元素分配内存,创造节点
	sta->data = element;
	sta->next = NULL;
	sta->next = top->next;	//连接新节点与原栈顶节点
	top->next = sta;		//更新栈顶节点
	top->data++;			//更新栈长
}

完整代码

typedef int datatype;
typedef struct mystack
{
	datatype data;
	mystack* next;
}MyStack;
MyStack* initstack()//创建空栈
{
	MyStack* top = (MyStack*)malloc(sizeof(MyStack));
	top->data = -1;
	top->next = NULL;
	return top;
}
bool stackempty(MyStack* top)//判断栈空
{
	return top->data == -1;
}
void push(MyStack* top, datatype element)//压栈
{
	MyStack* sta = (MyStack*)malloc(sizeof(MyStack));
	sta->data = element;
	sta->next = NULL;
	sta->next = top->next;
	top->next = sta;
	top->data++;
}
datatype pop(MyStack* top)//出栈
{
	if (stackempty(top)) {
		printf("栈空,出栈失败!!!");
		return NULL;
	}
	else {
		datatype element;
		MyStack* sta;
		sta = top->next;				//取栈顶节点
		element = sta->data;
		top->next = sta->next;			//删除栈顶元素
		top->data--;					//更新栈长
		free(sta);						//释放删除的节点所占内存
		return element;
	}
}
datatype stacktop(MyStack* top)//取栈顶元素
{
	if (stackempty(top)) {
		printf("栈空,取出失败!!!");
		return NULL;
	}
	else {
		return top->next->data;
	}
}

int main()//主函数只给出初始化和部分测试代码,可自己在主函数添加具体测试调用函数
{
	MyStack* top = initstack();		//创建空链栈
	printf("%d\n",top->data);

	if (stackempty(top))			//判断栈空
		printf("栈空!!!\n");
	else
		printf("栈不空!!!\n");

	push(top, 10);					//将10压栈
	printf("%d\n", stacktop(top));

	printf("%d\n", pop(top));		//弹出栈顶指向的节点元素

	for (int i = 0; i < 10; i++) {	//连续压栈
		push(top, i);
	}
	printf("%d\n", stacktop(top));
	return 0;
}

小结: 栈数据结构理解起来并不是太难,但是要熟练运用还是有一定难度的,要多练哦!

队列

定义: 队列(Queue) 是一种只在表的一端进行插入,而在另一端进行删除的数据结构。

  • 队头(front) 允许删除的一端,永远指向第一个元素前一个位置。
  • 队尾(rear) 允许插入的一端,永远是指向队列最后一个元素
  • 先进先出(First In First Out)的线性表,简称FIFO表
  • 空队列 当队列中没有元素

在这里插入图片描述

顺序队列

定义: 队列的顺序存储结构简称顺序队列

存储结构:

typedef int datatype;
#define MyQueuesize 10
typedef struct mystack
{
	datatype data[MyQueuesize] = { 0 };
	int front;
	int rear;
}MyQueue;

顺序队列特点:

  • 队列中亦有上溢和下溢现象。
  • 此外,顺序队列中还存在“假上溢” 现象。因为在入队和出队的操作中,头尾指针只增加不减小,致使被删除元素的空间永远无法重新利用。因此,尽管队列中实际的元素个数远远小于向量空间的规模,但也可能由于尾指针巳超出向量空间的上界而不能做入队操作。该现象称为假上溢。
    在这里插入图片描述
    队尾指针为5,队头指针为1,此时尾指针已经指向了顺序队列最后一个元素,但可以看到0,1两个
    并没有被使用,此时就为假上溢。

部分操作代码:

void initQueue(MyQueue* que)//创建空队列
{
	que->front = -1;
	que->rear = -1;
}

bool Queueempty(MyQueue* que)//判断队空
{
	return que->front == que->rear;
}

bool Queuefull(MyQueue* que)//判断队满
{
	return que->rear == MyQueuesize - 1;
}

循环队列

为了解决假上溢问题,引进循环队列,即把向量空间想象为一个首尾相接的圆环,在循环队列中进行出队、入队操作时,头尾指针仍要加1,朝前移动。只不过当头尾指针指向向量上界(MAXNUM-1)时,其加1操作的结果是指向向量的下界0。

但是引进的循环队列又出现了新的问题,看图:在这里插入图片描述
即队空和队满的时都满足q->front==q->rear,我们无法利用这个条件判断队空还是队满!!!该怎么解决这个问题呢?有两种方法:

  1. 少用一个数据元素空间,以队尾指示器加 l等于队头指示器判断队满,即队满条件为:
    (q-> rear+l)%MAXNUM=q->front
  2. 使用一个计数器记录队列中元素的总数(实际上是队列长度)。

第一种方法示例代码:

bool Queueempty(MyQueue* que)//判断队空
{
	return que->front == que->rear;
}

bool Queueempty(MyQueue* que)//判断队满
{
	return (que->rear + 1) % MyQueuesize == que->front;
}

第二种方法示例代码:

typedef int datatype;
#define MyQueuesize 10
typedef struct mystack			//存储结构
{
	datatype data[MyQueuesize] = { 0 };
	int front;
	int rear;
	int count;
}MyQueue;

bool Queueempty(MyQueue* que)//判断队空
{
	return que->count == 0;
}

bool Queuefull(MyQueue* que)//判断队满
{
	return que->count == MyQueuesize ;
}

好了这个问题顺利解决!

循环队列的其他操作代码:

void enterQueue(MyQueue* que, datatype element)//入队
{
	if (Queuefull(que)) {
		printf("队列已满,入队失败!!!\n");
	}
	else {
		que->rear = (que->rear + 1) % MyQueuesize;
		que->count++;
		que->data[que->rear] = element;
		printf("入队成功!\n");
	}
}

datatype deletQueue(MyQueue* que)			//出队列
{
	if (Queueempty(que)) {
		printf("队列已空,出队失败!!!\n");
		return NULL;
	}
	else {
		que->front = (que->front + 1) % MyQueuesize;
		que->count--;
		datatype element = que->data[que->front];	//取队头元素
		que->data[que->front] = 0;					//删除队头元素
		printf("出队成功!\n");
		return element;
	}
}
int main()			//主函数只给出初始化和部分测试代码,可自己在主函数添加具体测试调用函数
{
	MyQueue* que = (MyQueue*)malloc(sizeof(MyQueue));
	return 0;
}

链队列

定义: 队列的链式存储结构简称为链队列,它是限制仅在表头删除和表尾插入的单链表。 显然仅有单链表的头指针不便于在表尾做插入操作,为此再增加一个尾指针,指向链表的最后一个结点。于是,一个链队列由头指针和尾指针唯一确定。
在这里插入图片描述

存储结构:
将头指针与尾指针封装在一个结构体中便于操作。

typedef int datatype;
typedef struct myQueue	//元素节点
{
	datatype data;
	myQueue* next;
}MyQueue;

typedef struct queue	//头指针尾指针
{
	myQueue* front;
	myQueue* rear;
}myqueue;

判断队空

链队队空条件是pointer->front->next == NULL && pointer->rear->next == NULL而不是pointer->front == pointer->rear这是因为当队空的时候pointer->frontpointer->rear的值不一样。

原因: 如上图所示,当创建空链队时,队列头指针、尾指针都指向上图中黑色节点(这个黑色节点相当于顺序队列的-1,是一个初始节点),当链队列添加元素后尾指针会改变它指向的节点,此时pointer->rear的值是它指向的尾结点的地址。而当队列删除元素时,改变的是黑色节点的next指针(这样做是为了使头指针满足它的特点,具体看上边队列特点),这样pointer->front的值永远是黑色节点的地址。所以pointer->frontpointer->rear的值不一样,不能作为判断队空的条件。

bool Queueempty(myqueue* pointer)//判断队空
{
	return pointer->front->next == NULL && pointer->rear->next == NULL;
}

出队列:

此功能需要注意的就是当队列只剩一个元素时,即pointer->front->next == pointer->rear,而你要删除此元素,就需要先改变尾指针指向的节点,再删除,释放最后一个元素节点的内存。因为如果你不改变的话,则尾指针是指向最后一个元素节点的,而你删除释放最后一个元素节点的内存后,此时pointer->rear->next是一个野指针(野指针的危害就不用我说了吧!!!),会导致程序无法正常运行!!!

datatype deletQueue(myqueue* pointer)
{
	MyQueue* p;
	if (Queueempty(pointer)) {
		printf("队列已空,出队失败!!!\n");
		return NULL;
	}
	else {
		p = pointer->front->next;
		if (p == pointer->rear) {
			pointer->rear = pointer->front;
			pointer->front->next = NULL;
		}
		datatype element = p->data;			
		pointer->front->next = p->next;
		free(p);
		printf("出队成功!\n");
		return element;
	}
}

其它操作示例代码

#include<stdio.h>
typedef int datatype;
typedef struct myQueue	//元素节点
{
	datatype data;
	myQueue* next;
}MyQueue;

typedef struct queue	//头指针尾指针
{
	myQueue* front;
	myQueue* rear;
}myqueue;

void initQueue(myqueue* pointer)//创建空队列
{
	pointer->front = (MyQueue*)malloc(sizeof(MyQueue));
	pointer->rear = pointer->front;
	pointer->front->next = NULL;
}

void enterQueue(myqueue* pointer, datatype element)//入队
{
	MyQueue* que = (MyQueue*)malloc(sizeof(MyQueue));
	que->data = element;
	que->next = NULL;
	pointer->rear->next = que;
	pointer->rear = que;
	printf("入队成功!\n");
}
int main()		//主函数只给出初始化和部分测试代码,可自己在主函数添加具体测试调用函数
{
	myqueue* pointer = (myqueue*)malloc(sizeof(myqueue));
	initQueue(pointer);
	return 0;
}

栈和队列讲到这里就结束了。理解后还是需要多练习啊。也希望我的这篇博客能够帮助到你!同时也欢迎各位朋友们评论留言,数据结构系列博客会持续更新,一起加油啊!冲冲冲!!!

标签:sta,队列,top,datatype,que,数据结构,pointer,图文
来源: https://blog.csdn.net/weixin_46015333/article/details/116296719

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有