栈和队列(详解)

博客主页:️自信不孤单

文章专栏:数据结构与算法

代码仓库:破浪晓梦

欢迎关注:欢迎大家点赞收藏+关注

文章目录

    • 1. 栈的概念及结构
    • 2. 栈的实现
      • 2.1 初始化栈
      • 2.2 入栈
      • 2.3 检测栈是否为空
      • 2.4 出栈
      • 2.5 获取栈顶元素
      • 2.6 获取栈中有效元素个数
      • 2.7 销毁栈
    • 3. 接口测试
  • 队列
    • 1. 队列的概念及结构
    • 2. 队列的实现
      • 2.1 初始化队列
      • 2.2 入队列
      • 2.3 检测队列是否为空
      • 2.4 出队列
      • 2.5 获取队列头部元素
      • 2.6 获取队列队尾元素
      • 2.7 获取队列中有效元素个数
      • 2.8 销毁队列
    • 3. 接口测试
    • 4. 拓展——环形队列


1. 栈的概念及结构

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

  • 压栈:栈的插入操作叫做进栈/压栈/入栈。入数据在栈顶

  • 出栈:栈的删除操作叫做出栈。出数据也在栈顶

栈和队列(详解)_第1张图片

栈和队列(详解)_第2张图片

2. 栈的实现

栈一般可以使用数组或者链表实现,相对而言数组的结构实现更优一些。因为数组在尾上插入数据的代价比较小。

首先创建两个文件来实现栈:

  1. Stack.h(节点的声明、接口函数声明、头文件的包含)
  2. Stack.c(栈接口函数的实现)

接着创建 test.c 文件来测试各个接口
如图:

栈和队列(详解)_第3张图片

Stack.h 文件内容如下:

#pragma once
#include
#include
#include

typedef int STDataType;
typedef struct Stack
{
	STDataType* a;
	int top;
	int capacity;
}ST;

// 初始化栈
void STInit(ST* pst);
// 入栈
void STPush(ST* pst, STDataType x);
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0
bool STEmpty(ST* pst);
// 出栈
void STPop(ST* pst);
// 获取栈顶元素
STDataType STTop(ST* pst);
// 获取栈中有效元素个数
int STSize(ST* pst);
// 销毁栈
void STDestroy(ST* pst);

接下来,我们在 Stack.c 文件中实现各个接口函数。

2.1 初始化栈

对栈进行置空。

void STInit(ST* pst)
{
	assert(pst);
	pst->a = NULL;
	pst->top = 0;
	pst->capacity = 0;
}

2.2 入栈

判断扩容,然后直接尾插即可。

void STPush(ST* pst, STDataType x)
{
	assert(pst);
	if (pst->capacity == pst->top)
	{
		STDataType NewCapacity = (pst->capacity == 0 ? 4 : (pst->capacity) * 2);
		STDataType* tmp = (STDataType*)realloc(pst->a, NewCapacity * sizeof(STDataType));
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}
		pst->a = tmp;
		pst->capacity = NewCapacity;
	}
	pst->a[pst->top++] = x;
}

2.3 检测栈是否为空

栈为空返回 ture,否则返回 false。

bool STEmpty(ST* pst)
{
	return pst->top == 0;
}

2.4 出栈

断言检测栈是否为空,非空直接尾删即可。

void STPop(ST* pst)
{
	assert(pst);
	assert(!STEmpty(pst));
	pst->top--;
}

2.5 获取栈顶元素

断言检测栈是否为空,非空返回栈顶元素。

STDataType STTop(ST* pst)
{
	assert(pst);
	assert(!STEmpty(pst));
	return pst->a[pst->top - 1];
}

2.6 获取栈中有效元素个数

返回栈中元素个数。

int STSize(ST* pst)
{
	assert(pst);
	return pst->top;
}

2.7 销毁栈

释放动态开辟好的内存,并对数据进行置空。

void STDestroy(ST* pst)
{
	assert(pst);
	free(pst->a);
	pst->a = NULL;
	pst->top = pst->capacity = 0;
}

注:在每个接口函数中一定要合理地使用assert函数断言防止对空指针的引用。

3. 接口测试

test.c 文件内容如下:

#include "Stack.h"
#include 

void TestStack()
{
	ST stack;
	STInit(&stack);
	STPush(&stack, 4);
	STPush(&stack, 1);
	printf("%d ", STTop(&stack));
	STPop(&stack);
	STPush(&stack, 3);
	STPush(&stack, 2);

	while (!STEmpty(&stack))
	{
		printf("%d ", STTop(&stack));
		STPop(&stack);
	}

	STDestroy(&stack);
}

int main()
{
	TestStack();
	return 0;
}

运行结果:

在这里插入图片描述

队列

1. 队列的概念及结构

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

  • 入队列:队列的插入操作叫做入队列。从队尾插入数据。
  • 出队列:队列的删除操作叫做出队列。从对头删除数据。

栈和队列(详解)_第4张图片

2. 队列的实现

队列也可以用数组或者链表来实现,相对而言使用链表结构的实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低。

首先创建两个文件来实现队列:

  1. Queue.h(节点的声明、接口函数声明、头文件的包含)
  2. Queue.c(队列接口函数的实现)

接着创建 test.c 文件来测试各个接口
如图:

栈和队列(详解)_第5张图片

Queue.h 文件内容如下:

#pragma once
#include
#include
#include

typedef int QDataType;
typedef struct QueueNode
{
	struct QueueNode* next;
	QDataType data;
}QNode;

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

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

接下来,我们在 Queue.c 文件中实现各个接口函数。

2.1 初始化队列

对队列进行置空。

void QueueInit(Queue* pq)
{
	assert(pq);
	pq->phead = NULL;
	pq->ptail = NULL;
	pq->size = 0;
}

2.2 入队列

在堆上申请一个节点,判断是否为第一个节点。如果是第一个节点,就让队列的头尾指针指向该节点;如果不是第一个节点,直接尾插即可。

void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	QNode* NewNode = (QNode*)malloc(sizeof(QNode));
	if (NewNode == NULL)
	{
		perror("malloc fail");
		return;
	}

	NewNode->data = x;
	NewNode->next = NULL;

	if (pq->phead == NULL)
	{
		pq->phead = pq->ptail = NewNode;
	}
	else
	{
		pq->ptail->next = NewNode;
		pq->ptail = NewNode;
	}
	pq->size++;
}

2.3 检测队列是否为空

队列为空返回 ture,否则返回 false。

int QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->size == 0;
}

2.4 出队列

断言检测队列是否为空,非空直接头删,最后判断删除的是否是最后一个节点。如果是最后一个节点,需要将尾指针置空。

void QueuePop(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	QNode* next = pq->phead->next;
	free(pq->phead);
	pq->phead = next;
	pq->size--;
	if (pq->size == 0)
	{
		pq->ptail = NULL;
	}
}

2.5 获取队列头部元素

QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->phead->data;
}

2.6 获取队列队尾元素

QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->ptail->data;
}

2.7 获取队列中有效元素个数

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

2.8 销毁队列

将队列中的所有元素都执行出队列操作即可。

void QueueDestroy(Queue* pq)
{
	assert(pq);
	while (!QueueEmpty(pq))
	{
		QueuePop(pq);
	}
}

注:在每个接口函数中一定要合理地使用assert函数断言防止对空指针的引用。

3. 接口测试

test.c 文件内容如下:

#include "Queue.h"
#include 

void TestQueue()
{
	Queue qu;
	QueueInit(&qu);
	QueuePush(&qu, 5);
	QueuePush(&qu, 0);
	QueuePush(&qu, 2);
	printf("%d ", QueueFront(&qu));
	printf("%d ", QueueBack(&qu));

	QueuePop(&qu);
	printf("%d ", QueueFront(&qu));

	QueuePop(&qu);
	QueuePop(&qu);

	QueuePush(&qu, 1);
	QueuePush(&qu, 3);
	QueuePush(&qu, 1);
	QueuePush(&qu, 4);

	while (!QueueEmpty(&qu))
	{
		printf("%d ", QueueFront(&qu));
		QueuePop(&qu);
	}

	QueueDestroy(&qu);
}

int main()
{
	TestQueue();
	return 0;
}

运行结果:

在这里插入图片描述

4. 拓展——环形队列

实际中我们有时还会使用一种队列叫循环队列。

循环队列是一种线性数据结构,其操作表现基于 先进先出 原则,并且 队尾被连接在队首之后以形成一个循环。它也被称为 “环形缓冲器”

栈和队列(详解)_第6张图片

栈和队列(详解)_第7张图片

栈和队列(详解)_第8张图片

注意:

(1)为了避免 混淆,无法区分,那么可以 多开一个空间

(2)当 head == tail 时,就为

(3)当 tail 的下一个位置是 head 时,就是满。

循环队列如果用链表实现的话,不容易找尾,所以一般我们用数组来实现。


你可能感兴趣的:(数据结构与算法,数据结构,c语言,c++)