作者: 华丞臧.
专栏:【数据结构】
各位读者老爷如果觉得博主写的不错,请诸位多多支持(点赞+收藏+关注)。如果有错误的地方,欢迎在评论区指出。

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

栈的实现一般可以是用数组或者链表实现。那么选用哪一种呢?
O(1);而使用链表,对栈进行入栈出栈操作是在栈顶进行的,栈顶就是链表尾部,那我们知道对链表尾部的操作的时间复杂度为O(N);显然选择数组更加合理。
// 支持动态增长的栈
#pragma once
#include
#include
#include
#include
typedef int STDataType;
typedef struct Stack
{
STDataType* data; //数组
int top; //栈顶
int capacity; //容量
}Stack;
//初始化栈
void StackInit(Stack* ps);
//销毁栈
void StackDestroy(Stack* ps);
//入栈
void StackPush(Stack* ps, STDataType x);
//出栈
void StackPop(Stack* ps);
//获取栈顶元素
STDataType StackTop(Stack* ps);
//获取栈中有效元素个数
int StackSize(Stack* ps);
//检查栈是否为空,如果为空返回非0结果,如果不为空返回0
bool StackEmpty(Stack* ps);
//初始化栈
void StackInit(Stack* ps)
{
assert(ps);
ps->data = NULL;
ps->capacity = ps->top = 0;
}
//销毁栈
void StackDestroy(Stack* ps)
{
assert(ps);
free(ps->data);
ps->data = NULL;
ps->capacity = ps->top = 0;
}
栈的特性是后进先出,栈顶是数组的尾部,栈底是数组的头部;对数据的操作只能在栈顶进行,入栈就是把数据存储在栈顶;既然用数组存放就需要考虑扩容的情况,那么为了达到既不浪费空间又不频繁扩容,每次扩容原先的两倍。
//入栈
void StackPush(Stack* ps, STDataType x)
{
assert(ps);
//判断是否需要扩容
if (ps->top == ps->capacity)
{
int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
STDataType* tmp = (STDataType*)realloc(ps->data,sizeof(STDataType) * newCapacity);
if (tmp == NULL)
{
perror("malloc fail");
exit(-1);
}
ps->data = tmp;
ps->capacity = newCapacity;
}
ps->data[ps->top] = x;
++ps->top;
}
出栈即把栈顶的数据推出去,而定义栈的结构体中的成员变量top表示栈中有效元素的个数,top-1就可以理解为出栈一个元素。
//出栈
void StackPop(Stack* ps)
{
assert(ps);
assert(!StackEmpty(ps));
--ps->top;
}
栈顶元素即数组当中最后一个元素。
//获取栈顶元素
STDataType StackTop(Stack* ps)
{
assert(ps);
assert(!StackEmpty(ps));
return ps->data[ps->top - 1];
}
结构体当中top表示栈中有效元素的个数,那么top的值就是元素个。
//获取栈中有效元素个数
int StackSize(Stack* ps)
{
assert(ps);
return ps->top;
}
top为0表示栈为空。
//检查栈是否为空,如果为空返回非0结果,如果不为空返回0
bool StackEmpty(Stack* ps)
{
assert(ps);
return ps->top == 0;
}
#include "Stack.h"
void Test1()
{
Stack ps;
StackInit(&ps);
//压栈
StackPush(&ps, 1);
printf("%d ",StackTop(&ps));
//压栈
StackPush(&ps, 2);
printf("%d ", StackTop(&ps));
//压栈
StackPush(&ps, 3);
printf("%d ", StackTop(&ps));
//压栈
StackPush(&ps, 4);
printf("%d ", StackTop(&ps));
//元数个数
printf("\nsize = %d\n", StackSize(&ps));
printf("\n");
//出栈
printf("%d ", StackTop(&ps));
StackPop(&ps);
//出栈
printf("%d ", StackTop(&ps));
StackPop(&ps);
//出栈
printf("%d ", StackTop(&ps));
StackPop(&ps);
//出栈
printf("%d ", StackTop(&ps));
StackPop(&ps);
//元数个数
printf("\nsize = %d\n", StackSize(&ps));
//销毁
StackDestroy(&ps);
}
int main()
{
Test1();
return 0;
}
程序运行结果:

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

队列也可以数组和链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,需要挪动数据,效率会比较低。
队列栈不同,队列在队尾入数据在队头出数据;也就是说在操作队列时,既要知道队头又要知道队尾,所以专门定义一个结构体Queue用来保存队列的头尾以及队列的元素个数,这样在对队列操作时,既不需要传二级指针也提高了效率。
#pragma once
#include
#include
#include
#include
typedef int QEDataType;
typedef struct QueueNode
{
struct QueueNode* next;
QEDataType data;
}QNode;
typedef struct Queue
{
QNode* head;
QNode* tail;
int size;
}Queue;
//初始化队列
void QueueInit(Queue* pq);
//销毁队列
void QueueDestroy(Queue* pq);
//入队列
void QueuePush(Queue* pq, QEDataType x);
//出队列
void QueuePop(Queue* pq);
//队列头元素
QEDataType QueueFront(Queue* pq);
//队列尾元素
QEDataType QueueBack(Queue* pq);
//队列判空
bool QueueEmpty(Queue* pq);
//队列元素个数
int QueueSize(Queue* pq);
队列初始化为空,队列元素个数为0。
//初始化队列
void QueueInit(Queue* pq)
{
assert(pq);
pq->size = 0;
pq->head = pq->tail = NULL;
}
保持队列头尾的指针需要置为空指针。
//销毁队列
void QueueDestroy(Queue* pq)
{
assert(pq);
QNode* cur = pq->head;
while (cur)
{
QNode* tmp = cur;
cur = cur->next;
free(tmp);
}
pq->head = NULL;
pq->tail = NULL;
}
入队列分为两种情况:
Queue结构体中的head和tail指针;tail指针即可。//入队列
void QueuePush(Queue* pq, QEDataType x)
{
assert(pq);
QNode* newNode = (QNode*)malloc(sizeof(QNode));
if (newNode == NULL)
{
perror("malloc fail");
exit(-1);
}
newNode->data = x;
newNode->next = NULL;
if (pq->head == NULL)
{
pq->head = newNode;
pq->tail = newNode;
}
else
{
pq->tail->next = newNode;
pq->tail = newNode;
}
pq->size++;
}
在队列头部出数据,即释放队头结点并且改变head指针的指向;当出队列时,队列只有一个元素,操作完需要把head和tail指针要置为NULL。
//出队列
void QueuePop(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));//判空
QNode* tmp = pq->head;
pq->head = pq->head->next;
free(tmp);
tmp = NULL;
pq->size--;
}
head和tail分别是指向队列头尾的指针,只需要返回这两个结点的数据即可。
//队列头元素
QEDataType QueueFront(Queue* pq)
{
assert(pq);
return pq->head->data;
}
//队列尾元素
QEDataType QueueBack(Queue* pq)
{
assert(pq);
return pq->tail->data;
}
当头部指针为NULL时,队列为空;或者在结构当中定义一个整形的成员变量size用来计数,那么size为零队列为空。
队列的元素个数为size 。
//队列判空
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->size == 0;
}
//队列元素个数
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
#include "Queue.h"
void Test2()
{
Queue pq;
QueueInit(&pq);
//入队列
QueuePush(&pq, 1);
printf("%d ", QueueFront(&pq));
printf("tail = %d\n", QueueBack(&pq));//队尾元素
QueuePush(&pq, 2);
printf("%d ", QueueFront(&pq));
printf("tail = %d\n", QueueBack(&pq));//队尾元素
QueuePush(&pq, 3);
printf("%d ", QueueFront(&pq));
printf("tail = %d\n", QueueBack(&pq));//队尾元素
QueuePush(&pq, 4);
printf("%d ", QueueFront(&pq));
printf("tail = %d ",QueueBack(&pq));//队尾元素
printf("\nsize = %d\n", QueueSize(&pq));
//出队列
printf("%d ", QueueFront(&pq));
QueuePop(&pq);
printf("%d ", QueueFront(&pq));
QueuePop(&pq);
printf("\nsize = %d\n", QueueSize(&pq));
QueueDestroy(&pq);
}
int main()
{
Test2();
return 0;
}
程序运行结果:
