C++ の優先キュー (priority_queue)、ファンクター (関数オブジェクト)

目次

priority_queue の使用

ファンクター (関数オブジェクト)

優先キューにある一般的な古典的な OJ の質問 (面接でよくテストされる)


優先キューは、厳密な弱い順序付け基準に従って、最初の要素が常にそれに含まれる要素の最大であるコンテナ アダプタです。優先キューは 0 個以上の要素のコレクションであり、各要素には優先順位または値があります。優先キューで実行される操作は、1) 要素の検索、2) 新しい要素の挿入、3) 要素の削除です。これらの操作に対応する機能は、それぞれ、top、push、pop です。最小優先度キュー (min priority queue) では、最も優先度の低い要素が検索および削除されます。最大優先度キュー (max priority queue) では、最も高い優先度の要素が検索および削除されます。優先。優先キューの要素は同じ優先度を持つことができ、そのような要素の検索と削除は任意の順序で処理できます。優先キューはヒープに似ており、いつでも要素を挿入でき、最大のヒープ要素 (優先キューの先頭にある要素) のみを取得できます優先キューは、特定のコンテナ クラスをその基礎となるコンテナ クラスとしてカプセル化するコンテナ アダプタとして実装され、キューはその要素にアクセスするための特定のメンバー関数のセットを提供します。要素は、優先キューの先頭と呼ばれる特定のコンテナの「末尾」からポップされます。

priority_queue の使用

プライオリティ キューは、デフォルトで基礎となるデータ ストレージ コンテナーとしてベクトルを使用し、ベクトルに対してヒープ アルゴリズムを使用して、ベクトル内の要素をヒープ構造に構築します。そのため、priority_queue はヒープであり、必要な場合はどこでも priority_queue の使用を検討できます。ヒープを使用します。

注: priority_queue はデフォルトでは大きなヒープです。

関数宣言 インターフェースの説明
priority_queue()/priority_queue(最初,最後)  空の優先キューを構築する
空の() 優先キューが空かどうかを確認し、true を返し、それ以外の場合は false を返します。
上() 優先キュー内の最大 (最小の要素)、つまりヒープの最上位の要素を返します。
プッシュ(x) 要素 x を優先キューに挿入します
ポップ() 優先キュー内の最大 (最小) 要素、つまりヒープの最上位要素を削除します。

1. デフォルトでは、priority_queue は大きな山です。 

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

class Date
{ public:     Date(int year = 1900, int month = 1, int day = 1)         : _year(year)         , _month(month)         , _day(day)     {}     bool 演算子<(const Date& d)const     {         return (_年 < d._年) ||             (_year == d._year && _month < d._month) ||             (_year == d._year && _month == d._month && _day < d._day);     bool 演算子>(const Date& d)const     {         return (_year > d._year)     ||             (_year == d._year && _month > d._month) ||             (_year == d._year && _month == d. _月 && _日 > d._日);     }


















大きい<日付>> q2;     q2.push(日付(2023,3,29));


















  

    q2.push(日付(2023,3,28));
    q2.push(日付(2023,3,30));
    cout << q2.top() << endl;

ファンクター (関数オブジェクト)

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

プログラマがアルゴリズムのパラメータとして特定の「操作」を使用したい場合、一般に 2 つの方法があります。
(1) 1 つの方法は、最初に「操作」を関数として設計し、次に関数ポインタをパラメータとして使用することです。アルゴリズムの。
(2) 「操作」をファンクター(言語レベルのクラス)として設計し、そのファンクターを使用してオブジェクトを生成し、このオブジェクトをアルゴリズムのパラメーターとして使用します。

最初の方法はスケーラビリティが低く、関数パラメータが変更されると古いコードと互換性がなくなるため、明らかに 2 番目の方法の方が優れています。コードを書いていると、いくつかの機能コードが継続的に使用されることに気づくことがあります。コードを再利用するには、コードをパブリック関数として実装することが回避策です。ただし、関数で使用される一部の変数はパブリック グローバル変数である場合があります。グローバル変数を導入すると、同じ名前の競合が発生しやすく、保守が不便になります。

ファンクターを使用して単純なクラスを作成し、クラスの基本的なメンバー関数を維持することに加えて、operator() 演算子をオーバーロードするだけで済みます。このようにして、一部のパブリック変数のメンテナンスを回避でき、再利用されたコードを次回再利用するために分離できます。さらに、関数のより優れた特性と比較して、ファンクターは依存関係、組み合わせ、継承も実行できるため、リソース管理に役立ちます。

  優先キューでは、デフォルトは大きなルート ヒープですが、小さなルート ヒープが必要な場合は、ファンクターを使用できます。C++ では、通常、ファンクターを自分で記述する必要はなく、ライブラリ関数があります。ヘッダー ファイル #include <function> をインクルードする必要があります。

int findKthLargest(vector<int>& nums, int k) {
        //优先级队列默认大堆,使用仿函数建立小堆
        priority_queue<int,vector<int>,greater<int>> pq(nums.begin(),nums.begin()+k);
        for(int i=k;i<nums.size();i++)
        {
            if(nums[i]>pq.top())
            {
                pq.pop();
                pq.push(nums[i]);
            }
        }
        return pq.top();
    }

 アドレス比較などの特殊なファンクターは独自に実装する必要があります。

class PDateLess 
{ 
public: 
    bool Operator()(const Date* p1, const Date* p2) 
    { 
        return *p1 < *p2; 
    } 
}; 

class PDateGreater 
{ 
public: 
    bool Operator()(const Date* p1, const Date* p2) 
    { 
        return *p1 > *p2; 
    } 
};

int main()
{     priority_queue<日付*, ベクトル<日付*>, PDateGreater> q;     q.push(新しい日付(2023, 10, 29));     q.push(新しい日付(2023, 10, 30));     q.push(新しい日付(2023, 10, 28));     cout << *(q2.top()) << endl; }





優先キューにある一般的な古典的な OJ の質問 (面接でよくテストされる)

 オファー 41 を指す。データ ストリームの中央値

データストリーム内の中央値を取得するにはどうすればよいですか? 奇数の値がデータ ストリームから読み取られた場合、中央値は並べ替えられたすべての値の中央の値になります。偶数の値がデータ ストリームから読み取られた場合、中央値は、すべての値が並べ替えられた後の中央の 2 つの数値の平均になります。

例えば、

[2,3,4] の中央値は 3 です

[2,3] の中央値は (2 + 3) / 2 = 2.5 です。

次の 2 つの操作をサポートするデータ構造を設計します。

  • void addNum(int num) - データ ストリームからデータ構造に整数を追加します。
  • double findMedian() - これまでのすべての要素の中央値を返します。

大きな上部ヒープには中央値以下の数値、つまり順序​​付けされた配列の左半分が格納され、小さな
上部ヒープには中央値より大きい数値、つまり右半分が格納されます。順序付けされた配列の半分の部分
大きな上部ヒープの上部は左半分の最大の数を表し、
小さな上部ヒープの上部は右半分の最小の数を表します
。 2 つのヒープに格納される要素の数が 1 を超えることはありません。2
つのヒープに格納される要素の合計が奇数の場合、大きな上部ヒープの上部 (つまり、ヒープの左半分) が中央値になります
。 2 つのヒープに格納されている数が偶数である場合、接続の中央値は、
大きな上部スタックの上部と小さな上部スタックの上部を組み合わせることで得られます。挿入問題、つまり、新しい要素が来たときにデータを挿入する方法を考えてみましょう。で? 
2 つのヒープ内のデータ量が偶数の場合、最初に小さい上部ヒープに挿入して最小値 (ヒープ上部要素) を見つけ、次にその最小値を
大きい上部ヒープ (左半分) に入れます。 2 つのヒープ データの量は奇数で、中央値はビッグ トップ ヒープの最上部です
。2 つのヒープ内のデータ量が奇数の場合は、最初にビッグ トップ ヒープに挿入して最大値を見つけます (ヒープ)先頭要素)を取得し、
その最大値を上部の小さいヒープ(右半分)にGoに入れます このとき、2つのヒープのデータ量は均等となり、中央値は2つのヒープの先頭から計算されます。

class MedianFinder {
public:
    // 最大堆,存储左边一半的数据,堆顶为最大值
    priority_queue<int, vector<int>, less<int>> maxHeap;
    // 最小堆, 存储右边一半的数据,堆顶为最小值
    priority_queue<int, vector<int>, greater<int>> minHeap;
    /** initialize your data structure here. */
    MedianFinder() {
    }

    // 维持堆数据平衡,并保证左边堆的最大值小于或等于右边堆的最小值
    void addNum(int num) {
        /*
         * 当两堆的数据个数相等时候,左边堆添加元素。
         * 采用的方法不是直接将数据插入左边堆,而是将数据先插入右边堆,算法调整后
         * 将堆顶的数据插入到左边堆,这样保证左边堆插入的元素始终是右边堆的最小值。
         * 同理左边数据多,往右边堆添加数据的时候,先将数据放入左边堆,选出最大值放到右边堆中。
         */
        if (maxHeap.size() == minHeap.size()) {
            minHeap.push(num);
            int top = minHeap.top();
            minHeap.pop();
            maxHeap.push(top);
        } else {
            maxHeap.push(num);
            int top = maxHeap.top();
            maxHeap.pop();
            minHeap.push(top);
        }
    }
    
    double findMedian() {
        if (maxHeap.size() == minHeap.size()) {
            return (maxHeap.top()+minHeap.top())*1.0/2;
        } else {
            return maxHeap.top()*1.0;
        }
    }
};

 配列を最小の数に並べ替えます

負でない整数の配列を入力し、配列内のすべての数値を連結して数値を形成し、連結できるすべての数値の中で最小のものを出力します。

この質問は、結合できる最小の数値を求めるもので、本質的には並べ替えの問題です。配列 nums 内の任意の 2 つの数値の文字列を x と y とすると、ソートの判定ルールは次のように規定されます。

連結された文字列 x+y>y+x の場合、x は y より「大きい」、
それ以外の場合、x+y<y+x の場合、x は y より「小さい」、x は y
より「小さい」ことを意味します。ソートが完了すると、配列内で x が y の左側にある必要があり、「より大きい」はその逆になります。

クラス ソリューション {

公共:

    // ファンクター

    bool 演算子()(文字列 s1,文字列 s2)

    {

        s1+s2 < s2+s1 を返します。

    }

    文字列 minNumber(vector<int>& nums)

    {

        ベクトル<文字列> a;

        for(int i=0;i<nums.size();i++)

        {

            a.push_back(to_string(nums[i]));

        }

        sort(a.begin(),a.end(),Solution());//ファンクターの使用

        文字列レット。

        for(int i=0;i<a.size();i++)

        {

            ret += a[i];

        }

        retを返します。

    }

};

おすすめ

転載: blog.csdn.net/m0_55752775/article/details/129860166