栈和队列讲解

在学习了顺序表和链表之后,为了深入了解其结构,接下来由小编带大家再学习两种数据结构栈和队列,这两种数据结构内部也用到了关于顺序表和链表的相关知识,也能对前面的知识进行回顾,废话不多讲了,Let's go!

1.栈

1.1栈的概念及其结构

栈是一种特殊的线性表,其只允许在固定的一端进行插入和删除操作。我们通常将数据插入和删除的一端称为栈顶,另一端称为栈底。此外,栈中的元素遵循后进先出的原则(Last In First Out)

栈的插入操作叫做进栈/压栈/入栈,入数据是在栈顶

栈的删除操作叫做出栈,出数据也是在栈顶。

进栈和出栈图解:

1.2栈的实现和分类

栈主要分为两类,分别是数组栈和链式栈,但是由于链式栈查询栈顶也就是链表的结尾节点的效率比较低,同时要进栈时还要去多次申请节点,这样会有一定的消耗,效率也比较低,所以小编在这就用顺序表来实现栈。

与顺序表和链表一样,我们首先要先创建3个文件,分别是stack.h(包括栈的定义和一些栈的接口函数的定义),stack.c(包括栈的接口函数的实现),test.c(用于测试接口函数)。

如图:

首先要定义一下栈,代码如下:

// 支持动态增长的栈
typedef int STDataType;
typedef struct Stack
{
	STDataType* a;   //定义一个数组,用于存储数据
	int top;		 // 用于标定栈顶
	int capacity;   // 栈的容量 
}St;//将struct Stack重定义为St

再定义栈之后,接下来初始化栈。

// 初始化栈 
void StackInit(St* ps)
{
	assert(ps);
	ps->a = NULL;//将栈中的a置空
	ps->capacity = 0;//将栈的容量初始化为0
	ps->top = 0;//表示top指向栈顶元素的下一个位置
	//ps->top=-1; //表示top指向栈顶元素
    //以上这两种情况均可以,不过对应的接口函数也应该与其对应
}

入栈:

void StackPush(St* ps, STDataType data)
{
	assert(ps);
	if (ps->capacity == ps->top)//如果top=0时,top也可以理解为栈内元素个数,如果栈内元素个数与栈 
    //的容量相等时这时候入栈是需要扩容的,一般情况是扩大为原来的一倍
	{
		int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
		ps->capacity = newcapacity;
		STDataType* tmp = (STDataType*)realloc(ps->a,newcapacity*sizeof(STDataType));
		if (tmp == NULL)
		{
			perror("malloc fail");
			return;
		}
		ps->a = tmp;
		tmp = NULL;
	}
	ps->a[ps->top] = data;//由于top在数组中指向的是栈顶元素的下一个,所以应该先赋值再自增
	ps->top++;
}

图示:

出栈:

// 出栈 
void StackPop(St* ps)
{
	assert(ps);
	assert(ps->top > 0);//由于top可以表示为栈内元素的个数,所以如果栈内没有元素的话,肯定是无法出 
    //栈的,所以这里要设置一个断言,确保栈中至少有一个元素的时候才可以出栈
	ps->top--;//由于我们是通过top来访问栈顶的,所以我们只需要将top--就可以做到出栈了,由于原top位 
    //置的数据随后可能还要重新赋值,所以不用置0/1之类的
}

在讲完入栈和出栈之后,为了验证我们是否入栈或者出栈成功,我们还需要获取栈顶元素来进一步确定,获取栈顶元素:

STDataType StackTop(St* ps)
{
	assert(ps);
    assert(ps->top>0);//栈如果为空肯定也是无法获取栈顶元素的,这也要设置一个断言
	return ps->a[ps->top - 1];//由于top标定的是栈顶元素的下一个位置,所以访问栈顶元素的时候,要用
   //top-1来获取栈顶元素
}

获取栈中有效元素的个数:

int StackSize(St* ps)
{
	assert(ps);
	return ps->top;//top由于标定的是栈顶元素的下一个位置,所以可以理解为栈的有效数据的个数
}

检测栈是否为空:

// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0 
int StackEmpty(St* ps)
{
	assert(ps);
	return ps->top == 0;//栈中有效元素个数top如果等于0表示栈为空返回非0,否则返回0
}

最后当我们使用完一个数据结构之后,我们通常要销毁它,避免该数据结构一直在内存中占空间。

销毁栈:

void StackDestroy(St* ps)
{
	assert(ps);
	free(ps->a);//释放申请的数组空间
	ps->a = NULL;//置空
	ps->top = ps->capacity = 0;//将数据容量和有效数据的个数置0
}

最后我们还要通过测试,来判断我们的接口函数是否正确:

void Stacktest01()
{
	St st;//创建一个栈
	StackInit(&st);//初始化
	StackPush(&st, 1);
	StackPush(&st, 2);
	StackPush(&st, 3);
	StackPush(&st, 4);
	StackPush(&st, 5);
	StackPush(&st, 6);
	StackPush(&st, 7);
	StackPush(&st, 8);//入栈
	if (StackEmpty(&st) != 0)
	{
		printf("栈为空\n");
	}
	else
	{
		printf("栈不为空\n");
	}
	printf("栈内元素个数为:%d\n", StackSize(&st));
	while(!StackEmpty(&st))
	{
		int tmp = StackTop(&st);
		printf("%d ", tmp);
		StackPop(&st);//出栈的同时,并打印
	}
	printf("\n");
	StackDestroy(&st);//销毁栈
}

总的结构:

stack.h:

#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
// 支持动态增长的栈
typedef int STDataType;
typedef struct Stack
{
	STDataType* a;
	int top;		// 栈顶
	int capacity;  // 容量 
}St;
// 初始化栈 
void StackInit(St* ps);
// 入栈 
void StackPush(St* ps, STDataType data);
// 出栈 
void StackPop(St* ps);
// 获取栈顶元素 
STDataType StackTop(St* ps);
// 获取栈中有效元素个数 
int StackSize(St* ps);
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0 
int StackEmpty(St* ps);
// 销毁栈 
void StackDestroy(St* ps);

stack.c:

#include"Stack.h"
// 初始化栈 
void StackInit(St* ps)
{
	assert(ps);
	ps->a = NULL;
	ps->capacity = 0;
	ps->top = 0;//表示top指向栈顶元素的下一个位置
	//ps->top=-1 表示top指向栈顶元素
}
// 入栈 
void StackPush(St* ps, STDataType data)
{
	assert(ps);
	if (ps->capacity == ps->top)
	{
		int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
		ps->capacity = newcapacity;
		STDataType* tmp = (STDataType*)realloc(ps->a,newcapacity*sizeof(STDataType));
		if (tmp == NULL)
		{
			perror("malloc fail");
			return;
		}
		ps->a = tmp;
		tmp = NULL;
	}
	ps->a[ps->top] = data;
	ps->top++;
}
// 出栈 
void StackPop(St* ps)
{
	assert(ps);
	assert(ps->top > 0);
	ps->top--;
}
// 获取栈顶元素 
STDataType StackTop(St* ps)
{
	assert(ps);
	assert(ps->top > 0);
	return ps->a[ps->top - 1];
}
// 获取栈中有效元素个数 
int StackSize(St* ps)
{
	assert(ps);
	return ps->top;
}
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0 
int StackEmpty(St* ps)
{
	assert(ps);
	return ps->top == 0;
}
// 销毁栈 
void StackDestroy(St* ps)
{
	assert(ps);
	free(ps->a);
	ps->a = NULL;
	ps->top = ps->capacity = 0;
}

test.c:

#include"Stack.h"
#include"Queue.h"
#include<stdbool.h>
void Stacktest01()
{
	St st;
	StackInit(&st);
	StackPush(&st, 1);
	StackPush(&st, 2);
	StackPush(&st, 3);
	StackPush(&st, 4);
	StackPush(&st, 5);
	StackPush(&st, 6);
	StackPush(&st, 7);
	StackPush(&st, 8);
	if (StackEmpty(&st) != 0)
	{
		printf("栈为空\n");
	}
	else
	{
		printf("栈不为空\n");
	}
	printf("栈内元素个数为:%d\n", StackSize(&st));
	while(!StackEmpty(&st))
	{
		int tmp = StackTop(&st);
		printf("%d ", tmp);
		StackPop(&st);
	}
	printf("\n");
	StackDestroy(&st);
}
int main()
{
	Stacktest01();
	return 0;
}

2.队列

2.1队列的概念及结构

队列是一种只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,同时队列具有先进先出的规则(First In First Out)。

此外,入队列:(进行插入操作的一端称为队尾)在队尾插入数据 出队列:(进行删除操作的一端称为对头)在队头删除数据

2.2队列的实现及分类

队列也分为数组队列和链表队列的两种结构,使用链表会更好一点,由于使用数组队列的时候,在出队列时,需要挪动数据,效率比较低,所以小编就实现一下链式队列。

这里和栈一样,也要定义三个文件queue.c(其中包含关于队列接口函数的实现)queue.h(包含队列的定义和接口函数的声明)test.c(接口函数的测试)

首先定义队列,由于我们只对队头和队尾进行操作,所以我们只用定义两个指针来指向队头和队尾即可,同时也要定义节点。

// 链式结构:表示队列 
typedef  int QDataType;
typedef struct QListNode
{
	struct QListNode* next;
	QDataType data;
}QNode;

// 队列的结构 
typedef struct Queue
{
	QNode* begin;//指向队头
	QNode* tail;//指向队尾
	int size;//队列有效元素个数
}Queue;

队列的初始化:

// 初始化队列 
void QueueInit(Queue* q)
{
	assert(q);
	q->begin = q->tail = NULL;//由于还未入队列,故都要置空
	q->size = 0;//有效元素初值为0
}

队尾入队列:

void QueuePush(Queue* q, QDataType data)
{
	assert(q);
	QNode* tmp = (QNode*)malloc(sizeof(QNode));//向内存中申请一个节点
	if (tmp == NULL)//如果malloc申请失败,程序返回,不再向下进行
	{
		perror("malloc fail");
		return;
	}
	tmp->data = data;//赋值
	tmp->next = NULL;//其next指针指空
	if (q->begin ==NULL)
	{
		q->begin = q->tail = tmp;//如果队列为空时,让队尾和队头指针都指向队列的第一个节点
	}
	else {
		q->tail->next = tmp;
		q->tail = q->tail->next;//此时队列中不为空,让队尾指针的next指针指向要入队列的节点,同时 
   //再让队尾指针在指向新入队列的节点,保持队头指针指向不动
	}
	q->size++;//每入队列一次,队列的有效数据个数加1
}

出队列:

// 队头出队列 
void QueuePop(Queue* q)
{
	assert(q);
	assert(q->begin);//保证队列中有数据,如果没有数据,则无法出队列
	QNode* tmp = q->begin;//保存要删除头节点的地址
	q->begin = q->begin->next;//让队头指针指向其next指针
	if (q->begin == NULL)
	{
		q->tail = NULL;//如果这时begin指向NULL,表明队列中只有一个元素,所以将这个数据删除之后,
    //为了避免野指针的出现,同时也需要将尾指针也指向NULL
	}
	free(tmp);//释放原头节点
	tmp = NULL;
	q->size--;//每出队列一次,队列的有效数据个数减1
}

获取队列头部元素:

// 获取队列头部元素 
QDataType QueueFront(Queue* q)
{
	assert(q);
	assert(q->begin);//同样队列不能为NULL,为NULL也无法获取队头元素
	return q->begin->data;//直接通过解引用获取数据
}

获取队列尾部元素:

// 获取队列队尾元素 
QDataType QueueBack(Queue* q)
{
	assert(q);
	assert(q->tail);//同样队列不能为NULL,如果队列为空,也是无法获取队尾元素
	return q->tail->data;//通过访问尾结点来获取其数据
}

获取队列中的有效元素个数:

// 获取队列中有效元素个数 
int QueueSize(Queue* q)
{
	assert(q);
	return q->size;//size为队列中有效元素的个数
}

检测队列是否为空:

// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
int QueueEmpty(Queue* q)
{
	assert(q);
	if (q->size == 0)
	{
		return 1;
	}
	else
	{
		return 0;
	}
}

在使用完队列之后,避免它占用内存空间,我们同样也要销毁它,队列的销毁:

// 销毁队列 
void QueueDestroy(Queue* q)
{
	assert(q);
	QNode* cur = q->begin;
	while (cur)
	{
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}//由于队列是由一个个节点组成,所以我们要依次释放队列中的每一个节点
	q->begin = q->tail = NULL;//将队尾和队头指针都指向空
	q->size = 0;//将有效数据个数置0
}

上述我们定义了一些关于队列的接口函数,接下来我们需要测试我们的接口函数:

test.c

void QueueTest01()
{
	Queue q;//定义一个队列
	QueueInit(&q);//初始化
	QueuePush(&q, 1);
	QueuePush(&q, 2);
	QueuePush(&q, 3);
	QueuePush(&q, 4);
	QueuePush(&q, 5);
	QueuePush(&q, 6);
	QueuePush(&q, 7);
	QueuePush(&q, 8);//入队列
	printf("队列的有效数据为:%d\n", QueueSize(&q));
	while(QueueEmpty(&q) == 0)//若队列不为空,就一直出队列
	{
		int tmp = QueueFront(&q);
		QueuePop(&q);
		printf("%d ",tmp);//出队列的同时并打印
	}
	printf("\n");
	QueueDestroy(&q);销毁队列
}

int main()
{
	//Stacktest01();
	QueueTest01();
	return 0;
}

队列实现的总结构:

queue.c

#include"Queue.h"
// 初始化队列 
void QueueInit(Queue* q)
{
	assert(q);
	q->begin = q->tail = NULL;
	q->size = 0;
}
// 队尾入队列 
void QueuePush(Queue* q, QDataType data)
{
	assert(q);
	QNode* tmp = (QNode*)malloc(sizeof(QNode));
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}
	tmp->data = data;
	tmp->next = NULL;
	if (q->begin ==NULL)
	{
		q->begin = q->tail = tmp;
	}
	else {
		q->tail->next = tmp;
		q->tail = q->tail->next;
	}
	q->size++;
}
// 队头出队列 
void QueuePop(Queue* q)
{
	assert(q);
	assert(q->begin );
	QNode* tmp = q->begin;
	q->begin = q->begin->next;
	if (q->begin == NULL)
	{
		q->tail = NULL;
	}
	free(tmp);
	tmp = NULL;
	q->size--;
}
// 获取队列头部元素 
QDataType QueueFront(Queue* q)
{
	assert(q);
	assert(q->begin);
	return q->begin->data;
}
// 获取队列队尾元素 
QDataType QueueBack(Queue* q)
{
	assert(q);
	assert(q->tail);
	return q->tail->data;
}
// 获取队列中有效元素个数 
int QueueSize(Queue* q)
{
	assert(q);
	return q->size;
}
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
int QueueEmpty(Queue* q)
{
	assert(q);
	if (q->size == 0)
	{
		return 1;
	}
	else
	{
		return 0;
	}
}
// 销毁队列 
void QueueDestroy(Queue* q)
{
	assert(q);
	QNode* cur = q->begin;
	while (cur)
	{
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}
	q->begin = q->tail = NULL;
	q->size = 0;
}

queue.h

#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
// 链式结构:表示队列 
typedef  int QDataType;
typedef struct QListNode
{
	struct QListNode* next;
	QDataType data;
}QNode;

// 队列的结构 
typedef struct Queue
{
	QNode* begin;
	QNode* tail;
	int size;
}Queue;

// 初始化队列 
void QueueInit(Queue* q);
// 队尾入队列 
void QueuePush(Queue* q, QDataType data);
// 队头出队列 
void QueuePop(Queue* q);
// 获取队列头部元素 
QDataType QueueFront(Queue* q);
// 获取队列队尾元素 
QDataType QueueBack(Queue* q);
// 获取队列中有效元素个数 
int QueueSize(Queue* q);
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
int QueueEmpty(Queue* q);
// 销毁队列 
void QueueDestroy(Queue* q);

 今天关于栈和队列的分享就到此结束了,很感谢你能花费你宝贵的时间来阅读我的博客,希望能够对正在学习数据结构的你有所帮助,最后给博主点个赞吧,拜拜,我们下期再见。

  • 7
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值