文章目录
一、栈的概念和结构
栈一种特殊的线性表,它只允许在固定的一端进行插入和删除元素操作;进行数据插入和删除操作的一端称为栈顶,另一端称为栈底;栈中的数据元素遵守后进先出 LIFO(Last In First Out)的原则;
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶;出栈:栈的删除操作叫做出栈,出数据也在栈顶;
注意:不要把栈区和栈混为一谈:栈区是内存划分的一块区域,属于操作系统学科;而栈是用于管理数据的一种结构,它在堆区上申请空间,属于数据结构学科。
二、顺序栈的实现
1. 结构的定义
栈可以用顺序表实现,也可以用链表实现,这里选用顺序表实现,原因如下:
1、栈的插入和删除操作都在栈顶,即在数据的尾部进行,而顺序表在尾部插入和删除数据的效率为O(1),完美的避开了顺序表的缺陷;
2、顺序表扩容和链表频繁 malloc 在整体上的效率是差不多的,只是顺序表会存在一定的空间浪费;
3、顺序表支持随机访问,且其缓存利用率更高;
综合考虑以上几种因素,我们采用顺序表实现栈;
typedef int ASDataType;
typedef struct Stack
{
ASDataType* val;
int top;
int capacity;
}Stack;
这里的top指针和顺序表的size是等价的
2. 初始化栈
函数调用者需要在外面先定义一个Stack变量,然后把栈的地址传进来,在Init
函数中初始化栈为空
void InitStack(Stack* ps)
{
assert(ps);
ps->val = NULL;
ps->top = 0;
ps->capacity = 0;
}
3. 入栈
由于栈只能在栈顶插入元素,所以我们只需要在 push 函数中进行检查容量并扩容,而不需要把 CheckCapacity 单独封装成一个函数。
void EnStack(Stack* ps, ASDataType x)
{
assert(ps);
if (ps->capacity == ps->top)//扩容
{
int NewCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
ASDataType* temp = (ASDataType*)realloc(ps->val, NewCapacity * sizeof(ASDataType));
if (temp == NULL)
{
perror("realloc");
exit(-1);
}
ps->val = temp;
ps->capacity = NewCapacity;
}
ps->val[ps->top] = x;
ps->top++;
}
4. 出栈
出栈之前我们需要检查栈是否为空
void DeStack(Stack* ps)
{
assert(ps);
if (ps->top == 0)
{
printf("栈为空,删除失败!\n");
return;
}
ps->top--;
}
5. 获取栈顶元素
获取栈顶元素时我们也需要检查栈是否为空,避免造成对空指针的解引用。
ASDataType StackTop(Stack* ps)
{
assert(ps);
assert(ps->top > 0);
return ps->val[ps->top - 1];
}
6. 获取栈的长度
int StackSize(Stack* ps)
{
assert(ps);
return ps->top;
}
7. 判空
bool IsEmptyStack(Stack* ps)
{
assert(ps);
return ps->top == 0;
}
8. 销毁栈
void DestroyStack(Stack* ps)
{
assert(ps);
free(ps->val);
ps->val = NULL;
ps->capacity = ps->top = 0;
}
需要注意的是,我们不需要定义栈的打印函数,因为栈不能遍历,如果我们想得到栈顶的前一个元素,我们就必须先把栈顶的元素给删除掉,让后面一个元素变成栈顶。
三、完整代码
1. Stack.h
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int ASDataType;
typedef struct Stack
{
ASDataType* val;
int top;
int capacity;
}Stack;
//初始化栈
void InitStack(Stack* ps);
//入栈
void EnStack(Stack* ps, ASDataType x);
//出栈
void DeStack(Stack* ps);
//判空
bool IsEmptyStack(Stack* ps);
//取栈顶元素
ASDataType StackTop(Stack* ps);
//求栈的大小
int StackSize(Stack* ps);
//销毁栈
void DestroyStack(Stack* ps);
2. Stack.c
#include"Stack.h"
void InitStack(Stack* ps)
{
assert(ps);
ps->val = NULL;
ps->top = 0;
ps->capacity = 0;
}
void EnStack(Stack* ps, ASDataType x)
{
assert(ps);
if (ps->capacity == ps->top)//扩容
{
int NewCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
ASDataType* temp = (ASDataType*)realloc(ps->val, NewCapacity * sizeof(ASDataType));
if (temp == NULL)
{
perror("realloc");
exit(-1);
}
ps->val = temp;
ps->capacity = NewCapacity;
}
ps->val[ps->top] = x;
ps->top++;
}
void DeStack(Stack* ps)
{
assert(ps);
if (ps->top == 0)
{
printf("栈为空,删除失败!\n");
return;
}
ps->top--;
}
bool IsEmptyStack(Stack* ps)
{
assert(ps);
return ps->top == 0;
}
ASDataType StackTop(Stack* ps)
{
assert(ps);
assert(ps->top > 0);
return ps->val[ps->top - 1];
}
int StackSize(Stack* ps)
{
assert(ps);
return ps->top;
}
void DestroyStack(Stack* ps)
{
assert(ps);
free(ps->val);
ps->val = NULL;
ps->capacity = ps->top = 0;
}
3. test.c
#include"Stack.h"
int main()
{
Stack S;
InitStack(&S);
EnStack(&S, 1);
EnStack(&S, 2);
EnStack(&S, 3);
EnStack(&S, 4);
EnStack(&S, 5);
printf("栈的大小:%d\n", StackSize(&S));
int n = 5;
while (n--)
{
printf("%d\n", StackTop(&S));
DeStack(&S);
}
printf("%d\n", IsEmptyStack(&S));
DestroyStack(&S);
return 0;
}
四、 队列的概念和结构
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表;队列中的数据元素遵守先进先出 FIFO(First In First Out)的原则;
入队列:进行插入操作的一端称为队尾;出队列:进行删除操作的一端称为队头。
五、链式队列的实现
1. 队列结构的定义
和栈一样,队列既可以使用顺序表实现,也可以使用链表实现,这里我们使用单链表实现,原因如下:
1、队列需要删除头部的元素,单链表头删的效率为O(1);
2、使用链表可以按需申请空间,避免了空间的浪费;
但是我们发现使用单链表实现队列存在一个问题,那就是单链表尾插以及计算链表长度的效率都为O(N),不符合我们的预期,那么我们需要把单链表改造为循环链表吗?可以是可以,但是这样又把队列的结构搞复杂了;所以综合考虑,这里我们增加三个变量,一个用于记录队尾,一个用于记录队头,还有一个用于记录队列的长度。
typedef int LQDataType;
typedef struct QNode //队列结点
{
LQDataType val;
struct QNode* next;
}QNode;
typedef struct Queue //队列
{
QNode* head; //队头指针
QNode* tail; //队尾指针
int size; //记录队列长度
}Queue;
2. 初始化队列
void InitQueue(Queue* pq)
{
assert(pq);
pq->head = NULL;
pq->tail = NULL;
pq->size = 0;
}
3. 入队
由于我们使用了一个结构体来记录队列的头指针和尾尾指针,那么这里我们改变队列的头指针和尾指针时只需要改变结构体即可,所以只需要传递一级指针;
另外,队列也只能从队尾入数据,所以我们也没有必要单独封装一个 BuyNode 函数。
void EnQueue(Queue* pq, LQDataType x)
{
assert(pq);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("EnQueue");
exit(-1);
}
newnode->next = NULL;
newnode->val = x;
if (pq->head == NULL)
{
pq->head = newnode;
pq->tail = newnode;
}
else
{
newnode->next = pq->tail->next;
pq->tail->next = newnode;
pq->tail = newnode;
}
pq->size++;
}
4. 出队
这里除了正常地对队列进行判空之外,还有一个需要特别注意的地方:当队列只有一个元素时,我们再次头删虽然会让head指向NULL,但是tail仍然指向头删之前的那个节点,没有置空,会形成野指针,所以我们这里需要单独处理这种情况
void DeQueue(Queue* pq)
{
assert(pq);
assert(pq->head);
if (pq->head->next == NULL)
{
free(pq->head);
pq->head = NULL;
pq->tail = NULL;
}
else
{
QNode* temp = pq->head->next;
free(pq->head);
pq->head = temp;
}
pq->size--;
}
5. 获取队头元素
LQDataType QueueFront(Queue* pq)
{
assert(pq);
assert(pq->head);
return pq->head->val;
}
6. 获取队尾元素
LQDataType QueueBack(Queue* pq)
{
assert(pq);
assert(pq->tail);
return pq->tail->val;
}
7. 获取队列长度
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
8. 判空
bool IsEmptyQueue(Queue* pq)
{
assert(pq);
return pq->size == 0;
}
9. 销毁队列
void DestroyQueue(Queue* pq)
{
assert(pq);
while (pq->head)
{
DeQueue(pq);
}
}
注意:和栈一样,队列也不需要定义 print 函数,因为队列也不能遍历,要想取出队头后面的元素,我们必须先 pop 掉队头的元素,让该元素成为队头。
六、完整代码
1. Queue.h
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int LQDataType;
typedef struct QNode //队列结点
{
LQDataType val;
struct QNode* next;
}QNode;
typedef struct Queue //队列
{
QNode* head;
QNode* tail;
int size;
}Queue;
//初始化队列
void InitQueue(Queue* pq);
//入队
void EnQueue(Queue* pq, LQDataType x);
//出队
void DeQueue(Queue* pq);
//判空
bool IsEmptyQueue(Queue* pq);
//队列大小
int QueueSize(Queue* pq);
//返回队头元素
LQDataType QueueFront(Queue* pq);
//返回队尾元素
LQDataType QueueBack(Queue* pq);
//销毁队列
void DestroyQueue(Queue* pq);
2. Queue.c
#include"Queue.h"
void InitQueue(Queue* pq)
{
assert(pq);
pq->head = NULL;
pq->tail = NULL;
pq->size = 0;
}
void EnQueue(Queue* pq, LQDataType x)
{
assert(pq);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("EnQueue");
exit(-1);
}
newnode->next = NULL;
newnode->val = x;
if (pq->head == NULL)
{
pq->head = newnode;
pq->tail = newnode;
}
else
{
newnode->next = pq->tail->next;
pq->tail->next = newnode;
pq->tail = newnode;
}
pq->size++;
}
void DeQueue(Queue* pq)
{
assert(pq);
assert(pq->head);
if (pq->head->next == NULL)
{
free(pq->head);
pq->head = NULL;
pq->tail = NULL;
}
else
{
QNode* temp = pq->head->next;
free(pq->head);
pq->head = temp;
}
pq->size--;
}
bool IsEmptyQueue(Queue* pq)
{
assert(pq);
return pq->size == 0;
}
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
LQDataType QueueFront(Queue* pq)
{
assert(pq);
assert(pq->head);
return pq->head->val;
}
LQDataType QueueBack(Queue* pq)
{
assert(pq);
assert(pq->tail);
return pq->tail->val;
}
void DestroyQueue(Queue* pq)
{
assert(pq);
while (pq->head)
{
DeQueue(pq);
}
}
3. test.c
#include"Queue.h"
int main()
{
Queue Q;
InitQueue(&Q);
EnQueue(&Q, 1);
printf("%d\n", QueueBack(&Q));
EnQueue(&Q, 2);
printf("%d\n", QueueBack(&Q));
EnQueue(&Q, 3);
printf("%d\n", QueueBack(&Q));
EnQueue(&Q, 4);
printf("%d\n", QueueBack(&Q));
EnQueue(&Q, 5);
printf("%d\n", QueueBack(&Q));
printf("队列大小为:%d\n", QueueSize(&Q));
int n = 5;
while (n--)
{
printf("%d\n", QueueFront(&Q));
DeQueue(&Q);
}
printf("%d\n", IsEmptyQueue(&Q));
DestroyQueue(&Q);
return 0;
}