这篇文章讲解一下什么叫队列什么叫栈。
这两种结构往往并不新鲜,其实就是简化版的数组或者链表,但是确实这两种结构应用的范围应该比数组和链表的应用要广一点,至少对于我来说,实现一些复杂操作离不开这两种结构。
这个里面的操作很多都是前两个部分的继承,对数组和链表还不熟的请查看上面两篇文章。
队列结构
队列是所谓先进后出的一种特殊结构,回想当年Walker插队的时候,多么风光,这下没办法了,明令禁止不允许插队,Walker只能乖乖的到队尾,慢慢排呗,什么时候排到自己了,什么时候再出来。
计算机中实现队列可不是很容易,因为数组从末尾加进去还好,要是从队首出去,如果每个人都往前迈一步那岂不是很麻烦,这里介绍一种比较高效的方法,就是利用取模运算来实现。
如果你Wow了一下,我想我会非常骄傲,但是实际上,这种操作也比较常规,下面介绍一下我的写法。
#include<stdio.h>
int main()
{
int Queue[20] = {0};//还是一个数组
int back = 0;//队尾,永远指向下次应该添加的位置
int front = 0;//队首,永远指向下次应该删除的位置和最先访问的位置
//两个数相等,判断为队空
return 0;
}
队列的增删操作有些情形下也可以直接写成
//还是那句话,自己掂量着是否越界的问题
Queue[back] = 100;//直接在back下标位置添加
back++;//然后back右移一位
front++;//和数组的思想一致,front++,有效范围缩小,即删除操作
现在,我们设想这个队列每次在增删操作的时候,任何元素都不动,这样就导致了一个问题,如果我在不断的从后方入队,从前方出队,然后后方入队到了队尾,而前方却还有空余,从逻辑角度,这种状态并非队满,但是确确实实不能再从数组末尾添加了,怎么办呢?
按照常规逻辑,应该从数组首部进行添加,没有毛病,但是怎么实现呢。聪明的你可能想到用ifelse判断一下如果真的到了数组尾部就从数组首部添加,是个办法,但是你不感觉有点费劲么?
更简单的办法是利用取余这种运算的优势进行实现,设想一下,假设队列最大长度只有二十,那么,如果我每次运算都模除20,会是什么结果,假设back的值为19,然后模除20,结果还是19,也就是说,back的值如果是19即小于20的话,那么一定是真实值19,但是如果back的值是20呢,我不可能再从Queue[20]的位置添加数据了,但是我模除20,我惊异的发现,结果是零,正好就是队首,同理,front也是一个道理,这样,我可以不用管back的取值,只要在每次增删改查的时候都对back和front取模就可以解决这个问题。
Queue[back % 20] = 100;
back++;//仍旧back++,但是在取Queue种元素的时候进行取余的操作
front++;//同样的道理,删除操作也是对front++
Queue[front % 20] = 100;//取队首元素的时候,进行取余操作
总结一下,涉及到实际的数组操作的时候进行取余,中括号中取余,括号外照常加。
你可能会想过用取余来直接改变back和front的值,从而省去了存取时取余的操作,或许你最开始就是这么想的,我也是这么想的,但是到了后面就出现一个问题,当两个数一样大时,我是应该当队空呢,还是当队满呢(自己思考是不是这么个道理,队空的时候也是相等,队满的时候也是相等)?这涉及到一个数论上的知识叫做同余,你每次都将back和front改变成取余后的值,那么两个数实际差20时,因为取余操作,这个20被你算没了,你无法判断他俩是否真的相等,队空那是真相等,但是队满,两个数实际应该相差20。所以,我没有直接改变这两个数,而是选择了在数据存取的时候麻烦一些做了取余操作。
所以按照这个逻辑,这个队空时,两个数是相等的
队满时,两个数正好差了20,为了保险起见,写了大于等于
if(back == front)
printf("队空!\n");
if(back - front >= 20)
printf("队满!\n");
这就是队列的顺序存储的基本操作
现在我们讨论链式存储的操作,链表型的
链表操作相较于数组操作,新增的内容并不多,只是多了一个尾指针,别的还是链表的操作。
首先创建一个链表
typedef struct Node
{
int data;
struct Node* next;
}node, *pnode;
int main()
{
pnode LinkList = (pnode)malloc(sizeof(node));
LinkList->next = NULL;
LinkList->data = 0;
pnode toil = LinkList;//多了一个尾指针
return 0;
}
有了尾指针,增加操作就可以分几步来进行,其本质就是尾插法,插到队尾去。
pnode ptr = (pnode)malloc(sizeof(node));
ptr->data = 100;
ptr->next = NULL;
toil->next = ptr;
toil = toil->next;//尾插法五行
删除也是队首删除的办法
pnode ptr = LinkList->next;
LinkList->next = LinkList->next->next;
free(ptr);
访问自然就是直接写LinkList->next就好了呗。很简单。
栈结构
栈结构是所谓先进先出,就是好比穿衣服,只能从里往外穿,脱也要从外套先脱,如果觉得有点热,我想如果不是脑洞新奇的人都会先给外套脱掉吧。。
栈结构就相比的粗暴得多,既然是先进先出,那岂不正中数组下怀
#include<stdio.h>
int main()
{
int Stack[20] = {0};
int top = 0;//这就是一个栈
Stack[top] = 100;
top++;//这就是入栈
top--;//这就是出栈
Stack[top - 1] = 99;//注意这才是真正的栈顶元素。
return 0;
}
完了。。数组就这么实现。。简单粗暴
链表呢,链表也有这种轻便的操作,直接上代码
#include<stdio.h>
#include<stdlib.h>
typedef struct Node
{
int data;
struct Node* next;
}node, *pnode;
int main()
{
pnode LinkList = (pnode)malloc(sizeof(node));
LinkList->next = NULL;
LinkList->data = 0;//这就是一个链表
pnode ptr = (pnode)malloc(sizeof(node));
ptr->data = 100;
ptr->next = LinkList->next;
LinkList->next = ptr;//经典的头插法插入
ptr = LinkList->next;
LinkList->next = LinkList->next->next;
free(ptr);//删除元素也很经典
LinkList->next->data = 100;//这就是访问,也很简单
return 0;
}
当然了,栈和队列的实现相对方便,但是使用他们将是数据结构和算法的重要课题,这两种结构是递归转化为非递归程序的重要辅助工具。熟练使用它们是学算法的基础。
好了,对于线性结构的基础知识我已经介绍完了,欢迎大家批评指正。