【数据结构】栈和队列

发布于:2025-07-07 ⋅ 阅读:(16) ⋅ 点赞:(0)

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

在我们有了顺序表和链表的基础(没有基础的可以到主页浏览),我们可以轻松上手接下来的两种特殊的线性表:栈和队列。


提示:以下是本篇文章正文内容,下面案例可供参考

一、栈

1.1 栈的概念及结构

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

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

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

注意:访问栈内元素数据只能访问栈顶元素的数据。

按照上图的顺序,一次能访问的元素数据为:3,4,5,4,3,2。

1.2 栈的实现

栈的实现一般可以使用数组或者链表实现,相对而言数组的结构更优一些,因为数组在末尾上插入删除元素数据的代价更小,这里实现的是用动态数组实现。

栈的基本操作:Init(初始化),Push(插入),Pop(删除),Top(访问栈顶元素),Empty(判断栈是否为空),Size(栈的大小),Destroy(销毁)。

栈的定义(这里以int类型为例):

typedef int STDataType;
typedef struct Stack
{
	STDataType* a; // 指向栈的指针
	int top;       // 栈当前元素个数
	int capacity;  // 栈的空间容量
}ST;

栈的初始化:

// 栈的初始化
void STInit(ST* ps)
{
	assert(ps);

	ps->a = NULL;
	ps->top = 0;
	ps->capacity = 0;
}

 栈的插入:

// 栈的插入
void STPush(ST* ps, STDataType x)
{
	assert(ps);

	if (ps->top == ps->capacity)
	{
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		// 当数组空间不够用时,动态开辟空间,每次开辟空间为当前的2倍
		STDataType* tmp = (STDataType*)realloc(ps->a, newcapacity * sizeof(STDataType));
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}

		ps->a = tmp;
		ps->capacity = newcapacity;
	}

	ps->a[ps->top] = x;
	ps->top++;
}

栈的删除:

// 栈的删除
void STPop(ST* ps)
{
	assert(ps);
	assert(!STEmpty(ps));

	ps->top--;
}

 访问栈顶元素:

// 访问栈顶元素数据
STDataType STTop(ST* ps)
{
	assert(ps);
	assert(!STEmpty(ps));

	// top表示元素个数,对应为数组最后一个元素下标+1,所以这里要减1
	return ps->a[ps->top - 1]; 
}

栈的元素个数:

// 栈的大小
int STSize(ST* ps)
{
	assert(ps);

	return ps->top;
}

 栈的判空:

// 栈的判空
bool STEmpty(ST* ps)
{
	assert(ps);

	return ps->top == 0;
}

栈的销毁:

// 栈的销毁
void STDestroy(ST* ps)
{
	assert(ps);

	free(ps->a);
	ps->a = NULL; // 防止野指针访问
	ps->top = ps->capacity = 0;
}

二、队列

2.1 队列的概念及结构

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

注意:访问队列的元素只可以访问队头和队尾的元素。

2.2 队列的实现

队列也可以数组和链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,需要将整个数组往前移动一位,效率会比较低。这里用链表实现。

队列的基本操作:Init(初始化),Push(插入),Pop(删除),Front(访问队头元素),Empty(判断栈是否为空),Size(栈的大小),Destroy(销毁),Back(访问队尾元素)。

队列的定义:

// 链式结构表示
typedef int QDataType;
typedef struct QueueNode
{
	int val;
	struct QueueNode* next;
}QNode;

// 队列的结构
typedef struct Queue
{
	QNode* phead;  // 队列的队头元素
	QNode* ptail;  // 队列的队尾元素
	int size;      // 队列的大小
}Queue;

队列的初始化:

void QueueInit(Queue* pq)
{
	assert(pq);

	pq->phead = NULL;
	pq->ptail = NULL;
	pq->size = 0;
}

 队列的插入:

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

	newnode->val = x;
	newnode->next = NULL;

	if (pq->ptail)
	{
		// 队列中有元素
		pq->ptail->next = newnode;
		pq->ptail = newnode;
	}
	else
	{
		// 队列中没有元素
		pq->phead = pq->ptail = newnode;
	}

	pq->size++;
}

队列的删除:

// 出队列
void QueuePop(Queue* pq)
{
	assert(pq);

	// 暴力检查 
	assert(pq->phead != NULL);

	// 一个节点
	// 多个节点
	if (pq->phead->next == NULL)
	{
		free(pq->phead);
		pq->phead = pq->ptail = NULL;
	}
	else
	{
		QNode* next = pq->phead->next;
		free(pq->phead);
		pq->phead = next;
	}

	pq->size--;
}

 访问队头元素:

// 访问队列队头元素
QDataType QueueFront(Queue* pq)
{
	assert(pq);

	// 暴力检查 
	assert(pq->phead != NULL);

	return pq->phead->val;
}

访问队尾元素:

// 访问队列队尾元素
QDataType QueueBack(Queue* pq)
{
	assert(pq);

	// 暴力检查 
	assert(pq->ptail != NULL);

	return pq->ptail->val;
}

 队列的判空:

// 队列的判空
bool QueueEmpty(Queue* pq)
{
	assert(pq);

	return pq->size == 0;
}

队列的大小:

// 队列的大小
int QueueSize(Queue* pq)
{
	assert(pq);

	return pq->size;
}

 队列的销毁:

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

	QNode* cur = pq->phead;
	while (cur)
	{
		QNode* next = cur->next;
		free(cur);

		cur = next;
	}

	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}

总结

我们了解了两种特殊的线性表:栈和队列。

栈的特点:后进先出,只能访问栈顶元素,只能在栈顶进行数据的插入和删除

队列的特点:先进先出,只能访问队头和队尾元素,只能在队尾进行数据的插入,队头进行数据的删除

这两种线性表都能使用数组和链表来实现,但在实现时,栈比较适合使用数组实现,在数组末尾插入和删除数据更加便捷;队列比较适合使用链表实现,在删除数据是不需要向数组一样移动整个数组,能直接删除。


网站公告

今日签到

点亮在社区的每一天
去签到