C++ コンテナ アダプタのスタックとキュー (deque、priority_queue を含む)

目次

1. コンテナアダプター

  1.1 アダプターとは

  1.2 STL標準ライブラリのスタックとキューの基礎構造

  1.3と

        1.3.1 deque 原則の概要 (理解)

        1.3.2 deque の長所と短所

        1.3.3 スタックとキューの基礎となるデフォルトのコンテナとして deque を選択する理由

2. スタックの導入と利用

  2.1 スタックの概要

  2.2 スタックの使用

  2.3 スタックシミュレーションの実装

3. キューの導入と使用

  3.1 キューの概要

  3.2 キューの使用

  3.3 キューシミュレーションの実装

4. 優先キュー

  4.1 priority_queue の概要

  4.2 priority_queue の使用

  4.3 priority_queue シミュレーションの実装


1. コンテナアダプター

  1.1 アダプターとは

アダプターは、 クラスのインターフェースを顧客が望む別のインターフェースに変換する 設計パターン (設計パターンは、ほとんどの人に知られ、分類およびカタログ化され、コード設計の経験を要約 た繰り返し使用のセットです) です

  1.2 STL標準ライブラリのスタックとキューの基礎構造

要素はstack queue に格納することもできます が、 STLではコンテナのランクに分割されていませんが、 スタックとキューは他のコンテナのインターフェイスをラップするだけであるため、 これらはコンテナ アダプタ と呼ばれます。STLstackqueue では、 dequeはデフォルトで使用されます

  

  1.3と

        1.3.1 deque 原則の概要 (理解)

Deque ( double-ended queue ) : 二重に開いた 連続空間データ構造 です 。二重開きの意味は、挿入と削除操作が先頭と末尾の両端で実行でき、計算量は です。 O(1) . Vectorに比べてプラグイン効率が高く、要素を移動する必要がないため、Listに比べて空間使用率が比較的高くなります。

Deque は実際の連続空間ではありませんが、連続した小さな空間によって接続されています。実際の Deque は 動的な 2 次元 配列 に似ています。その基礎となる構造は次の図に示されています。
両端キューの最下層は連続空間の錯覚であり、実際にはセグメント化され連続しています。その「 全体 的な連続性 とランダム アクセスの錯覚を維持するために 両端 キュー の反復子 に当てはまります。以下に示すように、両端キュー

deque は反復子の助けを借りて、どのようにして仮想的な連続構造を維持するのでしょうか? 

        1.3.2 deque の長所と短所

 

deque利点は vector 比較して 、head の挿入や削除の際に要素を移動する必要がなく、特に効率が高いことと、展開する際に多数の要素を移動する必要がないため、その効率は高くなければなりません
list 比較すると 、その最下層は連続した空間であり、 空間利用率が比較的高く 、追加のフィールドを格納する必要がありません。
ただし、 dequeには 致命的な 欠陥があります : トラバーサル中に dequeの反復子 が狭い領域の境界 に移動したかどうかを頻繁にチェックする必要があり 、効率が低いため、トラバーサルには適していません。頻繁に Traversal する必要があるため、実際には線形構造が必要な場合はベクトルリストが優先されることが多いですがdequeの応用例はそれほど多くなく今のところ見られる応用例の 1 つはSTLそれを使用することです。スタックキュー構造の基礎となるデータ

deque: 1.operator[ ] の計算は少し複雑で、大量に使用されるため、パフォーマンスが低下します (ベクトルと比較して)。

              2. 途中の挿入や削除の効率が高くない

              3. 基礎となる角度反復子は複雑になる可能性があります

        1.3.3 スタックとキューの基礎となるデフォルトのコンテナとして deque を選択する理由

スタックは 後入れ先出しの特殊な線形データ構造であるため、 push_back() および Pop_back()操作を備えた線形構造であれば、 vectorなどのスタックの基礎となるコンテナー として使用できます。 list ; queueは特別な先入れ先出しである 線形データ構造は、 Push_back および Pop_front 操作を伴う線形構造を持っている限り、listなどqueueの基礎となるコンテナとして使用できます
ただし、 STL では、主に次の理由から、 deque が stack および queueの基礎となるコンテナとしてデフォルトで選択されます
1. スタック キューを トラバースする必要はなく ( したがって、 スタック キューに はイテレータがありません ) 、固定の一方または両方の端で動作するだけで済みます。
2. スタック内の要素が増加する deque はVectorよりも効率的になります(拡張時に大量のデータを移動する必要がありません) キュー内の要素が増加すると、dequeは効率が高いだけでなく、メモリ使用量も多くなります。 。
dequeの利点を 組み合わせることで 、その欠陥を完全に回避します。
結論は:

1. Head-to-Tail の挿入と削除が非常に適しており、Vector や List と比較して、スタックやキューのデフォルトの適応コンテナに非常に適しています

2. 途中の多目的リストの挿入と削除

3. ランダムアクセス多目的ベクトル

2. スタックの導入と利用

  2.1 スタックの概要

  • stack は、特に後入れ先出し操作を使用するコンテキスト環境で使用されるコンテナ アダプタであり、その削除ではコンテナの一方の端から要素を挿入および抽出することしかできません。
  • スタックはコンテナ アダプタとして実装されます。コンテナ アダプタは、特定のクラスをその基礎となるコンテナとしてカプセル化し、特定のクラスをその基礎となる要素固有のコンテナ テール(つまり、スタック) がプッシュおよびポップされます。
  • スタックの基礎となるコンテナーは、任意の標準コンテナー クラス テンプレートまたはその他の特定のコンテナー クラスにすることができ、これらのコンテナー クラスは次の操作をサポートする必要があります。

        empty:判定空演算

        size: スタック内の有効な要素の数を返します。

        back: 末尾要素の操作を取得します。

        Push_back: 要素の末尾挿入操作

        Pop_back: 要素の末尾削除操作

  • 標準コンテナーの Vector、deque、および list はすべて、これらの要件を満たしています。スタックに対して特定の基になるコンテナーが指定されていない場合、デフォルトでは deque が使用されます。

  2.2 スタックの使用

機能説明
インターフェースの説明
スタック() 空のスタックを構築する
空() スタックが空かどうかを確認する
サイズ() スタック内の要素の数を返します。
上() スタックの最上位要素への参照を返します。
押す() 要素 val をスタックにプッシュします
ポップ()

要素をスタックの最後にポップします

 

 

  2.3 スタックシミュレーションの実装

	template<class T,class Container=deque<T>>
	class stack
	{
	public:
		void push(const T& x)
		{
			_con.push_back(x);
		}
		void pop()
		{
			_con.pop_back();
		}
		T& top()
		{
			return _con.back();
		}
		const T& top() const
		{
			return _con.back();
		}
		bool empty() const
		{
			return _con.empty();
		}
		size_t size() const
		{
			return _con.size();
		}

	private:
		Container _con;
	};

3. キューの導入と使用

  3.1 キューの概要

  • キューは、要素がコンテナーの一方の端から挿入され、もう一方の端から抽出される FIFO コンテキスト (先入れ先出し) で動作するように設計されたコンテナー アダプターです。
  • キューは、特定のコンテナ クラスを基になるコンテナ クラスとしてカプセル化するコンテナ アダプタとして実装され、キューはその要素にアクセスするための特定のメンバ関数のセットを提供します。要素は末尾からキューに入り、先頭からデキューされます。
  • 基礎となるコンテナーは、標準のコンテナー クラス テンプレートの 1 つ、または特別に設計された別のコンテナー クラスです。基礎となるコンテナは、少なくとも次の操作をサポートする必要があります。
        empty: キューが空かどうかを確認します
        size: キュー内の有効な要素の数を返します。
        フロント: キューの先頭要素への参照を返します。
        back: キューの最後にある要素への参照を返します。
        Push_back: キューの最後からキューに入る
        Pop_front: キューの先頭でキューから外されます
  • 標準コンテナ クラスの deque および list は、これらの要件を満たします。デフォルトでは、キューのインスタンス化にコンテナー クラスが指定されていない場合は、標準のコンテナー両端キューが使用されます。

  3.2 キューの使用

関数宣言 インターフェースの説明
列() 空のキューを構築する
空() キューが空かどうかを確認し、true を返し、それ以外の場合は false を返します。
サイズ() キュー内の有効な要素の数を返します。
正面() キューの先頭にある要素への参照を返します。
戻る() キューの最後にある要素への参照を返します。
押す() 要素 val をキューの最後にエンキューします
ポップ() キューの先頭にある要素をデキューします

 

 

 

 

 

 

 

 

 

  3.3 キューシミュレーションの実装

	template<class T,class Container=deque<int>>
	class queue
	{
	public:
		void push(const T& x)
		{
			_con.push_back(x);
		}
		void pop()
		{
			_con.pop_front();
		}
		T& back()
		{
			return _con.back();
		}
		T& front()
		{
			return _con.front();
		}
		const T& back() const
		{
			return _con.back();
		}
		const T& front() const
		{
			return _con.front();
		}
		bool empty() const
		{
			return _con.empty();
		}
		size_t size() const
		{
			return _con.size();
		}
	private:
		Container _con;
	};

4. 優先キュー

  4.1 priority_queue の概要

ファンクター は、関数オブジェクト (Function オブジェクト) とも呼ばれ、関数関数を実行できるクラスです。

ファンクターの構文は通常の関数呼び出しとほぼ同じですが、ファンクターのクラスとして、operator() 演算子をオーバーロードする必要がありますなぜなら、ファンクターを呼び出すことは、実際にはクラスオブジェクトを通じてオーバーロードされたoperator()演算子を呼び出すことになるからです。

  • 優先キューは、厳密な弱い順序付け基準に従って、最初の要素が常にそれに含まれる要素の最大であるコンテナ アダプタです。
  • このコンテキストはヒープに似ており、いつでも要素を挿入でき、最大のヒープ要素 (優先キューの先頭にある要素) のみを取得できます。
  • 優先キューは、特定のコンテナ クラスをその基礎となるコンテナ クラスとしてカプセル化するコンテナ アダプタとして実装され、キューはその要素にアクセスするための特定のメンバー関数のセットを提供します。要素は、優先キューの先頭と呼ばれる特定のコンテナの「末尾」からポップされます。
  • 基礎となるコンテナーには、標準のコンテナー クラス テンプレート、または特定の設計の他のコンテナー クラスを使用できます。コンテナーはランダム アクセス イテレーターを介してアクセス可能であり、次の操作をサポートする必要があります。
        empty(): コンテナが空かどうかを確認します
        size(): コンテナ内の有効な要素の数を返します。
        Front(): コンテナ内の最初の要素への参照を返します。
        Push_back(): コンテナの最後に要素を挿入します。
        Pop_back(): コンテナの末尾要素を削除します。
  • 標準コンテナ クラスの Vector と deque は、これらのニーズを満たします。デフォルトでは、特定の priority_queue クラスのインスタンス化にコンテナ クラスが指定されていない場合は、vector が使用されます。
  • ヒープ構造が常に内部的に維持されるように、ランダム アクセス反復子をサポートする必要があります。コンテナー アダプターは、必要に応じてアルゴリズム関数 make_heap、push_heap、pop_heap を自動的に呼び出すことで、これを自動的に行います。

  4.2 priority_queue の使用

プライオリティ キューは、デフォルトで基礎となるデータ ストレージコンテナーとして Vector を使用し、 Vector でヒープ アルゴリズムを使用してVector 内の要素をヒープ構造に構築します。そのため、priority_queue は heap であり、必要に応じて priority_queue の使用を検討できます。ヒープを使用します。注: priority_queue はデフォルトでは大きなヒープです。
関数宣言 インターフェースの説明

優先キュー()

priority_queue(最初、最後)

空の優先キューを構築する
空() 優先キューが空かどうかを検出し、true を返し、それ以外の場合は false を返します。
上() 優先キュー内の最大 (最小) 要素、つまり最上位の要素を返します。
押す() 要素 val を優先キューに挿入します
ポップ() 優先キュー内の最大 (最小) 要素、つまりヒープの最上位要素を削除します。
大きなトップヒープ (降順)
//构造一个空的优先队列(此优先队列默认为大顶堆)
priority_queue<int> big_heap;   

//另一种构建大顶堆的方法
priority_queue<int,vector<int>,less<int> > big_heap2;  

小さなトップヒープ (昇順)

//构造一个空的优先队列,此优先队列是一个小顶堆
priority_queue<int,vector<int>,greater<int> > small_heap;  

知らせ:

より少ないものとより大きなものを使用する場合は、ヘッダー ファイルが必要です。

#include <functional>

カスタム タイプ データを priority_queue に配置する場合、ユーザーはカスタム タイプで > または < オーバーロードを指定する必要があります。 

  4.3 priority_queue シミュレーションの実装

template<class T, class Container = vector<T>,class Compare=std::less<T>>
	class priority_queue
	{
	public:
		priority_queue()
		{}
		template<class InputIterator>
		priority_queue(InputIterator first, InputIterator last)
		{
			while (first != last)
			{
				_con.push_back(*first);
				++first;
			}
			//建堆,(_con.size()-1-1)/2为末尾节点的父节点所在位置
			for (int i = ((_con.size() - 1 - 1) / 2); i >= 0; i--)
			{
				//向下建堆logN
				adjust_down(i);
			}
		}
		//向上建堆
		void adjust_up(size_t child)
		{
			/*size_t parent = (child - 1) / 2;
			while (child > 0)
			{
				//建大堆
				if (_con[parent] < _con[child])
				{
					std::swap(_con[parent], _con[child]);
					child = parent;
					parent = (child - 1) / 2;
				}
				else
				{
					break;
				}
			}*/
			Compare com;//仿函数,类对象像函数一样使用,重载operator()
			size_t parent = (child - 1) / 2;
			while (child > 0)
			{
				//建大堆
				if (com(_con[parent],_con[child]))
				{
					std::swap(_con[parent], _con[child]);
					child = parent;
					parent = (child - 1) / 2;
				}
				else
				{
					break;
				}
			}
		}
		void push(const T& x)
		{
            //先vector插入,再向上调整
			_con.push_back(x);

			adjust_up(_con.size() - 1);
		}
		//向下建堆
		void adjust_down(size_t parent)
		{
			/*size_t child = parent * 2 + 1;
			while (child < _con.size())
			{
				//选出左右孩子大的那一个
				if (child + 1 < _con.size() && _con[child] < _con[child + 1])
				{
					++child;
				}
				//建大堆
				if (_con[parent] < _con[child])
				{
					std::swap(_con[parent], _con[child]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
					break;
				}
			}*/
			Compare com;//仿函数,类对象像函数一样使用,重载operator()
			size_t child = parent * 2 + 1;
			while (child < _con.size())
			{
				//选出左右孩子大的那一个
				if (child + 1 < _con.size() && com(_con[child] , _con[child + 1]))
				{
					++child;
				}
				//建大堆
				if (com(_con[parent] , _con[child]))
				{
					std::swap(_con[parent], _con[child]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
					break;
				}
			}
		}
		void pop()
		{
            //根与最右下边的孩子交换,vector尾删,再下调整
			std::swap(_con[0], _con[_con.size() - 1]);
			_con.pop_back();

			adjust_down(0);
		}
		const T& top() const
		{
			return _con[0];
		}
		bool empty() const
		{
			return _con.empty();
		}
		size_t size() const
		{
			return _con.size();
		}
	private:
		Container _con;
	};

ここではヒープ構築プロセスについては詳しく説明しません。データ構造のコンテンツを検索して自分で確認できます。

おすすめ

転載: blog.csdn.net/bang___bang_/article/details/130552826