数据结构·栈和队列

目录

前言:

1 栈的概念和结构

2 栈的实现

3 队列的概念和结构

4 队列的实现


前言:

在学习C语言的时候我们可以了解到,局部变量是在栈中创建的,动态开辟的空间是在堆上开辟的,用了static关键字的变量是在静态区创建的,我们在学习数据结构的时候同样会涉及到栈和堆的概念,但是此栈非彼栈,堆也是,因为这是两门不同的学科。

局部变量在栈中创建等是隶属于操作系统的知识,而今天介绍的栈和队列,是隶属于数据结构的内容,他们是一种结构,比如栈,是后进先出的结构(Last in first out),即LIFO,队列是相反的,是先进先出的结构(First in First out),即FIFO。

现在就进入到数据结构中的栈和队列吧!


1 栈的概念和结构

栈,是一种后进先出的结构,存入数据读取数据都是在一端进行的,就像串肉一样,先串进去的,总是最后吃到。进行数据插入的数据和删除操作的一端叫做栈顶,另一端就是栈底。

栈分为动态栈和静态栈,是不是感觉和顺序表挺像的,顺序表的章节我们同样考虑了是动态顺序表还是静态顺序表的问题,这里一样,那么同理,静态栈实际生活中很少用到,所以我们实现的是动态栈。

压栈操作,就是栈的插入操作,比如插入数据,也可以叫做进栈/入栈

出栈操作,就是栈的删除操作,比如删除数据。

栈是基于数组或者链表实现的,那么我们是选择数组还是链表呢?

这里用数组实现或者是链表实现都可以的,链表实现的话压栈操作就是尾插,出栈操作就是尾删,

数组实现的话也不会涉及到数据移动,所以两种实现方式均可,那么我们这里采用数组实现,


2 栈的实现

需要的头文件有:stdio stdlib stdbool assert

实现的时候一般实现如下接口:

void StackInit(stack* ps);//初始化栈
void StackPush(stack* ps, DataType x);//入栈
void StackPop(stack* ps);//出栈
DataType StackTop(stack* ps);//获取栈顶元素
int StackSize(stack* ps);//获取栈的空间大小
bool StackEmpty(stack* ps);//判断栈是否为空
void StackDestroy(stack* ps);//销毁栈

同样需要结构体,三个成员变量,一个用来开辟空间,一个用来计算容量,一个用来专门指向栈顶元素。

typedef int DataType;

typedef struct Stack
{
	DataType* arr;
	int top;//栈顶
	int capacity;//容量
}stack;

初始化:
初始化同顺序表一样,赋值即可

void StackInit(stack* ps)//初始化栈
{
	assert(ps);
	ps->arr = NULL;
	ps->capacity = ps->top = 0;
}

销毁:
销毁需要释放空间,所以差别和初始化不太大

void StackDestroy(stack* ps)//销毁栈
{
	assert(ps);
	free(ps->arr);
	ps->arr = NULL;
	ps->capacity = ps->capacity = 0;
}

入栈操作:

入栈之前,因为是初始化了,所以capacity size都是0,并且在后面入栈的时候都应该判断一下是否需要扩容,所以这里的写法和顺序表那里是很像的,但是这里没有必要单独写一个类似于获取结点的函数,因为只有入栈会用到开辟空间

void StackPush(stack* ps, DataType x)//入栈
{
	assert(ps);
	if (ps->capacity == ps->top)
	{
		int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
		DataType* tem = (DataType*)realloc(ps->arr, sizeof(newcapacity));
		assert(tem);
		ps->arr = tem;
		ps->capacity = newcapacity;
	}
	ps->arr[ps->top++] = x;
}

这里唯一稍微麻烦一点的就是扩容的操作,如果栈顶和容量一样了就扩容,扩容我们可以使用三目操作符,随即进行赋值就行了,入栈之后top自增一下,整个入栈操作就完成了。

判断栈是否为空:
判断栈是否为空靠的是top的数值,如果top为0那么栈就是空,返回false就行

bool StackEmpty(stack* ps)//判断栈是否为空
{
	assert(ps);
	return ps->top == 0;
}

如果top为0,返回的是true,即为空

出栈操作:

出栈同顺序表,top自减一下就行了,访问不到=数据删除,但是出栈操作需要栈不为空,那么函数StackEmpty就有用了

void StackPop(stack* ps)//出栈
{
	assert(ps);
	assert(!StackEmpty(ps));
	ps->top--;
}

返回栈顶元素:
判断一下,返回值,完成了,因为我们使用的是数组,下标访问,那么top就应该减一个1才能得到我们想要的元素,并且返回栈顶元素需要栈有值才行

DataType StackTop(stack* ps)//获取栈顶元素
{
	assert(ps);
	assert(!StackEmpty(ps));
	return ps->arr[ps->top-1];
}

返回栈的大小:
判断一下,返回就ok了

int StackSize(stack* ps)//获取栈的空间大小
{
	assert(ps);
	return ps->capacity;
}

以上就是栈的功能实现

Tips 出栈操作和入栈操作顺序不对的话,可能影响你的预期结构哦


3 队列的概念和结构

队列,就像是排队一样,和栈是完全相反的一种结果,栈是出数据和进数据都是在一端,队列不一样,队列是一端进数据,另一端出数据,所以队列是First in first out ,即FIFO

那么我们用什么来实现队列呢?数组还是链表?

假如我们使用数组的话,每次出队的时候就会涉及到移动数据,这个弊端有点类似于顺序表,一旦数据多了起来的话,那么时间浪费是比较严重的,降低了程序运行效率,所以实现队列我们使用链表来实现。

那么我们使用单链表还是双向链表呢?

其实我们在使用链表的时候,能使用单链表就使用单链表,双向链表固然方便,但是双向链表使用在这里犹如杀鸡用牛刀,使用双向链表的时候我们要单独加一个指针进去,会降低空间利用,而且队列使用单链表实现足矣,没必要为一点点的时间去使用双向链表。

我们使用单链表实现队列的时候,先进先出,所以入队的时候就是一直尾插即可,出队的时候就是一直头删就行,那么结构体我们怎么定义呢?

基于单链表实现,所以结点的定义是和单链表定义是一样的。

typedef int  DataType;

typedef struct QNode//对结点定义
{
	DataType val;
	struct QNode* next;
}QNode;

一个值和一个指向下一个结点的地址。

那么实现队列涉及队头队尾,所以对尾结点就比较需要,那我们指定不能每写一个函数就用循环找一下尾结点吧?所以这里我们再定义一个结构体,里面的成员变量是头结点,尾结点,队列大小。

typedef struct QueueNode//对队列定义
{
	QNode* phead;
	QNode* ptail;
	int size;
}Queue;

单独用一个结构体存储队列的头尾变量实现起来会方便很多。

那么实现队列的基本接口如下:

void QueueInit(Queue* pq);//初始化队列

void QueuePush(Queue* pq,DataType x);//入队

void QueuePop(Queue* pq);//出队

DataType QueueHead(Queue* pq);//获取队头元素

DataType QueueBack(Queue* pq);//获取队尾元素

bool QueueEmpty(Queue* pq);//判断队列是否为空

int QueueSize(Queue* pq);//获取队列中的有效元素个数

void QueueDestroy(Queue* pq);//销毁队列

4 队列的实现

队列的初始化:
初始化即把指针置为空,防止变为野指针,存储的变量一般置为0:

void QueueInit(Queue* pq)//初始化队列
{
	assert(pq);
	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}

队列的销毁:
我们是对每个结点进行销毁,while循环是必要的,释放完指针置为空,变量置为0即可

void QueueDestroy(Queue* pq)//销毁队列
{
	assert(pq);
	QNode* pcur = pq->phead;
	while (pcur)
	{
		QNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}

判断队列是否为空:

判断队列是否为空很多种方法,一是直接用size进行判断,即是( 1 == pq->size),或者判断phead是否为空,ptail判断为空也可以,最后返回的是布尔类型:

bool QueueEmpty(Queue* pq)//判断队列是否为空
{
	assert(pq);
	return pq->size == 0;
}

入队:
入队的时候不用考虑空间是否足够,每入队一个元素就开辟一个空间,不存在空间浪费的问题,入队是在队尾入队,即一直尾插即可,但是有个特殊情况需要考虑一下,就是队列是否为空,如果队列为空,那么头结点和尾结点都置为新节点就行,记得size自增一下:

void QueuePush(Queue* pq,DataType x)//入队
{
	//判断队列是否为空
	assert(pq);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	assert(newnode);
	newnode->val = x;
	newnode->next = NULL;
	//队列为空
	if (pq->phead == NULL)
	{
		pq->phead = pq->ptail = newnode;
	}
	//队列不为空
	else
	{
		pq->ptail->next = newnode;
		pq->ptail = newnode;
	}
	pq->size++;
}

出队:
出队就是头删,队头的元素出去,那么头结点就网下一个结点后移,出队之前也要判断队列是否为空,除了队列为空的情况,队列中只有一个元素的情况下,出队之后我们就要关心两个元素,一个是头结点,一个是尾节点,一个元素出队,头尾结点都要置为空,并且出队指针都要free出队的结点,最后size自减一下就完成了。

void QueuePop(Queue* pq)//出队
{
	assert(pq);
	assert(!QueueEmpty(pq));
	//一个结点
	if (1 == pq->size)
	{
		free(pq->phead);
		pq->phead = pq->ptail = NULL;
	}
	//多个结点
	else
	{
		QNode* newphead = pq->phead->next;
		free(pq->phead);
		pq->phead = newphead;
	}
	pq->size--;
}

获取队头/尾元素以及队列的大小:
获取元素和队列大小是最简单的,都是判断一下队列是不是为空,返回对应的值就行了。
队头:

DataType QueueHead(Queue* pq)//获取队头元素
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->phead->val;
}

队尾:

DataType QueueBack(Queue* pq)//获取队尾元素
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->ptail->val;
}

队列大小:

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

以上就是栈和队列的基本实现。

感谢阅读!

相关推荐

  1. 数据结构---队列

    2024-03-19 11:10:02       40 阅读
  2. 数据结构:队列

    2024-03-19 11:10:02       38 阅读
  3. 数据结构队列

    2024-03-19 11:10:02       23 阅读
  4. 数据结构队列

    2024-03-19 11:10:02       23 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-03-19 11:10:02       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-03-19 11:10:02       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-03-19 11:10:02       19 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-03-19 11:10:02       20 阅读

热门阅读

  1. 设计模式之策略模式

    2024-03-19 11:10:02       20 阅读
  2. 华为设备配置命令大全

    2024-03-19 11:10:02       21 阅读
  3. node后端helmet中间件

    2024-03-19 11:10:02       22 阅读
  4. win git filter-repo教程

    2024-03-19 11:10:02       20 阅读
  5. Vim替换时区分大小写

    2024-03-19 11:10:02       19 阅读
  6. word 及PPT 中修改公式字体

    2024-03-19 11:10:02       19 阅读
  7. Spring MVC中redirect重定向几种方式(重构)

    2024-03-19 11:10:02       20 阅读