提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
在我们有了顺序表和链表的基础(没有基础的可以到主页浏览),我们可以轻松上手接下来的两种特殊的线性表:栈和队列。
提示:以下是本篇文章正文内容,下面案例可供参考
一、栈
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;
}
总结
我们了解了两种特殊的线性表:栈和队列。
栈的特点:后进先出,只能访问栈顶元素,只能在栈顶进行数据的插入和删除
队列的特点:先进先出,只能访问队头和队尾元素,只能在队尾进行数据的插入,队头进行数据的删除
这两种线性表都能使用数组和链表来实现,但在实现时,栈比较适合使用数组实现,在数组末尾插入和删除数据更加便捷;队列比较适合使用链表实现,在删除数据是不需要向数组一样移动整个数组,能直接删除。