数据结构编程笔记八:第三章 栈和队列 顺序栈和进位制程序的实现

来源:互联网 发布:淘宝香港aape正品店 编辑:程序博客网 时间:2024/05/16 04:39

这次我们一起看看一种特殊的线性表——栈的顺序存储结构实现。

还是老规矩:

程序在码云上可以下载。
地址:https://git.oschina.net/601345138/DataStructureCLanguage.git

栈是一个被限制了插入和删除操作发生位置的线性表。栈的插入和删除操作只能发生在线性表一端,这个位置称之为栈顶,与之相反的另一端称之为栈底。栈不允许从栈顶和栈底中间的任意位置插入和删除元素。这样的操作限制导致栈具备了“先进后出,后进先出”(FIFO)的特性。这也使得栈的可以执行的操作不像线性表那么多,那么随意了。

还要向大家强调一点:栈和队列是非常重要的两种数据结构,我们在后期的程序中会经常把栈和队列引入,作为一种工具来使用。比如:二叉树先序、中序、后续的非递归遍历,图的深度优先和广度优先遍历都会涉及到这两种数据结构中的一种,所以请大家一定亲自动手做一下栈和队列的程序,这对后期程序的理解和实现是有帮助的。

在计算机中,函数调用的过程必然会涉及到栈。细心的童鞋会发现,无论是嵌套调用还是递归调用,函数调用的顺序非常符合“先进后出”的特征。每调用一个函数,就要在运行时栈中压入这个函数的信息(包括实参和返回值)(压栈),函数运行结束后,这个函数的信息会从栈中弹出(弹栈)。非常像计算机中的一个概念“中断”。理解运行时栈的这一特性有利于理解函数递归调用的整个过程,也就会明白递归算法的效率为什么会比非递归的差了。这对于后期理解二叉树先序、中序、后续的递归遍历算法的执行过程有好处。好多老师讲课根本不会讲到这一知识点,这个知识点在书上56页有详细说明。很多时候程序运行发生了栈溢出(Java语言中的StackOverflowException),你就该明白那是什么意思了。

首先来看看栈的抽象数据类型定义:

ADT Stack{  数据对象:D ={ai | ai∈Elemset,(i=1,2,…,n, n≥0)}  数据关系:R1 = {<ai-1,ai>|ai-1,ai ∈ D,(i=2,3,…,n)}                     约定an为栈顶, a1为栈底。  基本操作:       InitStack(&S)          操作结果:构造一个空的栈S。       DestroyStack(&S)          初始条件: 栈S已经存在。          操作结果: 销毁栈S。       ClearStack(&S)          初始条件: 栈S已经存在。          操作结果: 将栈S重置为空栈。       StackIsEmpty(S)          初始条件: 栈S已经存在。          操作结果: 若栈S为空栈,则返回TURE;否则返回FALSE。       StackLength(S)          初始条件: 栈S已经存在。          操作结果: 返回栈S中的数据元素个数。       GetTop(S, &e)          初始条件: 栈S已经存在且非空。          操作结果: 用e返回栈S中栈顶元素的值。       Push(&S, e)       //入栈操作          初始条件: 栈S已经存在。          操作结果: 插入元素e为新的栈顶元素。       Pop(&S, &e)       //出栈操作          初始条件: 栈S已经存在且非空。          操作结果: 删除S的栈顶元素并用e返回其值。       StackTraverse(S, visit ())          初始条件: 栈S已经存在且非空。          操作结果: 从栈底到栈顶依次对S的每个元素调用函数visit ()。一旦visit ()失败,则操作失败。      }ADT Stack

一起来看看程序的实现。

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>引入头文件<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<#include <stdio.h>   //使用了标准库函数 #include <stdlib.h>  //使用了动态内存分配函数 //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>自定义符号常量<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< #define STACK_INIT_SIZE 100  //存储空间初始分配量 #define STACKINCREMENT 10    //存储空间分配增量 #define OVERFLOW -2         //内存溢出错误常量#define OK 1                //表示操作正确的常量 #define ERROR 0             //表示操作错误的常量//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>自定义数据类型<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<typedef int Status;       //用typedef给int起个别名,也便于程序的维护 typedef int SElemType;   //用typedef给int起个别名,也便于程序的维护typedef struct {     //栈的顺序存储表示     SElemType *base;            //栈底指针,在栈构造之前和销毁之后,base的值为NULL     SElemType *top;             //栈顶指针    int stacksize;              //当前已分配的存储空间,以元素为单位 }SqStack; //-------------------------------------------顺序栈的主要操作-----------------------------------------//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>1.初始化顺序栈<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< /*    函数:InitStack_Sq    参数:SqStack &S 顺序栈引用      返回值:状态码,OK表示操作成功     作用:构造一个空的顺序栈 */Status InitStack_Sq(SqStack &S){    //动态申请顺序栈的内存空间,并检查内存空间是否成功分配    //if(!(S.base = (SElemType *)malloc(STACK_INIT_SIZE * sizeof(SElemType))))    //这句代码相当于以下两行代码:    //S.base = (SElemType *)malloc(STACK_INIT_SIZE * sizeof(SElemType));    //if(!S.base)  <=>  if(S.base == NULL)     if(!(S.base = (SElemType *)malloc(STACK_INIT_SIZE * sizeof(SElemType)))){        printf("内存分配失败,程序即将退出!\n");        exit(OVERFLOW);    }//if    //由于刚动态分配完的栈是空栈,所以栈顶指针和栈底指针都指向栈底       S.top = S.base;    //栈的大小就是栈的初始化大小参数STACK_INIT_SIZE     S.stacksize = STACK_INIT_SIZE;    //操作成功     printf("顺序栈S所需内存已分配成功!\n");    return OK; }//InitStack_Sq//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>2.销毁顺序栈<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<</*    函数:DestoryStack_Sq    参数:SqStack &S 顺序栈引用      返回值:状态码,OK表示操作成功     作用:释放顺序栈S所占内存空间 */Status DestoryStack_Sq(SqStack &S){    //栈底指针保存的是顺序栈内存空间的首地址     free(S.base);    //操作成功     printf("顺序栈内存释放成功!\n");     return OK; }//DestoryStack_Sq//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>3.置空顺序栈<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<</*    函数:ClearStack_Sq    参数:SqStack &S 顺序栈引用      返回值:状态码,OK表示操作成功     作用:将顺序栈S中的元素清空*/Status ClearStack_Sq(SqStack &S){    //只需要重新设置栈顶指针到初始位置,保留现有空间    //栈顶指针和栈底指针都指向栈底表示此栈是空栈     S.top = S.base;    //操作成功     return OK; }//ClearStack_Sq//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>4.判断顺序栈是否为空<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<</*    函数:StackIsEmpty_Sq    参数:SqStack S 顺序栈S     返回值:若顺序栈S是空栈返回1,否返回0     作用:判断顺序栈S是否为空栈*/Status StackIsEmpty_Sq(SqStack S){    //栈顶指针和栈底指针都指向栈底表示此栈是空栈     return S.top == S.base; }//StackIsEmpty_Sq//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>5.获取顺序栈的长度<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<</*    函数:StackLength_Sq    参数:SqStack S 顺序栈S     返回值:若顺序栈S是空栈返回1,否返回0     作用:判断顺序栈S是否为空栈*/Status StackLength_Sq(SqStack S){    //栈的长度就是栈顶指针和栈底指针之间的元素个数     return (S.top - S.base); }//StackLength_Sq//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>6.获得栈顶元素的值<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<</*    函数:GetTop    参数:SqStack S 顺序栈S    返回值:成功获取顺序栈S栈顶元素的值后返回OK,否则返回ERRROR    作用:用e返回栈顶元素的值,但是栈顶元素不做出栈操作 */Status GetTop(SqStack S, SElemType &e){    //空栈没有栈顶元素,所以要先判断栈是否为空     //注意栈是否为空和栈是否存在不是一个概念,所以不可以用     //S.base != NULL判断栈是否为空     if(StackIsEmpty_Sq(S)) {          return ERROR;    }//if    //注意:栈顶指针指向栈顶元素的下一个位置    e = *(S.top - 1);      /*   注意:此处不能使用“e = *(--S.top); ”的原因          1. --S.top自减操作改变了栈顶指针本身的指向,使得该指针向前移动一位,相当于删除了原来栈中的最后一个元素(最后一个元素出栈);          2. S.top-1 仅仅表示栈顶指针的上一个位置,并没有改变S.top的值,*(S.top-1)表示取栈顶指针前一个位置的值,即栈顶元素的值           3. 这两种写法造成的结果是不同的,如果是纯代数运算,两者没有差别,但在指向数组            (顺序结构在C语言中是用一维数组描述的)的指针变量运算中,这两个表达式有特殊含义             在指针运算中,“指针变量-1 ”表示该指针变量所指位置的前一个位置,            这种做法并不改变指针变量本身的值。             --指针变量   不仅使得该指针指向原来所指位置的上一个位置, 还修改了指针变量本身的值            在栈中,栈顶指针和栈底指针所指向的位置有特殊的含义,故两者不等价。            */      //操作成功      return OK; }//GetTop_Sq//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>7.在顺序栈中插入元素(入栈)<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<</*    函数:ReallocStack_Sq    参数:SqStack &S 顺序栈S引用     返回值:状态码,操作成功返回OK,否则返回ERRROR    作用:将栈S扩容,每扩容一次,栈的大小增加STACKINCREMENT*/Status ReallocStack_Sq(SqStack &S){    //为顺序栈重新分配内存(扩容),扩展的空间大小是STACKINCREMENT    /*if(!(S.base = (SElemType *)realloc(S.base,                 (STACK_INIT_SIZE + STACKINCREMENT) * sizeof(SElemType))))      这句代码相当于:      S.base = (SElemType *)realloc(S.base,                         (STACK_INIT_SIZE + STACKINCREMENT) * sizeof(SElemType));      if(!S.base) <=> if(S.base == NULL)    */    if(!(S.base = (SElemType *)realloc(S.base,                (STACK_INIT_SIZE + STACKINCREMENT) * sizeof(SElemType)))){        printf("内存分配失败,程序即将退出!\n");        exit(OVERFLOW);    }//if    //由于扩容前栈已经满了,所以栈顶指针位置就是栈底指针+原来栈的大小     S.top = S.base + S.stacksize;    //扩容后,栈的大小增加了STACKINCREMENT     S.stacksize += STACKINCREMENT;    //操作成功     printf("顺序栈S所需内存已重新分配成功!\n");    return OK; }//ReallocStack_Sq/*    函数:Push_Sq    参数:SqStack &S 顺序栈引用          SElemType e 被插入的元素e     返回值:成功获取顺序栈S栈顶元素的值后返回OK,否则返回ERRROR    作用:(入栈、压栈)插入元素e为新的栈顶元素*/Status Push_Sq(SqStack &S, SElemType e){     //入栈时发现栈满了,就要追加存储空间(扩容)     if(S.top - S.base >= S.stacksize) {          //调用扩容函数        ReallocStack_Sq(S);    }//if    //插入前,栈顶指针指向当前栈顶元素的下一个位置     //将e赋值给栈顶指针所指存储空间(插入元素e),栈顶指针后移    //*S.top++ = e;  <=>  *(S.top) = e;  S.top++;     *S.top++ = e;}//Push_Sq//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>8.在顺序栈中删除元素(出栈)<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<</*    函数:Pop_Sq    参数:SqStack &S 顺序栈引用          SElemType &e 带回被删除的元素值e     返回值:删除成功返回OK,否则返回ERRROR    作用:(出栈,弹栈)若栈不空,则删除S的栈顶元素,用e返回其值*/Status Pop_Sq(SqStack &S, SElemType &e){     //在空栈中执行出栈操作没有意义,所以要判断栈是否为空    //注意栈是否为空和栈是否存在不是一个概念,所以不可以用     //S.base != NULL判断栈是否为空     if(StackIsEmpty_Sq(S)) {          return ERROR;    }//if    //删除前,栈顶指针指向当前栈顶元素的下一个位置    //--S.top;之后,栈顶指针刚好指向被删除元素     //栈顶指针前移,保存被删除的元素值到e    //e=*--S.top;  <=>  --S.top;   e=*(S.top);    e = *--S.top;    //操作成功     return OK; }//Pop_Sq//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>9.遍历整个顺序栈<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<</*    函数:Print    参数:ElemType e 被访问的元素     返回值:状态码,操作成功返回OK,操作失败返回ERROR     作用:访问元素e的函数,通过修改该函数可以修改元素访问方式,          该函数使用时需要配合遍历函数一起使用。 */Status Print(SElemType e){    printf("%5d  ", e);    return OK;}//Print /*    函数:StackTraverse_Sq    参数:SqStack S 顺序栈S           Status(* visit)(SElemType) 函数指针,指向元素访问函数。     返回值:状态码,操作成功返回OK,操作失败返回ERROR     作用:调用元素访问函数按出栈顺序完成顺序栈的遍历,但并未真正执行出栈操作 */Status StackTraverse_Sq(SqStack S, Status(* visit)(SElemType)) {    //在空栈中执行遍历操作没有意义,所以要判断栈是否为空    //注意栈是否为空和栈是否存在不是一个概念,所以不可以用     //S.base != NULL判断栈是否为空     if(StackIsEmpty_Sq(S)) {        printf("此栈是空栈");         return ERROR;    }//if    //调用元素访问函数依次访问栈中的每个元素    for(int i = 0; i < StackLength_Sq(S); ++i){        //调用元素访问函数,一旦访问失败则退出          if(!visit(S.base[i])) {            return ERROR;        }//if     }//for    //输出换行,是控制台显示美观     printf("\n");    //操作成功     return OK;}//StackTraverse_Sq//-------------------------------------------主函数--------------------------------------------------- int main(int argc,char *argv[]){    SqStack S;    SElemType e;    int tmp,tmp1=0,i,n;    while(1){        printf("\n*****************************顺序栈完整版测试程序******************************\n");        printf("->1.初始化顺序栈S\n");        printf("->2.清空顺序栈S\n");        printf("->3.输出顺序栈S的所有元素\n");        printf("->4.查看栈的长度(元素个数)\n");        printf("->5.获得栈顶元素的值\n");        printf("->6.在顺序栈中插入元素(入栈)\n");        printf("->7.在顺序栈中删除元素(出栈)\n");        printf("->8.进位制转换程序\n");        printf("->9.销毁顺序栈S\n");        printf("->10.退出程序\n");         while(1){            printf("请输入您想要操作的代号:");            scanf("%d",&tmp);            switch(tmp){                case 1:{                     if(tmp1){//若栈已经被初始化并且栈中含有元素                        printf("顺序栈已经被创建,正在销毁...\n");                        DestoryStack_Sq(S);                     }//if                     InitStack_Sq(S);                     printf("您想为顺序栈S输入几个元素?\n");                      scanf("%d",&n);                     printf("请输入顺序栈S的所有元素,每个元素用空格隔开:\n");                     for(i=0;i<n;i++){                        scanf("%d",&e);                        Push_Sq(S,e);                     }//for                     printf("创建后的顺序栈S的结果为:\n");                     StackTraverse_Sq(S, Print);                     printf("顺序栈S创建成功!\n");                     tmp1=1;                     break;                }//case 1                case 2:{                     if(!tmp1){                        printf("顺序栈S不存在,您可能还未创建,请在创建(执行操作1)后执行该操作!\n\n");                        break;                     }//if                        ClearStack_Sq(S);                     printf("栈已清空,长度为%d\n",StackLength_Sq(S));                     break;                }//case 2                case 3:{                     if(!tmp1){                        printf("顺序栈S不存在,您可能还未创建,请在创建(执行操作1)后执行该操作!\n\n");                        break;                     }//if                     if(!StackLength_Sq(S)){                        printf("栈可能已被清空或删光,没有元素可供输出,请初始化后执行操作!\n");                        break;                     }//if                     printf("顺序栈S内的全部元素为:\n");                     StackTraverse_Sq(S, Print);                     break;                }//case 3                case 4:{                     if(!tmp1){                        printf("顺序栈S不存在,您可能还未创建,请在创建(执行操作1)后执行该操作!\n\n");                        break;                     }//if                     printf("顺序栈S的长度为%d\n",StackLength_Sq(S));                     break;                }//case 4                case 5:{                                  if(!tmp1){                        printf("顺序栈S不存在,您可能还未创建,请在创建(执行操作1)后执行该操作!\n\n");                        break;                     }//if                     if(!StackLength_Sq(S)){                        printf("栈可能已被清空或删光,没有元素可供输出,请初始化后执行操作!\n");                        break;                     }//if                     if(GetTop(S,e))                        printf("栈顶元素的值为%5d\n",e);                       break;               }//case 5               case 6:{                     if(!tmp1){                        printf("顺序栈S不存在,您可能还未创建,请在创建(执行操作1)后执行该操作!\n\n");                        break;                     }//if                     printf("您想为顺序栈S插入几个元素?\n");                      scanf("%d",&n);                     printf("插入前栈S内的全部元素为:\n");                     StackTraverse_Sq(S, Print);                     printf("请依次输入您想插入的所有元素,每个元素用空格隔开:\n");                     for(i=0;i<n;i++){                        scanf("%d",&e);                        Push_Sq(S,e);                     }//for                     printf("插入后栈S内的全部元素为:\n");                     StackTraverse_Sq(S, Print);                     break;               }//case 6               case 7:{                     if(!tmp1){                        printf("顺序栈S不存在,您可能还未创建,请在创建(执行操作1)后执行该操作!\n\n");                        break;                     }//if                     if(!StackLength_Sq(S)){                        printf("栈可能已被清空或删光,没有元素可供输出,请初始化后执行操作!\n");                        break;                     }//if                     printf("您想在栈中删除几个元素?\n");                      scanf("%d",&n);                     printf("删除前栈S内的全部元素为:\n");                     StackTraverse_Sq(S, Print);                     for(i=0;i<n;i++){                        Pop_Sq(S,e);                      }//for                        printf("删除后栈S内的全部元素为:\n");                        StackTraverse_Sq(S, Print);                                      break;               }//case 7               case 8:{                     if(!tmp1){                        free(S.base);                        break;                     }//if                     int i;                     InitStack_Sq(S);                     printf("请输入一个十进制整数:\n");                     scanf("%d",&n);                     printf("您想把它转化成几进制,请输入一个不超过16的正整数:\n");                     scanf("%d",&i);                     if(i<=0||i>16){                        printf("您输入的数字不合法\n");                        break;                     }//if                     while(n){                        Push_Sq(S,n%i);                        n=n/i;                     }//while                     printf("您想要的%d进制数为:",i);                     while(!StackIsEmpty_Sq(S)){                        Pop_Sq(S,e);                        switch(e){                            case 1:       case 2:       case 3:      case 4:       case 5:                            case 6:       case 7:       case 8:      case 9:                            {           printf("%d",e);   break;  }                            case 10:{   printf("A");      break;  }                            case 11:{   printf("B");      break;  }                            case 12:{   printf("C");      break;  }                            case 13:{   printf("D");      break;  }                            case 14:{   printf("E");      break;  }                            case 15:{   printf("F");      break;  }                             default :{  printf("程序出现错误!\n"); break;   }                        }//switch                     }//while                     printf("\n");                     DestoryStack_Sq(S);                     break;               }//case 8               case 9:{                     if(!tmp1){                        printf("顺序栈S不存在,您可能还未创建,请在创建(执行操作1)后执行该操作!\n\n");                        break;                     }//if                     printf("->销毁顺序栈S:");                      DestoryStack_Sq(S);                     tmp1=0;                     break;               }//case 9               case 10:{                     printf("程序退出,再见!\n\n");                     exit(0);                     break;               }//case 10               default:{                     printf("您的输入非法,请重新输入!\n\n");                     break;                }//default            }//switch            break;          }//while          system("pause");         system("cls");  //清屏         fflush(stdin);  //清空标准输入缓冲区,避免多输入字符(如回车、空格)造成影响     }//while            return 0;}

以下是程序运行时的部分输出和我输入的测试数据:

注:程序中涉及到菜单刷新,这里不再重复截取菜单,只保留输入数据和程序的结果输出 *****************************顺序栈完整版测试程序******************************->1.初始化顺序栈S->2.清空顺序栈S->3.输出顺序栈S的所有元素->4.查看栈的长度(元素个数)->5.获得栈顶元素的值->6.在顺序栈中插入元素(入栈)->7.在顺序栈中删除元素(出栈)->8.进位制转换程序->9.销毁顺序栈S->10.退出程序请输入您想要操作的代号:1顺序栈S所需内存已分配成功!您想为顺序栈S输入几个元素?5请输入顺序栈S的所有元素,每个元素用空格隔开:1 4 5 9 2创建后的顺序栈S的结果为:    1      4      5      9      2顺序栈S创建成功!请按任意键继续. . .请输入您想要操作的代号:3顺序栈S内的全部元素为:    1      4      5      9      2请按任意键继续. . .请输入您想要操作的代号:4顺序栈S的长度为5请按任意键继续. . .请输入您想要操作的代号:5栈顶元素的值为    2请按任意键继续. . .请输入您想要操作的代号:6您想为顺序栈S插入几个元素?2插入前栈S内的全部元素为:    1      4      5      9      2请依次输入您想插入的所有元素,每个元素用空格隔开:45 67插入后栈S内的全部元素为:    1      4      5      9      2     45     67请按任意键继续. . .请输入您想要操作的代号:7您想在栈中删除几个元素?3删除前栈S内的全部元素为:    1      4      5      9      2     45     67删除后栈S内的全部元素为:    1      4      5      9请按任意键继续. . .请输入您想要操作的代号:8顺序栈S所需内存已分配成功!请输入一个十进制整数:45您想把它转化成几进制,请输入一个不超过16的正整数:16您想要的16进制数为:2D顺序栈内存释放成功!请按任意键继续. . .请输入您想要操作的代号:2栈已清空,长度为0请按任意键继续. . .请输入您想要操作的代号:3栈可能已被清空或删光,没有元素可供输出,请初始化后执行操作!请按任意键继续. . .请输入您想要操作的代号:9->销毁顺序栈S:顺序栈内存释放成功!请按任意键继续. . .请输入您想要操作的代号:10程序退出,再见!--------------------------------Process exited with return value 0Press any key to continue . . .

总结:
本次的程序涉及到很多的编程技巧,尤其是对指针变量做++和–操作要格外小心,因为它们与普通的变量做++和–操作的含义和后果都不一样,稍有不慎就会惹出麻烦。

下次的文章会介绍另一种特殊的线性表——队列的链式存储实现。请大家继续关注我的博客,期待下次再见!

阅读全文
0 0
原创粉丝点击