• 栈与队列


    简介 

    堆栈和队列都属于线性结构,是两种在运算上受到某些限制的特殊线性表,他们比一般线性表更简单,被广泛应用于类型的程序设计中,可以用来存放许多中间信息,在系统软件设计以及递归问题处理方面都离不开堆栈和队列。

    栈 

    栈的操作原则是:先进后出,后进先出

    二、栈的特点

    根据栈的定义可知,最先放入栈中元素在栈底,最后放入的元素在栈顶,而删除元素刚好相反,最后放入的元素最先删除,最先放入的元素最后删除。 也就是说,栈是一种后进先出(Last In First Out)的线性表,简称为LIFO表。

    三、栈的运算 

    1.初始化栈:INISTACK(&S)

    将栈S置为一个空栈(不含任何元素)。

    2.进栈:PUSH(&S,X)

    将元素X插入到栈S中,也称为 “入栈”、 “插入”、 “压入”。

    3.出栈: POP(&S) 

    删除栈S中的栈顶元素,也称为”退栈”、 “删除”、 “弹出”。

    4.取栈顶元素: GETTOP(S)

    取栈S中栈顶元素。

    5.判栈空: EMPTY(S)

    判断栈S是否为空,若为空,返回值为1,否则返回值为0。

    栈总是处于栈空、栈满或不空不满三种状态之一,它们是通过栈顶指针top的值体现出来的。 规定:top的值为下一个进栈元素在数组中的下标值。 栈空时(初始状态),top=0; 栈满时,top=MAXN.

    三、栈的五种运算 (一) 进栈 1) 进栈算法 (1) 检查栈是否已满,若栈满,进行“溢出”处理。 (2) 将新元素赋给栈顶指针所指的单元。 (3) 将栈顶指针上移一个位置(即加1)。 

    (二) 出栈 1) 出栈算法 (1) 检查栈是否为空,若栈空,进行“下溢”处理。 (2)将栈顶指针下移一个位置(即减1) 。 (3)取栈顶元素的值,以便返回给调用者。 

    四.栈的共享存储单元 有时,一个程序设计中,需要使用多个同一类型的栈,这时候,可能会产生一个栈空间过小,容量发生溢出,而另一个栈空间过大,造成大量存储单元浪费的现象。 为了充分利用各个栈的存储空间,这时可以采用多个栈共享存储单元,即给多个栈分配一个足够大的存储空间,让多个栈实现存储空间优势互补。

     

    4、两个栈共享同一存储空间      当程序中同时使用两个栈时,可以将两个栈的栈底设在向量空间的两端,让两个栈各自向中间延伸。当一个栈里的元素较多,超过向量空间的一半时,只要另一个栈的元素不多,那么前者就可以占用后者的部分存储空间。     只有当整个向量空间被两个栈占满(即两个栈顶相遇)时,才会发生上溢。因此,两个栈共享一个长度为m的向量空间和两个栈分别占用两个长度为 └ m/2┘和┌m/2┐的向量空间比较,前者发生上溢的概率比后者要小得多。

     队列 

     1、定义      队列(Queue)是只允许在一端进行插入,而在另一端进行删除的运算受限的线性表

    (1)允许删除的一端称为队头(Front)   (2)允许插入的一端称为队尾(Rear)   (3)当队列中没有元素时称为空队列   (4)队列亦称作先进先出(First In First Out)的线性表,简称为FIFO表      队列的修改是依先进先出的原则进行的。新来的成员总是加入队尾(即不允许"加塞"),每次离开的成员总是队列头上的(不允许中途离队),即当前"最老的"成员离队。

     

    队列的存储结构:

     

    顺序队列 队列的顺序存储结构称为顺序队列,顺序队列实际上是运算受限的顺序表,和顺序表一样,顺序队列也是必须用一个数组来存放当前队列中的元素。由于队列的队头和队尾的位置是变化的,因而要设两个指针和分别指示队头和队尾元素在队列中的位置。 

     

    (3) 顺序队列的基本操作 队列也有队空、队满或不空不满三种情况。 1.第一种表示方法 规定:head指向队首元素的位置,tail指向队尾元素的位置。队列初始状态设为head=0,tail=-1. 当队列非空时,tail>=head; 当队列空时, head>tail; 当队列满时, tail=maxsize-1. 2.第二种表示方法 规定:head指向队首元素的前一个位置,tail指向队尾元素的位置。队列初始状态设为head=tail=-1. 1)当队列非空时,tail>head; 2)当队列空时, head=tail; 3)当队列满时, tail=maxsize-1.   ①入队时:将新元素插入rear所指的位置,然后将rear加1。   ②出队时:删去front所指的元素,然后将front加1并返回被删元素。

     

    (4)顺序队列中的溢出现象   ① "下溢"现象      当队列为空时,做出队运算产生的溢出现象。“下溢”是正常现象,常用作程序控制转移的条件。   ② "真上溢"现象      当队列满时,做进栈运算产生空间溢出的现象。“真上溢”是一种出错状态,应设法避免。   ③ "假上溢"现象 从顺序存储的队列可以看出,有可能出现这样情况,尾指针指向一维数组最后,但前面有很多元素已经出队,即空出很多位置,这时要插入元素,仍然会发生溢出。例如,在下图中,若队列的最大容量maxsize=4,此时,tail=3,再进队时将发生溢出。我们将这种溢出称为“假溢出”。 要克服“假溢出”,可以将整个队列中元素向前移动,直到头指针head为零,或者每次出队时,都将队列中元素前移一个位置。因此,顺序队列的队满判定条件为tail=maxsize-1。但是,在顺序队列中,这些克服假溢出的方法都会引起大量元素的移动,花费大量的时间,所以在实际应用中很少采用,一般采用下面的循环队列形式。

    循环队列

    一)定义 为了克服顺序队列中假溢出,通常将一维数组queue[0]到q[maxsize-1]看成是一个首尾相接的圆环,即queue[0]与queue[maxsize-1]相接在一起。将这种形式的顺序队列称为循环队列 。

    若tail+1=maxsize,则令tail=0. 这样运算很不方便,可利用数学中的求模运算来实现。

    入队:tail=(tail+1) mod maxsize;squeue[tail]=x. 出队:head=(head+1) mod maxsize.

    二)循环队列的变化

    在循环队列中,若head=tail,则称为队空, 若(tail+1) mod maxsize=head, 则称为队满,这时,循环队列中能装入的元素个数为maxsize-1,即浪费一个存储单元,但是这样可以给操作带来较大方便。 


    三)循环队列上五种运算实现 

    1.进队列

    1)进队列算法

    (1)检查队列是否已满,若队满,则进行溢出错误处理;

    (2)将队尾指针后移一个位置(即加1),指向下一单元;

    (3)将新元素赋给队尾指针所指单元。

    2) 进队列实现程序

    int head=0,tail=0;

    int enqueue (elemtype queue[], elemtype x)

    { if ((tail+1)%maxsize = = head)   return(1);

    else

    {

    tail=(tail+1)%maxsize;

    queue[tail]=x;

    return(0);

    }

    2. 出队列

    1)出队列算法

    (1)检查队列是否为空,若队空,则进行下溢错误处理;

    (2)将队首指针后移一个位置(即加1);

    (3)取队首元素的值。

    2) 出队列实现程序

    int head=0,tail=0;

    int dlqueue(elemtype queue[ ],elemtype *p_x )

    {

    if (head= =tail) return(1);

    else

    {

    head =(head+1) % maxsize;

    *p_x=queue[head]];

    return(0);

    }

    }

    (3) 队列初始化 head=tail=0;

    (4) 取队头元素(注意得到的应为头指针后面一个位置值)

    elemtype gethead(elemtype queue[ ] )

    { if (head= =tail)   return(null);

    else return (queue[(head+1)%maxsize]);

    }

    (5) 判队列空否

    int empty(elemtype queue[ ] )

    {

    if (head= =tail) reurn (1);

    else return (0);

    (1) 循环队列的基本操作    

     循环队列中进行出队、入队操作时,头尾指针仍要加1,朝前移动。

    只不过当头尾指针指向向量上界(QueueSize-1)时,其加1操作的结果是指向向量的下界0。

    这种循环意义下的加1操作可以描述为:

    ① 方法一:     if(i+1==QueueSize) //i表示front或rear        i=0;    

                          else         i++;

    ② 方法二--利用"模运算"    

    i=(i+1)%QueueSize;

    (2) 循环队列边界条件处理     

    循环队列中,由于入队时尾指针向前追赶头指针;出队时头指针向前追赶尾指针,造成队空和队满时头尾指针均相等。因此,无法通过条件front==rear来判别队列是"空"还是"满"。

    【参见动画演示】      解决这个问题的方法至少有三种:  

     ① 另设一布尔变量以区别队列的空和满;  

     ② 少用一个元素的空间。约定入队前,测试尾指针在循环意义下加1后是否等于头指针,若相等则认为队满(注意:rear所指的单元始终为空);   

    ③使用一个计数器记录队列中元素的总数(即队列长度)。

    (3) 循环队列的类型定义

         #define Queur Size 100   //应根据具体情况定义该值     

    typedef char Queue DataType;  //DataType的类型依赖于具体的应用     

    typedef Sturet{               //头指针,队非空时指向队头元素         

       int front;              //尾指针,队非空时指向队尾元素的下一位置           

    int rear;               //计数器,记录队中元素总数          

      DataType data[QueueSize]    

      }CirQueue;

    (4) 循环队列的基本运算   用第三种方法,循环队列的六种基本运算:  

     ① 置队空     

      void InitQueue(CirQueue *Q)      

    {          

         Q->front=Q->rear=0;       

            Q->count=0;     //计数器置0   

         }

      

    ② 判队空      

      int QueueEmpty(CirQueue *Q)   

         {        

         return Q->count==0;  //队列无元素为空      

       }  

     ③ 判队满

    int QueueFull(CirQueue *Q)     

        {         

        return Q->count==QueueSize;  //队中元素个数等于QueueSize时队满         

    }  

     ④ 入队

    void EnQueue(CirQueuq *Q,DataType x)      

        {          

       if(QueueFull((Q))                            

              Error("Queue overflow");     //队满上溢        

         Q->count ++;                        //队列元素个数加1       

          Q->data[Q->rear]=x;                 //新元素插入队尾         

        Q->rear=(Q->rear+1)%QueueSize;      //循环意义下将尾指针加1   

    ⑤ 出队

    DataType DeQueue(CirQueue *Q)      

         {       

            DataType temp;  

                 if(QueueEmpty((Q))                  

      Error("Queue underflow");     //队空下溢              

    temp=Q->data[Q->front];             

      Q->count--;                        //队列元素个数减1        

           Q->front=(Q->front+1)&QueueSize;   //循环意义下的头指针加1         

          return temp;          

      }                 

    ⑥取队头元素

    DataType QueueFront(CirQueue *Q)         

        {               

      if(QueueEmpty(Q))      

                   Error("Queue if empty.");      

               return Q->data[Q->front];         

        }

    链队列

    一、队列的链式存储结构 队列的链式存储是用一个线性链表来实现一个队列的方法,线性链表表示的队列称为链队列。

    在链队列中,链表的第一个节点存放队列的队首结点,链表的最后一个节点存放队列的队尾首结点,队尾结点的链接指针为空。并设一头指针指向队首结点,设一尾指针指向队尾结点。 当队列为空时,有head=NULL.

    二、链队列上的基本运算

    1 .入队列

    1)进队算法

    (1) 为待进队元素申请一个新结点,给该结点赋值;

    (2)将x结点链到队尾结点上;

    (3)队尾指针改为指向x结点。

    2)实现程序

    NODE *head=NULL, *tail;

    Void link_ins_queue(elemtype x)

    {

    NODE *p;

    p=(NODE *)malloc(sizeof(NODE));

    p->data=x;

    p->link=null;

    if (head==null)   head=p;

    else tail->link=p;

    tail=p;

    }

    2. 出队列

    1)出队算法

    (1) 检查队列是否为空,若为空进行下溢错误处理;

    (2)取队首元素的值并将队首指针暂存;

    (3)头指针后移,指向新队首结点,并删除原队首结点。

    2) 实现程序

    int del_queue(elemtype *p_x)

    { NODE *p;

    if (head= =NULL) 

    return(1);

    *p_x=head->data;

    p=head;

    head=head->link;

    free(p);

    return(0);

    }

     

       注意:      增加指向链表上的最后一个结点的尾指针,便于在表尾做插入操作。      链队列示意图见上图,图中Q为LinkQueue型的指针。

     注意:      ①和链栈类似,无须考虑判队满的运算及上溢。      ②在出队算法中,一般只需修改队头指针。但当原队中只有一个结点时,该结点既是队头也是队尾,故删去此结点时亦需修改尾指针,且删去此结点后队列变空。      ③以上讨论的是无头结点链队列的基本运算。和单链表类似,为了简化边界条件的处理,在队头结点前也可附加一个头结点,增加头结点的链队列的基本运算。

     

     

     

     

  • 相关阅读:
    Column Transformer with Heterogeneous Data Sources -- of sklearn
    Column Transformer with Mixed Types -- of sklearn
    Pipelines and composite estimators of sklearn
    MySQL深入研究--学习总结(1)
    Java进阶专题(二十六) 数据库原理研究与优化
    Java进阶专题(二十五) 分布式锁实现业务幂等
    Java进阶专题(二十三) RPC深入原理
    Java进阶专题(二十二) 微服务架构体系-SpringCloudAlibaba
    Java进阶专题(二十一) 消息中间件架构体系(3)-- Kafka研究
    Elasticsearch(二)
  • 原文地址:https://www.cnblogs.com/wc1903036673/p/3499933.html
Copyright © 2020-2023  润新知