資料結構實現(四):迴圈佇列(C++版)
資料結構實現(四):迴圈佇列(C++版)
1. 概念及基本框架
通過第三節我們知道,陣列佇列 在出隊操作中需要花費 O(n) 的時間複雜度,原因在於出隊時需要移動大量元素。如果我們可以迴圈利用空間,那麼問題就迎刃而解。所以,迴圈佇列
(這裡為了直觀,將佇列繪製成環形,實際記憶體依舊是連續的記憶體塊。)
首先迴圈佇列作為佇列的一種,基本特性和陣列佇列相同:
1.佇列 有 隊頭 和 隊尾 兩端。
2.入隊 操作只能從 隊尾 進行,出隊 操作只能從 隊頭 進行。
3.先 入隊 的先 出隊 ,即 先進先出(First In First Out),FIFO 。
還有一個隱含特性,佇列可以自行 擴容(縮容),而不需要使用者關心。
由上圖結構可知,隊空和隊滿時,隊頭和隊尾都指向同一塊記憶體。因為迴圈佇列和動態陣列有一定差異,所以對迴圈佇列從底層重新進行了實現。首先,依舊使用一個由 純虛擬函式 構成的 抽象類
template <class T>
class Queue{
public:
virtual int size() = 0;
virtual bool isEmpty() = 0;
virtual void print() = 0;
//入隊操作
virtual void enqueue(T num) = 0;
//出隊操作
virtual void dequeue() = 0;
//獲得隊首元素
virtual T front() = 0;
};
下面從底層重新構建一個迴圈佇列類。
template <class T>
class LoopQueue : public Queue<T>{
public:
LoopQueue(int len = initialLen){
T *p = new T[len];
m_data = p;
m_capacity = len;
m_size = m_front = m_rear = 0;
}
...
private:
T *m_data;
int m_capacity;
int m_size;
int m_front;
int m_rear;
};
這個類內部定義一個數組,為了相容更多型別,這裡使用了泛型的概念。然後定義了佇列的容量、大小以及隊頭和隊尾。同理,構造陣列時,可以初始化佇列的佇列容量,(預設是10)隊頭和隊尾都是0。
與動態陣列類似,為了實現自行 擴容(縮容),類內部建立了一個 resize 函式來實現。
template <class T>
class LoopQueue : public Queue<T>{
public:
...
void resize(int len){
T *p = new T[len];
if (m_size == 0){}
else{
for (int i = 0; i < m_size; ++i){
p[i] = m_data[(i + m_front) % m_capacity];
}
}
delete[] m_data;
m_data = p;
m_capacity = len;
m_front = 0;
m_rear = m_size;
}
...
};
原理上與動態陣列類似,這裡的 取餘 操作保證了迴圈。下面對各種基本操作進行了重寫實現。
2. 基本操作程式實現
2.1 入隊操作
template <class T>
class LoopQueue : public Queue<T>{
public:
...
//入隊操作
void enqueue(T num){
if (m_size >= m_capacity){
resize(2 * m_capacity);
}
m_data[m_rear] = num;
m_size++;
m_rear = (m_rear + 1) % m_capacity;
}
...
};
入隊操作時可能會呼叫 擴容函式 。
2.2 出隊操作
template <class T>
class LoopQueue : public Queue<T>{
public:
...
//出隊操作
void dequeue(){
if (m_size <= 0){
cout << "佇列為空,出隊操作失敗!" << endl;
return;
}
if (m_size <= m_capacity / 4){
resize(m_capacity / 2);
}
m_size--;
m_front = (m_front + 1) % m_capacity;
}
...
};
出隊操作時可能會呼叫 縮容函式 。
2.3 查詢操作
template <class T>
class LoopQueue : public Queue<T>{
public:
...
//獲得隊首元素
T front(){
if (m_size <= 0){
cout << "佇列為空,操作失敗!" << endl;
return NULL;
}
return m_data[m_front];
}
...
};
同樣的,佇列只能獲得隊首元素,所以這裡的查詢操作也非常簡單。
2.4 其他操作
template <class T>
class LoopQueue : public Queue<T>{
public:
...
int size(){
return m_size;
}
...
bool isEmpty(){
return m_size == 0;
}
void print(){
cout << "LoopQueue: ";
cout << "Size = " << m_size << endl;
cout << "front[";
if (m_size == 0){}
else{
for (int i = 0; i < m_size; ++i){
cout << m_data[(i + m_front) % m_capacity];
if (i != m_size - 1){
cout << ',';
}
}
}
cout << "]rear" << endl;
}
...
};
為了保證正確輸出,print 函式中也採用了 取餘 操作。
3. 演算法複雜度分析
3.1 入隊操作
函式 | 最壞複雜度 | 平均複雜度 |
---|---|---|
enqueue | O(1+n) = O(n) | O(1+1) = O(1) |
最壞複雜度 O(1+n) 中第一個 1 是指元素移動操作,第二個 n 是指 resize 函式,以下同理。
入隊可能會引發擴容操作,平均而言,每增加 n 個元素,會擴充套件一次,會發生 n 個元素的移動,所以平均下來是 O(1) 。
3.2 出隊操作
函式 | 最壞複雜度 | 平均複雜度 |
---|---|---|
dequeue | O(1+n) = O(n) | O(1+1) = O(1) |
3.3 查詢操作
函式 | 最壞複雜度 | 平均複雜度 |
---|---|---|
front | O(1) | O(1) |
總體情況:
操作 | 時間複雜度 |
---|---|
增 | O(1) |
刪 | O(1) |
查 | O(1) |
通過第三節我們知道,陣列佇列操作的增、查都是 O(1) 級別的時間複雜度,而刪是 O(n) 級別的時間複雜度,因為每次出隊的都是隊首元素,後面的元素需要一個個向前移動。而對於迴圈佇列,不需要移動元素,所以增、刪、查都是 O(1) 級別的時間複雜度,實現了對陣列佇列的優化。
注:佇列並不提供改的操作。
4. 完整程式碼
抽象類 介面程式碼:
#ifndef __QUEUE_H__
#define __QUEUE_H__
template <class T>
class Queue{
public:
virtual int size() = 0;
virtual bool isEmpty() = 0;
virtual void print() = 0;
//入隊操作
virtual void enqueue(T num) = 0;
//出隊操作
virtual void dequeue() = 0;
//獲得隊首元素
virtual T front() = 0;
};
#endif
迴圈佇列 程式碼:
#ifndef __LOOPQUEUE_H__
#define __LOOPQUEUE_H__
#include "Queue.h"
using namespace std;
template <class T>
class LoopQueue : public Queue<T>{
public:
LoopQueue(int len = initialLen){
T *p = new T[len];
m_data = p;
m_capacity = len;
m_size = m_front = m_rear = 0;
}
int size(){
return m_size;
}
void resize(int len){
T *p = new T[len];
if (m_size == 0){}
else{
for (int i = 0; i < m_size; ++i){
p[i] = m_data[(i + m_front) % m_capacity];
}
}
delete[] m_data;
m_data = p;
m_capacity = len;
m_front = 0;
m_rear = m_size;
}
bool isEmpty(){
return m_size == 0;
}
void print(){
cout << "LoopQueue: ";
cout << "Size = " << m_size << endl;
cout << "front[";
if (m_size == 0){}
else{
for (int i = 0; i < m_size; ++i){
cout << m_data[(i + m_front) % m_capacity];
if (i != m_size - 1){
cout << ',';
}
}
}
cout << "]rear" << endl;
}
//入隊操作
void enqueue(T num){
if (m_size >= m_capacity){
resize(2 * m_capacity);
}
m_data[m_rear] = num;
m_size++;
m_rear = (m_rear + 1) % m_capacity;
}
//出隊操作
void dequeue(){
if (m_size <= 0){
cout << "佇列為空,出隊操作失敗!" << endl;
return;
}
if (m_size <= m_capacity / 4){
resize(m_capacity / 2);
}
m_size--;
m_front = (m_front + 1) % m_capacity;
}
//獲得隊首元素
T front(){
if (m_size <= 0){
cout << "佇列為空,操作失敗!" << endl;
return NULL;
}
return m_data[m_front];
}
private:
T *m_data;
int m_capacity;
int m_size;
int m_front;
int m_rear;
};
#endif