数据结构看书笔记(四)--栈与队列

时间:2021-02-09 10:30:25

栈与队列
 栈是限定尽在表尾进行插入和删除操作的线性表 
 队列是只允许在一端进行插入操作、而在另一端进行删除操作的线性表
栈的定义:

 栈(Stack)是限定仅在表尾进行插入和删除操作的线性表
 其中允许插入的一端称为栈顶(top),另一端称为栈底(bottom),不含任何数据元素的栈称为空栈。栈又称为先进后出(Last In First Out)的线性表,简称为LIFO结构。
 特殊之处在与限制了这个线性表的插入和删除位置只能是在栈顶。
 
 栈的插入操作,叫做进栈,也称为压栈、入栈。
 栈的删除操作,叫做出栈,也有的叫做弹栈。

栈的抽象数据类型:

ADT 栈(Stack)
    Data
        同线性表。元素具有相同的类型,相邻元素具有前驱和后继关系。
    Operation
        InitStatic(*S):初始操作,建立一个空栈S
        DestroyStack(*S):若栈存在则销毁它。
        ClearStack(*S):将栈清空
        StackEmpty(S):若栈为空,则返回true,否则返回false
        GetTop(S,*s):若栈存在且非空,用e返回S的栈顶元素
        Push(*S,e):若栈S存在,插入新的元素e到
        Pop(*S,*e):删除栈S中栈顶元素,并用e返回其值
        StackLength(S):返回栈S的元素个数。
    endADT

栈的顺序存储结构及实现:
 栈的顺序存储结构
 定义:

        typedef int SElemType;
        typedef struct
        {
            SElemType data[MAXSIZE];
            int top;
        }SqStack;
数据结构看书笔记(四)--栈与队列数据结构看书笔记(四)--栈与队列
    Status Push(SqStack *s,SElemType e)
    {
        if(S->top==MAXSIZE-1)
            return ERROR;
        S->top++;
        s->data[S->top] = e;
        return OK;
    }
栈的顺序存储结构--进栈操作
数据结构看书笔记(四)--栈与队列数据结构看书笔记(四)--栈与队列
    
    Status Pop(SqStack *S,SElemType *e)
    {
        if(S->top==-1)
            return ERROR;
        *e = S->data[S->top];
        S->top--;
        return OK;
    }
栈的顺序存储结构--出栈操作

两栈共享空间
 两栈共享空间的结构的代码如下:

typedef struct
    {
        SElemType data[MAXSIZE];
        int top1;
        int top2;
    }SqDoubleStack;
    
    Status Push(SqDoubleStack *S,SElemType e,stackNumber)
    {
        if(S->top+=S->top2)//栈满
            return ERROR;
        if(stackNumber==1)
            S->data[++S->top1] = e;
        else if(stackNumber==2)
            S->data[--S->top2] = e;
        return OK;
    }
    
    Status Pop(SqDoubleStack *S,SElemType *e,int stackNumber)
    {
        if(stackNumber==1)
        {
            if(S->top1==-1)
                return ERROR;
            *e=S->data[S->top1--];
        }
        else if(stackNumber==2)
        {
            if(S-top2==MAXSIZE)
                return ERROR;
            *e=S->data[S->top2++];
        }
        return OK;
    }

栈的链式存储结构及实现
 对于链栈来说基本不存在栈满的情况,除非内存已经没有可以使用的空间。
 链栈的结构代码如下:

typedef struct StackNode
    {
        SElemType data;
        struct StackNode *next;
    }StackNode,*LinkStackPtr;
    
    typedef struct LinkStack
    {
        LinkStackPtr top;
        int count;
    }LinkStack;
数据结构看书笔记(四)--栈与队列数据结构看书笔记(四)--栈与队列
    Status Push(LinkStack *S,SElemType e)
    {
        LinkStackPtr s = (LinkStackPtr)malloc(sizeof(StackNode));
        s->data = e;
        s->next = S->top;
        s->top = s;
        S->count++;
        return OK;
    }
栈的链式存储结构--进栈操作
数据结构看书笔记(四)--栈与队列数据结构看书笔记(四)--栈与队列
    Status Pop(LinkStack *S,SElemType *e)
    {
        LinkStackPtr p;
        if(StackEmpty(*S))
            return ERROR;
        *e=S->top->data;
        p = S->top;
        S->top = S->top->next;
        free(p);
        S->count--;
        return OK;
    }
栈的链式存储结构--出栈操作

注:关于顺序栈和链栈的讨论
   如果栈的使用过程中元素变化不可预料,有时很小,有时非常大,那么最好是使用链栈,反之,如果它的变化在可控的范围内,建议使用顺序栈会更好一些。
   栈的作用:栈的引入简化了程序设计的问题,划分了不同关注层次,使得思考范围缩小,更加聚焦于我们要解决的问题核心。反之,像数组等,因为要分散精力去考虑数组的下标增减等细节问题,反而掩盖了问题的本质。

 

栈的应用--递归
 斐波那契数列(Fibonacci)

    //直接计算
    int main()
    {
        int i;
        int a[40];
        a[0] = 0;
        a[1] = 1;
        printf("%d",a[0]);
        printf("%d",a[1]);
        for(i=2;i<40;i++)
        {
            a[i] = a[i-1]+a[i-2];
            printf("%d\n",a[i]);
        }
        return 0;
    }
//使用递归计算,测试后知道,使用递归会急剧增加运算时间
    int Fbi(int i)
    {
        if(i<0)
            return 0;
        else if(i==0)
            return 0;
        else if(i==1)
            return 1;
        else 
            return Fbi(i-1)+Fbi(i-2);
    }
    int main()
    {
        int i;
        for(i=0;i<40;i++)
            printf("%d\n",Fbi(i));
        return 0; 
    }

对递归的定义
    把一个直接调用自己活通过一系列的调用语句间接的调用自己的函数,称做递归函数。
    每个递归定义至少必须有一个条件,满足递归时不再进行,即不再引用自身而是返回值退出。

栈的应用--四则运算表达式求值:
 后缀(逆波兰)表示法定义:不需要括号的后缀表示法,我们也把它称为逆波兰(Reverse Polish Notation,RPN)表示。

 后缀表达式计算结果:
 此处为了表达方便,应有配图
 
 中缀表达式转后缀表达式:
 此处为了表达方便,应有配图

 

队列的定义:
   队列是只允许在一端进行插入操作、而在另一端进行删除操作的线性表。
   队列是一种先进先出(First in First Out)的线性表,简称为FIFO.允许插入的一端称为队尾,允许删除的一端称为队头。

队列的抽象数据类型:

    ADT 队列(Queue)
    Data
        同线性表。元素具有相同的类型,相邻元素具有前驱和后继关系。
    Operation
        InitQueue(*Q):初始化操作,建立一个空队列Q.
        DestroyQueue(*Q):若队列Q存在则销毁它。
        ClearQueue(*Q):将队列清空
        QueueEmpty(Q):若队列为空则返回true,否则返回false
        GetHead(Q,*e):若队列存在且非空,用e返回队列Q的队头元素。
        EnQueue(*Q,e):若队列Q存在,插入新元素e到队列Q中并成为队尾元素
        DeQueue(*Q,*e):删除队列Q的队头元素,并且用e返回其值
        QueueLength(Q):返回队列Q的元素个数
    endADT

循环队列
   队列也存在顺序存储和链式存储
 
   队列顺序存储的不足:队头指针和队尾指针的问题,如果不循环,那么就可能造成位置空缺的问题。
 
   循环队列的定义:队列的头尾相接的顺序存储结构称为循环队列。
   队列满的条件是:

(rear+1)%QueueSize == front

    (取模“%”的目的就是为了整合rear与front大小为一个问题)

  通用的计算队列长度的公式:

  (rear-front+QueueSize)%QueueSize

  定义:

    typedef int QElemType;
    typedef struct
    {
        QElemType data[MAXSIZE];
        int front;
        int rear;
    }SqQueue;

  操作:

    //初始化空队列Q
    Status InitQueue(SqQueue *Q)
    {
        Q->front = 0;
        Q->rear = 0;
        return OK;
    }
    
    //返回Q的元素个数
    int QueueLength(SqQueue Q)
    {
        return (Q.rear-Q.front+MAXSIZE)%MAXSIZE;
    }
    
    //若队列未满,则插入元素e为Q新的队尾元素
    Status EnQueue(SqQueue *Q,QElemType e)
    {
        if((Q->rear+1)%MAXSIZE==Q->front)
            return ERROR;
        Q->data[Q->rear] = e;
        Q->rear = (Q->rear+1)%MAXSIZE;
        
        return OK;
    }
    
    //出队列
    Status DeQueue(SqQueue *Q,QElemType *e)
    {
        if(Q->front = Q->rear) //队列为空
            return ERROR;
        *e=Q->data[Q->front];
        Q->front = (Q->front+1)%MAXSIZE;
        return OK;
    }

队列的链式存储结构及实现

    队列的链式存储结构,其实就是线性表的单链表,只不过它只能尾进头出而已,我们把它简称为链队列。  

    链队列的结构为:

    typedef int QElemType;
    typedef struct QNode
    {
        QElemType data;
        struct QNode *next;
    }QNode,*QueuePtr;
    
    typedef struct
    {
        QueuePtr front,rear;
    }LinkQueue;    

 

数据结构看书笔记(四)--栈与队列数据结构看书笔记(四)--栈与队列
    //入队操作
    Status EnQueue(LinkQueue *Q,QElemType e)
    {
        QueuePtr s = (QueuePtr)malloc(sizeof(QNode));
        if(!s)    //存储分配失败
            exit(OVERFLOW);
        s->data = e;
        s->next = NULL;
        Q->rear->next = s;
        
        Q->rear = s;
        return OK;
    }
入队操作
数据结构看书笔记(四)--栈与队列数据结构看书笔记(四)--栈与队列
    //出队操作
    Status DeQueue(LinkQueue *Q,QElemType *e)
    {
        QueuePtr p;
        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;
    }
出队操作

本章内容

  栈
      顺序栈
      两栈共享空间

  链栈
  队列

    顺序队列

    循环队列
    链队列