数据结构入门----栈的概念及应用

栈的逻辑结构

栈:限定仅在表尾进行插入和删除操作的线性表。(a1, a2, ……, an)

空栈:不含任何数据元素的栈。

允许插入和删除的一端称为栈顶,另一端称为栈底

插入:入栈、进栈、压栈

删除:出栈、弹栈

栈的操作特性:后进先出为原则LIFO

注意:栈只是对表插入和删除操作的位置进行了限制,并没有限定何时进行插入和删除操作。

栈的抽象数据类型定义

 InitStack (&S)  (构造空栈 ) 操作结果:构造一个空栈S。

 DestroyStack(&S)   (销毁栈结构) 初始条件:栈S已存在。 操作结果:栈S被销毁。

 ClearStack (&S)  (栈清空) 初始条件:栈S已存在。 操作结果:栈S清为空栈。

 StackEmpty (S)   (判空) 初始条件:栈S已存在。 操作结果:若栈S为空栈,则返回TRUE, 否则FALSE。

 StackLength (S) (求栈长) 初始条件:栈S已存在。 操作结果:返回S的元素个数,即栈的长度。

StackTraverse (S, visit( )) (遍历栈) 初始条件:栈S已存在且非空。 操作结果:从栈底到栈顶依次对S的每个数据元素调用函数visit()。一旦visit()失败,则操作失效。

 GetTop (S, &e)   (求栈顶元素) 初始条件:栈S已存在且非空。 操作结果:用e返回S的栈顶元素。

 Push (&S, e) (入栈) 初始条件:栈S已存在。 操作结果:插入元素e为新的栈顶元素。

 Pop (&S, &e)  (出栈) 初始条件:栈S已存在且非空。 操作结果:删除S的栈顶元素,并用e返回其值。

栈的顺序存储结构及实现

顺序栈——栈的顺序存储结构

确定用数组的哪一端表示栈底。

附设指针top指示栈顶元素的下一个位置。

进栈:top加1

出栈:top减1

栈空:top= base

栈满:top-base= stacksize

栈的顺序存储表示

#define  STACK_INIT_SIZE      100;
#define  STACKINCREMENT   10;
typedef  struct {
   SElemType   *base;//在栈构造之前和销毁之后,base的值为NULL。 
   SElemType   *top;//栈顶指针 
   int   stacksize;//当前已分配的存储空间,以元素为单位。 
}SqStack;

初始化栈

 Status  InitStack ( SqStack  &S){
    S.base=(SElemType *) alloc (STACK_INIT_SIZE *sizeof (SElemType);
    if (!S.base)  exit (OVERFLOW);  
    S.top=S.base
    S.stacksize= STACK_INIT_SIZE;
    return OK;
}

获取栈顶元素

Status  GetTop( SqStack  S, SElemType  &e){   
    if ( S.top == S.base )   return ERROR;
    e=*(S.top-1)
    return OK;
}

入栈

Status  Push ( SqStack  &S, SElemType  e){
    if ( S.top-S.base>=S.stacksize ){
        S.base=(SElemType*)realloc (S.base,(S.stacksize+STACKINCREMENT)*sizeof (SElemType);
        if (!S.base)  exit (OVERFLOW);  
        S.top=S.base + S.stacksize;
        S.stacksize+=STACKINCREMENT;
        } //if 
        *S.top++ = e;
        return OK;
}//Push

出栈

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

顺序栈S不存在的条件:    S.base=NULL;

顺序栈为 空 的条件 :       S.base=S.top;

顺序栈 满 的条件  :          S.top-S.base=S.stacksize;

栈的链式存储结构及实现

链栈:栈的链式存储结构

将链头作为栈顶,方便操作。链栈不需要附设头结点。

两种示意图在内存中对应同一种状态

//公共说明部分: 
typedef struct SNODE{
    SElemType   data; 
    struct snode *link;
}SNODE,*LinkStack;

LinkStack  top, p;
int  m=sizeof (SNODE); 

(以头指针为栈顶,在头指针处插入或删除!)

入栈

操作接口:Push(LinkStack &top, SElemType x);

Push ()  {
    p=(NODE*) malloc (m);
    if (!p){上溢}
    else {
    p->data=x; 
    p->link=top; //插入表头
    top=p;
    }
}

出栈

操作接口:  Pop(Push(LinkStack &top, SElemType &x );

pop(){
    if (top==NULL){下溢}
    else {
        x=top->data;
        p=top;
        top=top->link; //从表头删除
        free(p);
        }
}

①  链栈不必设头结点,因为栈顶(表头)操作频繁,只在栈顶插入和删除;

②  采用链栈存储方式,可使多个栈共享空间;当栈中元素个数变化较大,且存在多个栈的情况下,链栈是栈的首选存储方式。

顺序栈和链栈的比较

时间性能: 相同,都是常数时间O(1)。

空间性能:

顺序栈:有元素个数的限制和空间浪费的问题。

链栈:没有栈满的问题,只有当内存没有可用空间时才会出现栈满,但是每个元素都需要一个指针域,从而产生了结构性开销。

总之,当栈的使用过程中元素个数变化较大时,用链栈是适宜的,反之,应该采用顺序栈。

设计堆栈有什么独特用途?

调用函数或子程序非它莫属;

递归运算的有力工具;

用于保护现场和恢复现场;

简化了程序设计的问题。

栈与递归的实现

递归的定义

子程序(或函数)直接调用自己或通过一系列调用语句间接调用自己,是一种描述问题解决问题的基本方法。

递归的基本思想

问题分解:把一个不能或不好解决的大问题转化为一个或几个小问题,再把这些小问题进一步分解成更小的小问题,直至每个小问题都可以直接解决。  

递归的要素

⑴ 递归边界条件:确定递归到何时终止,也称为递归出口;

⑵ 递归模式:大问题是如何分解为小问题的,也称为递归体。

阶乘函数

//递归算法
int fact  ( int n )
{
    if ( n == 1 ) return 1;
    else return  n * fact (n-1);
}

递归过程与递归工作栈

递归过程在实现时,需要自己调用自己。

层层向下递归,返回次序正好相反:

递归的经典问题——汉诺塔问题

在世界刚被创建的时候有一座钻石宝塔(塔A),其上有64个金碟。所有碟子按从大到小的次序从塔底堆放至塔顶。紧挨着这座塔有另外两个钻石宝塔(塔B和塔C)。从世界创始之日起,婆罗门的牧师们就一直在试图把塔A上的碟子移动到塔C上去,其间借助于塔B的帮助。每次只能移动一个碟子,任何时候都不能把一个碟子放在比它小的碟子上面。当牧师们完成任务时,世界末日也就到了。

汉诺塔问题的递归求解:

如果 n = 1,则将这一个盘子直接从塔A移到塔 C 上。否则,执行以下三步:

⑴ 将塔A上的n-1个碟子借助塔C先移到塔B上;

⑵ 把塔A上剩下的一个碟子移到塔C上;

⑶ 将n-1个碟子从塔B借助于塔A移到塔C上。  

void Hanoi(int n, char A, char B, char C){  
     if (n==1) Move(A, 1,C); 
     else {                   
              Hanoi(n-1, A, C, B);
              Move(A, n,C);
              Hanoi(n-1, B, A, C);              
     }        
}
void Move (char A, int n, char C){  
  printf(“%i:把%i号盘子从%c移动到%c”,++c,n,A,C);  
}//把c定义为一个全局变量初值为0

递归函数的内部执行过程

递归函数的运行轨迹

⑴ 写出函数当前调用层执行的各语句,并用有向弧表示语句的执行次序

⑵ 对函数的每个递归调用,写出对应的函数调用,从调用处画一条有向弧指向被调用函数入口表示调用路线,从被调用函数末尾处画一条有向弧指向调用语句的下面表示返回路线

⑶ 在返回路线上标出本层调用所得的函数值。

 

代码实现:顺序栈的基本操作https://blog.csdn.net/qq1457346557/article/details/107122847

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值