zoukankan      html  css  js  c++  java
  • 栈与队列

    栈与队列

     

    简介 

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

    栈 

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

    二、栈的特点

    根据栈的定义可知,最先放入栈中元素在栈底,最后放入的元素在栈顶,而删除元素刚好相反,最后放入的元素最先删除,最先放入的元素最后删除。 也就是说,栈是一种后进先出(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型的指针。

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

  • 相关阅读:
    permission 文档 翻译 运行时权限
    TabLayout ViewPager Fragment 简介 案例 MD
    Log 日志工具类 保存到文件 MD
    OkHttp 官方wiki 翻译 MD
    Okhttp 简介 示例 MD
    OkHttp 官方Wiki之【使用案例】
    DialogPlus
    倒计时 总结 Timer Handler CountDownTimer RxJava MD
    RecyclerView 判断滑到底部 顶部 预加载 更多 分页 MD
    CSS3的媒体查询(Media Queries)与移动设备显示尺寸大全
  • 原文地址:https://www.cnblogs.com/java2016/p/7662146.html
Copyright © 2011-2022 走看看