STL容器適配器:stack、queue、priority_queue

前言

容器適配器是一個封裝了序列容器的類模板,它在一般序列容器的基礎上提供了一些不同的功能,之所以稱之爲容器適配器,是因爲它可以通過適配容器的現有接口來提供不同的功能

簡單的理解容器適配器,其就是將不適用的序列式容器(包括 vector、deque 和 list)變得適用。

STL中的三大容器適配器:stack、queue、priority_queue。

一:deque(雙端隊列)

在學習容器適配器之前我們首先了解一個叫做deque的序列容器。

deque(雙端隊列): 是一個雙端操作,任意位置O(1)插入刪除加上隨機訪問的序列容器,並且不需要增容,deque集合了vector和list的優點,但是deque隨機訪問的效率低(排序)

  • 雙端隊列的底層:

雙端隊列底層是一段假想的連續空間,實際是分段連續的 ,由一段段連續的小空間組成。

問題一: deque怎麼管理這一段段連續的小空間呢?

這裏deque通過中控映射的方式管理這一段段連續的小空間,中控映射其實就是一個指針數組,通過指針指向一段段連續的小空間,如果擴容,只需要考慮指針數組的擴容,這樣代價就會小很多。

問題二: deque如何實現隨機訪問呢?

需要計算訪問的數據在哪個小空間中,數據量較大的場景下耗時會比較長。

問題三: deque的迭代器結構?

first指向一段小空間的開始,last指向一段小空間的結束,cur指向迭代器當前位置,node指向中控指針數組中的結點用於切換小空間。

注意:雙端隊列並不符合先進先出的序列特點。

  • 雙端隊列的應用:

雙端隊列被用於STL容器stack和queue的默認底層數據結構

1.stack和queue不需要遍歷,只需要在固定的一端或者兩端進行操作。

2.在stack中元素增長時,deque比vector的效率高(擴容時不需要搬移大量數據);queue中的元素增長
時,deque不僅效率高,而且內存使用率高。

二:stack(棧)

stack類的基本使用這裏就不多做講解,我們主要來研究一下stack類的模擬實現。

stack是一個封裝了deque< T >容器的適配器類模板,默認實現的是一個後入先出(Last-In-First-Out,LIFO)的壓入棧。stack< T > 模板定義在頭文件 stack 中。

#pragma once
#include<vector>
#include<list>

// 用STL容器封裝適配轉換實現出來的stack(複用性)
namespace WJL{
	// Container:容器
	template<class T, class Container>
	class Stack{
	public:
		void push(const T& x){
			_con.push_back(x);
		}

		void pop(){
			_con.pop_back();
		}

		size_t size(){
			return _con.size();
		}

		bool empty(){
			return _con.empty();
		}

		T& top(){
			return _con.back();
		}
	private:
		Container _con;
	};
	
	void Test(){
		Stack<int, vector<int>> st;
		//Stack<int, list<int>> st;
		st.push(1);
		st.push(2);
		st.push(3);
		st.push(4);
		st.push(5);

		while (!st.empty()){
			cout << st.top() << " ";
			st.pop();
		}
		cout << endl;
	}
}

輸出結果:5 4 3 2 1

三:queue(隊列)

queue類的基本使用這裏就不多做講解,我們主要來研究一下queue類的模擬實現。

queue是一個封裝了deque< T >容器的適配器類模板,默認實現的是一個先入先出(First-In-First-Out,LIFO)的隊列。queue< T > 模板定義在頭文件queue中。

注意:vector不提供支持頭部操作的接口,所以vector不能作爲queue的適配容器。

#pragma once
#include<list>

// 用STL容器封裝適配轉換實現出來的queue(複用性)
namespace WJL{
	// Container:容器
	template<class T, class Container>
	class Queue{
	public:
		void push(const T& x){
			_con.push_back(x);
		}

		void pop(){
			// vector不提供pop_front()接口
			_con.pop_front();
		}

		size_t size(){
			return _con.size();
		}

		bool empty(){
			return _con.empty();
		}

		T& front(){
			return _con.front();
		}

		T& back(){
			return _con_back();
		}
	private:
		Container _con;
	};


	void Test(){
		Queue<int, list<int>> q;

		q.push(1);
		q.push(2);
		q.push(3);
		q.push(4);
		q.push(5);

		while (!q.empty()){
			cout << q.front() << " ";
			q.pop();
		}
		cout << endl;
	}
}

輸出結果:1 2 3 4 5

四:priority_queue(優先級隊列)

優先級隊列是一種容器適配器,它的第一個元素總是它包含元素中優先級最高的。

4.1 優先級隊列的使用

優先級隊列默認使用vector作爲其底層存儲數據的容器,在vector上又使用了堆算法將vector中元素構造成堆的結構,默認情況下priority_queue是大堆

#include<iostream>
#include<queue>
#include<functional> // 仿函數頭文件
using namespace std;

void test_priority_queue(){
	1. 默認大的優先級高,優先級隊列底層實際是一個大堆
	//priority_queue<int> pq;
	2. 應用仿函數使小的優先級高
	priority_queue<int, vector<int>, greater<int>> pq;
	pq.push(3);
	pq.push(1);
	pq.push(9);
	pq.push(4);
	pq.push(2);

	注:容器適配器不支持迭代器,容器適配器通常包含特殊的性質,迭代器會破壞容器適配器原有性質
	while (!pq.empty()){
		cout << pq.top() << " ";
		pq.pop();
	}
}

int main(){
	test_priority_queue();
	return 0;
}
4.2 優先級隊列的模擬實現

優先級隊列運用仿函數控制優先級大小(大小堆)。

#include<vector>
namespace WJL{
	// 仿函數(函數對象)
	template<class T>
	// class:成員部分公有 部分私有
	// struct:成員全部公有
	struct less{
		bool operator()(const T& x1, const T& x2){
			return x1 < x2;
		}
	};

	template<class T>
	struct greater{
		bool operator()(const T& x1, const T& x2){
			return x1 > x2;
		}
	};

	// 默認大堆
	template<class T,class Container = vector<T>,class Compare = less<T>>
	class priority_queue{
	public:
		// 向上調整算法
		void AdjustUp(int child){
			Compare com;
			int parent = (child - 1) / 2;
			while (child > 0){
				// child > parent
				if (com(_con[parent], _con[child])){
					swap(_con[child], _con[parent]);
					child = parent;
					parent = (child - 1) / 2;
				}
				else{
					break;
				}
			}
		}
		void push(const T& x){
			_con.push_back(x);
			AdjustUp(_con.size() - 1);
		}

		//向下調整算法
		void AdjustDown(int root){
			Compare com;
			size_t parent = root;
			size_t child = parent * 2 + 1;
			while (child < _con.size()){
				// 選出左右孩子中較大的
				if (child+1 < _con.size()&&com(_con[child],_con[child + 1])){
					child++;
				}
				// child > parent
				if (com(_con[parent], _con[child])){
					swap(_con[parent], _con[child]);
					parent = child;
					child = parent * 2 + 1;
				}
				else{
					break;
				}
			}
		}

		void pop(){
			swap(_con[0], _con[_con.size() - 1]);
			_con.pop_back();
			AdjustDown(0);
		}

		T& top(){
			return _con[0];
		}

		size_t size(){
			return _con.size();
		}

		bool empty(){
			return _con.empty();
		}
	private:
		Container _con;
	};

	void test_priority(){
		//priority_queue<int> pq;
		priority_queue<int,vector<int>,greater<int>> pq;

		pq.push(3);
		pq.push(1);
		pq.push(9);
		pq.push(4);
		pq.push(2);
		while (!pq.empty()){
			cout << pq.top() << " ";
			pq.pop();
		}
		cout << endl;
	}
}
運行結果:1 2 3 4 9

小結

STL中的容器適配器都是通過基礎序列容器適配轉換而來的,並不是原生實現的 , 提高了代碼的複用性。

需要注意的是,STL 中的容器適配器,其內部使用的基礎序列容器並不是固定的,用戶可以在滿足特定條件的多個基礎容器中自由選擇。並且,容器適配器不支持迭代器,因爲支持迭代器會導致適配器的性質改變。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章