栈与队列

简介 

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

栈 

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

二、栈的特点

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

 

栈与队列_第1张图片

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

 

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

 

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

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

 

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

 

 队列 

 

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

 

栈与队列_第3张图片

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

 

队列的存储结构:

 

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

 栈与队列_第4张图片

(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);

 

 

 

栈与队列_第5张图片

(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);

}

 

 栈与队列_第6张图片

 

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

 

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

 

 

 

 

 

你可能感兴趣的:(队列)