数据结构之队列、栈

数据结构之队列、栈

  1. 定义

    首先,从大的概念上来看,队列和栈都是线性表;并且它们都是只允许在端点处进行插入或者删除操作;

    栈:只允许在同一端进行插入或者删除的线性表叫做栈;它是一种具有First In Last Out (或者Last In First Out)特点的表;

    队列:只允许在一端进行插入、另一端进行删除操作的线性表叫做队列;它是一种具有First In First Out (或者Last In Last Out)特点的表;

  2. 基本概念
    1. 栈的基本概念
      • 允许进行操作的一端叫做栈顶;另一端叫做栈底,通常使用top指向栈顶;使用bottom指向栈底;
      • 向栈顶加入一个元素叫做进栈或者压入一个元素;从栈顶删除一个元素叫做出栈或者弹出一个元素;
    2. 队列的基本概念
      • 允许插入的一端叫做队尾,允许删除的一端叫做队首;
      • 向队尾插入一个元素叫做入队;从队首删除一个元素叫做出队;
  3. 分类

    按照实现的方式不同,可以分为顺序栈、链式栈以及顺序队和链式队;

    由于顺序存储比较方便(自然也有其缺点:线性表的容量不能动态扩张),队列、栈常常使用顺序存储完成;

    使用顺序存储的队列为了提高存储空间的利用率也可以设计为循环队列;

  4. 实现
    1. 链式栈:

      使用空栈底元素时(类似使用空头元素实现链表):

      • 初始化:bottom=empty_Element; top=bottom;
      • 栈空的判别条件为:top==bottom;

      不使用空栈底元素时:

      • 初始化:bottom=top=NULL;
      • 栈空的判别条件为:top==NULL;
    2. 顺序栈:(涉及判别栈满的情况,设size为申请的大小)

      top指向实体元素;

      • 初始化:top=-1;
      • 栈空的判别条件:top==-1;
      • 栈满的判别条件:top+1>=size;

      top指向下一个元素的存储位置:

      • 初始化:top=0;
      • 栈空的判别条件:top=0;
      • 栈满的判别条件:top==size;
    3. 链式队列:

      使用空队首元素:

      • 初始化:head=empty_Element; tail=head;
      • 队空的判别条件:head==tail;

      不使用空队首元素:

      • 初始化:head=tail=NULL;
      • 队空的判别条件:head=NULL;
    4. 顺序队列:(涉及判别栈满的情况,设size为申请的大小)

      一般顺序队列:

      head指向队首元素

      • 初始化:head=0; tail=-1;
      • 队列的判空条件:head>tail;
      • 队列判满的条件:tail+1>=size;

      head指向队首元素的上一个位置:

      • 初始化:head=-1;tail=-1;
      • 队列判空的条件:head==tail;
      • 队列判满的条件:tail+1>=size;

      以上队列:出队列时head++;入队列时tail++(指针则向前移动一位);

      以上两种顺序队列会产生假满现象,即队列里还有存储空间(这些空间为已经出队列的元素原来所占的空间),但是由于判满条件使得无法继续添加元素;解决该问题的方案为设计循环队列;

      所谓循环队列,即要求改变队列判满条件,从而充分利用申请的空间;从逻辑上来看,类似单向循环链表;

      如果我们使用head指向队首元素,tail指向队尾元素,初始时head=0; tail=-1; 由于我们会重复利用已经出队列的元素所占用的空间(这些空间位于head的左侧),所以tail有可能在到达数组边界后因为继续入队列操作而出现在head的左侧,所以也要修改判空条件;先总结队列为空的情景,假设此时head的值为1,tail的值为3,size的值为6,即0是空的,1,2,3为队列元素,4,5为空:

      1. 不断出队列,head增加到4,此时队列为空,head>tail;
      2. 不断入队列,tail增加到5,此时0是空的(至于怎么知道且看后文详解),所以下一个元素就添加到0的位置了,此时队列不为空,tail=0,但是head>tail。然后不断出队列,head不断增加到5,然后发现0是有元素的,结果head置0,然后出队列,head>tail;

      所以“使用head指向队首元素,tail指向队尾元素”是行不通的;

      那么我们使用head指向队首元素的上一个位置,tail指向对尾元素;并且head所在的位置不存储元素;那么同样的场景(1,2,3为队列元素,4,5为空,head此时为0,tail此时为3),我们再看:

      1. 不断出队列,head增加到3,head==tail,此时队列为空;
      2. 不断入队列,tail增加到5,此时0为空,所以再入队列,tail为0,然后不断出队列,head增加4,即5处还有元素,再出队列,head=5,当再出队列时,head只要变为0即可实现head==tail,从而找到判空条件,既然名为循环队列,数学中可以产生循环效果的运算便是取余操作啦,所以只要把head=head+1改为head=(head+1)%6即可实现,同理tail=(tail+1)%6。

      那么如何判满呢?

      当tail循环一圈(不断入队列操作)后出现在了head的左边,此时若tail+1==head,则说明如果再入队列,那么head指向的空间就要被占了,而且此时如果再允许入队列,那么head==tail,又和判空条件冲突,所以这时候是不允许再入队列的,即判满!故结合循环,判满条件为(tail+1)%6==head;

      循环队列:(head指向队首元素的前一个位置,tail指向队尾元素)

      • 初始化:head=-1; tail=-1;
      • 队列判空:head==tail;
      • 队列判满:(tail+1)%size==head

      出队列:head=(head+1)%size;入队列:tail=(tail+1)%size;

    本文源码github地址(这是自己使用Java语言(部分细节参考了Java集合类源码)实现的一套数据结构API,包括链表,栈、队列、树、图等内容,为算法学习提供基础,完善ing~)

猜你喜欢

转载自blog.csdn.net/slx3320612540/article/details/79461442