数据结构之栈
栈
- 用数组实现一个顺序栈
- 用链表实现一个链式栈
- 编程模拟实现一个浏览器的前进、后退功能
用数组实现一个顺序栈
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 中没有数据,那就说明没有页面可以点击前进按钮浏览了。