1. 程式人生 > >資料結構實現(四):迴圈佇列(C++版)

資料結構實現(四):迴圈佇列(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