您的位置:首页 > 理论基础 > 数据结构算法

数据结构与算法(四):栈和队列

2020-05-04 12:20 1201 查看

栈的定义

栈是一种重要的线性结构,栈是前面讲过的线性表的一种具体形式。
栈是一种后进先出的结构,例如子弹的压入和发出,例如c语言中的函数。
栈是一种后进先出(Last in first out,LIFO)的线性表,它要求只在表尾进行删除和插入操作。

  • 栈的元素必须“后进先出”
  • 栈的操作只能在这个线性表的表尾进行。
  • 对于栈来说,这个表尾称为栈的栈顶(top),相应的表头称为栈底(bottom)

栈的插入和删除操作

  • 栈的插入操作(Push),叫做进栈,也成为压栈、 入栈。类似于子弹压入弹夹的动作。
  • 栈的删除操作(Pop),叫做出栈,也称为弹栈。如同弹夹中的子弹出夹。

栈的顺序存储结构

  • 因为栈的本质是一个线性表,线性表有两种存储形式,那么栈也分为栈的顺序存储结构和栈的链式存储结构
  • 最开始的栈不含有任何数据,叫做空栈,此时栈顶就是栈底。然后数据从栈顶进入,栈底分离,整个栈的当前容量变大。数据出栈时从栈顶弹出,栈顶下移,整个栈的当前容量变小。
  • 栈的抽象数据类型定义
ADT Stack{
数据对象:
D={ai|ai∈ElemSet,i=1,2,....n,n≥0}
数据关系:
R1={<ai-1,ai>|ai-1,ai∈D,i=2,..n}
约定an端为栈顶,a1端为栈底。
基本操作:初始化、进栈、出栈、取栈顶元素等
}ADT Stack
typedef struct{
ElemType *base; //栈底指针
ElemType *top;  //栈顶指针
int stackSize;  //栈可用最大容量
}sqStack;
  • 这里定义了一个顺序存储的栈,它包含了三个元素:base,top,stacksize,其中base是指向栈底的指针,top是指向栈顶的指针,stacksize是指示当前栈当前可使用的最大容量。

  • 创建一个栈

status initStack(sqStack &s){
s.base=(ElemType *)malloc(MAX_SIZE * sizeof(ElemType));
if(!s.base) return ERROR;
s.top=s.base;// 栈顶=栈底
s.stackSize=MAX_SIZE;
}
  • 判断栈是否为空
Status StackEmpty(sqStack S){
if(S.top == S.base)
{
return TRUE;
}
else return FALSE;
}
  • 清空顺序栈
Status ClearStack(sqStack S){
if(S.base)S.top=S.base;
return OK;
}
  • 销毁顺序栈
Status DestroyStack(sqStack &S){
if(S.base){
free(S.base);
S.stacksize=0;
S.base=S.top=NULL;
}
return OK;
}
  • 入栈操作又叫压栈操作,就是向栈中存放数据。
Status Push(sqStack &s,ElemType e)
{
//如果栈满,返回错误
if(s.top-s.base>=s.stackSize)
{
return ERROR;
}
*s.top = e;
s.top++;
return OK;
}
  • 顺序栈的出栈
Status Pop(sqStack &s,ElemType &e){
if(s.top==s.base) return ERROR;
--s.top;
e=*s.top;
return OK;
}

栈的链式存储结构

  • 链栈是运算受限的单链表,只能在链表头部进行操作。
typedef struc StackNode{
ELemType data;
struct StackNode *next;
}StackNode,*LinkStack;
  • 链栈的特点:

    链表的头指针就是栈顶
  • 不需要头结点
  • 基本不存在栈满的情况
  • 空栈相当于头指针指向空
  • 插入和删除仅在栈顶处执行
  • 链栈的初始化

  • Status InitStack(LinkStack &S){
    //构造一个空栈,栈顶指针置为空
    S=NULL;
    return OK;
    }
    • 链栈的入栈
    Status Push(LinkStack &S,ElemType e){
    p = (StackNode*)malloc(sizeof(StackNode));//生成新结点P
    p->data = e;//将新结点数据域置为e
    p->next = S;//将新结点插入栈顶
    S=p;//修改栈顶指针
    return OK;
    }
    • 链栈的出栈
    Status Pop(LinkStack &S,ElemType &e){
    if(S==NULL)return ERROR;
    e = S->data;
    P=S;
    S=S->next;
    free(P);
    return OK;
    }

    栈与递归

    • 递归的定义:

      若一个对象部分地包括它自己,或用它自己给他自己定义,则称这个对象是递归的;
    • 若一个过程直接地或间接地调用自己,则称这个过程是递归的过程。 例如:递归求n的阶乘
    long Fact(long n){
    if(n==0) return 1;
    else return n*Fact(n-1);
    }
  • 以下三种情况常常用到递归方法
      递归定义的数学函数(斐波那契数列)
    • 具有递归特性的数据结构(树、广义表等)
    • 可递归求解的问题(汉诺塔)
  • 递归问题——用分治法求解:
      对于一个复杂的问题,能够分解成几个相对简单的方法且解法相同或类似的子问题来求解。
  • 必备的三个条件

      能够将一个问题变成一个新问题,而新问题与原问题的解法相同或类同。

    • 可以通过上述转化使问题简化

    • 必须有一个明确的递归出口,或称递归的边界

      void p(参数表){
      if(递归结束条件) 可直接求解步骤; //基本项
      else p(较小的参数);  //归纳项
      }
  • 函数调用过程:

      调用前,系统完成:
        将实参,返回地址等参数传递给被调用函数
      1. 为被调用函数的局部变量分配存储区
      2. 将控制转移到被调用函数的入口
    • 调用后,系统完成:
        保存被调用函数的计算结果
      1. 释放被调用函数的数据区
      2. 依照被调用函数保存的返回地址将控制转移到调用函数

    队列

    • 队列是一种先进先出的线性表,在表一端插入(尾部),在另一端(表头)删除,类似我们的排队。
    • 队列的存储结构为链队或顺序队(常用顺序循环队)

    队列的顺序存储结构

    ADT Queue{
    数据对象:D={ai|ai∈ElemSet,i=1,2....n(n≥0)}
    数据关系:R={<ai-1,ai>|ai-1,ai∈D,i=2,...n}
    基本操作:
    InitQueue(&Q)  操作结果:构造空队列Q
    DestroyQueue(&Q) 条件:队列Q已存在;操作结果:队列Q被销毁;
    ClearQueue(&Q)   条件:队列Q已存在;操作结果:将Q清空;
    QueueLength(&Q)  条件:队列Q已存在;操作结果:返回Q的元素个数,即队长
    GetHead(Q,&e)   条件:Q为非空状态;操作结果:用e返回队列Q点队头元素;
    EnQueue(&Q,e)   条件:队列Q已存在;操作结果:插入元素e为队列Q的队尾元素;
    DeQueue(&Q,&e)  条件:Q为非空队列;操作结果:删除Q的队头元素,用e返回。
    }ADT Queue
    #define MAXQSIZE 100 //最大度列长度
    typedef struct{
    QElemType *base; //初始化的动态分配存储空间
    int front;  	 //头指针
    int rear;		 //尾指针
    }SqQueue;
    • 队列的初始化
    Status InitQueue(SqQueue &Q){
    Q.base = (QElemType*)malloc(sizeof(QElemType)*MAXQSIZE);
    if(!Q.base) exit(OVERFLOW);   //存储分配失败
    Q.front = Q.rear = 0;      //头尾指针置为0 , 队列为空
    return OK;
    }
    • 求队列的长度
    int QueueLength(SqQueue Q){
    return ((Q.rear-Q.front+MAXQSIZE)%MAXQSIZE);
    }
    • 循环队列的入队
    Status EnQueue(SqQueue &Q,QElemType e){
    if((Q.rear+1)%MAXQSIZE ==Q.front) return ERROR; //队满
    Q.base[Q.rear]=e;
    Q.rear=(Q.rear+1)%MAXQSIZE;
    return OK;
    }
    • 循环队列的出队
    Status DeQueue(SqQueue &Q,QElemType &e){
    if(Q.front==Q.rear) return ERROR; //队空
    e=Q.base[Q.front];					//保存队头元素
    Q.front=(Q.front+1)%MAXSIZE;		//将队头指针+1
    return OK;
    }
    • 取队头元素
    QElemType GetHead(SqQueue Q){
    if(Q.front!=Q.rear)		//队列不为空
    return Q.base[Q.front];		//返回队头指针元素的值,队头指针不变
    }

    队列的链式存储结构

    • 若用户无法估计所用队列长度,宜采用链队列

      链队列的类型定义:
    #define MAXQSIZE 100  //最大队列长度
    typedef struct Qnode{
    QElemType data;
    struct Qnode *next;
    }QNode,*QuenePtr;
    typedef struct{
    QuenePtr front;  //队头指针
    QuenePtr rear;	 //队尾指针
    }LinkQueue;
  • 链队列的初始化

  • Status InitQueue (LinkQueue &Q)
    {
    Q.front = Q.rear = (QueuePtr)malloc(sizeof(QNode));
    if(!Q.front) exit(OVERFLOW);
    Q.front->next=NULL;
    return OK;
    }
    • 链队列的销毁
    Status DestroyQueue(LinkQueue &Q)
    {
    while(Q.front)
    {
    p=Q.front->next;
    free(Q.front);
    Q.front=p;
    }
    return OK;
    }
    • 将元素e入队
    Status EnQueue(LinkQueue &Q,QElemType e){
    p=(QueuePtr)malloc(sizeof(QNode));
    if(!p) exit(OVERFLOW);   //分配失败
    p->data = e;
    p->next=NULL;
    Q.rear->next=p;
    Q.rear = p;
    return OK;
    }
    • 链队列出队
    Status QUeue(LinkQueue &Q,QElemtype &e)
    {
    if(Q.front==Q.rear) return ERROR; //队空
    p=Q.front->next;
    e=p->data;
    Q.front->next=p->next;
    
    if(Q.rear==p)Q.rear=Q.front;
    //如果头指针的下一个就是尾指针,那么也要修改尾指针
    
    free(p);
    return OK;
    }
    • 求队头元素
    Status GetHead(LinkQueue Q,QElemType &e){
    if(Q.front==Q.rear)return ERROR;
    e=Q.front->next->data;
    return OK;
    }
    扎维耶小丑 原创文章 4获赞 16访问量 2661 关注 私信
    内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签: