目录
1、栈
1.1 栈的概念及结构
概念:
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端 称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫出栈。出数据也在栈顶。
注意:
(1)通常称入栈(插入)、出栈(删除)的这一端为栈顶(top),另一端称为栈底(Bottom)。
(2)当线性表中没有元素时叫称为空栈。
(3)栈为后进先出(Last In First Out)的线性表,简称为LIFO表。
栈的结构: 如下图。
1.2 栈的基本操作
(1)StackInit(Stack* ps):构造一个空栈。
(2)StackEmpty(Stack* ps):判栈空。若为空栈,则返回TRUE,否则返回FALSE。
(3)StackPush(Stack* ps, StackDataType x):进栈。将元素 x 插入到栈顶。
(4)StackPop(Stack* ps):退栈。若栈非空,则将栈顶的元素删去。
(5)StackTop(Stack* ps):取栈顶的元素。若栈非空,则返回栈顶元素,但不改变栈的状态。
(6)StackSize(Stack* ps):栈的大小。也就是栈的元素个数。
(7)StackDestroy(Stack* ps):销毁栈。栈使用结束,就对栈进行销毁。
1.3 栈基本操作的实现
实现栈的基本运算选择的是顺序表呢?还是链表呢?两者都是可以实现的,但是为了选择更为优一些的方法。首先,来对比一下这两个的优缺点,再做抉择。
1.3.1 顺序表和链表的优缺点比较
顺序表:
优点:
(1)支持随机访问(用小标访问)。需要随机访问的结构支持的算法可以很好的适用/
(2)CPU高速缓存命中率更高(不懂需要百度)。
缺点:
(1)头部中部插入删除时间效率低(原因:删除头部,还要把头部后面的全部数据往前移)。O(N)
(2)连续的物理空间,空间不够了以后需要增容。
a.增容有一定程度消耗。
b.为了避免频繁增容,一般我们都是按倍数去增容,用不完可能存在一定的空间浪费。
链表(带头双向循环链表):
优点:
(1)任意位置插入删除效率高。O(1)
(2)按需申请释放空间。
缺点:
(1)不支持随机访问。(不支持用下标访问)意味着:一些排序、二分查找等在这种结构不适合用。
(2)链表存储一个值,同时要存储链接指针,有一定的消耗。
(3) CPU高速缓存命中率更低(不懂需要百度)。
对比以上顺序表和链表的优缺点,基本上可以判断出顺序表实现栈更为优一些。栈的性质就是先进后出。假设顺序表和链表实现栈的结构如下图:
两者都是尾插和尾删。时间复杂度基本一致。链表(带头双向循环)唯一缺点就是CPU高速缓存命中率更低。基于这一缺点,这里我们是使用顺序表来实现栈的基本操作。
1.3.2 动态顺序表实现栈的基本操作——源码
stack.h
#pragma once
#include<stdlib.h>
#include<stdio.h>
#include<assert.h>
#include<stdbool.h>
typedef int StackDataType;//栈的数据类型
//顺序栈类型的定义----动态顺序表
typedef struct Stack
{
StackDataType* data; //栈的数据
int top; //栈顶
int capacity; //栈的容量
}Stack;
//栈基本操作的接口函数
void StackInit(Stack* ps);//初始化栈---构造一个空栈
void StackDestroy(Stack* ps);//销毁栈
void StackPush(Stack* ps, StackDataType x);//栈顶插入一个元素---进栈
void StackPop(Stack* ps);//删除栈的元素---退栈/出栈
StackDataType StackTop(Stack* ps);//取栈顶的数据
int StackSize(Stack* ps);//栈的元素个数
bool StackEmpty(Stack* ps);//判空
stack.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "stack.h"
void StackInit(Stack* ps)//初始化栈---构造一个空栈
{
assert(ps);
ps->data = NULL;
ps->top = 0; // top=-1,top指向栈顶的数据;top=0,top指向栈顶的下一个
ps->capacity = 0;//栈的初始容量
}
void StackDestroy(Stack* ps)//销毁栈
{
assert(ps);
free(ps->data); //释放内存
ps->capacity = 0;
ps->top = 0;
}
void StackPush(Stack* ps, StackDataType x)//栈顶插入一个元素---进栈
{
assert(ps);
if (ps->capacity == ps->top)//开辟内存及其内存空间不足扩容
{
ps->capacity = ps->capacity == 0 ? 6 : ps->capacity * 2;
StackDataType* temp = (StackDataType*)realloc(ps->data, sizeof(StackDataType) * ps->capacity);
if (temp == NULL)
{
printf("realloc fail\n");
exit(-1);//开辟失败异常退出
}
ps->data = temp;
}
ps->data[ps->top] = x;//插入元素数据
ps->top++;//指向栈顶元素的下一个
}
void StackPop(Stack* ps)//删除栈的元素---退栈/出栈
{
assert(ps);
assert(!StackEmpty(ps));//栈为空就不能删除了
ps->top--;//开始删除
}
StackDataType StackTop(Stack* ps)//取栈顶的数据
{
assert(ps);
assert(!StackEmpty(ps));//当栈为空时不取出数据
return ps->data[ps->top - 1];//因为top是指向栈顶数据的下一个,所以 top-1 才能取到栈顶的元素
}
int StackSize(Stack* ps)//栈的元素个数
{
return ps->top;
}
bool StackEmpty(Stack* ps)//判空---判断栈是否为空
{
assert(ps);
//if (ps->top == 0)
//{
// return true;
//}
//else
//{
// return false;
//}
return ps->top == 0;//当 top初始化为-1是需要写为 return ps->top==-1;
}
test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"stack.h"
void TestStack()
{
Stack st;
StackInit(&st);
StackPush(&st, 1);//插入数据
StackPush(&st, 2);
StackPush(&st, 3);
StackPush(&st, 4);
StackPush(&st,5);
//后进先出原则:5 4 3 2 1
//栈是不能像顺序表、链表那样直接随便取数据
//取栈的数据只有一个方式,就是需要把栈顶的数据取出来了才能取下一个,才符合栈的性质
while (!StackEmpty(&st))
{
printf("%d ", StackTop(&st));//先取栈顶的数据
//取了栈顶的数据,想要取栈顶的下一个数据,把当前的栈顶的数据 pop 掉,就是删除掉
//才能取下一个栈的数据
StackPop(&st);
}
StackDestroy(&st);//销毁栈
}
int main()
{
TestStack();
return 0;
}
2、队列
2.1 队列的概念及结构
概念:
注意:
(1)允许删除的一端称为队头(Front)。
(2)允许插入的一端称为队尾(Back)。
(3)当队列中没有元素时称为空队列。
(4)队列亦称做先进先出(First In First Out)的线性表,简称为 FIFO 表。
队列的结构:如下图。
2.2 队列的基本操作
(1)QueueInit(Queue* pq):置空队列。构造一个空队列。
(2)QueueEmpty(Queue* pq):判队空。若队列为空,则返回真值,否则返回假值。
(3)QueuePush(Queue* pq, QueueDataType x):将元素 x 插入队列的队尾,此操作简称为入队。
(4)QueuePop(Queue* pq):若队列非空,则删去队列的队头元素,此操作简称为出队。
(5)QueueFront(Queue* pq):取队头元素数据。若队列非空,则返回队头元素,但不改变队列的状态。
(6)QueueBack(Queue* pq):取队尾元素。若队列非空,则返回队尾元素,但不改变队列的状态。
(7)QueueSize(Queue* pq):队列元素个数。
(8)QueueDestroy(Queue* pq):销毁队列。
2.3 队列基本操作的实现
入队:
出队:
2.3.1单链表实现队列的基本操作——源码
queue.h
#pragma once
#include<stdlib.h>
#include<stdio.h>
#include<assert.h>
#include<stdbool.h>
typedef int QueueDataType;//队列元素的数据类型
//链表队列类型定义
typedef struct QueueNode
{
struct QueueNode* next;
QueueDataType data;
}QueueNode;
typedef struct Queue
{
QueueNode* head; //队列头指针
QueueNode* tail; //队列尾指针
}Queue;
//队列基本操作的函数接口
void QueueInit(Queue* pq);//初始化队列--置空队列。构造一个空队列。
void QueueDestroy(Queue* pq);//销毁队列
void QueuePush(Queue* pq, QueueDataType x);//向队尾插入一个元素---入队列
void QueuePop(Queue* pq);//删除队头元素
QueueDataType QueueFront(Queue* pq);//取队头元素
QueueDataType QueueBack(Queue* pq);//取队尾元素
int QueueSize(Queue* pq);//队列元素个数
bool QueueEmpty(Queue* pq);//判空--判断队列是否为空
queue.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"queue.h"
void QueueInit(Queue* pq)//初始化队列--置空队列。构造一个空队列。
{
assert(pq);
pq->head = NULL;
pq->tail = NULL;
}
void QueueDestroy(Queue* pq)//销毁队列
{
assert(pq);
QueueNode* current = pq->head;//记录头指针
while (current)
{
QueueNode* next = current->next;//记录头指针的下一个结点的指针
free(current);
current = next;
}
pq->head = pq->tail = NULL;//释放结束将头尾指针置空
}
void QueuePush(Queue* pq, QueueDataType x)//向队尾插入一个元素---入队列
{
assert(pq);
QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
newnode->data = x;
newnode->next = NULL;
//尾插
if (pq->head == NULL)//第一次入队
{
pq->head = pq->tail = newnode;
}
else
{
pq->tail->next = newnode;
pq->tail = newnode;//更新尾指针
}
}
void QueuePop(Queue* pq)//删除队头元素
{
assert(pq);
assert(!QueueEmpty(pq));
//开始头删
QueueNode* headNext = pq->head->next;
free(pq->head);
pq->head = headNext;
if (pq->head == NULL)//当把队列删空了,不仅要把 head 置空,还要把 tail 置空
{
pq->tail = NULL;
}
}
QueueDataType QueueFront(Queue* pq)//取队头元素
{
assert(pq);
assert(!QueueEmpty(pq));//队列非空就取
return pq->head->data;
}
QueueDataType QueueBack(Queue* pq)//取队尾元素
{
assert(pq);
assert(!QueueEmpty(pq));//队列非空就取
return pq->tail->data;
}
int QueueSize(Queue* pq)//队列元素个数
{
//遍历队列,进行计数,返回出结果
assert(pq);
int count = 0;
QueueNode* current = pq->head;
while (current)
{
++count;
current = current->next;
}
return count;
}
bool QueueEmpty(Queue* pq)//判空--判断队列是否为空
{
assert(pq);
return pq->head == NULL;//队列为空就为真,否则为假
}
test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"queue.h"
void TestQueue()
{
Queue qu;
QueueInit(&qu);
QueuePush(&qu, 1);//入队
QueuePush(&qu, 2);
QueuePush(&qu, 3);
QueuePush(&qu, 4);
QueuePush(&qu, 5);
//出队-----先进先出----1 2 3 4 5
//取队头的元素,想要取出队头的下一个元素,将队头 Pop,才能取下一个
while (!QueueEmpty(&qu))
{
QueueDataType front = QueueFront(&qu);
printf("%d ", front);
QueuePop(&qu);
}
printf("\n");
QueueDestroy(&qu);//销毁队列
}
int main()
{
TestQueue();
return 0;
}