栈与队列

Posted 阿呆

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了栈与队列相关的知识,希望对你有一定的参考价值。

栈是一种只能在一端进行插入或者删除操作的线性表,其中允许进行插入或删除的一端称为栈顶。

顺序栈

栈的顺序存储结构称为顺序栈,通常用数组存放栈中元素,下标为0的那一端作为栈底。

typedef struct
{
  int data[MaxSize];
  int top;
}SqStack;

对于一个顺序栈st,一共有 4个要素,包括两个特殊的状态和两个操作:

  • 两个状态

    • 栈空状态 st.top==-1;
    • 栈满状态 st.top==MaxSize-1;
  • 两个操作

    • 元素x进栈 ++(st.top); st.data[top]=x;
    • 元素x出栈 x=st.data[top]; --(st.top);

顺序栈的几个基本操作(详细版):

  • 初始化栈

     void initStack(SqStack &st)
     {
          st.top = -1;
     }
    
  • 判断栈空

    bool isEmpty(SqStack st)
    {
        if (st.top == -1)
            return true;
        else
            return false;
    }
    
  • 判断栈满

    bool isFull(SqStack st)
    {
        if (st.top == MaxSize - 1)
            return true;
        else
            return false;
    }
    
  • 进栈算法

    bool push(SqStack &st,int x)
    {
      if(isFull(st)) 
            return false;
       ++st.top;
       st.data[st.top]=x;
      return true;
    }
    
  • 出栈算法

    bool pop(SqStack &st, int &x)
    {
        if (isEmpty(st))
            return false;
        x = st.data[st.top];
        --st.top;
        return true;
    }
    

在实际的题目中初始化栈、进栈以及出栈不必写的上述那么详细

  • 初始化栈 int stack[MaxSize]; int top==-1;
  • 元素 x 进栈 stack[++top]=x;
  • 元素 x 出栈 x=stack[top--];

链栈

栈的链式存储结构称为链栈,通常用带头结点的单链表表示

typedef struct linkLode
{
  int data;
  struct linkLode*next;
}LiStack;

对于一个有带头节点的链栈 ls ,也包括 4 个要素,包括两个状态和两个操作:

  • 两个状态

    • 栈空状态: ls->next==NULL;
    • 栈满状态:一般认为没有(但是受内存限制,即内存满的时候就无法申请新的节点,此时为栈满)
  • 两个操作

    • 元素(指针ptr指向)进栈操作,即为头插法

      ptr->next = ls->next;
      ls->next = ptr;
      
    • 出栈操作

      ptr = ls->next;
      x = ptr->data;
      ls->next = ptr->next;
      free(ptr);
      ptr = NULL;
      

共享栈

两个栈共享一个数组A[0..MaxSize-1]的空间,数组 A 两端都是栈底,栈顶向中间生长
左边栈的 4 要素为:

  • 栈空:top1==-1
  • 栈满:top1==top2-1
  • 进栈:++top1; A[top1]=x;
  • 出栈: x=A[top1]; --top1;

右边栈的 4 要素为:

  • 栈空:top2==MaxSize
  • 栈满:top2==top1+1
  • 进栈:--top2; A[top2]=x;
  • 出栈: x=A[top2]; ++top2;

如果要解决的问题出现了这样一个状态:单凭现有的条件不能判断当前的状态是否可以解决,此时需要记录一些东西,等待以后出现可以解决当前状态的条件后返回来再解决。这种问题往往可以使用栈来解决。


相关题目

括号匹配

假设表达式中允许包括 3种括号:圆括号、方括号、和大括号。设计一个算法采用顺序栈判断表达式中括号是否正确匹配。

 bool match(char exp[], int n)
    {
        char st[MaxSize];
        int top = -1;
        bool flag = true;
        int i = 0;
        while (i < n && flag == true)
        {
            if (exp[i] == \'(\' || exp[i] = \'[\' || exp[i] = \'{\')
            {
                ++top;
                st[top] = exp[i];
            }
            if (exp[i] == \')\')
            {
                if (st[top] == \'(\')
                    --top;
                else
                    flag = false;
            }
            if (exp[i] == \']\')
            {
                if (st[top] == \'[\')
                    --top;
                else
                    flag = false;
            }
            if (exp[i] == \'}\')
            {
                if (st[top] == \'{\')
                    --top;
                else
                    flag = false;
            }
            i++;
        }
        if (top >= 0)
            flag = false;

        return flag;
    }

对称问题

有一个带头结点的单链表 L ,用于存放整数序列,设计一个算法判断该序列是否是对称的。

    bool symtry(LinkList *L)
    {
        int st[MaxSize];
        int top = -1;
        LinkList *ptr = L->next;
        
        while (ptr != NULL)
        {
            ++top;
            st[top] = ptr->data;
            ptr = ptr->next;
        }
        ptr = L->next;
        while (ptr != NULL)
        {
            if (ptr->data == st[top])
                --top;
            else
                return flase;

            ptr = ptr->next;
        }
        return true;
    }

进制转换

编写一个算法,将一个非负的十进制数 N 转换为一个二进制数。

int trans(int N)
{
    int stack[MaxSize];
    int top = -1;
    int i, result = 0;

    while (N != 0)
    {
        i = N % 2;
        N = N / 2;
        stack[++top] = i;
    }

    while (top != -1)//可以根据需求改变
    {
        i = stack[top--];
        result = result * 10 + i;
    }
    return result;
}

表达式转换

后缀表达式是没有了括号并考虑了运算符优先级的一种表达式。将算术表达式 exp 转换为后缀表达式 postexp 的基本思想是:采用运算符栈比较运算符的优先级,所有运算符必须进栈,只将大于栈顶优先级的运算符直接进栈,否则需要退栈栈顶运算符(先退栈的先计算,同优先级的运算符在栈中的先计算)

  • 采用运算符栈:

    while(从 exp 中循环读取字符 ch)
    {
    ch 为数字: 将后续的所有数字均依次存放到 postexp中。
    ch 为左括号"(": 将其放入到运算符栈op中。
    ch 为右括号 ")": 将栈op中左括号"("以前的运算符依次出栈并存放到postexp中,然后将左括号"("出栈。
    ch 的优先级大于栈op的栈顶运算符的优先级或栈顶为"(":将ch进栈,否则依次退掉栈顶优先级大于或同级运算符到postexp中,然后将ch进栈。
    }
    

将栈op中的所以运算符出栈并存放到postexp中,最后得到后缀表达式。

  • 手工方法:需要人工判断表达式的执行顺序(即加括号),所以无法用程序实现
    • 先根据中缀表达式的求值次序加上括号
    • 将右括号用括号内的相应运算符替换
    • 去除所有的左括号

5+2*(1+6)-8/2为例说明以中缀表达式变成后缀表达式:

屏幕快照 2016-07-15 下午4.17.23.png

  • 对后缀表达式postexp求值

    while(从postexp中循环读取所有的字符ch)
    {
    ch 为数字:进到数值栈st中;
    ch 为"+":从数值栈st中退两个运算数,相加后进数值栈st中;
    ch 为"-":从数值栈st中退两个运算数,相加后进数值栈st中;
    ch 为"*":从数值栈st中退两个运算数,相加后进数值栈st中;
    ch 为"/":从数值栈st中退两个运算数,相加后进数值栈st中(若除数为0,则提示错误信息);
    }
    

队列

队列是一种插入和删除操作受限的线性表,其插入和删除操作在不同端进行,插入元素的一端为队尾,删除元素的一端为队头。

队列中一般使用 font 和 rear 来指示队头和队尾,front 指向队列中队首元素的前一个位置,rear 指向队尾元素,因此:

  • 进队操作是先将 rear++; 再将元素放到 rear 位置
  • 出队操作是先将队头 front++; 再取出队头元素

顺序队列

typedef struct
{
 int data[MaxSize];
 int front,rear;
}SqQueue;

进队操作进队操作是先将 rear++; 再将元素放到 rear 位置;出队操作是先将队头 front++; 再取出队头元素。这样导致队头和队尾指针只增加不减少,被出队的空间无法再使用。于是通过逻辑方法改为首尾相连的循环队列可以反复利用已经出队的空间。

  • 队空条件:
    qu.front==qu.rear 
  • 队满条件:
    (rear+1)%MaxSize==qu.front
  • 进队:
    qu.rear=(qu.rear+1)%MaxSize; qu.data[rear]=x;
  • 出队:
    qu.front=(qu.front+1)%MaxSize; x=qu.data[front];
  • 队中元素个数:
    (qu.rear-qu.front+MaxSize)%MaxSize; 

队列的基本运算(详细)

  • 初始化队

    void initQueue(SqQueue &qu)
    {
       qu.front = qu.rear = 0;
    }
    
  • 判断队空

    bool isEmpty(SqQueue qu)
    {
       if (qu.front == qu.rear)
            return true;
       else
         return false;
    }
    
  • 进队

     bool enQueue(SqQueue &qu, int x)
     {
          if ((qu.rear + 1) % MaxSize == qu.front)//是否队满
                 return false;
    
        qu.rear = (qu.rear + 1) % MaxSize;
        qu.data[qu.rear] = x;
        return true;
    }
    
  • 出队

     bool deQueue(SqQueue &qu, int &x)
     {
        if (qu.rear == qu.front)//是否队空
            return false;
        qu.front = (qu.front + 1) % MaxSize;
        x = qu.data[qu.front];
        return true;
     }
    

链队

    struct qnode
    {
        int data;
        struct qnode *next;
    } QNode;
    
    typedef struct
    {
        QNode *front; //队头指针
        QNode *rear;  //队尾指针
    } LiQueue;
  • 队空条件:

     qu->font == NULL;或 qu->rear == NULL; 

    bool isEmpty(LiQueue *lqu)
    {
        if (lqu->rear == NULL || lqu->front == NULL)
            return true;
        else
            return false;
    }
    
  • 队满条件:一般认为不存在(实际上受内存大小的限制)

  • 进队:要考虑原来队空操作

    void enQueue(LiQueue *lqu, int x)
    {
        QNode *ptr;
        ptr = (QNode *) malloc(sizeof(QNode));
        ptr->data = x;
        ptr->next = NULL;
    
        if (lqu->rear == NULL)
            lqu->front = lqu->rear = ptr;
        else
        {
            lqu->rear->next = ptr;
            lqu->rear = ptr;
        }
    }
    
  • 出队:要考虑队空和只有一个节点

    bool deQueue(LiQueue *lqu, int &x)
    {
        QNode *ptr;
    
        if (lqu->rear == NULL) //队空
            return false;
        else
            ptr = lqu->front;
    
        if (lqu->rear == lqu->front) //只有一个元素
            lqu->front = lqu->rear = NULL;
        else
            lqu->front = ptr->next;
    
        x = ptr->data;
        free(ptr);
        ptr = NULL;
    
        return true;
    }
    

双端队列

双端队列是指两端都可以进行进队和出队操作的的队列

VISIO_2016-07-15_21-34-12.png


int x;
int y;

以上是关于栈与队列的主要内容,如果未能解决你的问题,请参考以下文章

栈与队列:循环队列算法+可执行代码

[算法] leetcode栈与队列相关题目详解

数据结构《三》栈与队列的实现

栈与队列试题中的操作代码

从今天开始好好学数据结构02栈与队列

数据结构栈与队列