想當一個合格的程序員,你敢出去說你不會棧嗎?
我不敢的。
棧有很多用途,也分很多種類,順序棧、雙端棧、單調棧、鏈棧等。讓哥哥帶你,深入淺出堆棧系列。坐好上車咯。
(建議按目錄走,大佬可以直接跳到後面的)
①後進先出的叫棧
棧吶,你可以叫它彈(dan)棧,就像彈夾一樣。
入棧只能在棧頂,出棧也只能在棧頂,想象一下手槍彈夾。
也可以說,棧是一種僅限於在表尾進行抽插的線性表。
②API設計
類名 | stack |
---|---|
構造方式 | stack() |
成員方法: | |
入棧(壓棧) | push(void*) |
出棧(彈棧) | pop(void*) |
大小獲取 | size() |
棧頂元素獲取 | top() |
成員屬性: | |
對於鏈棧 | Node pres; Node prev; Node data; |
對於線棧 | int size; top |
上面缺省的數據類型,爲泛型。爲了方便理解,接下來全用int類型
③順序棧實現
#include <iostream>
#define StackSize 100 //棧長度
using namespace std;
class SeqStack {
private:
int data[StackSize]; //線性棧表
int top; //紀錄棧頂
public:
SeqStack(){top=-1;}; //將項頂指針置爲-1
~SeqStack(){}
void Push(int x){
if (top==StackSize-1){
cout<<"棧滿"<<endl;
return;
}
top++;
data[top] = x;
}
int Pop(){
if (top==-1){
cout<<"棧空"<<endl;
return;
}
return data[top];
top--
} //版本1:取出棧頂元素
//一般是用版本1,所以版本2不看了
int GetTop(){
if (top==-1){
cout<<"棧空"<<endl;
return;
}
return data[top];
}//取棧頂元素
int Empty(){
if (top==-1)
return 1;
} //判斷是否爲空
};
④雙端棧
雙棧是指兩個順序棧,是一種特殊的順序棧。
-
雙棧共享一個地址連續的存儲單元。即程序同時需要兩個棧時,可以定義一個足夠大的棧空間,該空間的兩端分別設爲兩個棧的棧底,用bottom[0]=-1和bottom[1]=maxSize指示。
-
壓入數據時,讓兩個棧的棧頂top[0]和top[1]都向中間伸展,如果指示棧頂的指針top[0]+1等於另一個棧頂的指針top[1]時兩棧已滿。
-
每次進棧時top[0]加1或top[1]減1,而退棧時top[0]減1或top[1]加1。
-
如果
top[0] == -1
或top[1] == maxSize
,有棧爲空。
實現
#include <iostream>
using namespace std;
const int defaultSize = 50; //默認棧空間大小
const int stackIncreament = 20; //棧溢出時擴展空間的增量
const int n = 2; //設置n=2個棧共有一個棧空間
class DualStack
{
public:
DualStack(int sz = defaultSize); //構造函數
~DualStack(); //析構函數
public:
bool Push(const T& x, int d) ; //新元素x進棧
bool Pop(T& x, int d); //棧頂元素出棧,並將該元素的值保存至x
bool getTop(T& x, int d) const; //讀取棧頂元素,並將該元素的值保存至x
bool IsEmpty() const; //判斷棧是否爲空
bool IsFull() const; //判斷棧是否爲滿
int getSize() const; //計算棧中元素個數
void MakeEmpty(); //清空棧的內容
void overflowProcess(); //棧的溢出處理
private:
int *vec; //存放棧中元素
int top[n-1]; //棧頂指針
int maxSize; //棧最大可容納元素個數
};
//構造函數
DualStack::DualStack(int sz)
{
if (sz >= 0)
{
maxSize = sz;
top[0] = -1;
top[1] = maxSize;
vec = new int[maxSize];
}
}
//析構函數
DualStack::~DualStack()
{
delete[] vec;
vec = NULL;
}
//新元素x進棧
bool DualStack::Push(const int x, int d)
{
if (true == IsFull())
return false;
if (0 == d)
top[0]++;
else
top[1]--;
vec[top[d]] = x;
return true;
}
//棧頂元素出棧,並將該元素的值保存至x
bool DualStack::Pop(int x, int d)
{
if (true == IsEmpty())
return false;
x = vec[top[d]];
if (0 == d)
top[0]--;
else
top[1]++;
return true;
}
//讀取棧頂元素,並將該元素的值保存至x
bool DualStack::getTop(int x, int d) const
{
if (true == IsEmpty())
return false;
x = vec[top[d]];
return true;
}
//判斷棧是否爲空
bool DualStack::IsEmpty() const
{
return ((-1 == top[0]) && (maxSize == top[1])) ? true : false;
}
//判斷棧是否爲滿
bool DualStack::IsFull() const
{
return (top[0] + 1 == top[1]) ? true : false;
}
//計算棧中元素個數
int DualStack::getSize() const
{
return (top[0] + 1) + (maxSize - top[1]);
}
//清空棧的內容
void DualStack::MakeEmpty()
{
delete[] vec;
top[0] = -1;
top[1] = maxSize;
vec = new int[maxSize];
//如果用vector容器的話,就直接清空了
//但是爲了普遍性,還是把STL收起來了
}
//棧的溢出處理
void DualStack::overflowProcess()
{
int newSize = maxSize + stackIncreament;
int *neweVector = new int[newSize];
for (int i = 0; i <= top[0]; i++)
{
neweVector[i] = vec[i];
}
for (int i = maxSize - 1; i >= top[1]; i--)
{
neweVector[i + stackIncreament] = vec[i];
}
delete[] vec;
vec= neweVector;
maxSize = newSize;
top[1] += stackIncreament;
}
多端棧
多端棧推薦使用鏈棧實現。
⑤鏈棧
鏈棧,結合了鏈表和棧的優點。
鏈棧的實現思路同順序棧類似,通常我們將鏈表的頭部作爲棧頂,尾部作爲棧底,如圖所示:
- 將鏈表頭部作爲棧頂的一端,可以避免在實現數據 “入棧” 和 “出棧” 操作時做大量遍歷鏈表的耗時操作。
鏈表的頭部作爲棧頂,意味着:
在實現數據"入棧"操作時,需要將數據從鏈表的頭部插入;
在實現數據"出棧"操作時,需要刪除鏈表頭部的首元節點;
因此,鏈棧實際上就是一個只能採用頭插法插入或刪除數據的鏈表。
實現
前面都用cpp,這裏搞個c換換風格
//鏈表中的節點結構
typedef struct lineStack{
int data;
struct lineStack * next;
}lineStack;
- 壓棧實現
//stack爲當前的鏈棧,a表示入棧元素
lineStack* push(lineStack * stack,int a){
//創建存儲新元素的節點
lineStack * line=(lineStack*)malloc(sizeof(lineStack));
line->data=a;
//新節點與頭節點建立邏輯關係
line->next=stack;
//更新頭指針的指向
stack=line;
return stack;
}
- 出棧實現
//棧頂元素出鏈棧的實現函數
lineStack * pop(lineStack * stack){
if (stack) {
//聲明一個新指針指向棧頂節點
lineStack * p=stack;
//更新頭指針
stack=stack->next;
printf("出棧元素:%d ",p->data);
if (stack) {
printf("新棧頂元素:%d\n",stack->data);
}else{
printf("棧已空\n");
}
free(p);
}else{
printf("棧內沒有元素");
return stack;
}
return stack;
}
⑥漢諾塔
漢諾塔:漢諾塔(Tower of
Hanoi)源於印度傳說中,大梵天創造世界時造了三根金鋼石柱子,其中一根柱子自底向上疊着64片黃金圓盤。大梵天命令婆羅門把圓盤從下面開始按大小順序重新擺放在另一根柱子上。並且規定,在小圓盤上不能放大圓盤,在三根柱子之間一次只能移動一個圓盤。
–引用維基百科
a是起始柱,c是目標柱,b起到中轉作用
本來我是一頭霧水的,但是在力扣上被那個爬樓梯的“簡單”動態規劃題折磨之後,我有點茅廁頓開的感覺。
-
問題看起來並不複雜,當a柱子上只有一個盤子時只要把那個盤子直接移到c就行了。
有兩個盤子的話把1號盤先移到b柱,在把2號盤移到c柱,最後把b柱上的1號盤移到c柱就行了。 -
這裏我們先把上方的63個盤子看成整體,這下就等於只有兩個盤子,自然很容易了,我們只要完成兩個盤子的轉移就行了,好了現在我們先不管第64個盤子,假設a柱只有63個盤子,與之前一樣的解決方式,前62個盤子先完成移動目標。
-
嗯,就這樣一步步向前找到可以直接移動的盤子,62,61,60,…,2,1,最終,最上方的盤子是可以直接移動到c柱的,那就好辦了,我們的2號盤也能完成向c柱的轉移,這時c柱上時已經轉移成功的2個盤,於是3號盤也可以了,一直到第64號盤。
這個真的燒腦,主要配合上遞歸的思想
先看碼:
棧部分代碼,左邊有目錄,鏈棧。
void main()
{
int n = 64; //可以泛化
Stack a = init_stack();
Stack b = init_stack();
Stack c = init_stack();
while(n-->0){// 初始化棧a,代表最左邊柱子和盤子
push_stack(a,i);
}
hanoi(n,a,b,c);
}
void hanoi(int n,Stack a,Stack b,Stack c)
{
if(n == 1) // 盤子數爲1
pop_push(a,c);
else
{
hanoi(n-1,a,c,b); // 將棧a的n-1個盤子順序移到棧b
pop_push(a,c); // 將棧a的第n個盤子移到棧c
hanoi(n-1,b,c,a); // 將棧b的n-1個盤子順序移到棧c
}
}
不行,我要去補腦。。。
⑦單調棧
之前在力扣刷題,用到單調棧,不過那時候還不知道它叫單調棧哈哈,那個賣股票的題目。
單調棧就是棧內元素單調遞增或者單調遞減的棧,單調棧只能在棧頂操作。
性質:
- 單調棧的維護是 O(n) 級的時間複雜度,因爲所有元素只會進入棧一次,並且出棧後再也不會進棧了。
- 單調棧裏的元素具有單調性。
- 元素加入棧前,會在棧頂端把破壞棧單調性的元素都刪除
- 使用單調棧可以找到元素向左遍歷第一個比他小的元素,也可以找到元素向左遍歷第一個比他大的元素。
- 單調棧在用於維護區間距非常有優勢。
Q:給你一串序列,要你求所有子序列的最小值之和。
E:[-2,4,5,3,6,0,-3]