大家好!今天我们来学习数据结构中的栈和队列。
目录
1. 栈
1.1 栈的概念及结构
1.2 栈的定义
1.3 栈的接口实现
1.3.1 初始化栈
1.3.2 入栈
1.3.3 出栈
1.3.4 获取栈顶元素
1.3.5 获取栈中有效元素个数
1.3.6 检测栈是否为空
1.3.7 销毁栈
1.4 栈的完整代码
1.4.1 Stack.h
1.4.2 Stack.c
1.4.3 Test.c
2. 队列
2.1 队列的概念及结构
2.2 队列的定义
2.3 队列的接口实现
2.3.1 初始化队列
2.3.2 入队
2.3.3 出队
2.3.4 获取队头元素
2.3.5 获取队尾元素
2.3.6 获取队列中有效元素个数
2.3.7 检测队列是否为空
2.3.8 销毁队列
2.4 队列的完整代码
2.4.1 Queue.h
2.4.2 Queue.c
2.4.3 Test.c
3. 总结
栈(stack),又名堆栈。一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守先进后出的原则。
入栈:栈的插入操作叫做入栈/进栈/压栈,入数据在栈顶。(把新元素放在栈顶元素的上面,使之成为新的栈顶元素)。
出栈:栈的删除操作叫做出栈。出数据也在栈顶。(把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素)。
栈的实现一般可以使用数组或者链表实现,相对而言数组的结构实现更优一些。因为数组在尾上插入数据的代价比较小。因为静态顺序表不实用,所以我们使用动态顺序表实现栈。
//支持动态增长的栈
typedef int STDataType;
#define INIT_CAPACITY 4
typedef struct Stack
{
STDataType* a; //指向动态开辟的数组
int top; //栈顶
int capacity; //容量
}ST;
使用结构体创建一个支持动态增长的栈(用动态顺序表实现)。
用STDataType替换int,方便对不同类型的数据进行修改。
用ST替换struct Stack,方便简洁。
用宏定义INIT_CAPACITY将栈的初始容量设置为4。
栈的所有接口函数一览:
//初始化栈
void STInit(ST* ps);
//入栈
void STPush(ST* ps, STDataType x);
//出栈
void STPop(ST* ps);
//获取栈顶元素
STDataType STTop(ST* ps);
//获取栈中有效元素个数
int STSize(ST* ps);
//检测栈是否为空
bool STEmpty(ST* ps);
//销毁栈
void STDestroy(ST* ps);
这些接口函数主要实现了支持动态动态增长的栈的基本功能,接下来我们一一实现这些函数!
我们一开始将指针a置空,让栈顶top为0,栈的容量capacity也为0。这里要注意assert()断言,保证ps的合理性。
//初始化栈
void STInit(ST* ps)
{
assert(ps);
ps->a = NULL;
ps->top = 0;
ps->capacity = 0;
}
和动态顺序表的操作类似,入栈的时候,我们也需要考虑是否需要扩容。
这里当栈满了(ps->top==ps->capacity)的时候,就需要扩容。
我们定义了一个变量newCapacity来存储扩容后栈的新容量。我们将新容量扩充为原来的2倍,但是因为有可能原容量为0,所以我们这里使用了三目运算符。如果原容量为0,我们就将newCapacity设置为INIT_CAPACITY。否则,我们就让newCapacity是原容量的2倍。
之后使用realloc()函数进行扩容,我们用结构体指针tmp存取栈扩容后的地址。
这里我们扩展一个知识点:
我们可以知道,如果一开始如果传的指针a为空,realloc()函数的作用和malloc()函数作用相同。
扩容完后就是让指针a指向tmp(ps->a=tmp),
让容量变为newCapacity(ps->capacity=newCapacity)。
入栈就是添加新元素在栈顶(ps->a[ps->top]=x),同时让栈顶top++。
//入栈
void STPush(ST* ps, STDataType x)
{
assert(ps);
if (ps->top == ps->capacity)
{
int newCapacity = ps->capacity == 0 ? INIT_CAPACITY : ps->capacity * 2;
STDataType* tmp = (STDataType*)realloc(ps->a, sizeof(STDataType) * newCapacity);
if (tmp == NULL)
{
perror("realloc failed");
exit(-1);
}
ps->a = tmp;
ps->capacity = newCapacity;
}
ps->a[ps->top] = x;
ps->top++;
}
栈中必须有元素我们才能进行出栈的操作。所以我们要使用assert(ps->a>0)断言,同时也要用assert()保证指针ps的合理性。
出栈我们直接让栈顶top--即可。
//出栈
void STPop(ST* ps)
{
assert(ps);
//空
assert(ps->a > 0);
--ps->top;
}
栈中必须有元素我们才能获取栈顶元素。所以我们要使用assert(ps->a>0)断言,同时也要用assert()保证指针ps的合理性。
栈顶元素就是下标为top-1位置的元素。
//获取栈顶元素
STDataType STTop(ST* ps)
{
assert(ps);
//空
assert(ps->a > 0);
return ps->a[ps->top - 1];
}
assert()断言保证指针ps的合理性,直接返回top即可。
//获取栈中有效元素个数
int STSize(ST* ps)
{
assert(ps);
return ps->top;
}
assert()断言保证指针ps的合理性,判断top是否为0,并返回。
//检测栈是否为空
bool STEmpty(ST* ps)
{
assert(ps);
return ps->top == 0;
}
assert()断言保证指针ps的合理性。将指针a置空,将栈顶top和容量capacity都为0。
//销毁栈
void STDestroy(ST* ps)
{
assert(ps);
ps->a = NULL;
ps->top = ps->capacity = 0;
}
#pragma once
#include
#include
#include
#include
//支持动态增长的栈
typedef int STDataType;
#define INIT_CAPACITY 4
typedef struct Stack
{
STDataType* a; //指向动态开辟的数组
int top; //栈顶
int capacity; //容量
}ST;
//初始化栈
void STInit(ST* ps);
//入栈
void STPush(ST* ps, STDataType x);
//出栈
void STPop(ST* ps);
//获取栈顶元素
STDataType STTop(ST* ps);
//获取栈中有效元素个数
int STSize(ST* ps);
//检测栈是否为空
bool STEmpty(ST* ps);
//销毁栈
void STDestroy(ST* ps);
#define _CRT_SECURE_NO_WARNINGS 1
#include"Stack.h"
//初始化栈
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 ? INIT_CAPACITY : ps->capacity * 2;
STDataType* tmp = (STDataType*)realloc(ps->a, sizeof(STDataType) * newCapacity);
if (tmp == NULL)
{
perror("realloc failed");
exit(-1);
}
ps->a = tmp;
ps->capacity = newCapacity;
}
ps->a[ps->top] = x;
ps->top++;
}
//出栈
void STPop(ST* ps)
{
assert(ps);
//空
assert(ps->a > 0);
--ps->top;
}
//获取栈顶元素
STDataType STTop(ST* ps)
{
assert(ps);
//空
assert(ps->a > 0);
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);
ps->a = NULL;
ps->top = ps->capacity = 0;
}
#define _CRT_SECURE_NO_WARNINGS 1
#include"Stack.h"
void TestStack()
{
ST st;
STInit(&st);
STPush(&st, 1);
STPush(&st, 2);
STPush(&st, 3);
STPush(&st, 4);
STPush(&st, 5);
while (!STEmpty(&st))
{
printf("%d ", STTop(&st));
STPop(&st);
}
printf("\n");
STDestroy(&st);
}
int main()
{
TestStack();
return 0;
}
队列(queue)是一种特殊的线性表,只允许在表的后端(rear)进行插入数据操作,只允许在表的前端(front)进行删除数据操作。进行插入操作的一端称为队尾,进行删除操作的一端称为队头 。队列中的数据元素遵循先进先出的原则。
入队:在队尾插入一个队列元素称为入队。
出队:从队头删除一个队列元素称为出队。
队列也可以用数组和链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低。
typedef int QDataType;
typedef struct QueueNode
{
QDataType data;
struct QueueNode* next;
}QNode;
使用结构体创建一个队列(用单链表实现)。
用QDataType替换int,方便对不同类型的数据进行修改。
用QNode替换struct QueueNode,方便简洁。
typedef struct Queue
{
QNode* head;
QNode* tail;
int size;
}Que;
我们这里再用一个结构体存储头指针head,尾指针tail和队列的长度size。
队列的所有接口函数一览:
#pragma once
#include
#include
#include
#include
typedef int QDataType;
typedef struct QueueNode
{
QDataType data;
struct QueueNode* next;
}QNode;
typedef struct Queue
{
QNode* head;
QNode* tail;
int size;
}Que;
//初始化队列
void QueueInit(Que* pq);
//队尾入队列
void QueuePush(Que* pq,QDataType x);
//队头出队列
void QueuePop(Que* pq);
//获取队列头部元素
QDataType QueueFront(Que* pq);
//获取队列队尾元素
QDataType QueueBack(Que* pq);
//获取队列中有效元素个数
QDataType QueueSize(Que* pq);
//检测队列是否为空
bool QueueEmpty(Que* pq);
//销毁队列
void QueueDestroy(Que* pq);
这些接口函数主要实现了队列的基本功能,接下来我们一一实现这些函数!
将头指针head和尾指针tail均置空,将size置为0。这里要注意assert()断言,保证pq指针的合理性。
//初始化队列
void QueueInit(Que* pq)
{
assert(pq);
pq->head = pq->tail = NULL;
pq->size = 0;
}
首先使用assert()断言,保证pq指针的合理性。
接着我们使用malloc()函数创建一个新结点。如果malloc()函数的返回值为空(即这里的newnode==NULL),我们就用perror()函数打印提示相应错误,并用exit(-1)退出整个程序。
我们将这个新结点的值赋为x(newnode->data=x),
将新结点的next指针置空(newnode->next=NULL)。
入队其实就是进行单链表的尾插操作,但是也需要考虑不同情况:
(1)如果是尾插第一个结点,我们就让头指针head和尾指针tail都指向新结点newnode。
(2)其他情况就正常尾插,让tail的next指向新结点newnode,同时tail往后走一步。
最后我们让size++即可。
//入队
void QueuePush(Que* pq, QDataType x)
{
assert(pq);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloc failed");
exit(-1);
}
newnode->data = x;
newnode->next = NULL;
if (pq->tail == NULL)
{
pq->head = pq->tail = newnode;
}
else
{
pq->tail->next = newnode;
pq->tail = newnode;
}
pq->size++;
}
首先使用assert()断言,保证pq指针的合理性。因为我们要让队头元素出队,所以队列不能为空,当队列为空时,我们也使用assert()断言。
出队其实就是进行单链表的头删操作,出队时我们也要分情况讨论:
(1)当队列只有一个结点时,我们直接释放掉这个结点(free(pq->head)),再让头指针head和尾指针tail置空。
(2)当队列不止一个结点时,我们定义一个结构体指针next保存头结点的下一个结点,用free()释放掉头结点,再让头指针head指向next指针。
最后我们让size--即可。
//出队
void QueuePop(Que* pq)
{
assert(pq);
//队列为空
assert(!QueueEmpty(pq));
//只有一个结点
if (pq->head->next == NULL)
{
free(pq->head);
pq->head = pq->tail = NULL;
}
else
{
QNode* next = pq->head->next;
free(pq->head);
pq->head = next;
}
pq->size--;
}
首先使用assert()断言,保证pq指针的合理性。因为我们要获取队头元素,所以队列不能为空,当队列为空时,我们也使用assert()断言。
返回头结点的值(head->data)即可。
//获取队头元素
QDataType QueueFront(Que* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->head->data;
}
首先使用assert()断言,保证pq指针的合理性。因为我们要获取队尾元素,所以队列不能为空,当队列为空时,我们也使用assert()断言。
返回尾结点的值(tail->data)即可。
//获取队尾元素
QDataType QueueBack(Que* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->tail->data;
}
assert()断言保证指针pq的合理性,直接返回size即可。
//获取队列中有效元素个数
QDataType QueueSize(Que* pq)
{
assert(pq);
return pq->size;
}
先用assert()断言保证指针pq的合理性。判断head是否为空,并返回。
//检测队列是否为空
bool QueueEmpty(Que* pq)
{
assert(pq);
return pq->head == NULL;
}
assert()断言保证指针pq的合理性。定义一个结构体指针cur遍历整个链表,依次用free()释放掉每个结点。因为直接free释放当前的结点会导致找不到下一个结点,所以我们使用结构体指针next保存当前结点的下一个结点。通过while循环遍历,就能依次释放链表中的每个结点。
最后将头指针head和尾指针tail都置空。将队列长度size置为0。
//销毁队列
void QueueDestroy(Que* pq)
{
assert(pq);
QNode* cur = pq->head;
while (cur)
{
QNode* next = cur->next;
free(cur);
cur = next;
}
pq->head = pq->tail = NULL;
pq->size = 0;
}
#pragma once
#include
#include
#include
#include
typedef int QDataType;
typedef struct QueueNode
{
QDataType data;
struct QueueNode* next;
}QNode;
typedef struct Queue
{
QNode* head;
QNode* tail;
int size;
}Que;
//初始化队列
void QueueInit(Que* pq);
//入队
void QueuePush(Que* pq,QDataType x);
//出队
void QueuePop(Que* pq);
//获取队头元素
QDataType QueueFront(Que* pq);
//获取队尾元素
QDataType QueueBack(Que* pq);
//获取队列中有效元素个数
QDataType QueueSize(Que* pq);
//检测队列是否为空
bool QueueEmpty(Que* pq);
//销毁队列
void QueueDestroy(Que* pq);
#define _CRT_SECURE_NO_WARNINGS 1
#include"Queue.h"
//初始化队列
void QueueInit(Que* pq)
{
assert(pq);
pq->head = pq->tail = NULL;
pq->size = 0;
}
//入队
void QueuePush(Que* pq, QDataType x)
{
assert(pq);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloc failed");
exit(-1);
}
newnode->data = x;
newnode->next = NULL;
if (pq->tail == NULL)
{
pq->head = pq->tail = newnode;
}
else
{
pq->tail->next = newnode;
pq->tail = newnode;
}
pq->size++;
}
//出队
void QueuePop(Que* pq)
{
assert(pq);
//队列为空
assert(!QueueEmpty(pq));
//只有一个结点
if (pq->head->next == NULL)
{
free(pq->head);
pq->head = pq->tail = NULL;
}
else
{
QNode* next = pq->head->next;
free(pq->head);
pq->head = next;
}
pq->size--;
}
//获取队头元素
QDataType QueueFront(Que* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->head->data;
}
//获取队尾元素
QDataType QueueBack(Que* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->tail->data;
}
//获取队列中有效元素个数
QDataType QueueSize(Que* pq)
{
assert(pq);
return pq->size;
}
//检测队列是否为空
bool QueueEmpty(Que* pq)
{
assert(pq);
return pq->head == NULL;
}
//销毁队列
void QueueDestroy(Que* pq)
{
assert(pq);
QNode* cur = pq->head;
while (cur)
{
QNode* next = cur->next;
free(cur);
cur = next;
}
pq->head = pq->tail = NULL;
pq->size = 0;
}
#define _CRT_SECURE_NO_WARNINGS 1
#include"Queue.h"
void TestQueue()
{
Que q;
QueueInit(&q);
QueuePush(&q, 1);
QueuePush(&q, 2);
QueuePush(&q, 3);
QueuePush(&q, 4);
QueuePush(&q, 5);
while (!QueueEmpty(&q))
{
printf("%d ", QueueFront(&q));
QueuePop(&q);
}
printf("\n");
QueueDestroy(&q);
}
int main()
{
TestQueue();
return 0;
}
到这里,我们就用C语言实现了数据结构中的栈和队列。有什么问题欢迎在评论区讨论。如果觉得文章有什么不足之处,可以在评论区留言。如果喜欢我的文章,可以点赞收藏哦!