文章目录
#1 栈
#1.1 自定义栈结构体
// 支持动态增长的栈
typedef int DataType;
typedef struct Stack
{
DataType* arr;
int top; // 栈顶
int capacity; // 容量
}Stack;
在后续的初始化函数中,如果选择将top初始化为0,那么实际上可以把top当作是size,
标示栈中数据的个数。
#1.2 栈基本功能函数
// 初始化栈
void StackInit(Stack* ps);
// 入栈
void StackPush(Stack* ps, DataType data);
// 出栈
void StackPop(Stack* ps);
// 获取栈顶元素
DataType StackTop(Stack* ps);
// 获取栈中有效元素个数
int StackSize(Stack* ps);
// 检测栈是否为空
int StackEmpty(Stack* ps);
// 销毁栈
void StackDestroy(Stack* ps);
# 1.2.1 初始化栈
void StackInit(Stack* ps)
{
ps->arr = NULL;
ps->top = 0;
ps->capacity = 0;
}
很简单,没什么好说的。
# 1.2.2 入栈
void StackPush(Stack* ps, DataType data)
{
assert(ps);
//这个if用于判断栈是否需要扩容,第一次入栈也会进这个if
if (ps->top == ps->capacity)
{
int Newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
ps->capacity = Newcapacity;
DataType* tmp = (DataType*)realloc(ps->arr, ps->capacity * sizeof(DataType));
if (tmp == NULL)
{
perror("realloc failed");
exit(-1);
}
ps->arr = tmp;
}
//进行到这里就说明一定有足够的空间放入下一个数据,
//top的位置就是原本最后一个数据的下一个位置,也是新数据需要进入的位置。
ps->arr[ps->top] = data;
ps->top++;
}
# 1.2.3 出栈
void StackPop(Stack* ps)
{
assert(ps);
//if用于温柔的判断栈是否已经空了,当然也可以用后面的判空函数
if (ps->top != 0)
{
//所谓的删除数据,只用让个数减一就好。
ps->top--;
}
}
这里写的出栈并没有返回栈顶元素,需要搭配后面的获取栈顶元素来使用。
# 1.2.4 获取栈顶元素
本函数,以及接下来的两个函数,都只需要简单的返回值就可以。
之所以需要把简单的语句也封装为一个函数,是为了调用者能够方便的调用使用。
DataType StackTop(Stack* ps)
{
assert(ps);
return ps->arr[ps->top - 1];
}
# 1.2.5 获取栈中有效元素的个数
int StackSize(Stack* ps)
{
assert(ps);
return ps->top;
}
# 1.2.6 检测栈是否为空
int StackEmpty(Stack* ps)
{
assert(ps);
//这里是简便的写法
//空,返回1.为真
//非空,返回0,为假
return ps->top == 0;
}
注意该函数作用是判断栈是否为空,如果为空,就是真的,返回的就是1。
不要逻辑上搞错了,错误的认为空就是假。
# 1.2.7 销毁栈
void StackDestroy(Stack* ps)
{
assert(ps);
free(ps->arr);
ps->arr = NULL;
ps->top = 0;
ps->capacity = 0;
}
也没什么好说的,记得free之后要置空。
#2 队列
#2.1 自定义队列结构体
typedef int DataType;
// 每个结点的结构
typedef struct QListNode
{
struct QListNode* next;
DataType data;
}QNode;
// 队列的结构
typedef struct Queue
{
QNode* front; //队列的出口
QNode* rear; //队列的入口
int size; //队列的大小,元素个数
}Queue;
队列的结构选择使用双指针,分别指向队头和队尾,用于出队列与进队列。
#2.2 队列基本功能函数
// 初始化队列
void QueueInit(Queue* q);
// 队尾入队列
void QueuePush(Queue* q, DataType data);
// 队头出队列
void QueuePop(Queue* q);
// 获取队列头部元素
DataType QueueFront(Queue* q);
// 获取队列队尾元素
DataType QueueBack(Queue* q);
// 获取队列中有效元素个数
int QueueSize(Queue* q);
// 检测队列是否为空
int QueueEmpty(Queue* q);
// 销毁队列
void QueueDestroy(Queue* q);
# 2.2.1 初始化队列
void QueueInit(Queue* q)
{
assert(q);
q->front = NULL;
q->rear = NULL;
q->size = 0;
}
同初始化栈。
# 2.2.2 队尾入队列
void QueuePush(Queue* q, DataType data)
{
assert(q);
QNode* NewNode = (QNode*)malloc(sizeof(QNode));
if (NewNode == NULL)
{
perror("malloc failed");
exit(-1);
}
//这里在创建新节点的同时顺便初始化
NewNode->data = data;
NewNode->next = NULL;
//队列为空时加入第一个结点,将会进入这个if
//让这个新结点同时成为队头和队尾
if (q->front == NULL)
{
q->front = NewNode;
q->rear = NewNode;
}
//否则就只需要将队尾的结点连接向这个新结点
//然后这个新结点成为新的队尾
else
{
q->rear->next = NewNode;
q->rear = NewNode;
}
//记得个数加一
q->size++;
}
由于是用链表而不是动态数组写的,和上文栈的差别在于不需要进行容量的判断。
只需要创建新的结点,进行连接就好。
# 2.2.3 队头出队列
void QueuePop(Queue* q)
{
assert(q);
//一个温柔的判断,如果队列中没有数据,则该函数什么也不做
if (q->size != 0)
{
//新建变量保存一下队头的下一个结点
//这样才能在free队头结点后找到新的队头
QNode* next = q->front->next;
free(q->front);
q->front = next;
//一般出队头是不需要操作队尾的
//除非队列中只有一个结点。队头就是队尾
//那么就需要将队尾也置为空,避免野指针的存在
if (q->size == 1)
{
q->rear = NULL;
}
q->size--;
}
}
# 2.2.4 获取队列头部元素
DataType QueueFront(Queue* q)
{
//该函数有两种情况可能无法正确的返回
//第一种是这个队列根本不存在
//用第一个assert判断
//第二种是队列存在,但是里面没有结点
//用第二个assert判断
assert(q);
assert(q->front);
//函数走到这里,说明队头元素肯定存在,返回就好
return q->front->data;
}
# 2.2.5 获取队列队尾元素
有时也会有需要获取队尾元素的场合,所以也写上这个函数。
这并不是从队尾出队列,只是单纯的获取队尾元素。
队列只能头出。
DataType QueueBack(Queue* q)
{
//两个assert的情况与获取队头元素一致
assert(q);
assert(q->rear);
return q->rear->data;
}
# 2.2.6 获取队列中有效元素个数
int QueueSize(Queue* q)
{
assert(q);
return q->size;
}
# 2.2.7 检测队列是否为空
int QueueEmpty(Queue* q)
{
assert(q);
return q->size == 0;
}
# 2.2.8 销毁队列
由于队列是用链表结构写的,意味着无法像动态数组一样直接一次性全部释放空间。
必须一个结点一个结点的释放。
void QueueDestroy(Queue* q)
{
assert(q);
//定义cur用于指向当前要释放的结点
//定义mext保存下一个待释放的结点
QNode* cur = q->front;
QNode* next = NULL;
//不能直接初始化next为cur->next
//因为队列中可能没有元素,此时cur为NULL,那么这步初始化就访问空指针了
//应该如下另写一个判断
if (cur != NULL)
{
next = cur->next;
}
//循环,每次释放一个结点
while (cur != NULL)
{
free(cur);
cur = next;
//同样注意,在释放最后一个结点完成后,此时next为NULL
//如果不加这个if而直接让next=next->next的话,又会造成空指针的访问
if (next != NULL)
{
next = next->next;
}
}
//释放犬奴吧结束后,置空
q->front = NULL;
q->rear = NULL;
q->size = 0;
}