今天我们来完成栈和队列,首先我们要明白什么是栈,什么是队列。
目录
我们之前写过顺序表,链表,这都是数据结构,而栈和队列,也是数据结构,栈的特殊地方在于,栈是后进先出,也叫LIFO(last in first out),比如我们平时在桌子上堆放的书,我们堆放了十本书,如果不直接把书抽出来的话,我们要拿到最下面那本书,需要把上面九本先拿下去,也就是先要从最上面的那一本开始拿,而最上面的那一本,是我们最后放上去的,这就是一个栈。再说队列,队列的特殊地方在于先进先出,也叫FIFO(first in first out),比如我们日常生活里的排队就是一个队列,不考虑插队这些,正常情况下,第一个来的人一定第一个完成自己的业务,最后一个来的人最后完成,这就是一个队列。
我们还是采取工程化的写法。
首先我们来完成栈,栈的实现可以使用数组或者链表,那我们选取哪个比较好呢?
我们来比较一下二者的优缺点。
首先我们看数组: 使用数组就相当于之前顺序表的尾插和尾删,用尾去做栈顶,非常合适,唯一的缺点是空间不足时需要扩容。完美避开了顺序表插入删除时需要挪动数据的缺点。
我们再来看链表,链表又有多种结构,我们介绍一种使用单链表来完成的,插入和删除都只有O(1)。
使用这种结构即可完成,最初栈顶和栈底都为空,插入第一个元素后,栈顶栈底都指向第一个元素,每次插入新元素后,只需将其赋给栈顶,删除时,先删除节点,再把栈顶指向栈顶的next即可。
如果用尾做栈顶,那么使用双向链表最好,如果使用头做栈顶,那么单链表最好,插入删除都是O(1)。
知道了两种结构完成栈的优缺点,大家会选择哪一个呢?我们这里选择数组来完成栈,因为使用数组的话各方面效率都会更优,这里知识和预加载有关,这里就不多介绍,我们举个例子,使用数组和链表的区别,学生时代家里每个月都会给你打生活费,数组就像是一个月给你打一次生活费,够你一个月花,链表就像每天给你打一次,很明显,使用数组要比链表效率高。
决定好了用什么来实现栈,接着我们来完成栈的结构和各类接口
- typedef int STDataType;
-
- typedef struct Stack {
- STDataType* a;
- int top;
- int capacity;
- }ST;
我们将其写为动态栈,即a不直接写为数组,不然就写死了,并且我们使用top来记录栈顶位置
接着我们来完成栈的初始化
- void StackInit(ST* ps)//初始化
- {
- assert(ps);
- ps->a = (STDataType*)malloc(sizeof(STDataType) * 4);
- if (ps->a == NULL) {
- printf("malloc fail\n");
- exit(-1);
- }
- ps->capacity = 4;
- ps->top = 0;
- }
这里的top选取0还是-1看自己喜欢,选取-1代表的是top就是当前栈顶元素,选取0表示top为栈顶元素的后一位,我这里选择初始赋0,同时,如果初始化失败,我们输出提示并结束程序
- void StackDestory(ST* ps)//销毁
- {
- assert(ps);
- free(ps->a);
- ps->a = NULL;
- ps->top = ps->capacity = 0;
- }
- void StackPush(ST* ps, STDataType x)//入栈
- {
- assert(ps);
- if (ps->top == ps->capacity) {//满了扩容
- STDataType* tmp= (STDataType*)realloc(ps->a,ps->capacity*2*sizeof(STDataType));
- if (tmp == NULL) {
- printf("realloc fail\n");
- exit(-1);
- }
- else {
- ps->a = tmp;
- ps->capacity *= 2;
- }
- }
- ps->a[ps->top] = x;
- ps->top++;
- }
入栈时我们要判断栈是否满了,满了需要扩容,扩容时我们扩容到原来的二倍,并且要判断是否扩容失败,失败的话我们给出提示并且结束程序,入栈后要记得给top+1
- void StackPop(ST* ps)//出栈
- {
- assert(ps);
- assert(ps->top>0);
- ps->top--;
- }
出栈我们直接使top-1,有人可能会先让栈顶元素置为0,但是这句是不需要的,因为入栈元素有时候本身就可能为0,并且我们不能随意出栈,比如top为0时,即空栈,所以我们加一句断言
- STDataType StackTop(ST* ps)//返回栈顶元素
- {
- assert(ps);
- assert(ps->top > 0);
- return ps->a[ps->top - 1];
- }
和出栈同理,栈为空时不能返回,我们加一句断言
- void StackSize(ST* ps)//计算数据个数
- {
- assert(ps);
- return ps->top;
- }
- bool StackEmpty(ST* ps)//判断是否为空
- {
- assert(ps);
- return ps->top == 0;
- }
我们直接return ps->top==0,使用bool类型,如果top==0,为真,返回true,否则返回false
我们来测试一下我们的代码
可以看到,没有问题。
完成了栈,我们接着来看队列
和栈一样,队列也需要在数组和链表里选择一个,队列只允许在它的一端插入数据,在另一端删除数据
这次其实我们要选择链表,因为数组会出现假溢出现象,比如这个数组可以放5个元素,现在已经放了四个,我们出队列一个元素,再入队列一个,就会发现数组下标0的位置是空的,但队列却显示已满,如果要挪动数据的话也非常麻烦,还有一种办法就是写成循环队列,但那是后续的事情,而且也有局限性。选取链表的话,用单链表就可以解决,采用上图结构,队列就只需要尾插和头删,效率都很高。
选择好了用什么来实现队列后,我们来设计队列的结构吧
- typedef int QDataType;
-
- typedef struct QueueNode
- {
- struct QueueNode* next;
- QDataType data;
- }QNode;
-
- typedef struct Queue
- {
- QNode* head;
- QNode* tail;
- }Queue;
我们设计好队列的节点,然后采取两个指针,队头和队尾来设计队列即可。而且采用这样的结构可以避免使用二级指针(后续接口如果参数传QNode的话是需要二级指针的)
- void QueuePush(Queue* pq, QDataType x)//入队列
- {
- assert(pq);
- QNode* newnode = (QNode*)malloc(sizeof(QNode));
- if (newnode == NULL) {
- printf("malloc fail\n");
- 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;
- }
- }
入队列是在队尾入元素,入队列我们需要判断队列是否为空,入队列需要一个新节点,我们给节点申请一个空间,申请失败我们输出提示并且报错,然后将x赋给节点的data,节点的next置为空,我们还要判断队列是否有节点,如果一个没有,那么队头和队尾都指向这个新节点,否则队尾的next指向新节点,再把队尾指向新节点
- void QueuePop(Queue* pq)//出队列
- {
- assert(pq);
- assert(pq->head);
- if (pq->head->next == NULL) {
- free(pq->head);
- pq->head = pq->tail = NULL;
- }
- else {
- Queue* next = pq->head->next;
- free(pq->head);
- pq->head = next;
- }
- }
出队列是出队头元素,我们要进行断言,我们出队列需要把头节点的空间释放,然后让head指向它的next,但在释放前我们需要把head的next保存起来,否则我们就找不到next了,这是正常情况,如果队列只有一个元素,出队列后会造成tail的野指针现象,所以我们需要额外判断,所以我们把出队列分为队列有一个元素和多个元素的情况
- bool QueueEmpty(Queue* pq)//判断是否为空
- {
- assert(pq);
- return pq->head == NULL;
- }
我们直接返回该判断语句即可,为空返回true,否则返回false
- QDataType QueueFront(Queue* pq)//取队头元素
- {
- assert(pq);
- assert(pq->head);
- return pq->head->data;
- }
我们直接返回队头元素即可,最好再加一句断言,有时候会很有用,比如初始化后,队列里没有元素,这种情况没有第二句断言我们就不知道程序哪里出现了错误
- QDataType QueueBack(Queue* pq)//取队尾元素
- {
- assert(pq);
- assert(pq->head);
- return pq->tail->data;
- }
取队尾元素与取队头元素同理
- int QueueSize(Queue* pq)//计算数据个数
- {
- assert(pq);
- int size = 0;
- QNode* cur = pq->head;
- while (cur) {
- cur = cur->next;
- size++;
- }
- return size;
- }
这个接口有人可能会把循环条件写成cur!=pq->tail,但这样是错误的,会少计算一个元素
- void QueueDestory(Queue* pq)//销毁
- {
- assert(pq);
- QNode* cur = pq->head;
- while (cur) {
- QNode* next = cur->next;
- free(cur);
- cur = next;
- }
- pq->head = pq->tail = NULL;
- }
销毁需要注意先要保存cur的下一个,不然会找不到,然后就是最后要把head和tail置为空
我们最后再来测试一下代码
没有问题,到这里,我们的栈和队列以及他们的接口就全部实现了,希望大家可以有所收获,下面我会附上全部代码
- #pragma once
- //Stack.h
- #include
- #include
- #include
- #include
- typedef int STDataType;
-
- typedef struct Stack {
- STDataType* a;
- int top;
- int capacity;
- }ST;
-
- void StackInit(ST* ps);//初始化
- void StackDestory(ST* ps);//销毁
- void StackPush(ST* ps,STDataType x);//入栈
- void StackPop(ST* ps);//出栈
- STDataType StackTop(ST* ps);//返回栈顶元素
- void StackSize(ST* ps);//计算数据个数
- bool StackEmpty(ST* ps);//判断是否为空
- //Stack.c
- #include "Stack.h"
- void StackInit(ST* ps)//初始化
- {
- assert(ps);
- ps->a = (STDataType*)malloc(sizeof(STDataType) * 4);
- if (ps->a == NULL) {
- printf("malloc fail\n");
- exit(-1);
- }
- ps->capacity = 4;
- ps->top = 0;
- }
- void StackDestory(ST* ps)//销毁
- {
- assert(ps);
- free(ps->a);
- ps->a = NULL;
- ps->top = ps->capacity = 0;
- }
- void StackPush(ST* ps, STDataType x)//入栈
- {
- assert(ps);
- if (ps->top == ps->capacity) {//满了扩容
- STDataType* tmp= (STDataType*)realloc(ps->a,ps->capacity*2*sizeof(STDataType));
- if (tmp == NULL) {
- printf("realloc fail\n");
- exit(-1);
- }
- else {
- ps->a = tmp;
- ps->capacity *= 2;
- }
- }
- ps->a[ps->top] = x;
- ps->top++;
- }
- void StackPop(ST* ps)//出栈
- {
- assert(ps);
- assert(ps->top>0);
- ps->top--;
- }
- STDataType StackTop(ST* ps)//返回栈顶元素
- {
- assert(ps);
- assert(ps->top > 0);
- return ps->a[ps->top - 1];
- }
- void StackSize(ST* ps)//计算数据个数
- {
- assert(ps);
- return ps->top;
- }
- bool StackEmpty(ST* ps)//判断是否为空
- {
- assert(ps);
- return ps->top == 0;
- }
- #pragma once
- //Queue.h
- #include
- #include
- #include
- #include
- typedef int QDataType;
-
- typedef struct QueueNode
- {
- struct QueueNode* next;
- QDataType data;
- }QNode;
-
- typedef struct Queue
- {
- QNode* head;
- QNode* tail;
- }Queue;
-
- void QueueInit(Queue* pq);//初始化
- void QueueDestory(Queue* pq);//销毁
- void QueuePush(Queue* pq, QDataType x);//入队列
- void QueuePop(Queue* pq);//出队列
- QDataType QueueFront(Queue* pq);//取队头元素
- QDataType QueueBack(Queue* pq);//取队尾元素
- int QueueSize(Queue* pq);//取数据个数
- bool QueueEmpty(Queue* pq);//判断是否为空
- //Queue.c
- #include"Queue.h"
-
- void QueueInit(Queue* pq)//初始化
- {
- assert(pq);
- pq->head = pq->tail = NULL;
- }
- void QueueDestory(Queue* pq)//销毁
- {
- assert(pq);
- QNode* cur = pq->head;
- while (cur) {
- QNode* next = cur->next;
- free(cur);
- cur = next;
- }
- pq->head = pq->tail = NULL;
- }
- void QueuePush(Queue* pq, QDataType x)//入队列
- {
- assert(pq);
- QNode* newnode = (QNode*)malloc(sizeof(QNode));
- if (newnode == NULL) {
- printf("malloc fail\n");
- 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;
- }
- }
- void QueuePop(Queue* pq)//出队列
- {
- assert(pq);
- assert(pq->head);
- if (pq->head->next == NULL) {
- free(pq->head);
- pq->head = pq->tail = NULL;
- }
- else {
- Queue* next = pq->head->next;
- free(pq->head);
- pq->head = next;
- }
- }
- QDataType QueueFront(Queue* pq)//取队头元素
- {
- assert(pq);
- assert(pq->head);
- return pq->head->data;
- }
- QDataType QueueBack(Queue* pq)//取队尾元素
- {
- assert(pq);
- assert(pq->head);
- return pq->tail->data;
- }
- int QueueSize(Queue* pq)//计算数据个数
- {
- assert(pq);
- int size = 0;
- QNode* cur = pq->head;
- while (cur) {
- cur = cur->next;
- size++;
- }
- return size;
- }
- bool QueueEmpty(Queue* pq)//判断是否为空
- {
- assert(pq);
- return pq->head == NULL;
- }
- //test.c
- #include "Stack.h"
- #include"Queue.h"
- void testStack() {
- ST st;
- StackInit(&st);
- StackPush(&st, 1);
- StackPush(&st, 2);
- StackPush(&st, 3);
- StackPush(&st, 4);
- while (!StackEmpty(&st)) {
- printf("%d ", StackTop(&st));
- StackPop(&st);
- }
-
- StackDestory(&st);
- }
-
- void testQueue() {
- Queue q;
- QueueInit(&q);
- QueuePush(&q, 1);
- QueuePush(&q, 2);
- QueuePush(&q, 3);
- QueuePush(&q, 4);
- while (!QueueEmpty(&q)) {
- printf("%d ", QueueFront(&q));
- QueuePop(&q);
- }
- QueueDestory(&q);
- }
- int main() {
- //testStack();
- testQueue();
- return 0;
- }
如有错误,还请指正。