栈-知识点

栈和队列是应用频率非常高的数据结构。对于栈,其有数组实现方式,也有链表的实现方式。故把握其数据特性十分重要。

定义

栈(stack)是一种特殊的线性表,其插入(入栈)和删除(出栈)都是在表的同一端进行。这一段也被称为栈顶(top),另一端被称为栈底(bottom)。栈是一个先进后出(last-in-first-out,LIFO)的数据结构

抽象数据类型(ADT)

对于栈,主要的操作有如下几个:

functions description
push(value) add an element to the top of the stack.
pop() aremove and return the top element in the stack.
peak() return(but do not remove)the top element in the stack.

接下来看看《数据结构、算法与应用 c++ 描述中》所给出的ADT定义:

数组实现

用数组来实现栈并不是一个难事,只需要把握住栈的特性:先进后出,即可。具体的实现看下面图片即可:(写的很好)

ps:在上述代码中,允许指定初始容量十分重要,在一定程度上可以避免后续操作对数组大小进行改变。

链表实现

当通过链表实现栈时,必须确定用链表的哪一端来代表栈顶。其实对于这个问题,已经有明确的答案。若选择右端作为栈顶,则栈操作 top、push 和 pop 的实现的时间复杂度为 O(size)。若选择左端作为栈顶,则栈操作top、push 和 pop 的实现的时间复杂度为 Θ(1)。因此我们选择链表的左端作为栈顶

栈的应用

栈的实现虽然不困难,但由于其具有先进后出的特性,因此也适合来解决类似于此种特性的问题,就如同递归函数栈一般。根据个人理解是栈具有存储以往信息并返回的功能,因此在部分需要进行试错的问题(如迷宫)可以使用。同时,左括号,右括号的匹配也神似入栈、出栈的过程,因此也可以用于括号匹配当中。

括号匹配

此处仅仅考虑的是单括号匹配的问题,因此实现过程较简单。

汉诺塔问题

对于汉诺塔问题,既可以采用递归解决,也可以采用栈解决。

递归解决

首先讲讲递归的思路:

你有 N 个盘子,3 座塔, N 的盘子从大到小,自下往上的堆叠在塔1的位置(下述通过Ⅰ来描述),我们需要做的,就是把Ⅰ的盘子,按照原有的顺序堆叠在Ⅱ。对于递归的问题,往往我们需要想清楚其基本情况和 base case 。当 N = 2时,我们需要把Ⅰ处最上方的盘子移至 Ⅲ,然后将最重的盘子移至Ⅱ,再把Ⅲ的盘子移至Ⅱ。当N = 3时,我们把Ⅰ最上方两个盘子移至Ⅲ(牢记,一次只能移动一个),然后将最重的盘子移至Ⅱ处。对于N时,我们需要把Ⅰ上方 N - 1 个盘子移至 Ⅲ 处,然后把最大的盘子移到Ⅱ,最后再将Ⅲ的 N - 1 个盘子移至 Ⅱ。Obviously,这里就出现了重复情况,而递归就是将一个问题通过重复不断的情况进行循环,直至基本情况,然后不断返回结果,最后得出解。

另外,当最终的盘子移动至Ⅱ时,则不需要再管他,因为他已经是目前最重的盘子,后续步骤也同理。

时间复杂度:$Θ(2^n)$。

栈解决

思路大致与递归时相同,区别在于,通过栈实现时,对数据的存取需要自己手动实现。

列车车厢重排

略。

离线等价类问题

略。(或待补充

迷宫问题

迷宫问题具体的题目要求不要过多赘述,下述主要提供一下思路。

迷宫的问题大致分为两类:一是找到能够到达的路径,二是找到最短的路径。这两者的区别在于后者是小于等于1的,即存在一条最短的路径或无路径可以到达出口。

在解决问题的过程中,可以将迷宫的位置考虑为状态,即存在可走和不可走的状态,同时,也存在走过和未走过的状态。前者是为了找到到达出口的路径,后者则是为了避免重复走相同的路径而陷入死循环中。同时也方向也存在4个状态,即上下左右。