单向循环队列
⚪简介
我看了网上C++实现的队列大都是用之前所学的链表来实现,因为不用考虑内存溢出问题,其实单向的队列主要功能是队尾入队和队头出队,是在头和尾实现,若是没有循环在队头这一块的处理动态数组的复杂度为O(n),而链表是O(1),当然选用链表来实现。但是当将动态数组优化使其循环,可做到几乎所有功能的平均复杂度都可以做到O(1)。
队头(front):用来存储队头元素的下标
若是要出队则将队头元素往下加1
若是要出队则往队尾添加元素
而循环队列的特别点在于当添加元素已放到最后一个位置时,此时是一种假溢出的情况,即整个数组并非是满的,但是在非循环队列需要动态扩容再开辟一个空间储存,而对于循环队列来说只需要把新添加的元素放到最前端。
则此时队头元素的下标为2,队尾元素的下标为0。
而当整个数组真的满的时候,即真正快要溢出时再进行动态扩容
其实单向循环队列比较好的地方主要是可以最大化的使用动态数组的空间,不必像之前的动态数组一样删除头部元素后方所有元素都要跟着移动,只需要将头节点指向下一个节点就好,在队尾入队使用也比较巧妙,不是再像之前的单纯的一直在后方加入元素,而是在后方位置不足时候查看数组最前端是否还有空余位置,若是有就放入前方,在不足的情况下再进行动态扩容。
⚪接口设计
#pragma once
#include <iostream>
using namespace std;
template <class E>
class Circle_Single_Queue
{
private:
int size;
int front;
E *elements;
int ELEMENT_LENGTH = 10;
int sum_capacity; //保存此时所有的容量
void Ensure_Capacity(int capacity); //动态扩容
int find_index(int index); //寻找队列中index位置对应在动态数组的位置
public:
Circle_Single_Queue() //默认构造函数
{
size = 0;
front = 0;
elements = new E[ELEMENT_LENGTH]; //默认长度为10
sum_capacity = ELEMENT_LENGTH;
}
int cau_size(); //求数据队列长度
bool isEmpty(); //检测是否为空
void enQueue(E element); //入队
E deQueue(); //出队
E look_front(); //观测队头元素
void print();
~Circle_Single_Queue() {};
};
⚪难点讲解
入队
在之前的动态数组进行尾部添加元素时所用的代码时element[size++] = element;
但是对于循环队列来说,当队头不在下标0处此句代码就无法实现我们所需要的功能。以上出图片为例,此时的size=3, 执行此句子代码,添加的元素就放入下标为3处,覆盖了原来的元素44。而其实我们真正添加元素下标应该是5即front+size 。
而对于此种情况,利用上方的front+size=7超过了所给数组最大下标值,若是此时动态扩容无法达到我们设计循环链表的目的。我们真正想要添加元素的下标应当时0,因此我们要对其取余,即使用(front + size)% Max_Size。
利用上图的情况来检测。front=2,size=6,Max_Size=7,(front+size)%Max_Size=1,正好就是我们所需的下标位置。
因此入队的总代码为:
template <class E>
void Circle_Single_Queue<E>::enQueue(E element) //入队
{
Ensure_Capacity(size + 1);
elements[(front+size)%Max_Size] = element; //队头+个数再对长度取余(若是数组队尾已满,可向数组头部添加元素)
size++;
}
出队
对于队列是只能从头部出队,就像日常生活中的排队一样,只有最先到的人才能最先出去。
想要出队我们先要获得队头元素的下标 E Front_Element = elements[front];
然后再将原队头元素置空elements[front]=NULL;
,接下来把队头往下移一格front++;
后size--;
,最后再返回出队的元素的值return Front_Elements;
当为上图情况时,我们将队头元素出队,当执行到front++
语句时就会报错,因此此时虽然队列中还存在元素,但是front++后front值为7,超过了数组下标的最大值,而我们想要的front的值应该变为下标0。所以我们在此处对front的值再进行取余变成front = (front + 1)%Max_Size;
因此出队的总代码为:
template <class E>
E Circle_Single_Queue<E>::deQueue() //出队
{
E front_element = elements[front];
elements[front] = NULL;
front = (front + 1)%Max_Size; //若队头已移至数组尾部,对其取余,到数组头部
size--;
return front_element;
}
动态扩容
当队列成为真正意义上的满时,我们要对其进行动态扩容。
那么此时我们要把原先的元素从队头到队尾依此放入新的大数组中,再将front重新置零。
此处的动态扩容与动态数组的动态扩容用的是一个原理,因此此处我们可以直接引用动态数组的代码再进行删改。
template <class E>
void ArrayList<E>::EnsureCapacity(int capacity) //检查是否需要扩容
{
int oldCapacity = size;
if (oldCapacity >= capacity)
{
return;
}
int newCapacity = oldCapacity + (oldCapacity >> 1); //此处为扩容1.5倍,因为2倍如果有动态缩容功能的话会产生复杂度震荡,经过大数据计算,1.5倍为利用率最高的倍数
int* NewElements = new int[newCapacity];
for (int i = 0; i < size; i++)
{
NewElements[i] = elements[i];
}
elements = NewElements;
}
但是对于循环队列来说,在for循环中不像动态数组的元素是按照下标的值依此排列直接一一赋值即可,而是需要将其改为newelements[i] = elements[(i + front) % Max_Size];
把其真正的索引的值放入新的大数组中,最后再将front=0。
动态扩容总代码:
template <class E>
void Circle_Single_Queue<E>::Ensure_Capacity(int capacity)
{
int Old_capacity = sum_capacity;
if (Old_capacity >= capacity)
{
return;
}
int New_capacity = (Old_capacity + (Old_capacity >> 1));
sum_capacity = New_capacity;
E* newelements = new E[New_capacity];
for (int i = 0; i < size; i++)
{
newelements[i] = elements[(i + front) % Max_Size];
}
elements = newelements;
front = 0;//重置front
索引映射封装
纵观上部的代码我们可以观察到我们在处理循环队列时,我们经常要对索引进行处理,通常我们要通过(front+size)%Max_Size来获得真正的索引对应的下标值,而其实若不是因为循环,我们直接放入本身的下标值其实逻辑上是对的,因此我们对索引映射到真正的索引对应的下标值进行一个函数封装,在用到索引时直接使用find_index(int index)函数来获得真正索引对应的下标,即newelements[i] = elements[(i + front) % Max_Size];
直接改为newelements[i] = elements[find_index(i)];
,增加代码的可读性。
封装函数的代码:
int Circle_Single_Queue<E>::find_index(int index) //寻找队列中index位置对应在动态数组的位置
{
return (front + index) % sum_capacity;
}
⚪总代码
Circle_Single_Queue.h
#pragma once
#include <iostream>
using namespace std;
template <class E>
class Circle_Single_Queue
{
private:
int size;
int front;
E *elements;
int ELEMENT_LENGTH = 10;
int sum_capacity; //保存此时所有的容量
void Ensure_Capacity(int capacity); //动态扩容
int find_index(int index); //寻找队列中index位置对应在动态数组的位置
public:
Circle_Single_Queue() //默认构造函数
{
size = 0;
front = 0;
elements = new E[ELEMENT_LENGTH]; //默认长度为10
sum_capacity = ELEMENT_LENGTH;
}
int cau_size(); //求数据队列长度
bool isEmpty(); //检测是否为空
void enQueue(E element); //入队
E deQueue(); //出队
E look_front(); //观测队头元素
void print();
~Circle_Single_Queue() {};
};
Circle_Single_Queue.cpp
#include "Circle_Single_Queue.h"
template <class E>
int Circle_Single_Queue<E>::cau_size() //求循环队列长度
{
return size;
}
template <class E>
bool Circle_Single_Queue<E>::isEmpty() //判断队列是否为空
{
return size == 0;
}
template <class E>
void Circle_Single_Queue<E>::enQueue(E element) //入队
{
Ensure_Capacity(size + 1);
elements[find_index(size)] = element; //队头+个数再对长度取余(若是数组队尾已满,可向数组头部添加元素)
size++;
}
template <class E>
E Circle_Single_Queue<E>::deQueue() //出队
{
E front_element = elements[front];
elements[front] = NULL;
front = find_index(1); //若队头已移至数组尾部,对其取余,到数组头部
size--;
return front_element;
}
template <class E>
E Circle_Single_Queue<E>::look_front()
{
return elements[front];
}
template <class E>
void Circle_Single_Queue<E>::print()
{
int n = size;
for (int i = 0; i < n; i++)
{
cout << elements[find_index(i)] << " ";
}
}
template <class E>
void Circle_Single_Queue<E>::Ensure_Capacity(int capacity)
{
int Old_capacity = sum_capacity;
if (Old_capacity >= capacity)
{
return;
}
int New_capacity = (Old_capacity + (Old_capacity >> 1));
sum_capacity = New_capacity;
E* newelements = new E[New_capacity];
for (int i = 0; i < size; i++)
{
newelements[i] = elements[find_index(i)];
}
elements = newelements;
front = 0;//重置front
}
template <class E>
int Circle_Single_Queue<E>::find_index(int index) //寻找队列中index位置对应在动态数组的位置
{
return (front + index) % sum_capacity;
}
mian.cpp
#include <iostream>
#include "Circle_Single_Queue.h"
#include "Circle_Single_Queue.cpp"
int main()
{
Circle_Single_Queue<int> csque;
csque.enQueue(10);
csque.enQueue(20);
csque.enQueue(30);
csque.enQueue(40);
csque.enQueue(50);
csque.enQueue(60);
csque.print();
csque.enQueue(70);
csque.enQueue(10);
csque.enQueue(20);
csque.enQueue(30);
csque.enQueue(40);
csque.enQueue(50);
csque.enQueue(60);
csque.enQueue(70);
csque.print();
return 0;
}
代码都经过测试检测,可放心食用~