哈喽大家好,我是野生的编程萌新,首先感谢大家的观看。数据结构的学习者大多有这样的想法:数据结构很重要,一定要学好,但数据结构比较抽象,有些算法理解起来很困难,学的很累。我想让大家知道的是:数据结构非常有趣,很多算法是智慧的结晶,我希望大家在学习数据结构的过程是一种愉悦的心情感受。因此我开创了《数据结构》专栏,在这里我将把数据结构内容以有趣易懂的方式展现给大家。
怎么给大家引入队列概念呢?有了,我们在中学学习化学时肯定接触过下面这个实验器材:

胶头滴管是我们在学习化学常使用的一种实验器材,胶头滴管用于精确地滴取液体。它通常由一个透明的玻璃或塑料瓶身和一个连接瓶身和滴液管的胶头组成。 我们使用时一般是将胶头浸入所需液体中,轻轻按压胶头,使其充满液体,将胶头滴管垂直放置到需要滴液的容器中,轻轻松开手指,让液体滴下。后进入胶头滴管的液体将会先滴出,先进入的胶头滴管的液体反而后滴出,像这样的例子反而可以引入数据结构——栈。
在我们软件应用中,栈这种后进先出的数据结构的应用是非常普遍的。比如我们用浏览器上网时不管什么浏览器都有一个“后退键”,当我们点击后可以按访问顺序逆序加载浏览过的网站。比如你正在浏览一篇介绍C语言相关内容的博客,你在看博主分享的代码,突然发现一个函数博主没详细介绍,你也不了解这个函数的功能,打开了CPlusPlus网站搜索它的功能,找到之后你又想返回去接着阅读,这时候单击左上角的“后退键”。如下图:

很多软件都有撤销操作,这也是使用栈来实现的,当然不同的软件具体实现代码会有很大差异,不过原理都是一样的。 栈是限定仅在表尾进行插入和删除操作的线性表。我们把允许插入和删除的一端称为栈顶,另一端称为栈底,不含任何数据元素的栈称为空栈,栈又称为先进后出(Last In First Out)的线性表,简称LIFO结构。首先我们要知道栈是一个线性表,也就是说,栈元素具有线性关系,即前驱后驱关系,只不过它是一种特殊的线性表,前面定义中提到了在线性表的表尾进行插入和删除操作,这里的表尾指得是栈顶,而不是栈底。它的特殊之处就在于限制了这个线性表的插入和删除位置,他始终只在栈顶进行,这也就使得:栈底是固定的,最先进入栈的只能在栈底。栈的插入操作,叫做进栈,也叫压栈、入栈。

栈的删除操作,叫做出栈,有的也叫弹线。

这个最先进栈的元素,是不是只能是最后出栈呢?答案是不一定的,要看什么情况。栈对线性表的插入和删除的位置进行了限制,并未对元素出栈的时间进行限制,也就是说,在不是所有的元素都进栈的情况下,事先进入栈的元素也可以出栈,只要保证是栈顶元素就行,举例来说,如果我们现在是3个整型数字1、2、3依次进栈,会有哪些出栈的次序呢?
有没有可能是3、1、2这样的次序呢?肯定是不会的,因为3先出栈的话,就意味着3曾经进过栈,既然3都进栈了,那么1和2也早就进栈了,此时2一定是在1的上面,更接近栈顶,那么出栈只可能是3、2、1,不然不满足1、2、3依次进栈的要求,所以不会出现1会比2先出栈的情况。下面给大家举两个简单的练习:
1.一个栈的初始状态为空。现将元素1、2、3、4、5、A、B、C、D、E依次入栈,然后再依次出栈,则元素出栈的顺序是( )。 A.12345ABCDE B.EDCBA54321 C.ABCDE12345 D.54321EDCBA 答案:B 2.若进栈序列为 1,2,3,4 ,进栈过程中可以出栈,则下列不可能的一个出栈序列是() A.1432 B.2341 C.3142 D.3421 答案:C
上面我们也提到了栈是一个特殊的线性表,所以线性表的顺序存储和链式存储对于栈来说,也同样适用。所以栈的实现一般可以使用数组或者链表实现,相对而言数组的结构实现更优一些:
所以在这里我们就是用数组来实现栈。我们先来定义一下栈的结构:
typedef int STDatatype;
typedef struct stack
{
STDatatype* a;
int top; //栈顶
int capacity;//容量
}ST;通过上面栈的结构的定义,可以看出来它和我们之前写的顺序表极其相似,栈的初始化和顺序表基本相同:函数接受一个指向栈对象的指针pst,并使用assert断言函数确保pst不为 NULL,以确保安全性。接下来,将栈对象pst的成员变量a设置为 NULL。这表示栈中的元素存储数组当前为空,因为还没有分配内存。将栈对象pst的成员变量capacity设置为 0,这表示栈的当前容量为0,还没有分配内存。那么top初始化也为0吗?很多的数据结构相关书籍在介绍栈的初始化的时候都将top初始化为-1,这是因为栈为空栈的时候top等于0,当栈中有一个元素的时候top也为0,因此通常把空栈的判定条件定位top等于-1。当然将top初始化为0也是可以的,top的意义会发生变化,这时候我们让top指向的时栈顶的下一个元素。大多数书上都是以-1来初始化,那我就用0来初始化。我们来实现一下栈的初始化函数:
void STInit(ST* pst)
{
assert(pst);
pst->a = NULL;
pst->capacity = 0;
pst->top = 0;
}栈的销毁是将栈占用的内存空间释放掉,栈的销毁具体操作步骤为:
我们来实现一下栈的销毁:
void STDestroy(ST* pst)
{
assert(pst);
free(pst->a);
pst->a = NULL;
pst->top = pst->capacity = 0;
}栈的入栈具体操作步骤为:
我们来实现一下入栈的操作:
void STPush(ST* pst, STDatatype x)
{
assert(pst);
if (pst->top == pst->capacity)
{
int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
ST* tmp = (ST*)realloc(pst->a, sizeof(ST));
if (tmp == NULL)
{
perror("realloc fail");
return -1;
}
pst->a = tmp;
pst->capacity = newcapacity;
}
pst->a[pst->top] = x;
pst->top++;
}栈的出栈具体操作步骤为:
我们来具体实现一下出栈的操作:
void STPop(ST* pst)
{
assert(pst);
assert(pst->top > 0);
pst->top--;
}我们在栈中能进行的操作不仅仅只有入栈和出栈两种操作,在这里再和大家介绍一些其他的操作。首先先来介绍获取栈顶元素操作,其具体操作步骤为:
我们来实现一下获取栈顶元素的函数:
STDatatype STTop(ST* pst)
{
assert(pst);
assert(pst->top > 0);
return pst->a[pst->top - 1];
}获取栈中元素的个数实现就简单了,直接返回top的值即可,我们来实现一下这个函数:
int STSize(ST* pst)
{
assert(pst);
return pst->top;
}判断栈是否为空我们只需要判断top是否为0即可,我们来实现一下 :
int STEmpty(ST* pst)
{
assert(pst);
return pst->top == 0;
}stack.h文件:
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int STDatatype;
typedef struct stack
{
STDatatype* a;
int top; //栈顶
int capacity;//容量
}ST;
void STInit(ST* pst);//栈的初始化
void STPush(ST* pst, STDatatype x);//栈的插入
void STPop(ST* pst);//栈的删除
STDatatype STTop(ST* pst);//获取栈顶元素
int STSize(ST* pst);//获取栈中有效的元素个数
int STEmpty(ST* pst);//检测栈是否为空
void STDestroy(ST* pst);//栈的销毁stack.c文件:
#include"stack.h"
void STInit(ST* pst)
{
assert(pst);
pst->a = NULL;
pst->capacity = 0;
pst->top = 0;
}
void STPush(ST* pst, STDatatype x)
{
assert(pst);
if (pst->top == pst->capacity)
{
int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
ST* tmp = (ST*)realloc(pst->a, sizeof(ST));
if (tmp == NULL)
{
perror("realloc fail");
return -1;
}
pst->a = tmp;
pst->capacity = newcapacity;
}
pst->a[pst->top] = x;
pst->top++;
}
void STPop(ST* pst)
{
assert(pst);
assert(pst->top > 0);
pst->top--;
}
STDatatype STTop(ST* pst)
{
assert(pst);
assert(pst->top > 0);
return pst->a[pst->top - 1];
}
int STSize(ST* pst)
{
assert(pst);
return pst->top;
}
int STEmpty(ST* pst)
{
assert(pst);
return pst->top == 0;
}
void STDestroy(ST* pst)
{
assert(pst);
free(pst->a);
pst->a = NULL;
pst->top = pst->capacity = 0;
}test.c文件(用来测试函数功能):
#include"stack.h"
void Test1()
{
ST s;
STInit(&s);
STPush(&s, 1);
STPush(&s, 2);
STPush(&s, 3);
STPush(&s, 4);
printf("%d ", STTop(&s));
STPop(&s);
printf("%d\n", STTop(&s));
STPop(&s);
STPush(&s, 4);
STPush(&s, 5);
while (!STEmpty(&s))
{
printf("%d ", STTop(&s));
STPop(&s);
}
printf("\n");
}
int main()
{
Test1();
return 0;
}
大家在使用电脑的时候都应该经历过这种情况:电脑有时候疑似死机状态,鼠标点击什么都好像没有效果,无论怎么点击文件都没动静,就当我们失去耐心的时候,想要将它重启的时候,它好像清醒过来一样,把你刚才的操作全部按顺序执行了一遍——疯狂弹窗。这是因为操作系统在当时CPU一时忙不过来,等前面的事情忙完之后,后面多个指令需要通过一个通道输出,按先后次序排队执行造成的结果。再比如我们在某个网红店铺进行网购时,我们想要询问客服时,客服人员相对于顾客是较少的,在所有客服都占线的情况下,客户会被要求等待,直到某个客服空下来,才能让和先等待的客户联系,这里也是因为对当前所有联系客服的客户进行了排队处理。
上面的例子中都是应用了一种数据结构来实现刚才提到的先进先出的排队功能,这就是队列。队列是只允许一端进行插入的操作,而在另一端进行删除操作的线性表。队列是一种先进先出(First In First Out)的线性表,简称FIFO,允许插入的一端称为队尾,允许删除的一端称为队头。下图中1就是这个队列的队头,4是这个队列的队尾。这样我们就可以删除时总是从队头开始,而插入时从队尾插入。这也符合我们的一个习惯:排在第一个优先出队列,最后来的排队尾。

队列和栈一样也是一种特殊的线性表,它一般也可以使用数组或者链表来实现,队列和栈不一样,它使用链表实现更优一些:
在这里我们使用链表来实现队列,我们先来定义一下队列的结构:
typedef struct QueueNode
{
QDataType data;
struct QueueNode* next;
}QNode;
typedef struct Queue
{
QNode* phead;
QNode* tail;
int size;
}Queue;在上面的定义中,我们首先定义了一个结构体QueueNode,用该结构体表示队列中的节点,它包含两个成员变量:
然后又定义了一个结构体Queue来表示队列本身,它包含三个成员变量:
我们初始化时只需要将队列的成员变量初始化为合适的初值就可以了,我们来实现一下队列的初始化:
void QueueInit(Queue* q)
{
assert(q);
q->phead = q->tail = NULL;
q->size = 0;
}队列销毁的操作具体步骤为:
我们来实现一下队列的销毁:
void QueueDestroy(Queue* q)
{
assert(q);
QNode* cur = q->phead;
while (cur)
{
QNode* next = cur->next;
free(cur);
cur = next;
}
q->phead = q->tail = NULL;
q->size = 0;
}使用上面的定义方法使我们在队列的操作更加方便快捷:
我们先来实现一下队列的入队操作:
void QueuePush(Queue* q, QDataType x)
{
assert(q);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloc fail");
return -1;
}
newnode->data = x;
newnode->next = NULL;
if (q->tail == NULL)
{
q->phead = q->tail = newnode;
}
else
{
q->tail->next = newnode;
q->tail = newnode;
}
q->size++;
}我们来实现一下队列的出队操作:
void QueuePop(Queue* q)
{
assert(q);
assert(q->phead);
QNode* cur = q->phead;
q->phead = q->phead->next;
free(cur);
cur = NULL;
if (q->phead == NULL)
q->tail = NULL;
q->size--;
}既然在上面提到了获取队列的大小,那我们就来实现一下:
int QueueSize(Queue* q)
{
assert(q);
return q->size;
}获取队列的队头元素的具体操作步骤为:
我们来实现一下获取队头元素的操作:
QDataType QueueFront(Queue* q)
{
assert(q);
assert(q->phead);
return q->phead->data;
}获取队尾元素操作的具体步骤为:
我们来实现一下获取队尾元素的操作:
QDataType QueueBack(Queue* q)
{
assert(q);
assert(q->tail);
return q->tail->data;
}Queue.h文件:
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int QDataType;
typedef struct QueueNode
{
QDataType data;
struct QueueNode* next;
}QNode;
typedef struct Queue
{
QNode* phead;
QNode* tail;
int size;
}Queue;
void QueueInit(Queue* q);//队列的初始化
void QueuePush(Queue* q, QDataType x);//队尾入队列
void QueuePop(Queue* q);//对头出队列
QDataType QueueFront(Queue* q);//获取队头元素
QDataType QueueBack(Queue* q);//获取队尾元素
int QueueSize(Queue* q);//获取队列中的元素个数
int QueueEmpty(Queue* q);//检测队列是否为空
void QueueDestroy(Queue* q);//队列的销毁Queue.c文件:
#include"Queue.h"
void QueueInit(Queue* q)
{
assert(q);
q->phead = q->tail = NULL;
q->size = 0;
}
void QueuePush(Queue* q, QDataType x)
{
assert(q);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloc fail");
return -1;
}
newnode->data = x;
newnode->next = NULL;
if (q->tail == NULL)
{
q->phead = q->tail = newnode;
}
else
{
q->tail->next = newnode;
q->tail = newnode;
}
q->size++;
}
void QueuePop(Queue* q)
{
assert(q);
assert(q->phead);
QNode* cur = q->phead;
q->phead = q->phead->next;
free(cur);
cur = NULL;
if (q->phead == NULL)
q->tail = NULL;
q->size--;
}
QDataType QueueFront(Queue* q)
{
assert(q);
assert(q->phead);
return q->phead->data;
}
QDataType QueueBack(Queue* q)
{
assert(q);
assert(q->tail);
return q->tail->data;
}
int QueueSize(Queue* q)
{
assert(q);
return q->size;
}
int QueueEmpty(Queue* q)
{
assert(q);
return q->phead == NULL;
}
void QueueDestroy(Queue* q)
{
assert(q);
QNode* cur = q->phead;
while (cur)
{
QNode* next = cur->next;
free(cur);
cur = next;
}
q->phead = q->tail = NULL;
q->size = 0;
}test.c文件(用来测试函数功能):
#include"Queue.h"
void Test1()
{
Queue q;
QueueInit(&q);
QueuePush(&q, 1);
QueuePush(&q, 2);
QueuePush(&q, 3);
printf("%d ", QueueFront(&q));
QueuePop(&q);
printf("%d ", QueueFront(&q));
QueuePop(&q);
printf("\n");
QueuePush(&q, 4);
QueuePush(&q, 5);
while (!QueueEmpty(&q))
{
printf("%d ", QueueFront(&q));
QueuePop(&q);
}
printf("\n");
QueueDestroy(&q);
}
int main()
{
Test1();
return 0;
}