数据结构之,栈与队列
数据结构之,栈与队列
- 1、栈
- 1.1、什么是“ 栈 ”
- 1.2、栈的实现
- 1.2.1、栈的定义
- 1.2.2、栈的初始化
- 1.2.3、栈的销毁
- 1.2.4、入栈
- 1.2.5、判断栈是否为空
- 1.2.6、出栈
- 1.2.7、取栈顶元素
- 1.2.8、获取栈中有效元素个数
- 2、队列
- 2.1、什么是队列
- 2.2、队列的实现
- 2.2.1、队列的定义
- 2.2.2、队列的初始化
- 2.2.3、入队列
- 2.2.4、判断队列是否为空
- 2.2.5、出队列
- 2.2.6、返回队首和队尾
- 2.2.7、返回节点个数
- 2.2.8、销毁队列
- 3、完整代码
1、栈
1.1、什么是“ 栈 ”
栈,是一种特殊的线性表。
栈,只能在该结构固定的一端,实现数据的添加,与删除。这一端,也叫做栈顶。对应的,另一端叫做栈底。
往栈中插入数据,叫做入栈 / 压栈 / 进栈。插入数据在栈顶。如图:

删除栈中的数据,叫做出栈。删除数据也在栈顶。如图:

栈中元素的改变,遵循后进先出( Last In First Out )的原则。
1.2、栈的实现
1.2.1、栈的定义
栈的元素,可以用类似于顺序表元素的结构体,也可以用类似于链表节点的结构体。
如果用类似链表的结构:
struct stack
{int data;struct stack* next;
};
我们在之前的学习中就知道,链表头删、头插的时间复杂度为O(1)。
那么在栈中,为了降低时间复杂度,我们只能把第一个栈元素,所在的一端,当作栈顶。这样一来,时间复杂度也为O(1)。
但是,如果要插入新的栈元素,每次就要申请一个栈元素(结构体)类型大小的空间。
如果用类似顺序表的结构:
struct stack
{int* arr;int size;int capacity;
};
结合之前所学,我们只能将数组的末端,作为栈顶,以降低时间复杂度( O(1) )。
然而,如果我们想在栈中插入数据,只需申请对应数据元素类型大小的空间。
随着要插入数据的不断增多,这种利用数组创建栈元素的方法,在申请次数和开辟空间大小上,将会比上一个结构更少。
所以,我们采用的创建栈的方法:
typedef int STADataType;
typedef struct stack
{STADataType* arr;int top; //栈顶位置int capacity; //最大容量
}stack;
1.2.2、栈的初始化
由于栈存在空间不够要扩容,导致栈本身(连同栈中的数组一起)(的地址)改变,那么我们像操作函数中,输入的是栈的地址。
代码演示:
void STAInit(stack* pst)
{assert(pst);pst->arr = NULL;pst->top = pst->capacity = 0;
}
1.2.3、栈的销毁
思路:
- 首先判断数组成员是否为
NULL,再释放空间 - 各项参数初始化
代码演示:
void STADestroy(stack* pst)
{if (pst->arr)free(pst->arr);pst->arr = NULL;pst->top = pst->capacity = 0;
}
1.2.4、入栈
已知栈顶为数组末尾。
思路:
- 判断指针有效性
- 判断是否需要扩容。如果需要:
- 重新确定最大容量
capacity:- 如果为
0,赋值一个初始值(比如4) - 如果不为0,2倍扩容
- 如果为
realloc()- 更改参数
- 重新确定最大容量
- 先赋值,后
top++。
代码实现:
void STAPush(stack* pst, STADataType val)
{assert(pst);if (pst->top == pst->capacity){int newcapacity = (capacity == 0) ? 4 : 2*capacity;STADataType* tmp = (STADataType*)realloc(newcapacity * sizeof(STADataType));if (tmp == NULL){perror("realloc failed\n");exit(1);}pst = tmp;pst->capacity = newcapacity;}pst->arr[pst->top++] = val;
}
1.2.5、判断栈是否为空
在出栈之前,我们有必要判断当前栈是否可以出栈,即判断栈是否为空。
判断的依据是top是否为0。这里我们借助布尔类型值,更方便理解。
代码演示:
#include<stdbool.h>
bool STAEmpty(stack* pst)
{assert(pst);return (pst->top == 0);
}
这里如果栈为空,返回ture;否则返回false。
1.2.6、出栈
思路:
- 判断栈是否为空
- 如果不为空,直接
top--就可以。这个位置还有有效值,无需担心,后续添加一个新元素就可以覆盖这个位置。
代码演示:
void STAPop(stack* pst)
{assert(!STAEmpty(pst));pst->top--;
}
1.2.7、取栈顶元素
很简单,就是返回top下标的前一个下标对应的元素:
STADataType STATop(stack* pst)
{assert(!STAEmpty(pst));return pst->arr[pst->top - 1];
}
1.2.8、获取栈中有效元素个数
就是返回top。
int STASize(stack* pst)
{assert(pst);return pst->top;
}
2、队列
提到了栈,就不得不提到队列。
2.1、什么是队列
队列,也是线性表的一种。
队列,只能在其一端插入数据,而在另一端删除数据。

插入数据的操作,为入队列,入队列的一端为队尾。
删除数据的操作,为出队列,出队列的一端为队头。
2.2、队列的实现
2.2.1、队列的定义
这时,我们又回到了类似的问题:
实现队列,是用数组呢,还是用链表呢?
从空间复杂度看,
如果我们使用数组,那么,入队列和出队列,必有一种操作,其空间复杂度为O(N)(遍历数组,挪数腾位),似乎不太理想。
而如果我们使用链表,只要我们能够实现快速找到队头节点,和队尾节点的方法,我们就能够打下空间复杂度:
typedef int QueueDataType;
typedef struct ListNode
{QueueDataType data;struct ListNode* next;
}ListNode;
typedef struct Queue
{ListNode* phead;//定位队头节点ListNode* ptail;//定位队尾节点int size; //统计节点个数
}Queue;
2.2.2、队列的初始化
代码演示:
void QueueInit(Queue* pq)
{assert(pq);pq->size = 0;pq->phead = pq->ptail = NULL;
}
2.2.3、入队列
入队列,有点像链表的尾插:
- 创建新节点
ptail后插入size++
只不过,我们还要另外判断一下,当前节点是否为空。
代码演示:
void QueuePush(Queue* pq, QueueDataType val)
{assert(pq);//创建新节点QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));if (newnode == NULL){perror("malloc failed\n");exit(1);}newnode->data = val;newnode->next = NULL;//判断节点是否为空if (pq->phead == NULL){pq->phead = pq->ptail = newnode;}else{pq->ptail = newnode;pq->ptail = pq->ptail->next;}//节点个数记得更改pq->size++;
}
2.2.4、判断队列是否为空
在出队列之前,有必要判断队列是否可以删除数据。
判断的依据是,phead是否为NULL,或者size是否为0:
bool QueueEmpty(Queue* pq)
{assert(pq);return pq->size == 0;
}
2.2.5、出队列
phead所在位置为队首。
先存下一个节点,然后释放,再更新、phead:
void QueuePop(Queue* pq)
{//判断是否可删assert(!QueueEmpty(pq));//判断是否只剩下一个节点if (pq->phead == pq->ptail){//此时,头尾指向同一个节点,就全部初始化free(pq->phead);pq->phead = pq->ptail = NULL;}else{//正常出队列QueueNode* next = pq->phead->next;free(pq->phead);pq->phead = next;}//节点个数记得更改pq->size--;
}
2.2.6、返回队首和队尾
首先也是要判断队列是否为空。
然后返回phead和ptail中对应的数据即可。
返回队首:
QueueDataType QueueHead(Queue* pq)
{assert(!QueueEmpty(pq));return pq->phead->data;
}
返回队尾:
QueueDataType QueueTail(Queue* pq)
{assert(!QueueEmpty(pq));return pq->ptail->data;
}
2.2.7、返回节点个数
这一步操作,回答了我们为什么要在队列中添加size成员。
即降低时间复杂度。
int QueueSize(Queue* pq)
{assert(pq);return pq->size;
}
2.2.8、销毁队列
这里的队列,由一个个节点组成。那么我们就可以利用之前销毁链表的做法:
- 存放下一个节点
- 释放当前节点
- 节点向前
代码演示:
void QueueDestroy(Queue* pq)
{assert(pq);//遍历QueueNode* pcur = pq->phead;while (pcur){QueueNode* next = pcur->next;free(pcur);pcur = pcur->next;}//出循环后,记得头尾指针及时置空pq->phead = pq->ptail = NULL;pq->size = 0;
}
3、完整代码
栈:
//stack.h
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<errno.h>
#include<stdbool.h>//定义栈
typedef int STADataType;
typedef struct stack
{STADataType* arr;int top; //定义栈顶的位置int capacity; //定义最大容量
}stack;//栈的初始化
void StackInit(stack* pst);//栈的销毁
void StackDestroy(stack* pst);//入栈
void StackPush(stack* pst, STADataType val);//判断栈是否为空
bool STAEmpty(stack* pst);//出栈
void StackPop(stack* pst);//取栈顶元素
STADataType STATop(stack* pst);//获取栈中有效元素个数
int STASize(stack* pst);
//stack.c
#include"stack.h"//栈的初始化
void StackInit(stack* pst)
{//判断assert(pst);//各个成员初始化pst->arr = NULL;pst->capacity = pst->top = 0;
}//栈的销毁
void StackDestroy(stack* pst)
{//首先判断数组成员是否为NULL,再释放if (pst->arr)free(pst->arr);pst->arr = NULL;pst->capacity = pst->top = 0;
}//入栈
void StackPush(stack* pst, STADataType val)
{//判断assert(pst);//首先判断空间够不够,不够,扩容//扩容前,还先判断capacity是否为0if (pst->top == pst->capacity){int newcapacity = (pst->capacity == 0) ? 4 : 2 * pst->capacity;STADataType* tmp = (STADataType*)realloc(pst->arr, newcapacity * sizeof(STADataType));//申请失败的判断if (tmp == NULL){perror("realloc falled\n");exit(1);}pst->arr = tmp;pst->capacity = newcapacity;}//放数pst->arr[pst->top++] = val;//先放数,后top++
}//判断栈是否为空
bool STAEmpty(stack* pst)
{assert(pst);return pst->top == 0;
}//出栈
void StackPop(stack* pst)
{//判断当前栈是否可删assert(!STAEmpty(pst));pst->top--;
}//取栈顶元素
STADataType STATop(stack* pst)
{assert(pst);return pst->arr[pst->top - 1];
}//获取栈中有效元素个数
int STASize(stack* pst)
{assert(pst);return pst->top;
}
//test.c
#include"stack.h"void test0()
{stack st;StackInit(&st);StackPush(&st, 1);StackPush(&st, 2);StackPush(&st, 3);StackPush(&st, 4);StackPush(&st, 5);//StackPop(&st);//printf("%d\n", STATop(&st));//printf("%d\n", STASize(&st));
}int main()
{test0();return 0;
}
队列:
//Queue.h
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>//利用节点创建队列,就定义节点与队列
typedef int QueueDataType;
typedef struct QueueNode
{QueueDataType data;struct QueueNode* next;
}QueueNode;
typedef struct Queue
{QueueNode* phead;QueueNode* ptail;int size;
}Queue;//队列初始化
void QueueInit(Queue* pq);//入队列
void QueuePush(Queue* pq, QueueDataType val);//判断队列是否为空
bool QueueEmpty(Queue* pq);//出队列
void QueuePop(Queue* pq);//获取队首元素
QueueDataType QueueHead(Queue* pq);//获取队尾元素
QueueDataType QueueTail(Queue* pq);//返回队列有效节点个数
int QueueSize(Queue* pq);//销毁队列
void QueueDestroy(Queue* pq);
//Queue.c
#include"Queue.h"//队列初始化
void QueueInit(Queue* pq)
{//判断assert(pq);//置空pq->phead = pq->ptail = NULL;pq->size = 0;
}//入队列
void QueuePush(Queue* pq, QueueDataType val)
{//判断assert(pq);//创建新节点,节点在堆上申请QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));//判断是否申请失败if (newnode == NULL){perror("malloc failed\n");exit(1);}newnode->data = val;newnode->next = NULL;//入队列之前,判断队列是否为空if (pq->phead == NULL){//为空,注意头尾和一pq->phead = pq->ptail = newnode;}else {//否则,正常入队列pq->ptail->next = newnode;pq->ptail = pq->ptail->next;}pq->size++;
}//判断队列是否为空
bool QueueEmpty(Queue* pq)
{assert(pq);return pq->phead == NULL;
}//出队列
void QueuePop(Queue* pq)
{//首先判断能不能出队列assert(!QueueEmpty(pq));//如果能//先判断是否只剩下一个节点if (pq->phead == pq->ptail){//直接释放,置空free(pq->phead);pq->phead = pq->ptail = NULL;}else{//先存下第一个节点的下一个节点QueueNode* next = pq->phead->next;//再释放free(pq->phead);//最后,更新第一个节点pq->phead = next;}pq->size--;
}//获取队首元素
QueueDataType QueueHead(Queue* pq)
{//首先判断队列是否为空assert(!QueueEmpty(pq));//然后返回return pq->phead->data;
}//获取队尾元素
QueueDataType QueueTail(Queue* pq)
{//首先判断队列是否为空assert(!QueueEmpty(pq));//然后返回return pq->ptail->data;
}//返回队列有效节点个数
int QueueSize(Queue* pq)
{assert(pq);//可以直接遍历,但是时间复杂度较大//可以在队列的定义中,加入统计节点个数的变量size,每次操作过后再变化return pq->size;
}//销毁队列
void QueueDestroy(Queue* pq)
{assert(pq);//遍历,存下一个节点,释放前一个结点QueueNode* pcur = pq->phead;while (pcur){QueueNode* next = pcur->next;free(pcur);pcur = next;}//野指针置空pq->phead = pq->ptail = NULL;pq->size = 0;
}
//test.c
#include"Queue.h"int main()
{Queue queue;QueueInit(&queue);QueuePush(&queue, 1);QueuePush(&queue, 2);QueuePush(&queue, 3);QueuePush(&queue, 4);QueuePop(&queue);//QueuePop(&queue);//QueuePop(&queue);//QueuePop(&queue);//QueuePop(&queue);printf("%d\n", QueueHead(&queue));printf("%d\n", QueueTail(&queue));printf("%d\n", QueueSize(&queue));QueueDestroy(&queue);return 0;
}
