目录
1.栈
1.1栈的概念以及结构:
1.2栈的实现
栈的初始化:
栈的销毁:
入栈:
获取栈顶数据:
判空:
获取元素的个数:
2.队列
2.1队列的概念及其结构
2.2队列的实现
队列的初始化
队列的销毁
入队
出队
获取队头数据
获取队尾数据
获取队列元素个数
判空
我们之前讲过了顺序表和链表,今天我们就来学习新的数据结构:栈和队列。
1.栈
1.1栈的概念以及结构:
栈:栈是一种特殊的线性表,它只允许在固定的一端进行数据的插入和删除。进行数据插入和删除的一端叫做栈顶,另一端叫做栈底。也就是说栈中的数据遵循后进先出的规则。
压栈:栈的插入操作叫做进栈/入栈/压栈,新插入的数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据也在栈顶。
就像枪械的弹匣一样:
先装进去的子弹被后来装进去的子弹压进去了下端,射击时也是后装进的子弹先发射出去,最先装进去的子弹最后出去。跟数据进栈出栈一模一样。
上图模拟实现了栈进出数据的大致过程。
进栈示意图:
出栈示意图:
了解了进栈出栈的过程后,我们接下来就开始进行栈的实现。
1.2栈的实现
栈的实现一般来说可以使用数组或者链表,相对来说用数组实现更加好一些。
因为数组可以直接通过下标来访问数组中的各个位置的数据,而链表的话代价就比较大一些,因为链表不能通过下标访问元素,只能通过遍历链表通过节点的next或prev指针来找下一个或者上一个节点。
所以我们这次就通过数组来实现栈。
话不多说,我们开始写代码:
老样子,我们创建三个文件,一个头文件:Stack.h,两个源文件:Stack.c和test.c。
我们先来看一下头文件代码:
我们大致要实现以上几个功能。
top是栈顶元素的位置或索引,可以通过top快速找到栈顶的元素。
capacity表示栈的容量。
栈的初始化:
我们初始化把top赋值为0,此时top就相当于顺序表中的size,表示有效数据的个数,top=0表示栈中没有元素。这里要注意一下。
栈的销毁:
代码比较简单,不在赘述。
入栈:
入栈检查是否需要扩容跟我们顺序表扩容一模一样,因为栈和顺序表都是通过数组来实现的,所以说代码会十分相似。这里的注意事项我们都在顺序表中讲过。
出栈:
只有短短几行代码,注意的是要断言一下保证top必须大于0,因为top等于0表示为空栈,此时不能再进行数据的删除。
获取栈顶数据:
我们上面讲过,初始化时top赋值为0这种方法top此时就相当于顺序表的size,指向栈顶位置的下一个元素,所以要找栈顶元素,下标为top-1,示意图如下:
判空:
这是一个比较巧妙的方法,pst->top==0为真,表示真,返回true;
pst->top不为0,表示假(即不是空栈),返回false。
获取元素的个数:
代码也是很简单,top和size类似,表示的就是有效数据的个数。
2.队列
2.1队列的概念及其结构
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的一种特殊的线性表。队列具有先进先出的特点。出队列:进行删除操作的一端称为对头,入队列:进行插入操作的一端称为队尾。
2.2队列的实现
我们通过上面的学习,知道了栈可以通过数据和链表来实现,通过数组实现栈效率更加高一些。
对于队列来说,也可以通过数组和链表来实现,但队列通过链表实现的效率更高一些,原因大致有:
1.插入和删除数据效率更高:链表队列插入删除时间复杂度为O(1),而数组队列插入删除数据时间复杂度为O(n)。
2.插入删除不用移动:由于链表队列不需要像数组队列那样在插入和删除操作时移动其他元素,因此在大部分情况下,链表队列的性能更稳定,不会因为元素数量的增加而导致操作时间的增加。
所以,我们接下来就用链表来实现队列。
我们还是创建3个文件,我们先来看头文件Queue.h的代码:
我们创建了两个结构体,QNode结构体表示链表队列的节点,而结构体Queue又有什么用呢?
假设我们没有结构体Queue,那么我们在实现入队出队函数时就要传二级指针了,(因为我们要修改一级指针phead的值,所以我们要进行传值调用,所以就要传一级指针的地址,拿二级指针接收)创建哨兵位不就可以传一级指针了吗?没错,但是插入数据尾指针该怎么办呢?我们需要维护尾指针的指向,还是比较麻烦。
所以我们就创建了Queue结构体,将相关的数据和操作组织在一起,提高代码的可读性和可维护性。构体 Queue
包含了队列的头指针 phead
、尾指针 ptail
和大小 size
,这些是队列操作所必需的基本元素
有点面向对象编程的意思了。
队列的初始化
代码如下:
参数为Queue结构体指针。没什么好讲的。
队列的销毁
这个和我们之前讲过的链表的销毁大同小异,需要注意的是最后千万不要free(pq),因为经过上面的while循环,phead和ptail指向的动态开辟的内存都已经释放掉了,再次释放会报错,这我们以前在动态内存管理中讲过。
入队
队列遵循先进先出,所以入队实际上就是头插数据,这个我们讲单链表讲过,不再赘述。
出队
出队列相当就是尾删。
获取队头数据
注意断言pq->phead,防止phead指向空,引发对空指针解引用的错误操作。
获取队尾数据
同时注意一下断言,比较简单。
获取队列元素个数
这就体现了我们在创建Queue结构体时,创建size成员变量的作用,大大的简化了我们的代码。
判空
思路和我们上面讲的栈的判空一模一样,不再赘述。
至此,我们学习并实现了栈和队列,那么肯定有小伙伴要问:学这个有什么用呢?下篇文章,我们就来讲解如何用栈和队列解决问题。希望大家能有所收获。