栈和队列

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

栈 

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

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

 

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

 

 

 

 

 

 

 

posted on 2012-06-03 17:33  沙漠之泉  阅读(516)  评论(0编辑  收藏  举报

导航