栈和队列
栈和队列本身作为特殊的线性表,要记住他俩本身就费劲。难受的是他俩还能分别考虑顺序结构和链式结构,很复杂,容易混淆。
其实比起FILO(先进后出)和FIFO(先进先出)等特点,更重要的是对指针的把握。进出顺序是在逻辑层面的,只要理解就行,难得是如何用指针来表示这种特点,于是我就此方面进行个总结。
顺序栈
虽然栈只需要对一段进行操作(栈顶),但我们除了栈顶指针(top)外,还需要设置栈底指针(base),主要是为了用于判断栈空栈满。
s.top == s.base; //当栈顶指针等于栈底指针时,栈为空
s.top - s.base == MAXSIZE; //当栈顶指针与栈顶指针的差为栈的大小时,栈满
由于栈顶指针永远指向栈顶元素的上面一个元素,所以栈顶指针并不指向栈顶元素,大部分时候是指向一个空的元素。当栈满时,栈顶指针将指向栈外。
要注意的是,入栈时先赋值再让栈顶指针加一;而出栈则相反,先让栈顶指针减一,再赋值。
括号匹配的问题是一个典型的栈问题,于是就对其进行了思路整理:括号匹配
链栈
由于栈只需要对一端(栈顶)进行操作,所以不需要设置头结点。
于是只需要一个头指针指向首元结点,然后对头指针进行操作即可,注意delete。
顺序队列(循环队列)
不同于栈,队列需要对两头进行操作,因此头尾指针不可缺。但这里的指针并不是指针变量,而是整形。循环队列用下标表示每个元素,因此这里的指针指的是对下标的记录。这里仍用“指针指向”表示“下标表示”。
由于FIFO的限制,入队会持续朝队列的顶端增进,而出队则导致队列的尾端空缺,而又不能将其空间重新利用。于是顺序栈会出现“假溢出”情况,即虽然栈的空间没满,但会导致非法访问。
因此采取循环队列的方式,但此时我们要额外空出一个空间作为标志空间。
q.rear = (q.rear + 1) % MAXSIZE; //取余后(q.rear+1)的值为0~MAXSIZE-1。当q.rear大于最大空间时,会回到队头。
q.front == q.rear; //队列空依然是对头等于队尾
(q.rear + 1) % MAXSIZE == q.front; //满足此条件队列为满。取余后(q.rear+1)的值为0~MAXSIZE-1
标志空间
标志空间的设置实际上是为了让队列的各种状态能有唯一的表示。如果没有标志空间,当队列为满时,队头指针和队尾指针指向同一个元素(最后入队的元素);而队列为空时,队头指针和队尾指针指向同一个元素(空元素)。这两种状态的表示都是队头指针等于队尾指针。
而有了标志空间后,表示空队列仍是队头指针等于队尾指针;但是队满时,q.rear指向标志空间,而q.front指向q.rear上一个元素(因为如果没有标志空间,队列能在入一个元素,q.front+1后与q.rear指向相同的元素)。因此只要用上述方法就能判断是否栈满,保证了状态的唯一表示。
虽然有一个空间不能被使用,但是比起前面的“假溢出”情况要好多啦。
于是,判断队满的(q.rear+1)就可以理解了。此时q.rear指向标志空间,而q.front在它上一个,加一后正好与q.front相等。
链队
两头操作的队列少不了头尾指针。
但是由于指针只能指向下一个元素,因此我们将链尾作为队头,链头作为队尾。入队时只用将新的结点指向链尾,而出队时只用将队尾的指针指向下一个元素后删除最有一个结点。
至于要不要头结点呢?
在删除元素中,不管有没有头结点,如果被删除的是最后一个元素,都要进行额外的操作。如果没有头结点,删除最后一个结点导致头指针和尾指针都变为野指针,因此要为他们额外赋空;如果有头结点,头指针一直指向头结点,但是尾指针会变成野指针,因此需要将尾指针额外赋空。没有哪种情况特别优越。
但是如果考虑状态表示的唯一性,显然是需要头结点的。
如果没有头结点,空队的表示是头指针与尾指针均指空,有一个元素的队列的表示是头指针和尾指针都指向那一个元素,两者都是头指针等于尾指针;有头节点,空对的表示为头指针等于尾指针,有一个元素的队列表示为,头指针指向头结点尾指针指向那一个元素。
结语
其实这一章的学习实践时间相比之前有些减少,因此没有把所有四种结构的所有功能都实现一边。但是有进行实践的结构,如顺序栈、链队(因为PTA上有题目不得不打),都会有个更好的理解。因此之后希望能将各种结构的各种功能都通过代码实现一遍。
原文地址:https://www.cnblogs.com/luoyang0515/p/10625225.html