1. 程式人生 > >淺談C++ STL中的優先佇列(priority_queue)

淺談C++ STL中的優先佇列(priority_queue)

開發十年,就只剩下這套架構體系了! >>>   

從我以前的博文能看出來,我是一個佇列愛好者,很多並不是一定需要用佇列實現的演算法我也會採用佇列實現,主要是由於佇列和人的直覺思維的一致性導致的。

今天講一講優先佇列(priority_queue),實際上,它的本質就是一個heap,我從STL中扒出了它的實現程式碼,大家可以參考一下。

首先函式在標頭檔案<queue>中,歸屬於名稱空間std,使用的時候需要注意。

佇列有兩種常用的宣告方式:

std::priority_queue<T> pq;
std::priority_queue<T, std::vector<T>, cmp> pq;

 

第一種實現方式較為常用,接下來我給出STL中的對應宣告,再加以解釋。

template<class _Ty,
    class _Container = vector<_Ty>,
    class _Pr = less<typename _Container::value_type> >
    class priority_queue

 

大家可以看到,預設模板有三個引數,第一個是優先佇列處理的類,第二個引數比較有特點,是容納優先佇列的容器。實際上,優先佇列是由這個容器+C語言中關於heap的相關操作實現的。這個容器預設是vector,也可以是dequeue,因為後者功能更強大,而效能相對於vector較差,考慮到包裝在優先佇列後,後者功能並不能很好發揮,所以一般選擇vector來做這個容器。第三個引數比較重要,支援一個比較結構,預設是less,預設情況下,會選擇第一個引數決定的類的<運算子來做這個比較函式。

接下來開始坑爹了,雖然用的是less結構,然而,佇列的出隊順序卻是greater的先出!就是說,這裡這個引數其實很傲嬌,表示的意思是如果!cmp,則先出列,不管這樣實現的目的是啥,大家只能接受這個實現。實際上,這裡的第三個引數可以更換成greater,像下面這樣:

std::priority_queue<T, std::vector<T>, greater<T>> pq;

 

一般大家如果是自定義類就乾脆過載<號時注意下方向了,沒人在這裡麻煩,這個選擇基本上是在使用int類還想小值先出列時。

從上面的剖析我們也就知道了,想要讓自定義類能夠使用優先佇列,我們要過載小於號。

複製程式碼

class Student
{
    int id;
    char name[20];
    bool gender;
    bool operator < (Student &a) const
    {
        return id > a.id;
    }
};

複製程式碼

 

就拿這個例子說,我們想讓id小的先出列,怎麼辦,就要很違和的給這個小於符號過載成實際上是大於的定義。

如果我們不使用自定義類,又要用非預設方法去排序怎麼辦?就比如說在Dijkstra中,我們當然不會用點的序號去排列,無論是正序還是反序,我們想用點到起點的距離這個值來進行排序,我們怎樣做呢?細心的讀者在閱讀我的有關Dijkstra那篇文章時應該就發現了做法——自定義比較結構。優先佇列預設使用的是小於結構,而上文的做法是為我們的自定義類去定義新的小於結構來符合優先佇列,我們當然也可以自定義比較結構。自定義方法以及使用如下,我直接用Dijkstra那篇的程式碼來說明:

複製程式碼

int cost[MAX_V][MAX_V];
int d[MAX_V], V, s;
//自定義優先佇列less比較函式
struct cmp
{
    bool operator()(int &a, int &b) const
    {
        //因為優先出列判定為!cmp,所以反向定義實現最小值優先
        return d[a] > d[b];
    }
};
void Dijkstra()
{
    std::priority_queue<int, std::vector<int>, cmp> pq;
    pq.push(s);
    d[s] = 0;
    while (!pq.empty())
    {
        int tmp = pq.top();pq.pop();
        for (int i = 0;i < V;++i)
        {
            if (d[i] > d[tmp] + cost[tmp][i])
            {
                d[i] = d[tmp] + cost[tmp][i];
                pq.push(i);
            }
        }
    }
}

複製程式碼

 

優先佇列的日常使用,瞭解上面那些就已經足夠。下面給出優先佇列的所有成員函式的STL實現方法,希望你看完沒有一臉臥槽的感覺。c就是你宣告時候的那個vector或者其他容器。

複製程式碼

void push(value_type&& _Val)
        {    // insert element at beginning
        c.push_back(_STD move(_Val));
        push_heap(c.begin(), c.end(), comp);
        }

    template<class... _Valty>
        void emplace(_Valty&&... _Val)
        {    // insert element at beginning
        c.emplace_back(_STD forward<_Valty>(_Val)...);
        push_heap(c.begin(), c.end(), comp);
        }


    bool empty() const
        {    // test if queue is empty
        return (c.empty());
        }

    size_type size() const
        {    // return length of queue
        return (c.size());
        }

    const_reference top() const
        {    // return highest-priority element
        return (c.front());
        }

    void push(const value_type& _Val)
        {    // insert value in priority order
        c.push_back(_Val);
        push_heap(c.begin(), c.end(), comp);
        }

    void pop()
        {    // erase highest-priority element
        pop_heap(c.begin(), c.end(), comp);
        c.pop_back();
        }

複製程式碼

https://www.cnblogs.com/ci