数据结构之栈

Masimaro 2020-07-19

  • 用数组实现一个顺序栈
  • 用链表实现一个链式栈
  • 编程模拟实现一个浏览器的前进、后退功能

用数组实现一个顺序栈

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 中没有数据,那就说明没有页面可以点击前进按钮浏览了。

相关推荐