1. 程式人生 > 實用技巧 >資料結構之棧

資料結構之棧

  • 用陣列實現一個順序棧
  • 用連結串列實現一個鏈式棧
  • 程式設計模擬實現一個瀏覽器的前進、後退功能

用陣列實現一個順序棧

class Stack(object):
def __init__(self):
self.stack=[]

def push(self,item):
self.stack.append(item)

def pop(self):
return self.stack.pop()

def peek(self):
if len (self.stack)>=1:
return self.stack[-1]
else :
return '棧空'
#測試用例
if __name__=='__main__':

st=Stack()
st.push(1)
st.push(2)
print(st.pop())
print(st.peek())
print(st.pop())

#include <iostream>


const int StackSize=100;

//

template <class Datatype>

class SeqStack {

private:

Datatype data[StackSize];

int top;

public:

SeqStack(){top=-1;}; //將項頂指標置為-1

~SeqStack(){}

void Push(Datatype x);

Datatype Pop();


Datatype GetTop();//取棧頂元素實現

int Empty(); //判斷是否為空

void printlist(); //列印

};


template <class Datatype>

int SeqStack<Datatype>::Empty()

{

if (top==-1) {

return 1;

}

else return 0;

}

//入棧操作

template <class Datatype>

void SeqStack<Datatype>::Push(Datatype x)

{

if (top==StackSize-1) throw "棧空間已滿,無法再新增";

data[++top]=x;

}

//出棧操作

template <class Datatype>

Datatype SeqStack<Datatype>::Pop()

{

if (top==-1) throw "棧已經為空";



Datatype x;

x=data[top--];

return x;



}


template <class Datatype>

Datatype SeqStack<Datatype>::GetTop()

{

if (top==-1) throw "這是空棧";

return data[top];



}

template <class Datatype>

void SeqStack<Datatype>::printlist()

{

if (top==-1) throw "這是空棧";

for (int i=0; i<=top; i++) {

std::cout<<data[i]<<"\n";

}

}


int main(int argc, const char * argv[]) {

SeqStack<int> Student = SeqStack<int>();

std::cout<<"is empty?"<<Student.Empty();

for (int i=0; i<10; i++) {

Student.Push(i);

}

std::cout<<"is empty?"<<Student.Empty();

std::cout<<"The top word is"<<Student.GetTop();

Student.printlist();

std::cout<<"Pop the pop word,and it's "<<Student.Pop()<<"\n";

std::cout<<"The top word is"<<Student.GetTop()<<"\n";

return 0;

}
主要操作函式如下:

InitStack(SqStack &s) 引數:順序棧s 功能:初始化 時間複雜度O(1)
Push(SqStack &s,SElemType e) 引數:順序棧s,元素e 功能:將e入棧 時間複雜度:O(1)
Pop(SqStack &s,SElemType &e) 引數:順序棧s,元素e 功能:出棧,e接收出棧元素值 時間複雜度O(1)
GetTop(SqStack s,SElemType &e) 引數:順序棧s,元素e 功能:得到棧頂元素 時間複雜度O(1)
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<iostream>
using namespace std;
#define Status int
#define SElemType int
#define MaxSize 100
//棧資料結構
typedef struct Stack
{
SElemType *base;//棧底指標 不變
SElemType *top;//棧頂指標 一直在棧頂元素上一個位置
int stacksize;//棧可用的最大容量
}SqStack;
//**************************************基本操作函式************************************//
//初始化函式
Status InitStack(SqStack &s)
{
s.base=new SElemType[MaxSize];//動態分配最大容量
if(!s.base)
{
printf("分配失敗\n");
return 0;
}
s.top=s.base;//棧頂指標與棧底相同 王道上top起初在base下面,感覺很彆扭,top應該高於或等於base
s.stacksize=MaxSize;
return 1;
}
//入棧
Status Push(SqStack &s,SElemType e)
{
if(s.top-s.base==s.stacksize) return 0;//棧滿
*(s.top++)=e;//先入棧,棧頂指標再上移 注意與王道上的不同,具體問題具體分析
return 1;
}
//出棧 用e返回值
Status Pop(SqStack &s,SElemType &e)
{
if(s.top==s.base) return 0;//棧空
e=*--s.top;//先減減 指向棧頂元素,再給e
return 1;
}
//得到棧頂元素,不修改指標
bool GetTop(SqStack s,SElemType &e) //嚴蔚敏版59頁有問題,應該用e去獲得,函式返回bool型別去判斷
{
if(s.top==s.base) return false;//棧空
else e=*--s.top;
return true;

}
//********************************功能實現函式**************************************//
//選單
void menu()
{
printf("********1.入棧 2.出棧*********\n");
printf("********3.取棧頂 4.退出*********\n");
}
//入棧功能函式 呼叫Push函式
void PushToStack(SqStack &s)
{
int n;SElemType e;int flag;
printf("請輸入入棧元素個數(>=1):\n");
scanf("%d",&n);
for(int i=0;i<n;i++)
{
printf("請輸入第%d個元素的值:",i+1);
scanf("%d",&e);
flag=Push(s,e);
if(flag)printf("%d已入棧\n",e);
else {printf("棧已滿!!!\n");break;}
}
}
//出棧功能函式 呼叫Pop函式
void PopFromStack(SqStack &s)
{
int n;SElemType e;int flag;
printf("請輸入出棧元素個數(>=1):\n");
scanf("%d",&n);
for(int i=0;i<n;i++)
{
flag=Pop(s,e);
if(flag)printf("%d已出棧\n",e);
else {printf("棧已空!!!\n");break;}
}
}
//取棧頂功能函式 呼叫GetTop
void GetTopOfStack(SqStack &s)
{
SElemType e;bool flag;
flag=GetTop(s,e);
if(flag)printf("棧頂元素為:%d\n",e);
else printf("棧已空!!!\n");
}
//主函式
int main()
{
SqStack s;int choice;
InitStack(s);
while(1)
{
menu();
printf("請輸入選單序號:\n");
scanf("%d",&choice);
if(choice==4) break;
switch(choice)
{
case 1:PushToStack(s);break;
case 2:PopFromStack(s);break;
case 3:GetTopOfStack(s);break;
default:printf("輸入錯誤!!!\n");
}
}
return 0;
}

用連結串列實現一個鏈式棧

鏈式棧

基於連結串列實現鏈式棧,對棧做以下操作:

  • 出棧
  • 入棧
  • 取棧頂元素
  • 初始化
  • 銷燬

基於棧後進先出的結構,入棧可以用頭插來實現,出棧用頭刪來實現即可。

初始化

void LinkStackInit(LinkStack** phead)
{
if(phead==NULL)
{
return;//非法輸入
}
*phead=NULL;
}



void LinkStackDestroy(LinkStack** phead)
{
if(phead==NULL)
{
return;
}
LinkStack* cur=*phead;
for(;cur!=NULL;cur=cur->next)
{
free(cur);
}
*phead=NULL;
}

void LinkStackPush(LinkStack** phead,LinkStackType value)
{
if(phead==NULL)
{
return;//非法輸入
}
LinkStack* new_node=CreatNode(value);
new_node->next=*phead;
*phead=new_node;
}
出棧

void LinkStackPop(LinkStack** phead)
{
if(phead==NULL)
{
return;
}
if(*phead==NULL)
{
return;//空棧
}
LinkStack* to_delete=*phead;
*phead=(*phead)->next;
free(to_delete);
}

package stack;

public class StackByLinkedlist {

    ListNode popNode;

    public boolean push(String item) {
        ListNode temp = popNode;
        popNode = new ListNode(item);
        popNode.next = temp;
        return true;
    }

    public String pop() {
        if (popNode == null) {
            return null;
        }
        String result = popNode.val;
        popNode = popNode.next;
        return result;
    }

    public String peek() {
        if (popNode == null) {
            return null;
        }
        String result = popNode.val;

        return result;
    }

    public boolean empty() {
        return popNode == null;
    }


    class ListNode {
        String val;
        ListNode next;

        ListNode(String x) {
            val = x;
        }
    }

}

程式設計模擬實現一個瀏覽器的前進、後退功能
#include<iostream>
#include<string>
#include<stack>
using namespace std;
int main(){
stack<string> backward,forward;
string now,b,c="csw.jlu.edu.cn";
while(1){
cin>>b;
if(b=="VISIT"){
cin>>now;
cout<<now<<endl;
backward.push(c);
while(!forward.empty()){
forward.pop();
}
}
if(b=="BACK"){
if(backward.size()!=0){
c=backward.top();
backward.pop();
cout<<c<<endl;
forward.push(now);
now=c;
}
else{
cout<<"Ignored"<<endl;
c="csw.jlu.edu.cn";
}
}
if(b=="FORWARD"){
if(forward.size()!=0){
c=forward.top();
forward.pop();
cout<<c<<endl;
backward.push(now);
now=c;
}
else{
cout<<"Ignored"<<endl;
}
}
if(b=="QUIT"){
break;
}
if(b!="VISIT"&&b!="BACK"&&b!="FORWARD"&&b!="QUIT"){
cout<<"輸入錯誤!"<<endl;
}
}
}
我們瀏覽網頁,會發現“前進”和“後退”是 Web 瀏覽器的常用功能,實現該功能的一種方式是使用兩個棧(backward 棧和forward 棧)來儲存使用者訪問的網址,使用者的不同操作對應的具體實現方法如下:

後退(BACK):如果backward 棧為空,則該命令被忽略。否則,將當前頁面壓入forward棧,並從backward 棧中彈出一個頁面作為當前頁面。
前進(FORWARD):如果forward 棧為空,則該命令被忽略。否則,將當前頁面壓入backward 棧,並從forward 棧中彈出一個頁面作為當前頁面。
訪問某網址(VISIT <URL>):將當前頁面壓入backward 棧,並將此次訪問的網頁作為當前頁面,清空forward 棧。

二、整體思路

根據棧結構後進先出的順序,我們很容易想到,可以把使用者當前瀏覽的網頁入棧,當用戶在新介面點選回退按鈕的時候,直接讓棧頂元素出棧,讓瀏覽器載入,不就實現了後退功能了嗎?前進的功能也類似,於是乎,我們想到用兩個棧分別儲存在使用者當前瀏覽網頁之前/之後的所有頁面地址。理想的結構(BackStack回退棧,ForwardStack前進棧),如下圖所示:

四、頁面按鈕實現的前進後退

<input type=button value=重新整理 onclick="window.location.reload()">
<input type=button value=前進 onclick="window.history.go(1)">
<input type=button value=後退 onclick="window.history.go(-1)">
<input type=button value=前進 onclick="window.history.forward()">
<input type=button value=後退 onclick="window.history.back()">
<input type=button value=後退並重新整理 onclick="window.history.go(-1);window.location.reload()">

下面是history原始碼,有沒有什麼發現呢?

interface History {
readonly length: number;
scrollRestoration: ScrollRestoration;
readonly state: any;
back(distance?: any): void;
forward(distance?: any): void;
go(delta?: any): void;
pushState(data: any, title?: string, url?: string | null): void;
replaceState(data: any, title?: string, url?: string | null): void;
}

我們瀏覽網頁,經常會用到前進,後退的功能。比如依次瀏覽了a-b-c 三個頁面,這時點後退,可以回到 b 頁面,再次點選回到 a 頁面。如果這時,又進入 新頁面d ,那就無法通過前進,後退功能回到b、c頁面了。

這樣一個功能,要如何實現呢?

其實這樣一個功能,可以藉助棧來實現。

什麼是棧?它是一個數據結構,資料先進後出,後進先出。打一個比方,放一疊盤子,放的時候,都放最上面,取的時候也從最上面取。典型的後進先出。

棧,可以用陣列來實現,也可以用連結串列來實現。前者叫順序棧,後者是鏈式棧。

為什麼會有棧這種資料結構,直接用陣列或是連結串列不就好了麼?棧有嚴格的限制,只能從棧頂壓入資料,從棧頂取資料,可以滿足特定場景的需求,而連結串列或是陣列暴露了太多的介面,容易出錯。
如何實現一個棧

public class ArrayStack{
private String [] items; // 陣列
private int count; // 棧中元素個數
private int n; //棧的大小

// 初始化陣列,申請一個大小為 n 的陣列空間
public ArrayStack(int n){
items = new String [n];
this.n = n;
count = 0;
}
// 入棧操作
public boolean push(String item){
if(count == n){
return false;
}
items[count-1] = item;
count++;
return true;
}
// 出棧操作
public String pop(){
if(count == 0){
return null;
}
String temp = items[count-1];
count--;
return temp;
}
}


棧的實際應用

棧作為一個基礎的資料結構,經典的應用場景是函式呼叫。

我們知道,作業系統,為每個執行緒分配了獨立的記憶體空間,這些記憶體空間組織成棧的結構,來儲存函式呼叫產生的臨時變數。每次進入一個函式,將臨時變數作為棧楨入棧,當函式呼叫完成,有結果返回時,函式對應的棧楨出棧。便於理解,我們看一看下面的一段程式碼

int main() {
int a = 1;
int ret = 0;
int res = 0;
ret = add(3, 5);
res = a+ret;
printf("%d", res);
return 0;
}
int add(int x, int y){
int sum = 0;
sum = x + y;
return sum;
}



為了清晰地看到這個函式呼叫的過程中,出棧、入棧的操作,貼一張圖
在這裡插入圖片描述
棧在表示式求值中的應用

再看一個常見的應用場景,編譯器如何利用棧來實現表示式求值。

我們用一個簡單的四則運算來說明這個問題,比如3+5*8-6,對於計算機來說,如何理解這個表示式,是個不太容易的事兒。

事實上,編譯器是通過兩個棧來實現的,其中一個儲存數字,暫且叫A,一個儲存運算子暫且叫B。從左到右遍歷表示式,當遇到數字,直接壓入A棧,當遇到運符時,就與B棧中棧頂元素進行比較。

如果比棧頂元素的優先順序高,就將運算子壓入棧,如果相同或者低,從B棧中取出棧頂元素,從A棧的棧頂取出兩個數字,進行計算,再把計算結果壓入A棧,繼續比較。如下圖在這裡插入圖片描述

解答開篇的那個問題

我們使用兩個棧,X和Y,首次瀏覽的頁面,依次壓入棧X。當點選回退時,依次從X棧中取出資料,並依次放入Y棧。點選前進時,從Y棧中取出資料,並依次放入X棧。當X棧中沒有資料時,就說明不能再回退了;當Y中沒有資料時,就說明不能再前進了。

比如你依次瀏覽了a b c 三個頁面,我們依次把 a b c 壓入棧,這時兩個棧是這個樣子
在這裡插入圖片描述
瀏覽器通過後退按鈕,回退到頁面 a 時,我們把 c b 依次壓入Y棧,這時兩個棧是這個樣子
在這裡插入圖片描述
這時候,你又通過前進按鈕,又回到 b 頁面,這時兩個棧的資料是這個樣子
在這裡插入圖片描述

這時,你由 b 頁面開啟新的頁面 d,那麼你就不能再通過後退回到 c 頁面,Y棧資料要清空。這時兩個棧是這個樣子
https://blog.csdn.net/every__day/article/details/83753385

實現瀏覽器的前進後退功能

使用兩個棧 X 和 Y,把首次瀏覽的頁面依次壓入棧 X,當點選後退按鈕時,再依次從棧 X 中出棧,並將出棧的資料依次放入棧 Y。當點選前進按鈕時,依次從棧 Y 中取出資料,放入棧 X 中。當棧 X 中沒有資料時,那就說明沒有頁面可以繼續後退瀏覽了。當棧 Y 中沒有資料,那就說明沒有頁面可以點選前進按鈕瀏覽了。