栈的定义:
栈是限定仅在尾部进行插入和删除操作的线性表,我们把允许插入和删除操作的一端叫做 “栈顶”,另一端叫做 “栈底”。
栈是一种 “先进后出,后进先出” 的线性表。
如图:
栈的存储结构分为:顺序结构和链式结构。
顺序结构就是使用数组的方式来存储,结构通常如下:
数组 + 当前栈顶的下标
这里的 “Data” 我们给的是一个 struct 的结构,数据可自定义,这里定义的是int,如下:
栈的通用的方法结构如下,参数不唯一,主要介绍方法:
顺序存储分析:
优点:代码结构简单方便。
缺点:需要用一个固定空间的数组来存储数据,如果栈满了,就需要继续申请更大的空间去存储,这个过程比较耗时,而且,如果栈是一个小栈,但是开辟了大量空间,就会导致空间浪费。
链式结构就是使用链表的方式来存储;
栈顶就是链表的头节点,每次进行插入删除的操作时,我们可以直接对链表的头部进行操作。
栈存储结构如下:头指针 + 链表长度
节点结构如下,当前值 + 下一个节点指针:
方法具体同顺序表,这里不再多做介绍。
链表存储分析:
优点:节省空间,要多少创建多少个节点,按需分配,不会造成空间浪费,也不用担心扩容的问题。
缺点:代码相较于顺序表复杂一点。
建议:如果栈的使用过程中元素变化不可预测,会忽大忽小的变化,建议使用链表,如果变化可控,且占用空间不大的情况下建议使用顺序表。
栈的应用—递归
例子:斐波那契数列
上图我们了解了斐波那契数列,那么接下来让我们用代码实现一下:
两种方式:循环 + 递归
循环:
void Fibonacci(int n)
{
if (n < 0)
{
return;
}
if (n == 0)
{
cout << 0;
}
else if (n == 1)
{
cout << 1;
}
else
{
int pre = 1;
int ppre = 0;
cout << ppre << " ";
cout << pre << " ";
int temp = 0;
for (int i = 2; i < n; i++)
{
temp = pre + ppre;
cout << temp << " ";
ppre = pre;
pre = temp;
}
}
}
递归:
int FibonacciNew(int n);
int main()
{
int fbiNum = 10;
for (int i = 0; i < fbiNum; i++)
{
cout << FibonacciNew(i) << " ";
}
}
int FibonacciNew(int n)
{
if (n <= 0)
{
return 0;
}
else if( n == 1)
{
return 1;
}
return FibonacciNew(n - 1) + FibonacciNew(n - 2);
}
总结,递归和循环的区别是:
迭代使用的是循环结构,递归使用的是选择结构。递归可以让代码更清晰,更简洁,更容易理解。但是大量的递归调用会建立函数的副本,会耗费大量的时间和内存,迭代不需要反复的调用函数占用额外的空间。
从空间复杂度的角度上来分析:
递归的空间复杂度是O(lgN),简单理解一点的话,N可以理解为你压栈的次数,即递归的次数。
迭代的空间复杂度是O(1)。所以,如果递归的次数较大的情况下建议使用循环的方式解决问题。