数据结构~栈
栈结构是先进后出,压入栈的操作(如果是以链表来解释的话)也可以类比单相表的头插入,即插入顺序与输出顺序刚好相反。
栈的定义:
typedef struct myStack* SPro;
typedef struct myStack{
int * base;//底指针
int * top;//顶指针
int stackSize; //栈的容量
}S;
这里提一点:
例如top这个顶部指针,top代表的是他指向的地址对应的内存,是可以被赋予元素值的,但是top是一个指针,储存的就是一个地址,指针top指向top储存的地址,这点一定要搞明白。下面是栈的操作,代码中的注释里给出了每个函数的介绍。
栈的操作:
/**
* 创建一个空栈,初始化储存空间为 n,并返回此栈
* @param n 储存空间
* @return 返回初始化好的空栈
*/
SPro createStack(int n){
SPro stack=(SPro)malloc(sizeof(S));
stack->base=(int *)malloc(n*sizeof(int));
if (!stack->base){
cout<<"内存不足,初始化失败"<<endl;
return NULL;
}
stack->top=stack->base;
stack->stackSize=n;
return stack;
}
/**
* 压栈
* @param stack 被压入的栈
* @param newElem 将这个元素压入stack
*/
void push(S &stack,int newElem){
if (stack.top-stack.base==stack.stackSize){
cout<<"储存已满,不能再继续往栈内放新元素"<<endl;
}
*stack.top++=newElem;
}
/**
* 栈顶元素出栈
* @param stack 栈
* @param e 赋给参数 e
*/
void pop(S &stack,int & e){
if (stack.top==stack.base){
cout<<"这是一个空栈"<<endl;
return;
}
e=*--stack.top;
}
/**
* 获取栈顶元素,不弹出
* @param stack 栈
* @return 返回栈顶元素
*/
int getTop(S &stack){
if (stack.top==stack.base){
cout<<"这是一个空栈"<<endl;
} else
return *(stack.top-1);
}
int main() {
SPro stack=NULL;
stack=createStack(5);//初始化栈
for (int i = 1; i <= 5; ++i) {//入栈
push(*stack,i);
}
cout<<"top: "<<getTop(*stack)<<endl;
int * top=(int *)malloc(sizeof(int));
for (int i = 1; i <= 5; ++i) {
pop(*stack,*top);
cout<<"be popped elem is: " <<*top<<", ";
if (stack->top!=stack->base)
cout<<"top elem is: "<<getTop(*stack)<<endl;
}
return 0;
}
栈的应用:
汉诺塔,具体问题描述在这里就不再赘述了,下面给出两种解题思路
分别为普通的递归解法,和栈的解法
1:递归解法
void move(int i,char a,char b){
cout<<i<<": "<<a<<"--->"<<b<<endl;
}
void hnt(int n,char a,char b,char c){
if (n==1){//如果a柱上剩下一个盘子,就把这个盘子从a柱移到c柱
move(n,a,c);
return;
} else{
//把第n个盘子上面的n-1个盘子从a柱利用c柱移动到b柱
hnt(n-1,a,c,b);
//此时可以把第n个盘子从a柱移动到c柱
move(n,a,c);
//然后再把这n-1个盘子利用空着的a柱,从b柱移动到c柱
hnt(n-1,b,a,c);
}
}
int main(){
hnt(3,'a','b','c');
}
可以看出,递归是很方便读懂的,而且很简短,具体思路就是一直把所有盘子看成n和n-1两部分,只有把n-1个盘子利用一个柱子做过渡,全部移到另一个柱子后,最大的盘子才能移到目的柱上,往后依次分两部分进行移动。
递归虽然很好,但是它的空间占用相对较大,一旦数据过大,程序就会崩溃,所以有了递归算法,也要想着把递归算法改为非递归,优化空间占用,防止程序崩溃,下面给出非递归的解法
2:栈解法
现在要解决的问题分三块,依次为:
一:需要把n-1个盘子先从A通过C全部移动到B
二:需要把A上剩下的那一个盘子移到C上
三:需要把B上的n-1个盘子通过A移动到C上
所以,通过上面的分析,应该把问题依次反向压入栈中,即先push(问题三),再push(问题二),再push(问题一),然后即可从栈顶开始解决问题,并pop出解决过的问题。具体代码如下(代码是借鉴了北大信息工程学院的数据结构课件后写的):
#include<iostream>
#include "stackDemo.h"
#include<stack>
using namespace std;
struct Problem{
int n;
char source,mid,destination;
//Problem:问题描述,即:将num个盘子,从source柱,通过mid柱当中介,移动到destination柱
Problem(int num, char s, char m, char d): n(num), source(s), mid(m), destination(d){ }
};
stack<Problem> stk;//定义问题栈
int main(){
int n;
cin>>n;
stk.push(Problem(n,'a','b','c'));//初始化问题,并压入栈
while(!stk.empty()){
Problem curPrb=stk.top();//每次循环 取 栈顶储存的问题内容 赋值给 curPrb
stk.pop();//取出后,从栈中弹出此问题
if(curPrb.n==1) cout << curPrb.source << "---->" << curPrb.destination << endl;//当这个问题里就剩一个盘子需要从source移动到destination时,直接输出即可
else{//分解问题,三个问题反向入栈
stk.push(Problem(curPrb.n-1, curPrb.mid, curPrb.source, curPrb.destination));//先将第三个问题压入栈
stk.push(Problem(1, curPrb.source, curPrb.mid, curPrb.destination));//再将第二个问题压入栈
stk.push(Problem(curPrb.n-1, curPrb.source, curPrb.destination, curPrb.mid));//最后将第一个问题压入栈
}
}
return 0;
}
栈的拓展:
双栈,就是两个栈共用一段储存空间,两端分别为两个栈的栈底,分别向中间压栈,实现起来,先初始化储存空间的大小,然后,如果是下边的栈需要入栈或者出栈,跟单栈一样,入栈后top++,出栈后top–,内存可别释放,不然还得申请,因为刚开始创建这个双栈的时候,就把空间申请好了,就是入栈的时候需要判断两个栈的顶指针是否相等,如果相等,就别往里边存了。如果是上边的栈进行入栈操作,就是他的top–即可,具体代码如下:(这里给出的是伪代码,不能直接运行)
typedef struct{
int top[2],bot[2];//栈顶和栈底指针
SElemType *V;//栈数组
in m;//栈最大可容纳元素的个数(也就是数组长度)
}DblStack;
//初始化
Status InitStack(Dblstack &S, int m){
S.V=(SElemType *)malloc(m*sizeof(SElemType)); //为双栈分配一个大小为m的数组空间
if(!S.V){
return ERROR; //存储分配失败
}
S.top[0]=-1;
S.bot[0]=-1;
S.top[1]=m;
S.bot[1]=m;
return OK;
}
//判断是否为空
Status DblstackEmpty(Dblstack &S){
if(S.top[0]==S.bot[0]&&S.top[1]==S.bot[1]){
return TRUE; //栈空
}else{
return FALSE; //栈非空
}
}
//判断是否满栈
Status DblstackFull(Dblstack &S){
if(S.top[1]-S.top[0]==1)
return TRUE; //栈满
else
return FALSE; //栈不满
}
//进栈
Status Push(Dblstack &S,int e,int n){
if(DblstackFull(S)){ //调用3中的判满函数
return ERROR; //栈满,无法入栈
}
if(e=0){
S.V[++S.top[e]]=n; //n存到0号栈栈顶,栈顶元素右移
}else if(e==1){
S.V[--S.top[e]]=n; //n存到1号栈栈顶,栈顶元素左移
}
return OK;
}
//出栈
Status Pop(Dblstack &S,int n1,int n2){
if(DblstackEmpty(S)){ //调用2中的判空函数
return ERROR; //栈为空,无法出栈
}
n1=S.V[S.top[0]--]; //将0号栈栈顶元素的值赋值给e1,栈顶元素左移
n2=S.V[S.top[1]++]; //将1号栈栈顶元素的值赋值给e2,栈顶元素右移
return OK;
}