数据结构_栈和队列

/********************************************************************************************************************/

声明:

  (1)*.h文件是代码声明, *.cpp文件是代码实现;

  (2)一般头文件的内容有: ①类型声明; ②函数声明; ③枚举; ④常量; ⑤宏

  (3)以下说明是为了方便代码文件的管理而设定的一些规则, 以后代码都会按照此规则编写:

    1)Pubuse.h 是几乎所有实验中都涉及到的, 包括一些常量定义, 系统函数原型声明和类型, Status重定义, 结果状态代码等;

    2)数据结构定义: 以Def.h为文件名;

    3)基本操作和算法: 以Algo.h为文件名;

    4)调用基本操作的主程序: 以Use.cpp为文件名;

    以上四个文件完成一个程序. 为了方便用户,再特地写一个cpp_1.cpp, 为单文件可运行程序.

/*-----------------------------此声明借鉴CSDN上"Rain-晴天"博主的规则,特此申明-------------------------------------/

/*********************************************************************************************************************/

<一>栈

1. 定义

栈(stack)是限定仅在表尾进行插入或删除操作的线性表. 因此, 对栈来说, 表尾端有其特殊含义, 称为栈顶(top), 相应的, 表头端称为栈底(bottom). 不含元素的空表称为空栈.

栈的修改是按后进先出的原则进行的, 因此, 栈又称为后进先出(last in first out)的线性表(简称LIFO结构).插入元素的操作称为入栈, 删除栈顶元素的操作称为出栈.

和线性表类似, 栈也有两种存储表示方法.

顺序栈, 即栈的顺序存储结构是利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素, 同时附设指针top指示栈顶元素在顺序栈中的位置. 通常的习惯做法是以top=0表示空栈, 先为栈分配一个基本容量, 然后在应用过程中, 当栈的空间不够使用时再逐段扩大. 为此设定两个常量:STACK_INIT_SIZE(存储空间初始分配量)和STACKINCREMENT(存储空间分配增量).

栈的初始化操作为: 按设定的初始分配量进行第一次存储分配, base可称为栈底指针, 在顺序栈中, 它始终指向栈底的位置, 若base的值为NULL, 则表明栈结构不存在. 称top为栈顶指针, 其初值指向栈底, 即top=base可作为栈空的标记, 每当插入一个新的栈顶元素时, 指针top增1; 删除栈顶元素时, 指针top减1, 因此, 非空栈的栈顶指针始终在栈顶元素的下一位置上.

2. 实现代码

 

/*    Pubuse.h*/
  //    几乎所有实验中都涉及到的,包含一些常量定义,系统函数原型声明和类型,重定义(typedef)和结果代码等
  //==================================系统函数原型声明===================================
 #include<string>  
 #include<ctype.h>  
 #include<malloc.h>
 #include<limits.h>  
 #include <stdio.h>
 #include <stdlib.h>
 #include<io.h>
 #include<iostream>
 #include<math.h> 
 #include<process.h>
 using namespace std;

 //==================================函数结果状态代码=================================== 
 #define TRUE             1  
 #define FALSE            0  
 #define OK               1  
 #define ERROR            0  
 #define INFEASIBLE      -1  
 //#define OVERFLOW      -2 
 //    因为在math. h 中已定义OVERFLOW 的值为3,故去掉此行
 
 //================================类型重定义代码========================================
 typedef int Status; 
 //    Status 是函数的类型,其值是函数结果状态代码,如OK 等
 typedef int Boolean;
 //    Boolean 是布尔类型,其值是TRUE 或FALSE  
 /*    Def.h*/
  //    数据结构定义
  //=========================数据结构定义=====================================================
#define STACK_INIT_SIZE 100        //存储空间初始分配量
#define STACKINCREMENT  10          //存储空间分配增量

typedef struct test
{
    char name[20];    //名字
    char num[20];     //学号
    float score;      //成绩
}SElemType;
typedef struct
{
    SElemType *base;
        //    在栈构造之前和销毁之后,base的值为NULL
    SElemType *top;
        //    栈顶指针
    int stacksize;
        //    当前已分配的存储空间,以元素为单位
}SqStack;

/*    Algo.h*/
       //基本操作和算法
   //=================================基本操作和算法==============================
Status InitStack(SqStack &S)
{
    S.base=(SElemType  *)malloc(STACK_INIT_SIZE * sizeof(SElemType));
        //    存储分配失败
    if(!S.base)
        exit(OVERFLOW);
    S.top=S.base;
    S.stacksize=STACK_INIT_SIZE;
    return OK;
}

Status DestroyStack(SqStack &S)
{
    free(S.base);
    free(S.top);
        //    疑问:为何是先释放base再释放top???
    S.base=NULL;
    return true;
}

Status ClearStack(SqStack &S)
{
    S.top=S.base;
        //    S.stacksize=0;
        //    重置的目的是针对使用了realloc()重新分配了地址的栈,
        //    但是realloc()函数分配的是一段连续的地址,指向栈底的指针指向了这段空间,
        //    即使将stacksize重置回初始值,增加的那段空间还是由指向栈底的指针来控制的
        //    (参照这句S.base=(Elemtype *)realloc(S.base,(S.stacksize+STACKINCREACE)*sizeof(Elemtype));思考)
        //    因此重置没有意义。类比顺序表的清空:顺序表的清空也是只将当前长度置为0,没有改变它的listsize值。
    return OK;
}

Status StackEmpty(SqStack S)
{
    if(S.base==S.top)
    {
        cout<<"此时栈为空!"<<endl;
        return true;
    }
    return false;
}

int StackLength(SqStack S)
{
    if(S.base==S.top)
        return false;
    return S.top-S.base;
}

Status GetTop(SqStack S,SElemType &e)
{
    if(S.top==S.base)
        return ERROR;
    e=*(S.top-1);
    cout<<"栈顶元素是:"<<e.name<<" "<<e.num<<" "<<e.score<<endl;
    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));
            //    编译时报错:missing “)”before ;
            //    错误原因:由于头文件中定义常量时后面加了;导致编译的时候将STACK_INIT_SIZE用100;代替,
            //    使得该函数提前结束语100处,malloc后的“(”找不到匹配的“)”而报错
            //    解决方法:去掉常量定义中的;
        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)
{
    if(S.top==S.base)
        return ERROR;
    S.top--;
        //    注意这里因为top指向栈中当前元素的上一个空间,所以要先将其位置减一
    e=*S.top;
    return OK;
}

Status StackTraverse(SqStack S)
{
    SElemType e;
    if(S.top==S.base)
        return false;
    while(S.base <S.top)
    {//    从栈底到栈顶的方向
        cout<<"姓名:"<<(*S.base).name<<" 学号:"<<(*S.base).num<<" 成绩:"<<(*S.base).score<<endl;
        S.base++;
    }
    GetTop(S,e);
    return true;
}

/*    Use.cpp*/
       //调用基本操作的主程序
   //=====================栈的顺序存储===========================
   
   #include"Pubuse.h"
   #include"Def.h"
   #include"Algo.h"
   
  
  //=====================函数原型声明=============================
Status InitStack(SqStack &S);
    //    构造一个空栈
Status DestroyStack(SqStack &S);
    //    销毁栈S,S不再存在
Status ClearStack(SqStack &S);
    //    把S置为空栈
Status StackEmpty(SqStack S);
    //    若栈S为空栈,则返回TRUE,否则返回FALSE
int StackLength(SqStack S);
    //    返回S的元素个数,即栈的长度
Status GetTop(SqStack S,SElemType &e);
    //    若栈不空,则用e返回S的栈顶元素,并返回OK,否则返回ERROR
Status Push(SqStack &S,SElemType e);
    //    插入元素e为新的栈顶元素
Status Pop(SqStack &S,SElemType &e);
    //    若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK,否则返回ERROR
Status StackTraverse(SqStack S);
    //    从栈底到栈顶依次对栈中每个元素调用函数visit().一旦visit()失败,则操作失败
void print();

  //=====================主函数==============================
int main(void)
{
    SqStack S;    //定义一个栈S
    SElemType et,e;     //et为栈顶元素,e为栈外元素
    char ch;           //控制用户输入选择
    InitStack(S);      //构造空栈S
    print();
    cin>>ch;
    while (ch!='5')
    {
        switch(ch)
        {
        case('1')://进栈
            {
                cout<<"请输入即将进栈的元素的姓名:"<<endl;
                cin>>e.name;
                cout<<"请输入即将进栈的元素的学号:"<<endl;
                cin>>e.num;
                cout<<"请输入即将进栈的元素的成绩:"<<endl;
                cin>>e.score;
                Push(S,e);
                et=e;
                StackTraverse(S);
                print();
                cin>>ch;
            }break;
        case('2')://出栈
            {
                Pop(S,et);
                StackTraverse(S);
                print();
                cin>>ch;
            }break;
        case('3')://遍历
            {
                StackTraverse(S);
                cout<<"此时该栈中共有"<<StackLength(S)<<"个元素"<<endl;
                GetTop(S,et);
                print();
                cin>>ch;
            }break;
        case('4')://清空
            {
                ClearStack(S);
                StackEmpty(S);
                print();
                cin>>ch;
            }break;
        default:
            {
                cout<<"您输入的数据有误!请重新选择!"<<endl;
                print();
                cin>>ch;
            }
        }
    }
    DestroyStack(S);
    system("pause");
    return 0;
}

//=============================函数定义============================
void print()
{
    cout<<"-----------------操作选择-----------------"<<endl;
    cout<<"------------------1.进栈------------------"<<endl;
    cout<<"------------------2.出栈------------------"<<endl;
    cout<<"------------------3.遍历------------------"<<endl;
    cout<<"------------------4.清空------------------"<<endl;
    cout<<"------------------5.退出------------------"<<endl;
    cout<<"------------------------------------------"<<endl;
}

 

<二>队列

1. 定义

 

队列(queue)是一种与栈相反的先进先出(first in first out,缩写为FIFO)的线性表. 它只允许在表的一段进行插入,而在另一端删除元素. 在队列中, 允许插入的一端叫做队尾(rear), 允许删除的一端则称为队头(front).队列的操作与栈的操作相似, 也有8个, 不同的是删除是在表的头部(即队头)进行.

除了栈和队列之外, 还有一种限定性数据结构是双端队列(deque). 双端队列是限定插入和删除操作在表的两端进行的线性表. 这两端分别称为端点1和端点2.

和线性表类似, 队列也可以有两种存储表示.

1) 用链表表示的队列简称为链队列. 一个链队列显然需要两个分别指示队头和队尾的指针(分别称为头指针和尾指针)才能唯一确定. 空的链队列的判决条件为头指针和尾指针均指向头结点.一般情况下, 删除队列头元素时仅需修改头结点中的指针, 但当队列中最后一个元素被删除后, 队列尾指针也丢失了, 因此需对队尾指针重新赋值(指向头结点).

2) 与顺序栈相似, 在队列的顺序存储结构中, 除了用一组地址连续的存储单元依次存储从队列头到队列尾的元素之外, 尚需附设两个指针front和rear分别指示队列头元素和队列尾元素的位置.约定: 初始化建立空队列时, 令front=rear=0 , 每当插入新的队列尾元素时, "尾指针增1";每当删除队列头元素时, "头指针增1". 因此, 在非空队列中, 头指针始终指向队列头元素, 而尾指针始终指向队列尾元素的下一个位置.

   循环队列是将顺序队列臆造成一个环状的空间, 指针和队列元素之间关系不变. 但是只凭Q.front=Q.rear无法判断队列空间是"空"还是"满". 可有两种处理方法: 其一是另设一个标志位以区别队列是"空"还是"满"; 其二是少用一个元素空间, 约定以"队列头指针在队列尾指针的下一位置(指环状的下一位置)上"作为队列呈"满"状态的标志. 所以, 在C语言中不能用动态分配的一维数组来实现循环队列. 如果用户的应用程序中设有循环队列, 则必须为它设定一个最大队列长度;若用户无法预估所用队列的最大长度, 则宜采用链队列.

 

2. 实现代码

 

/*    Pubuse.h*/
  //    几乎所有实验中都涉及到的,包含一些常量定义,系统函数原型声明和类型,重定义(typedef)和结果代码等
  //==================================系统函数原型声明===================================
 #include<string>  
 #include<ctype.h>  
 #include<malloc.h>
 #include<limits.h>  
 #include <stdio.h>
 #include <stdlib.h>
 #include<io.h>
 #include<iostream>
 #include<math.h> 
 #include<process.h>
 using namespace std;

 //==================================函数结果状态代码=================================== 
 #define TRUE             1  
 #define FALSE            0  
 #define OK               1  
 #define ERROR            0  
 #define INFEASIBLE      -1  
 //#define OVERFLOW      -2 
 //    因为在math. h 中已定义OVERFLOW 的值为3,故去掉此行
 
 //================================类型重定义代码========================================
 typedef int Status; 
 //    Status 是函数的类型,其值是函数结果状态代码,如OK 等
 typedef int Boolean;
 //    Boolean 是布尔类型,其值是TRUE 或FALSE  

 /*    Def.h*/
  //=========================数据结构定义=====================================================
 #define MAXQSIZE 5            //最大队列长度(对于循环队列,最大队列长度要减1)
 
 typedef int QElemType;  

 typedef struct  
 {  
   QElemType *base; 
    //    初始化的动态分配存储空间   
   int front; 
    //    头指针,若队列不空,指向队列头元素
   int rear; 
    //    尾指针,若队列不空,指向队列尾元素的下一个位置
 }SqQueue;  

/*    Algo.h*/
       //基本操作和算法
   //=================================基本操作和算法==============================
Status InitQueue(SqQueue &Q)  
 {//    构造一个空队列Q
   Q.base=(QElemType *)malloc(MAXQSIZE*sizeof(QElemType));  
   if(!Q.base)
     exit(OVERFLOW);  
   Q.front=Q.rear=0;  
   return OK;  
 }  
 Status DestroyQueue(SqQueue &Q)  
 {//    销毁队列Q,Q不再存在
   if(Q.base)  
     free(Q.base);  
   Q.base=NULL;  
   Q.front=Q.rear=0;  
   return OK;  
 }  
 Status ClearQueue(SqQueue &Q)  
 {//    将Q清为空队列 
   Q.front=Q.rear=0;  
   return OK;  
 }  
 Status QueueEmpty(SqQueue Q)  
 {//    若队列Q为空队列,则返回TRUE,否则返回FALSE 
   if(Q.front==Q.rear)
     return TRUE;  
   else  
     return FALSE;  
 }  
 int QueueLength(SqQueue Q)  
 {//    返回Q的元素个数,即队列的长度  
   return(Q.rear-Q.front);  
 }  
  
 Status GetHead(SqQueue Q,QElemType *e)  
 {//    若队列不空,则用e返回Q的队头元素,并返回OK,否则返回ERROR
   if(Q.front==Q.rear)
     return ERROR;  
   *e=*(Q.base+Q.front);  
   return OK;  
 }  
 Status EnQueue(SqQueue &Q,QElemType e)  
 {//    插入元素e为Q的新的队尾元素
   if(Q.rear>=MAXQSIZE)  
   {
     Q.base=(QElemType *)realloc(Q.base,(Q.rear+1)*sizeof(QElemType));  
     if(!Q.base) 
       return ERROR;  
   }  
   *(Q.base+Q.rear)=e;  
   Q.rear++;  
   return OK;  
 }  
  
 Status DeQueue(SqQueue &Q,QElemType &e)  
 {//    若队列不空,则删除Q的队头元素,用e返回其值,并返回OK,否则返回ERROR
   if(Q.front==Q.rear) /* 队列空 */  
     return ERROR;  
   e=Q.base[Q.front];  
   Q.front=Q.front+1;  
   return OK;  
 }  
  
 void visit(QElemType i)  
 {  
   printf("%d ",i);  
 }  

 Status QueueTraverse(SqQueue Q,void(*vi)(QElemType))  
 {//    从队头到队尾依次对队列Q中每个元素调用函数vi()。一旦vi失败,则操作失败
   int i;  
   i=Q.front;  
   while(i!=Q.rear)  
   {  
     vi(*(Q.base+i));  
     i++;  
   }  
   return OK;  
 }  

/*    Use.cpp*/
       //调用基本操作的主程序
   //=====================非循环队列-顺序结构===========================
   
   #include"Pubuse.h"
   #include"Def.h"
   #include"Algo.h"

  //=====================函数原型声明=============================
Status InitQueue(SqQueue &Q);
    //    构造一个空队列Q
Status DestroyQueue(SqQueue &Q);
    //    销毁队列Q,Q不再存在
Status ClearQueue(SqQueue &Q);
    //    将Q清为空队列
Status QueueEmpty(SqQueue Q);
    //    若队列Q为空队列,则返回TRUE,否则返回FALSE 
int QueueLength(SqQueue Q);
    //    返回Q的元素个数,即队列的长度  
Status GetHead(SqQueue Q,QElemType *e);
    //    若队列不空,则用e返回Q的队头元素,并返回OK,否则返回ERROR
Status EnQueue(SqQueue &Q,QElemType e);
    //    插入元素e为Q的新的队尾元素
Status DeQueue(SqQueue &Q,QElemType &e);
    //    若队列不空,则删除Q的队头元素,用e返回其值,并返回OK,否则返回ERROR
void visit(QElemType i);
Status QueueTraverse(SqQueue Q,void(*vi)(QElemType));
    //    从队头到队尾依次对队列Q中每个元素调用函数vi()。一旦vi失败,则操作失败

  //=====================主函数==============================
 int main(void)  
 {  
   Status j;  
   int i,n;  
   QElemType d;  
   SqQueue Q;  
   InitQueue(Q);  
   printf("初始化队列后,队列空否?%u(1:空 0:否)\n",QueueEmpty(Q));  
   printf("队列长度为:%d\n",QueueLength(Q));  
   printf("请输入队列元素个数n: ");  
   scanf_s("%d",&n);  
   printf("请输入%d个整型队列元素:\n",n);  
   for(i=0;i<n;i++)  
   {  
     scanf_s("%d",&d);  
     EnQueue(Q,d);  
   }  
   printf("队列长度为:%d\n",QueueLength(Q));  
   printf("现在队列空否?%u(1:空 0:否)\n",QueueEmpty(Q));  
   printf("现在队列中的元素为: \n");  
   QueueTraverse(Q,visit);  
   DeQueue(Q,d);  
   printf("删除队头元素%d\n",d);  
   printf("队列中的元素为: \n");  
   QueueTraverse(Q,visit);  
   j=GetHead(Q,&d);  
   if(j)  
     printf("队头元素为: %d\n",d);  
   else  
     printf("无队头元素(空队列)\n");  
   ClearQueue(Q);  
   printf("清空队列后, 队列空否?%u(1:空 0:否)\n",QueueEmpty(Q));  
   j=GetHead(Q,&d);  
   if(j)  
     printf("队头元素为: %d\n",d);  
   else  
     printf("无队头元素(空队列)\n");  
   DestroyQueue(Q);  
   system("pause");
 }  

 //=============================函数定义============================

 

 

 

 

 

posted @ 2017-04-08 18:27  辞镜_Chill  阅读(297)  评论(0编辑  收藏  举报