LoginSignup
13
10

More than 5 years have passed since last update.

std::priority_queue は 安定ではない

Last updated at Posted at 2014-01-31

std::priority_queue は要素 T を push() しておくと優先順位の高い(=大きい)順に pop() できます。

int を要素とし、その一ケタ目を優先順位とみなして 0...29 を push() し、一気に pop() してみましょう。

#include <iostream>
#include <queue>

// 一ケタ目だけを比較する
struct less_digit {
  bool operator()(int x, int y) const {
    return (x%10) < (y%10);
  }
};

using namespace std;

int main() {
  priority_queue<int,std::vector<int>,less_digit> pq;
  for ( int i = 0; i < 30; ++i ) {
    pq.push(i);
  }

  while ( !pq.empty() ) {
    cout << pq.top() << ' ';
    pq.pop();
  }
  cout << endl;
}

実行結果はどうなるでしょう。一ケタ目が大きいものが先に現れるなら、
9 19 29 8 18 28 7 17 27 ...
と出力されると期待しますが、実際には:
9 29 19 28 8 18 27 7 17 26 6 16 5 25 15 14 4 24 13 23 3 12 22 2 11 21 1 20 10 0
でした。優先順位の等しい要素が複数あるとき、pop順は安定(=push順)ではないのです。

その理由は priority_queue::push()/pop() の実装にあります。

clas priority_queue {
  ...
  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();
  }

ご覧のとおり、push_heap/pop_heap で実現しているんです。
そのため時間計算量は Nlog(N) と高速だけど安定ではないというわけ。

安定なpriority_queueが欲しいなら、要素の挿入順を比較キーに使うのが安直な方法でしょう。

#include <iostream>
#include <queue>

struct less_digit {
  bool operator()(int x, int y) const {
    return (x%10) < (y%10);
  }
};

using namespace std;

template<typename T, typename Compare> 
struct stable_compare {
  bool operator()(const std::pair<T,unsigned>& x, const std::pair<T,unsigned>& y) const {
    if ( comp_(x.first, y.first) ) return true;
    if ( comp_(y.first, x.first) ) return false;
    return x.second > y.second; // 優先順位が等しいときは先に挿入した方を選ぶ
  }
  Compare comp_;
};

template<typename T, typename Compare> 
struct helper {
  typedef std::pair<T,unsigned> value_type;
  typedef std::vector<value_type> container_type;
  typedef stable_compare<T,Compare> compare_type;
  typedef std::priority_queue<value_type, container_type, compare_type> stable_priority_queue;
};

int main() {
  helper<int,less_digit>::stable_priority_queue pq;
  unsigned order = 0;
  for ( int i = 0; i < 30; ++i ) {
    pq.push(std::make_pair(i,order++));
  }

  while ( !pq.empty() ) {
    cout << pq.top().first << ' ';
    pq.pop();
  }
  cout << endl;
}

/* 実行結果:
9 19 29 8 18 28 7 17 27 6 16 26 5 15 25 4 14 24 3 13 23 2 12 22 1 11 21 0 10 20
*/ 
13
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
10