作者: 华丞臧.
专栏:【数据结构】
各位读者老爷如果觉得博主写的不错,请诸位多多支持(点赞+收藏+关注
)。如果有错误的地方,欢迎在评论区指出。
文章目录
一、栈
1.1 栈的概念及结构
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作
。进行数据插入和删除操作的一段称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)
的原则。
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈,出数据也在栈顶。
1.2 栈的实现
栈的实现一般可以是用数组或者链表实现。那么选用哪一种呢?
- 根据栈的特殊性,即后进先出,只能在栈顶入数据,只能从栈顶出数据;那么使用数组就很好对栈进行入栈出栈,其操作的时间复杂度为
O(1)
;而使用链表,对栈进行入栈出栈操作是在栈顶进行的,栈顶就是链表尾部,那我们知道对链表尾部的操作的时间复杂度为O(N)
;显然选择数组更加合理。
1.3 栈的接口
// 支持动态增长的栈
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
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);
1.3.1 初始化栈、销毁栈
//初始化栈
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;
}
1.3.2 入栈
栈的特性是后进先出,栈顶是数组的尾部,栈底是数组的头部;对数据的操作只能在栈顶进行,入栈就是把数据存储在栈顶;既然用数组存放就需要考虑扩容的情况,那么为了达到既不浪费空间又不频繁扩容,每次扩容原先的两倍。
//入栈
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;
}
1.3.3 出栈
出栈即把栈顶的数据推出去,而定义栈的结构体中的成员变量top
表示栈中有效元素的个数,top-1
就可以理解为出栈一个元素。
//出栈
void StackPop(Stack* ps)
{
assert(ps);
assert(!StackEmpty(ps));
--ps->top;
}
1.3.4 栈顶元素
栈顶元素即数组当中最后一个元素。
//获取栈顶元素
STDataType StackTop(Stack* ps)
{
assert(ps);
assert(!StackEmpty(ps));
return ps->data[ps->top - 1];
}
1.3.5 元素个数
结构体当中top
表示栈中有效元素的个数,那么top
的值就是元素个。
//获取栈中有效元素个数
int StackSize(Stack* ps)
{
assert(ps);
return ps->top;
}
1.3.6 判空
top
为0
表示栈为空。
//检查栈是否为空,如果为空返回非0结果,如果不为空返回0
bool StackEmpty(Stack* ps)
{
assert(ps);
return ps->top == 0;
}
1.3.7 测试
#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;
}
程序运行结果:
二、队列
2.1 队列的概念及结构
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out)
。
入队列:进行插入操作的一端称为队尾;
出队列:进行删除操作的一端称为队头。
2.2 队列的实现
队列也可以数组和链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,需要挪动数据,效率会比较低。
2.2.1 队列的接口
队列栈不同,队列在队尾入数据在队头出数据;也就是说在操作队列时,既要知道队头又要知道队尾,所以专门定义一个结构体Queue
用来保存队列的头尾以及队列的元素个数,这样在对队列操作时,既不需要传二级指针也提高了效率。
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
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);
2.2.2 初始化队列
队列初始化为空,队列元素个数为0。
//初始化队列
void QueueInit(Queue* pq)
{
assert(pq);
pq->size = 0;
pq->head = pq->tail = NULL;
}
2.2.3 销毁队列
保持队列头尾的指针需要置为空指针。
//销毁队列
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;
}
2.2.4 入队列
入队列分为两种情况:
- 队列为空,这时需要改变
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++;
}
2.2.5 出队列
在队列头部出数据,即释放队头结点并且改变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--;
}
2.2.6 队头和队尾元素
head
和tail
分别是指向队列头尾的指针,只需要返回这两个结点的数据即可。
//队列头元素
QEDataType QueueFront(Queue* pq)
{
assert(pq);
return pq->head->data;
}
//队列尾元素
QEDataType QueueBack(Queue* pq)
{
assert(pq);
return pq->tail->data;
}
2.2.7 队列的判空和元素个数
当头部指针为NULL
时,队列为空;或者在结构当中定义一个整形的成员变量size
用来计数,那么size
为零队列为空。
队列的元素个数为size
。
//队列判空
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->size == 0;
}
//队列元素个数
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
2.2.8 测试
#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;
}
程序运行结果: