数据结构学习总结 ——5.栈
数据结构学习总结 ——5.栈
栈的定义:栈(stack)是限定仅在表尾进行插入和删除操作的线性表
5.1 栈的定义
栈(stack)是限定仅在表尾进行插入和删除操作的线性表
我们吧允许插入和删除的一端称为栈顶(top),另一端称为栈底(bottom),不含任何数据元素的栈称为空栈。栈又称为后进先出(Last In First Out)的线性表,简称 LIFO 结构。
理解栈的定义需要注意:
- 首先它是一个线性表,也就是说,栈元素具有线性关系,即前驱后继关系。只不过它是一种特殊的线性表而已。定义中说是在线性表的表尾进行插入和删除操作,这里表尾是指栈顶,而不是栈底。
- 栈的插入操作,叫做进栈,也称压栈、入栈。
- 栈的删除操作,叫做出栈,也有的叫做弹栈。
5.2 栈的顺序存储结构及实现
5.2.1 栈的顺序存储结构
既然栈是线性表的特例,那么栈的顺序存储其实也是线性表顺序存储的简化,我们简称为顺序栈。
我们定义一个 top 变量来指示栈顶元素在数组中的位置,它可以来回移动,意味着栈顶的 top 可以变大变小。若存储栈的长度为 StackSize ,则栈顶位置 top 必须小于 StackSize 。当栈存在一个元素时,top 等于 0,因此我们通常把空栈的判定条件定位 top 等于 -1。
5.2.2 栈的顺序存储结构——进栈操作
class Stack
{
protected $stack; //栈
protected $top = -1; //栈顶位置
protected $out; //出栈标识
protected static $maxLength; //栈长度
public function __construct($maxLength)
{
$this->stack = [];
self::$maxLength = $maxLength;
}
/**
* @return array
*/
public function getStack()
{
return $this->stack;
}
/**
* 入栈
*
* @param $elem
* @return bool|int
*/
public function push ($elem)
{
//满栈
if ($this->top == self::$maxLength - 1) {
return false;
}
//移动栈顶标识
$this->top++;
//赋值新栈顶
$this->stack[$this->top] = $elem;
return $this->top;
}
}
5.2.3 栈的顺序存储结构——出栈操作
class Stack
{
protected $stack; //栈
protected $top = -1; //栈顶位置
protected $out; //出栈标识
protected static $maxLength; //栈长度
public function __construct($maxLength)
{
$this->stack = [];
self::$maxLength = $maxLength;
}
...
/**
* 出栈
*
* @return bool|mixed
*/
public function pop ()
{
//空栈
if ($this->top == -1) {
return false;
}
//栈顶出栈
$this->out = $this->stack[$this->top];
//删除栈顶元素
unset($this->stack[$this->top]);
//移动栈顶位置
$this->top--;
return $this->out;
}
}
5.3 两栈空间共享
我们可以用一个数组来存储两个栈。数组有两个端点,两个栈有两个栈底,让一个栈底为数组的起始端,即下标为 0 处,另一个栈为栈的末端,即下标为数组长度 n - 1 处。这样。两个栈如果增加元素,就是两端点向中间延伸。
其实关键的思路是:它们是在数组的两端,向中间靠拢。 top1 和 top2 是栈 1 和 栈 2 的栈顶指针,只要两个栈顶不见面,两个栈就一可以一直使用。栈 1 为空时,就是 top1 等于 -1时;而当 top2 等于 n 时,即是栈 2 为空时。若栈 2 是空栈,栈 1 的 top1 等于 n - 1 时,就是栈 1 满了。当栈 1 为空栈时,top2 等于 0 时,为栈 2 满。但更多的情况,其实是我刚才说的,两个栈见面时,也就是两个指针之间相差 1 时,即 top1 + 1 == top2 为栈满。
5.3.1 共享栈进栈操作
<?php
class SharedStack
{
protected $stack; //栈
protected $topOne; //栈1顶位置
protected $topTwo; //栈2顶位置
protected static $maxLength; //栈长度
public function __construct($maxLength)
{
$this->stack = [];
self::$maxLength = $maxLength;
}
/**
* @return array
*/
public function getStack()
{
return $this->stack;
}
/**
* 进栈
*
* @param $stackNum 代表栈的标示 1/2
* @param $e //进栈元素
* @return bool|mixed
*/
public function push($stackNum, $e)
{
//判断栈满情况
if ($this->topOne + 1 == $this->topTwo) {
return false;
}
switch ($stackNum)
{
//栈1 进栈
case 1 :
$this->topOne++;
$this->stack[$this->topOne] = $e;
$key = $this->topOne;
break;
//栈2 进栈
case 2 :
$this->topTwo--;
$this->stack[$this->topTwo] = $e;
$key = $this->topTwo;
break;
default :
$this->topOne++;
$this->stack[$this->topOne] = $e;
$key = $this->topOne;
break;
}
return $key;
}
...
}
5.3.1 共享栈出栈操作
<?php
class SharedStack
{
protected $stack; //栈
protected $topOne; //栈1顶位置
protected $topTwo; //栈2顶位置
protected static $maxLength; //栈长度
public function __construct($maxLength)
{
$this->stack = [];
self::$maxLength = $maxLength;
}
/**
* @return array
*/
public function getStack()
{
return $this->stack;
}
...
/**
* 出栈
*
* @param $stackNum 代表栈的标示 1/2
* @return bool|mixed
*/
public function pop($stackNum)
{
switch ($stackNum) {
//栈1 出栈
case 1 :
if ($this->topOne == 0) {
return false;
}
$e = $this->stack[$this->topOne];
unset($this->stack[$this->topOne]);
$this->topOne--;
break;
//栈2 出栈
case 2 :
if ($this->topTwo == self::$maxLength) {
return false;
}
$e = $this->stack[$this->topTwo];
unset($this->stack[$this->topTwo]);
$this->topTwo++;
break;
default :
if ($this->topOne == 0) {
return false;
}
$e = $this->stack[$this->topOne];
unset($this->stack[$this->topOne]);
$this->topOne--;
break;
}
return $e;
}
}
5.4 栈的应用——递归
栈有一个真重要的应用:在程序设计语言中实现了递归
5.4.1 斐波那锲数列实现
斐波那契数列(Fibonacci sequence),又称黄金分割数列、因数学家列昂纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:1、1、2、3、5、8、13、21、34、……在数学上,斐波纳契数列以如下被以递推的方法定义:*F(1)=1,F(2)=1, F(n)=F(n-1)+F(n-2)(n>=3,n∈N)**在现代物理、准晶体结构、化学等领域,斐波纳契数列都有直接的应用,为此,美国数学会从1963年起出版了以《斐波纳契数列季刊》为名的一份数学杂志,用于专门刊载这方面的研究成果
class FibonacciSequence
{
public $num;
public function __construct($num)
{
$this->num = $num;
}
public function init()
{
$fbi = [];
for ($i = 0; $i < $this->num; $i++)
{
array_push($fbi, $this->Fbi($i));
}
$array['sum'] = array_sum($fbi);
$array['fbi'] = $fbi;
return $return;
}
protected function Fbi($i)
{
if ($i < 2) {
return $i == 0 ? 0 : 1;
}
return $this->Fbi($i - 1) + $this->Fbi($i - 2);
}
}
$fbi = new FibonacciSequence(10);
print_r($fbi->init());
5.4.2 递归定义
在高级语言中,调用自己和其他函数并没有本质的不同。我们把一个直接调用自己或通过一系列的调用语句间接地调用自己的函数,称作递归函数。
当然,写递归程最怕的就是陷入永不结束的无穷递归总,所以,每个递归定义必须至少有一个条件,满足时递归不再进行,即不再引用自身而是返回值退出。
5.4.3 栈的引用——四则运算表达式求值
5.4.3.1 后缀(逆波兰)表示法定义
一种不需要括号的后缀表达法,我们也把他称为逆波兰(Reverse Pollish Notation, RPN)表示。
5.4.3.2 后缀表达式计算结果
我们先来看,对于 “9 + (3 - 1)* 3 + 10 / 2” 。如果要用后缀表达式应该是 “9 3 1 - 3 * + 10 2 / +”,这样的表达式称为后缀表达式。叫后缀的原因在于所有的符号都是在要运算数字的后面出现。
后缀表达式:9 3 1 - 3 * + 10 2 / +
规则:从左到右遍历表达式的每个数字和符号,遇到是数字就直接进栈,遇到是符号,就将处于栈顶两个数字出栈,进行运算,运算结果进栈,一直到最终获得结果。
- 初始化一个空栈。此栈用来对要运算的数字进出使用。
- 后缀表达式中前三个都是数字,所以9,3,1 进栈。 得到栈 [ 9, 3, 1]。
- 接下来是 “-”,所以将栈中的1出栈作为减数,3出栈作为被减数,并运算 “3 - 1” 得到 2 , 再将 2 进栈。得到栈 [ 9, 2]。
- 接着是数字 3 进栈。得到栈 [ 9, 2, 3]。
- 后面是 “*” ,也就意味着栈中 3 和 2 出栈,2 与 3 相乘,得到 6 ,并将 6 进栈。得到栈 [ 9, 6]。
- 下面是 “+”,所以栈中 6 和 9 出栈,9 与 6 相加, 得到 15 , 将 15 进栈。得到栈 [15]。
- 接着是 10 与 2 两个数字进栈。得到栈 [15, 10, 2]。
- 接下来是符号 ”/“,因此栈顶的 2 与 10 出栈, 10 与 2 相除,得到 5 ,将 5 进栈。得到栈 [15, 5]。
- 最后一个是符号 ”+“,所以 15 与 5 出栈并相加,得到 20 ,将 20 进栈。得到栈 [20]。
- 结果是 20 出栈,栈变为空。 得到栈 [ ]。
5.4.3.3 中缀表达式转后缀表达式
我们把平时所用的标准四则运算表达式,即 “9 + (3 - 1)* 3 + 10 / 2” 叫做中缀表达式。因为所有的运算符号都在两数字中间,现在我们的问题是中缀到后缀的转化。
中缀表达式:“9 + (3 - 1)* 3 + 10 / 2” 转为后缀表达式 “9 3 1 - 3 * + 10 2 / +”。
规则:从左到右遍历中缀表达式的每个数字和符号,若是数字就输出,即成为后缀表达式的一部分;若是符号,则判断其与栈顶符号的优先级,是右括号或优先级低与栈顶符号(乘除优先加减)则栈顶元素依次出栈并输出,并将当前符号进栈,一直到最终输出后缀表达式为止。
- 初始化一空栈,用来对符号进出栈使用。得到栈 [ ]。得到表达式 “”。
- 第一个字符是数字 9 ,输出 9 ,后面是符号 ”+“,进栈。得到栈 [+]。得到表达式 “9”。
- 第三个符号是 “(” ,依然是符号,因其只是左括号,还未配对,故进栈。得到栈 [+, (]。得到表达式 “9”。
- 第四个字符是数字 3,输出,接着是 “–”,进栈。得到栈 [+, (,–]。得到表达式 “9 3”。
- 接下来是数字 1 ,输出,后面是符号 “)”,此时,我们需要去匹配此前的 “(”,所以栈顶依次出栈,并输出,直到 “(” 出栈为止。此时左括号上方只有 “–”,因此输出 “–”。得到栈 [+]。得到表达式 “9 3 1 --”。
- 接着是数字 3 ,输出。紧接着是符号 ”✖️“,因为此时的栈顶符号为 ”+“号,优先级低于 ”✖️“,因此不输出, ”*“进栈。得到栈 [+, *]。得到表达式 “9 3 1 – 3”。
- 之后是符号 ”+“,此时当前栈顶元素 ”*“,比这个 ”+” 的优先级高,因此栈中元素出栈并输出(没有比 “+” 更低的优先级,所以全部出栈)。然后将当前这个符号 “+” 进栈。得到栈 [+]。得到表达式 “9 3 1 – 3 * +”。
- 紧接着数字10,输出。后面是符号 “➗”,所以 “/” 进栈。得到栈 [+,/]。得到表达式 “9 3 1 – 3 * + 10”。
- 最后一个数组 2 ,输出,得到栈 [+,/]。得到表达式 “9 3 1 – 3 * + 10 2”。
- 已经到最后,所以将栈中符号全部出栈并输出。 得到栈 [ ]。得到表达式 “9 3 1 – 3 * + 10 2 / +”。
从刚才的推导中你会发现,要想让计算机具有处理我们通常的标准(中缀)表达式的能力,最重要的是两步:
- 将中缀表达式转化为后缀表达式(栈用来进出运算的符号)。
- 将后缀表达式进行运算得出结果(栈用来进出运算的数字)。