美文网首页
数据结构算法之队列和双向队列

数据结构算法之队列和双向队列

作者: Peakmain | 来源:发表于2019-03-27 14:52 被阅读0次

队列

队列(Queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。允许插入的端是队尾,允许删除的端是队头(先进先出)。
那么我们会发现,插入的时间复杂度是O(1)级别的,但是删除是O(n)级别的,因为删除对头之后,其它数据需要向前移动。那么怎么让删除的时间复杂度也变成O(1)级别呢,这就引出双向队列

双向队列

image.png

思路:我们首先会有个head和tail,分别代表队头和对尾,很明显当head==tail的时候代表该队列已经满了,这时候就需要扩容。
扩容前我们首先做几件事情
一、构造函数中数组的大小是2的幂次方,主要目的就是保证数据的分布均匀,具体原因可以继续往下看

template<class E>
ArrayQueue<E>::ArrayQueue(int size) {// 3 -> 4  6 ->8  17 ->32
    // 保证是 2 的幂次
    int init_size = size;
    if (init_size >= 4) {
        init_size |= init_size >> 1;
        init_size |= init_size >> 2;
        init_size |= init_size >> 4;
        init_size |= init_size >> 8;
        init_size |= init_size >> 16;
        // 比如你传入的是4,上面步骤结束后的大小是7所以需要+1
        init_size += 1;

    }
    this->size = init_size;
    array = (E *) malloc(sizeof(E) * size);
}

二、既然是扩容,肯定要是在添加数据的时候进行扩容

template<class E>
void ArrayQueue<E>::push(E e) {
    //实际大小就是size-1
    head = (head - 1) & (size - 1);// -1
    //直接赋值就可以了
    array[head] = e;
    if (tail == head) {
        // 扩容
        growArray();
    }
}
template<class E>
void ArrayQueue<E>::growArray() {
    // 大小扩大两倍
    int new_size = size << 1;

    // 重新开辟一块内存
    E *new_array = (E *) malloc(sizeof(E) * new_size);

    // 对数据进行 copy ,将 tail 后面的拷贝到前面,将tail前面的拷贝到后面
    int r = size - tail;
    copyElement(array, tail, new_array, 0, r);
    copyElement(array, 0, new_array, r, tail);

    free(array);
    head = 0;
    tail = size;
    size = new_size;
    array = new_array;
}
/**
 * 赋值
 * @param src 原数组
 * @param sPo  原数组开始的位置
 * @param dest  目标数组
 * @param dPo  目标数组开始的位置
 * @param len  原数组结束的位置
 */
template<class E>
void ArrayQueue<E>::copyElement(E *src, int sPo, E *dest, int dPo, int len) {
    for (int i = 0; i < len; ++i) {
        dest[dPo + i] = src[sPo + i];
    }
}

上面代码解释下:

  • head = (head - 1) & (size - 1);
    为什么说它的大小实际就是size-1呢,假设我们数组的大小通过构造函数传入的是4经过构造函数处理后我们的大小是8,具体构造函数中有解释,所以size-1的大小是7,二进制最后四位是0111,而head默认是0,也即是0-1=-1,二进制最后四位是1111,所以&运算结果是7,继续轮推我们会发现大小依次是6,5,4,3,2,1,0。
  • growArray扩容数组,直接看图会更清晰
    假设我们添加1-10的数


    image.png

    添加数据是从从右向左添加数据,那么tail默认是在0这个位置,也就是当head==tail==0的时候代表队列满了,这时候进行第一次扩容


    image.png
    新建一个数组,将之前数组拷贝到新数组,这时候我们需要将head设置为0,tail为队列的大小,此时继续添加数据,仍然是从右向左添加数据
    image.png
    此时tail和head相等,那么证明此时又需要进行扩容,如何扩容呢,实际还是开辟一个新数组,但是这里我们需要注意一点,我们在复制数据的时候,不能打乱原来的数据,处理的方式,将 tail 后面的拷贝到前面,将tail前面的拷贝到后面

三、判断队列是否满了和释放内存

template<class E>
bool ArrayQueue<E>::isEmpty() {
    return tail == head;
}
template<class E>
ArrayQueue<E>::~ArrayQueue() {
    free(array);
}

四、获取队首的位置,但是不移除

template<class E>
E ArrayQueue<E>::peek() {
    return array[(tail - 1) & (size - 1)];
}

实际我们的队首是head所指向的位置,那么如何获得队首head的下标呢


image.png

假设是上述这种情况,实际队首是这个队列的大小


image.png
上述这种情况下,队首实际也是这个队列的大小,可是怎么获得这个队列的大小呢,我们在默认数组大小是4的情况下,第一次扩容后,tail由0→8,而size由8→16,所以下标实际就是7,由此得出
(tail - 1) & (size - 1)

五、移除队首的元素

template<class E>
E ArrayQueue<E>::pop() {
    tail = (tail - 1) & (size - 1);
    return array[tail];
}

相关文章

  • 数据结构算法之队列和双向队列

    队列 队列(Queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。允许插入的端是队尾,允许删除的...

  • 排序算法

    什么是算法 书籍推荐 《数据结构与算法分析》 表、栈和队列 树 散列(hash) 优先队列(堆) 排序 定义 问题...

  • Java实现队列

    队列是一种先进先出的数据结构,和栈刚好相反,队列在算法中也应用广泛。本文,我们主要探讨Java实现队列。 队列 队...

  • 常见的数据结构

    常见的数据结构有: 数组 链表单链表、双向链表、循环链表、双向循环链表、静态链表 栈顺序栈、链式栈 队列普通队列、...

  • Java数据结构和算法概览

    Java数据结构和算法概览 数据结构 线性数据结构:常见的有一维数组,线性表,栈,队列,双队列,串。 非线性数据结...

  • 数据结构与算法系列-目录

    数据结构和算法目录表 线性结构 1.数组、单链表和双链表 2.Linux内核中双向链表的经典实现 栈 队列 树形结...

  • LeetCode 栈、队列、优先队列专题 1:栈和队列的使用

    这一部分,我们开始介绍“栈、队列、优先队列”。栈和队列虽然是简单的数据结构,但是使用这些简单的数据结构所解决的算法...

  • 链式队列的定义,ios基础知识篇!

    数据结构与算法-链式队列 链式队列的定义 链式队列是用链表来实现的队列,不存在队满的情况。链式队列也包里队列的特点...

  • 数据结构 03 双向队列

    双向队列 它是一种混合线性数据结构, 涵盖Stack和Queue的全部能力.

  • 数据结构 - 队列

    数据结构和算法动态可视化网站[https://visualgo.net/zh] 一、队列 Queue 队列是一种特...

网友评论

      本文标题:数据结构算法之队列和双向队列

      本文链接:https://www.haomeiwen.com/subject/nbogvqtx.html