数据结构基础与算法图解(3)—— 栈

3. 栈

3.1 栈的逻辑结构

栈是限定插入和删除只能在“固定端”进行的线性表。

ADT Stack{
    数据对象:
        D={ai|ai∈ElemSet,i=1,2,....n,n>=0}
    数据关系:
        R1={<ai-1,ai>|ai-1,ai∈D,i=2,...,n}
    基本操作:
        InitStack(&S)
        DestroyStack(&S)
        StackLength(S)
        StackEmpty(S)
        GetTop(S,&e)
        ClearStack(&S)
        Push(&S,e)
        Pop(&S,e)
        StackTravers(S,visit())
}ADT Stack

3.1.1 例题

  1. 栈中的元素是先进先出,后进后出(x)
  2. 栈有1个插入位置和2个删除位置(x)
  3. 栈底不进行元素的删除操作(X)

3.2 栈的顺序存储表示

在这里插入图片描述

#define STACK_INIT_SIZE 100;
#define STACKINCREMENT 10;
//栈的顺序存储表示
typedef struct{
    SElemType *base;
    SElemType *top;
    int stacksize;
}SqStack;

//顺序栈的初始化
Status InitStack(SqStack &S){
    //构造一个空栈S
    S.base=(ElemType*)malloc(STACK_INIT_SIZE*sizeof(ElemType));
    if(!S.base)exit(OVERFLOW);//存储分配失败
    S.top=S.base;//栈空的标志
    S.stacksize=STACK_INIT_SIZE;
    return OK;
}
//顺序栈的插入
Status Push(SqStack &S,SElemType e){
    if(S.top-S.base>=S.stacksize){
        S.base=(ElemType*)realloc(S.base,(S.stacksize+STACKINCREMENT)*sizeof(ElemType));
        if(!S.base)exit(OVERFLOW);
        S.top=S.base+S.stacksize;
        S.stacksize+=STACKINCREMENT;
    }
    *S.top++=e;
    return OK;
}

//顺序栈的删除
Status Pop(SqStack &S,SElemType e){
	//若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK
    //否则返回ERROR
    if(S.top==S.base)return ERROR;
    e=*--S.top;
    return OK;
}

3.2.1例题

  1. 顺序栈中的元素进制的顺序是 1、2、3、4、5、6,那么元素出栈的顺序不可能是

    2 3 5 1 4 6

3.3 栈的应用

3.3.1 编辑命令行

“每接受一个字符即存入存储器” 并不恰当!

在用户输入一行的过程中,运行用户输入出 差错,并在发现有误时可以即时更正。

合理的作法是:设立一个输入缓冲区,用以接受用户输入的一行字符,然后逐行存入用户数据区,并假设“#”为退格符,“@”为退行符。

假设从终端接受了这样两行字符:

whli##ilr#e(s#*s)

outcha@putchar(*s=#++);

实际有效的是这两行:

while(*s)

putchar(*s++);

编辑命令行的算法

void LineEdit(){
    InitStack(S);
    ch=getchar();   //从终端接受下一个字符
    while(ch!=EOF){  //以EOF为全文结束符
        while(ch!=EOF&&ch!='\n'){  //读入每一行
            switch(ch){
                    case'#':Pop(S,c);break;
                    case'@':ClearStack(S);break;
                default:Push(S,ch);break;
            }
            ch=getchar();//从终端接受下一字符
        }
        //将从栈底到栈顶的字符传送至调用过程的数据区
        ClearStack(S);  //重置S为空栈
        if(ch!=EOF)ch=getchar();
    }
    DestroyStack(S);
}//LineEdit

3.3.1.1 例题

假设从终端接受了这样两行字符:

*s.#.top####-+#>s#top:=##=x;

top+@o#s-><#top++;

实际的有效输入:

*s->top=x;

s->top++;

3.3.2 数值转换

算法原理:N=(N div d)x d+N mod d

//数值转换算法
void conversion(){
    InitStack(S);
    scanf("%d",N);
    while(N){
        Push(S,N%8);
        N=N/8;
    }
    while(!StackEmpty(S)){
        Pop(S,e);
        printf("%d",e);
    }
}

3.3.3 括号匹配

原理:

  1. 凡是出现左括弧,则进栈

  2. 凡是出现右括弧:

    若栈空: 则多余

    若栈不空,则与栈顶元素比较,若相匹配,则左括弧出栈,否则不匹配

  3. 表达式检验结束时,若栈空,则表明表达式中匹配正确,否则表明“左括弧”有余。

bool BracketCheck(char str[],int length)
{
	SqStack s;
	InitStack(s);
	int i;
	for(i=0;i<length;i++){
		if(str[i]=="("||str[i]=="["||str[i]=="{"){//左括号进栈 
			push(&s,str[i]);
		}
		else{
			if(StackEmpty(&s))//如果是右括号且栈空,则不匹配 
				return false;
			char topElem;
			Pop(&s,topElem); //判断栈顶的左括号与右括号是否匹配 
			if(str[i]==")"&&topElem!="(")
			 	return flase;
			if(str[i]=="]"&&topElem!="[")
			 	return flase;
			if(str[i]=="}"&&topElem!="{")
			 	return flase;
		}
	} 
	return StackEmpty(&s);//如果栈空,则匹配成功 
}
3.3.3.1 例题

< ( { [ ] } ( [ ( ) ] ) ) > √
{ ( ( ) [ < >]){ } { } X
( ( ) } [ < >]){ } { } X

3.3.4 迷宫求解

常用穷举求解的方法

若当前位置“可通”,则纳入路径,继续前进;
若当前位置“不可通”,则后退,换方向继续探索;
若四周“均无通路”,则将当前位置从路径中删除出去。

求迷宫中一条从入口到出口的路径的算法:
设定当前位置的初值为入口位置;
 do{
   若当前位置可通,
   则{将当前位置插入栈顶; 
           若该位置是出口位置,则算法结束;            
           否则切换当前位置的东邻方块为
                   新的当前位置;
   }
   否则 {  ……
   }
}while (栈不空);
若栈空,则表明迷宫没有通路。
     // ……  
      若栈不空且栈顶位置尚有其他方向未被探索,
          则设定新的当前位置为: 沿顺时针方向旋转找到的栈顶位置的下一相邻块;
      若栈不空但栈顶位置的四周均不可通,
      则{删去栈顶位置;      // 从路径中删去该通道块                   
              若栈不空,则重新测试新的栈顶位置,
                     直至找到一个可通的相邻块或出栈至栈空;
           }

3.3.5 表达式求值

  • 运算符的优先级等级比较
c1\c2+-*/()#
+>><<<>>
->><<<>>
*>>>><>>
/>>>><>>
(<<<<<=
)>>>>>>
#<<<<<>

1.建立一个运算符栈和一个操作数栈。
2.首先在oprt栈中压入一个‘#’,用以提示程序表达式的边界。
3.从左向右扫描
①如果读到的是数字字符,先将这些数字字符按顺序存储到缓冲区(temp栈),直到读到运算符时,将缓冲区的数字按照权重相加,构造出完整的运算数,压入运算数栈中去,同时清空temp栈。
②如果读到的是运算符,那么按照规则比较oprt栈顶运算符和此运算符的优先级:(下列三种情况)
a.oprt栈顶运算符高于此运算符,则表明此时已经可以进行计算。将num栈依次出栈两个元素A、B,计算B(op)A,将结果压入num栈。
b.oprt栈顶运算符低于此运算符,则先将这个运算符入栈,然后继续读取。
c.oprt栈顶运算符优先级等于此运算符,例如左右括号匹配时、表达式头‘#’和尾‘#’匹配时,则不将此运算符入栈,反而将oprt栈顶运算符出栈。
重复上述①②操作,直到oprt栈顶元素为‘#’或读取到的字符为‘#’(结束)。此时num栈顶元素即为此表达式的结果。

OperandType EvaluateExpression(){
	InitStack(OPTR,"#");//OPTR是运算数栈
	InitStack(OPND);c=getchar();//OPND是运算符栈
	while(c!='#'||GetTop(OPTR)!='#'){
	if(!ln(c,OP)){
	Push(OPND,c);
	c=getchar();
	else{
	
	}//while
	return GetTop(OPND)
	}
	}
	Push(OPTR)
}
//
switch(Precede(GetTop(OPTR),c)){
        case'<':Push(OPTR,c);c=getchar();break;
        case'=':Push(OPTR,x);c=getchar();break;
        case'>':Pop(OPTR,theta);Pop(OPND,b);Pop(OPND,a);//退栈
        Push(OPND,Operate(a,theta,b));break;//将运算符结果入栈
}//switch
3.3.5.1 前缀表达式

前缀表达式:- + 1 2 × 3 4 (最后一个运算符与右边两个操作数结合进行运算,依次计算下去)

  • 从右往左扫描表达式扫到数字就让数字入栈,
  • 扫到运算符就让栈顶的两个数出栈,用该运算符对它们做相应的计算,并将结果入栈
  • 重复上述过程直到表达式最左端,最后运算得出的值即为表达式的结果
3.3.5.2 中缀表达式

中缀表达式:1+2-3×4

计算过程 如上述代码;

3.3.5.3 后缀表达式

后缀表达式:3 4 × 1 2 + - (第一个运算符与左边两个操作数结合进行运算,依次计算下去)

计算过程与前缀表达式类似,只是顺序是从左至右:

  • 从左至右扫描表达式遇到数字时,将数字压入堆栈,
  • 遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(次顶元素op 栈顶元素 ),并将结果入栈
  • 重复上述过程直到表达式最右端,最后运算得出的值即为表达式的结果

3.4 用栈实现递归

  • 当在一个函数的运行期间调用另一个函数时,在运行该被调用函数之前,需先完成三项任务:

    ​ 将所有的实在参数、返回地址等信息传递给被调用函数保存;

    ​ 为被调用函数的局部变量分配存储区;

    ​ 将控制转移到被调用函数的入口。

  • 从被调用函数返回调用函数之前,应该完成下列三项任务:

    ​ 保存被调函数的计算结果;
    释放被调函数的数据区;
    依照被调函数保存的返回地址将控制转移到调用函数。

后调用先返回 !

汉诺塔问题:

void hanoi (int n, char x, char y, char z) {
// 将塔座x上按直径由小到大且至上而下编号为1至n
// 的n个圆盘按规则搬到塔座z上,y可用作辅助塔座。
   if (n==1)
   move(x, 1, z);   // 将编号为1的圆盘从x移到z
  else {
    hanoi(n-1, x, z, y);  // 将x上编号为1至n-1的圆盘移到y, z作辅助塔
    move(x, n, z);         // 将编号为n的圆盘从x移到z
    hanoi(n-1, y, x, z);  // 将y上编号为1至n-1的圆盘移到z, x作辅助塔
  }
 }

3.5八皇后问题

#include<stdio.h>
#define  n  8   // n为皇后个数,m为摆法计数
int m=0,a[n];  // a[i]存放第i个皇后放置的行号
int ok(int i, int j)  //检查(i,j)上能否放棋子
{  j1=j; i1=i;  ok1=1;  //检查第i行上能否放棋子
  while( (j1>1)&&ok1)
       {j1--;         ok1=a[j1]!=i ;}
   j1=j; i1=i;    //检查对角线上能否放棋子
  while( (j1>1)&&(i1>1)&&ok1) 
       {j1--; i1--; ok1=a[j1]!=i1 ;} 
   j1=j; i1=i;     //检查另一对角线上能否放棋子
   while( (j1>1)&&(i1<n)&&ok1) 
         {j1--; i1++; ok1=a[j1]!=i1 ;}
   return  ok1
}
Void  queen(int j) //从第j列开始逐个试探
{  if (j>n) 
      { m++;  printf("m=%d   ",m);
         for (i=1;i<=n;i++)  printf("    %d",a[i]);
         printf(“\n”);
       }    
   else  for( i=1; i<=n;i++)
             if(ok(i,j))    //检查(i,j)上能否放棋子
                { a[j]=i;  //在(i,j)上放一个棋子
                   queen(j+1) ;
                 } 
}
main()
  {queen(1);}

3.6 四色问题

​ “四染色”定理是计算机科学中著名的定理之一。使地图中相邻的国家或行政区域不重色,最少可用四种颜色对地图着色。利用栈采用回溯法对地图着色。
​ 思想:对每个行政区编号:1-7;对颜色编号;a、b、c、d。从第一号行政区开始逐一染色,每一个区域逐次用四种颜色进行试探,若所取颜色与周围不重,则用栈记下来该区域的色数,否则依次用下一色数进行试探。若出现a-d均与周围发生重色,则需退栈回溯,修改当前栈顶的色数。

void  mapcolor(int R[ ][ ],int n,int s[ ])
{  s[1]=1;        // 1号区域染1色
    a=2;  J=1;    // a为区域号,J为染色号
    while ( a<=n)
    { while(( J<=4)&&(a<=n))  
         { k=1;     // k表示已经着色的区域号
           while(( k<a)&&(s[k]*R[a-1][k-1]!=J))    k=k+1;  // 若不相邻,或若相邻且不重色,对下一个相邻已染色区域判断                
            if (k<a)   J=J+1//相邻且重色
           else { s[a]=J;  a=a+1;  J=1; } //相邻且不重色
          }
       if (J>4) { a=a-1; J=s[a]+1  } //回到上一个区域,选择下一号颜色试探
    }
 }

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zyw2002

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值