3.1 栈
3.1.1 基本概念
栈(Stack)是一种逻辑结构,是特殊的线性表。
- 只能在固定的一端进行插入或删除的操作的线性表
碗不能从下端抽出
只要满足上述条件,那么这种特殊的线性表就会呈现一种“后进先出”的逻辑,这种逻辑就被称为栈。栈在生活中到处可见,比如堆叠的盘子、电梯中的人们、嵌套函数的参数等等。
由于约定了只能在线性表固定的一端进行操作,于是给栈这种特殊的线性表的“插入”、“删除”,另起了下面这些特定的名称:
- 栈顶:可以进行插入删除的一端
- 栈底:栈顶的对端
- 入栈:将节点插入栈顶之上,也称为压栈,函数名通常为push()
- 出栈:将节点从栈顶剔除,也称为弹栈,函数名通常为pop()
- 取栈顶:取得栈顶元素,但不出栈,函数名通常为top()
基于这种固定一端操作的简单约定,栈获得了“后进先出(LIFO)”的基本特性,如下图所示,最后一个放入的元素,最先被拿出来:
3.1.2 栈的存储形式
一般有两种存储方式:
- 顺序栈
- 链式栈
3.1.3 栈的基本实现
链式栈的组织形式与链表无异,只不过插入删除被约束在固定的一端。为了便于操作,通常也会创建所谓管理结构体,用来存储栈顶指针、栈元素个数等信息:
定义结构体代码如下:
//栈结点
typedef struct stack{
int data;//数据域
struct stack *next;//指针域
}stack;
// 链式栈管理结构体
typedef struct
{
stack *top; // 链式栈栈顶指针
int size; // 链式栈当前节点个数
}linkStack;
栈的初始化:构造一个空栈S
//生成管理栈结构体
linkStack *init_linkStack(void)
{
linkStack *q = (linkStack *)malloc(sizeof(linkStack));
if(q == NULL)
{
printf("malloc fail\n");
return NULL;
}
q->top = NULL;
q->size = 0;
return q;
}
//栈节点初始化
stack *init_stack(int data)
{
stack *new = (stack *)malloc(sizeof(stack));//结构体指针变量
if(new == NULL){
printf("malloc fail\n");
return NULL;
}
new->data = data;
new->next = NULL;
//返回开辟节点空间的首地址
return new;
}
压栈:若栈未满,则将x加入使之成为新栈顶。(增)
//压栈
int push(linkStack *s,stack *new)
{
if(s->top == NULL){
s->top = new;
s->size++;
}
else{
new->next = s->top;
s->top = new;
s->size++;
}
return 0;
}
出栈:若栈非空,则弹出栈顶元素,并将栈顶元素返回
//出栈,每调用一次,出一个数据
int pop(linkStack *s, int *pdata)
{
stack *p;
if(s->size == 0 && s->top == NULL){
return -1;
}
else{
*pdata = s->top->data;//先存储栈顶地址
p = s->top;//更新栈顶
s->top = s->top->next;
s->size--;
free(p);//释放节点
return 0;
}
}
注意:当栈顶元素被清空后,此时栈为空栈
3.1.4 栈的要点
合法出栈顺序有:
e、d、c、b、a
b、e、d、c、a
3.2 队列
3.2.1 基本概念
队列(Queue)是只允许在一端进行插入,在另一端删除的线性表,特点是“先进先出(FIFO)”
由于约定了只能在线性表固定的两端进行操作,于是给队列这种特殊的线性表的插入删除,起个特殊的名称:
- 队头:可以删除节点的一端
- 队尾:可以插入节点的一端
- 入队:将节点插入到队尾之后
- 出队:将队头节点从队列中剔除
- 取队头:取得队头元素,但不出队
出队之后:
动画如下所示:
3.2.2 队列的存储方式
跟平常的线性表一样,分为:
- 顺序队列
- 链式队列
3.2.3 队列的实现
定义结构体:
typedef struct queue_node {
QueueDataType data;
struct queue_node* next;
} QueueNode;
typedef struct queue {
QueueNode* front;
QueueNode* rear;
} Queue;
初始化队列:构造一个空队列。
void initQueue(Queue* q) {
q->front = q->rear = NULL;
}
判断队列是否为空:
int isEmptyQueue(Queue q) {
return q.front == NULL;
}
入队:若队列未满,将x加入,使之成为新的队尾。
void enqueue(Queue* q, QueueDataType data) {
QueueNode* newNode = (QueueNode*)malloc(sizeof(QueueNode));
newNode->data = data;
newNode->next = NULL;
if (isEmptyQueue(*q)) {
q->front = q->rear = newNode;
} else {
q->rear->next = newNode;
q->rear = newNode;
}
}
出队:若队列非空,删除对头元素,将数据返回。
QueueDataType dequeue(Queue* q) {
if (isEmptyQueue(*q)) {
return NULL;
}
QueueNode* oldFront = q->front;
QueueDataType data = oldFront->data;
q->front = oldFront->next;
if (q->front == NULL) {
q->rear = NULL;
}
free(oldFront);
return data;
}
销毁队列:
void clearQueue(Queue* q) {
while (!isEmptyQueue(*q)) {
dequeue(q);
}
}