数据结构-栈和队列实现

本文详细介绍了栈和队列的基本概念、功能及其实现,包括栈的后进先出特性、队列的先进先出特性,以及它们在编程中的应用场景。通过顺序表和链表的实例展示了栈和队列的创建过程和常见操作。

文章目录

前言

一、栈和队列的基本概念

1.栈的概念

2.队列的概念

3.栈和队列的区别

4.栈和队列的意义

二、栈的定义和功能实现

1.栈的定义

2.栈的初始化

 3.入栈

4.出栈

5.获取栈顶元素

 6.栈中有效数据范围

 7.检测栈是否为空

 8.栈销毁

 三、队列的定义和功能实现

1.队列的定义

2. 初始化队列

 3.入队

4.出队 

5.获取队头元素

 6.获取队尾元素

7.获取元素个数

 8.检测队列是否为空

9.销毁队列

四、栈与队列的完整代码

队列

总结


前言

前面章节中在线性表内容我们学习了顺序表和链表的创建,那接下来我们就要学习栈和队列的创建了,那栈和队列之间的功能和实际意义又有什么呢?在主要内容中我会逐个给大家解释清楚。


一、栈和队列的基本概念

1.栈的概念

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

栈顶压栈出栈: 

压栈:栈的插入操作叫做进栈 / 压栈 / 入栈, 入数据在栈顶
出栈:栈的删除操作叫做出栈。 出数据也在栈顶

栈的主要功能:入栈、出栈、取出栈顶元素 

对栈的理解:栈就好比是个弹夹容器,每个元素就好比子弹,先进去是在最底下,出来也是最后出,反过来相比最后进的子弹,那就最先出来,所以后进先出这说法就这么来了,当然说先进后出也是没错的。

 

2.队列的概念

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

入队列:进行插入操作的一端称为队尾。

出队列:进行删除操作的一端称为队头。

队列的主要功能:入队、出队、取出对头元素 

3.栈和队列的区别

1.可以看出栈和队列的出入方式是不一样的:栈是后进先出、队列是先进先出。
2.那么栈和队列在具体实现的时候操作的位置不同:因为栈是后进先出,它只在一段进行操作;而队列是先进先出,实现的时候在两端进行。

4.栈和队列的意义

栈和队列都是一种典型的线性表,都是基于线性表(顺序表和链表)来实现的,那么我们研究栈和队列的目的何在?因为在栈和队列定义后,只有那三种操作,而那三种操作都是最常用的,它支持的操作越少,我们在使用的时候关心的点也就越少,用起来就越不容易出错。在计算机中“少即是多”,少意味着功能比较少、比较呆板。多意味着功能很多,用的时候要操的心就越多,就越容易出错。综上:栈和队列存在的意义就是减少线性表的基本操作,提取常用操作,让人们使用起来更方便,更不容易出错。


在编写栈和队列的难度相对于创建一个顺序表或者链表来说难度会低很多,但是会有很多的抽象概念变化成代码去编写,以及栈和队列转换的灵活运用,后面我们会提到。

二、栈的定义和功能实现

1.栈的定义

栈的实现有很多种方式,这边用顺序表来定义栈,有兴趣的伙伴也可以使用链表独自实现一下。

typedef int SLDataType;

typedef struct Stack {
	SLDataType* a;
	int top;//栈顶
	int capacity;//容量
}Stack;

这里使用的是动态分配顺序表结构,大致结构就不提了,主要是top栈顶这里和之前顺序表size的区别,接下来下面栈的初始化会展开去细谈。 

2.栈的初始化

在初始化前我们要好好思考top栈顶这个元素域作用是什么?是栈顶,还是栈顶的下一个元素,这个很关键,我们肯定会说简单啊就是栈顶啊!那给到以下两个图给选择,top指向哪里?

到这里暂停一下,我们之前创建一个顺序表的时候我们使用size就可以直接插入元素,因为他是从0开始,也就是代表有效数据的下一个位置,那我们top就要好好注意一下top的位置到底是要指向哪里, 像以上两个top的位置都是可以的!!!想不到吧哈哈哈,但是初始化的时候要清楚自己到底要初始化为多少以便后续的控制,不要出现越界现象。初始化时有两个选择一个是-1、一个是0。我这边的话就用-1来初始化top的位置,因为我想存在有效数据时top就一直指向栈顶元素。

void StackInit(Stack* ps) {
	assert(ps);
	ps->a = NULL;
	ps->top = -1;
	ps->capacity = 0;
}

 3.入栈

因为我们这边使用的是顺序表的结构,所以在插入数据时要注意容量空间问题,此问题顺序表章节有介绍,这边不再赘述了,我们在初始化时top是-1,那插入数据时一定要先++后访问下标位置,以防不理解,这里也给上图:

void StackPush(Stack* ps, SLDataType x) {
	assert(ps);
	if (ps->top + 1 == ps->capacity) {//增容
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		SLDataType* p = (SLDataType*)realloc(ps->a, newcapacity * sizeof(SLDataType));
		if (p == NULL) {
			perror("realloc p");
			return;
		}
		ps->a = p;
		ps->capacity = newcapacity;
	}
	ps->a[++ps->top] = x;
}

4.出栈

出栈直接让栈顶元素往后走一位即可,但是需要注意的是如果本身就没数据又往后走一位需要给出相应的警告,还有需要注意的是,我们-1为空数据,不是0,也是需要注意的地方。

void StackPop(Stack* ps) {
	assert(ps);
	assert(ps->top > -1);
	ps->top--;
}

5.获取栈顶元素

获取栈顶元素,easy啦,由于我们上面讨论时就认定top就是栈顶元素,所以我们直接返回a数组下下标为top的位置就可以了。

SLDataType StackTop(Stack* ps) {
	assert(ps);
	assert(ps->top > -1);
	return ps->a[ps->top];
}

 6.栈中有效数据范围

既然都做到这里了,我们多实现几个功能吧,top这边是用-1开始的,那我们要确认有效数据的话需要+1个空间,可以想想开始是-1,添加一个数据后top是为0,那0个有效数据肯定是有问题的,所以以此推理我们top需要+1的!!!

int StackSize(Stack* ps) {
	assert(ps);
	return ps->top + 1;
}

 7.检测栈是否为空

判断是否为空,只需要判断top是否为-1即可,切记切记不是0。

bool StackEmpty(Stack* ps) {
	assert(ps);
	return ps->top == -1;
}

 8.栈销毁

销毁栈前需要注意指针a是否有分配过空间,如果没有对NULL进行销毁是会出现错误的,这是需要注意的地方,在顺序表那章节也提到过。

void StackDestory(Stack* ps) {
	assert(ps);
	if (ps->a) {
		free(ps->a);
		ps->a = NULL;
		ps->top = 0;
		ps->capacity = 0;
	}
}

栈的实现基本上就这么多了,接下来我们就来到队列的实现吧。

 三、队列的定义和功能实现

我们想实现队列时,我们要思考一下,队列用顺序表还是链表好些?

第一 队列是先进先出的,那使用顺序表的话入队没什么问题,但是出队会很麻烦,我们需要把对头元素提取出来,也就是数组的第一个位置,那删除他我们就需要把后面的元素往前挪动,就会导致时间复杂度为O(n)的现象。

第二 我们想想如果使用链表会怎么样,我们常规使用链表的时候进行尾插,需要遍历到尾节点才能进行插入节点,会比较麻烦,但是!!!有解决办法,只需要创建一个指针,一直指向尾节点即可,好!!!方法有了,那我们用链表出队会像顺序表一样的问题吗?不会,我们只需要将头节点指向下一个节点就可以啦,再将本原本的头节点销毁掉即可,所以从以上的点分析得出,使用链表是最好的,进行入队和出队时间复杂度都是O(1)的存在,开始安排!!!

1.队列的定义

对队列的定义,刚刚也分析了一下我们需要一个指针一直指向尾节点方便插入数据的,但是需要怎么创建呢?我们之前只学过弄一个头指针,那么我们可以在创建一个结构体去存储两个指针域一头一尾,那么size是干什么的呢?只为了后面需要查看有多少个有效数据时进行记录,我们只要入队就记录一次,出队也做相应的记录,那么以后就不需要遍历去计算,也就达成为时间复杂度O(n)了。

typedef int DataType;
typedef struct QListNode {
	DataType val;
	struct QListNode* next;
}QNode;

typedef struct Queue {
	QNode* phead;
	QNode* plist;
	int size;
}Queue;

2. 初始化队列

void QListInit(Queue* q) {
	assert(q);
	q->phead = q->plist = NULL;
	q->size = 0;
}

 3.入队

入队的话我们需要注意的是是否为第一次插入数据,要进行单独判断,如果不是第一次,只需要将尾指针的下一个位置指向新节点即可,可能会有小朋友会问,为什么我们之前链表创建新节点都会封装成一个函数,为什么这次就不需要呢?因为队列只有入队需要创建新节点的操作,所以我们只需要写一次就可以啦,当然你想封装也是可以的,不影响。

void QListPush(Queue* q, DataType x) {
	assert(q);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL) {
		perror("malloc newnode");
		exit(-1);
	}
	newnode->val = x;
	newnode->next = NULL;
	if (q->phead == NULL) {
		q->phead = q->plist = newnode;
	}
	else {
		q->plist->next = newnode;
		q->plist = newnode;
	}
	q->size++;
}

4.出队 

出队只需要单独判断头节点即可。

void QListPop(Queue* q) {
	assert(q);
	assert(q->phead);
	QNode* del = q->phead;
	if (del->next == NULL) {
		free(del);
		q->phead = q->plist = NULL;
	}
	else {
		QNode* next = del->next;
		free(del);
		del = NULL;
		q->phead = next;
	}
	q->size--;
}

5.获取队头元素

DataType QueueFront(Queue* q) {
	assert(q);
	assert(q->phead);
	return q->phead->val;
}

 6.获取队尾元素

DataType QueueBack(Queue* q) {
	assert(q);
	assert(q->plist);
	return q->plist->val;
}

7.获取元素个数

int QueueSize(Queue* q) {
	assert(q);
	assert(q->phead);
	return q->size;
}

 8.检测队列是否为空

bool QueueEmpty(Queue* q) {
	assert(q);
	return q->phead == NULL;
}

9.销毁队列

void QListDestory(Queue* q) {
	assert(q);
	QNode* tail = q->phead;
	while (tail) {
		QNode* next = tail->next;
		free(tail);
		tail = next;
	}
	q->phead = q->plist = NULL;
	q->size = 0;
}

四、栈与队列的完整代码

stack.h

#pragma once

#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>

typedef int SLDataType;

typedef struct Stack {
	SLDataType* a;
	int top;//栈顶
	int capacity;//容量
}Stack;

//初始化栈
void StackInit(Stack* ps);  
//入栈
void StackPush(Stack* ps, SLDataType x);
//出栈
void StackPop(Stack* ps);
//获取栈顶元素
SLDataType StackTop(Stack* ps);
//栈中有效数据范围
int StackSize(Stack* ps);
//检测栈是否为空
bool StackEmpty(Stack* ps);
//栈销毁
void StackDestory(Stack* ps);

stack.c

#define  _CRT_SECURE_NO_WARNINGS 1
#include"stack.h"

void StackInit(Stack* ps) {
	assert(ps);
	ps->a = NULL;
	ps->top = -1;
	ps->capacity = 0;
}

void StackPush(Stack* ps, SLDataType x) {
	assert(ps);
	if (ps->top + 1 == ps->capacity) {//增容
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		SLDataType* p = (SLDataType*)realloc(ps->a, newcapacity * sizeof(SLDataType));
		if (p == NULL) {
			perror("realloc p");
			return;
		}
		ps->a = p;
		ps->capacity = newcapacity;
	}
	ps->a[++ps->top] = x;
}

void StackPop(Stack* ps) {
	assert(ps);
	assert(ps->top > -1);
	ps->top--;
}

SLDataType StackTop(Stack* ps) {
	assert(ps);
	assert(ps->top > -1);
	return ps->a[ps->top];
}

int StackSize(Stack* ps) {
	assert(ps);
	return ps->top + 1;
}

bool StackEmpty(Stack* ps) {
	assert(ps);
	return ps->top == -1;
}

void StackDestory(Stack* ps) {
	assert(ps);
	if (ps->a) {
		free(ps->a);
		ps->a = NULL;
		ps->top = 0;
		ps->capacity = 0;
	}
}

队列

Queue.h

#pragma once

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

typedef int DataType;
typedef struct QListNode {
	DataType val;
	struct QListNode* next;
}QNode;

typedef struct Queue {
	QNode* phead;
	QNode* plist;
	int size;
}Queue;

//初始化队列
void QListInit(Queue* q);
//队列入队列
void QListPush(Queue* q, DataType x);
//队列出队列
void QListPop(Queue* q);
//获取队列头部元素
DataType QueueFront(Queue* q);
//获取队列尾部元素
DataType QueueBack(Queue* q);
//获取队列的元素个数
int QueueSize(Queue* q);
//检测队列为空 空=TRUE
bool QueueEmpty(Queue* q);
//销毁队列
void QListDestory(Queue* q);

Queue.c

#define  _CRT_SECURE_NO_WARNINGS 1
#include "Queue.h"

//初始化队列
void QListInit(Queue* q) {
	assert(q);
	q->phead = q->plist = NULL;
	q->size = 0;
}
//队列入队列
void QListPush(Queue* q, DataType x) {
	assert(q);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL) {
		perror("malloc newnode");
		exit(-1);
	}
	newnode->val = x;
	newnode->next = NULL;
	if (q->phead == NULL) {
		q->phead = q->plist = newnode;
	}
	else {
		q->plist->next = newnode;
		q->plist = newnode;
	}
	q->size++;
}
//队列出队列
void QListPop(Queue* q) {
	assert(q);
	assert(q->phead);
	QNode* del = q->phead;
	if (del->next == NULL) {
		free(del);
		q->phead = q->plist = NULL;
	}
	else {
		QNode* next = del->next;
		free(del);
		del = NULL;
		q->phead = next;
	}
	q->size--;
}
//获取队列头部元素
DataType QueueFront(Queue* q) {
	assert(q);
	assert(q->phead);
	return q->phead->val;
}
//获取队列尾部元素
DataType QueueBack(Queue* q) {
	assert(q);
	assert(q->plist);
	return q->plist->val;
}
//获取队列的元素个数
int QueueSize(Queue* q) {
	assert(q);
	assert(q->phead);
	return q->size;
}
//检测队列为空 空=TRUE
bool QueueEmpty(Queue* q) {
	assert(q);
	return q->phead == NULL;
}
//销毁队列
void QListDestory(Queue* q) {
	assert(q);
	QNode* tail = q->phead;
	while (tail) {
		QNode* next = tail->next;
		free(tail);
		tail = next;
	}
	q->phead = q->plist = NULL;
	q->size = 0;
}

总结

对栈和队列我们都是需要掌握的,主要还是要学会简单的造造轮子,后面会讲解一下栈和队列的相互关系,以及循环队列的问题,那本章就学到这里啦,感谢大家的观看喔!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值