10
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

C#で優先度付きキュー

Last updated at Posted at 2019-09-20

動機

  • 最近AtCoderを始めた
  • C#の標準機能で優先度付きキューがない
  • IEnumerable<T> を実装したい
    • 優先度をつけながら処理した結果の合計を求めたりしたい
  • 昇順か降順かを指定したい
  • 事前に要素数を指定したくない
    • 配列で実装するほうが速かったですが、この理由で妥協しました

サンプル

数値とかを扱う場合

var q = new PriorityQueue<int>();
foreach (var i in Enumerable.Range(1, 100))
{
    q.Enqueue(i);
}
q.Dequeue(); //100

数値とかを扱う場合(昇順)

var q = new PriorityQueue<int>(isDescending: false);
foreach (var i in Enumerable.Range(1, 100))
{
    q.Enqueue(i);
}
q.Dequeue(); //1

自作のクラスとかを扱う場合

//対象のクラス
class Employee
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
}
var q = new PriorityQueue<int, Employee>(e => e.Age);
foreach (var i in Enumerable.Range(1, 100))
{
    q.Enqueue(new Employee { Id = i, Name = $"Name{i}", Age = 20 + i % 40 });
}
q.Dequeue(); //Id:39, Name:Name39, Age:59

自作のクラスとかを扱う場合(昇順)

var q = new PriorityQueue<int, Employee>(e => e.Age, isDescending: false);
foreach (var i in Enumerable.Range(1, 100))
{
    q.Enqueue(new Employee { Id = i, Name = $"Name{i}", Age = 20 + i % 40 });
}
q.Dequeue(); //Id:40, Name:Name40, Age:20

おまけ

PriorityQueue<int, Employee>(e => e.Age) って書くの、めんどくさい!!!
以下のような拡張メソッドがあると便利。

public static class Extension
{
    public static PriorityQueue<T> AsPriorityQueue<T>(this IEnumerable<T> source, bool isDescending = true)
    {
        var queue = new PriorityQueue<T>(isDescending);
        foreach (var item in source)
        {
            queue.Enqueue(item);
        }

        return queue;
    }

    public static PriorityQueue<TKey, TSource> AsPriorityQueue<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, bool isDescending = true)
    {
        var queue = new PriorityQueue<TKey, TSource>(keySelector, isDescending);
        foreach (var item in source)
        {
            queue.Enqueue(item);
        }

        return queue;
    }
}
var q = Enumerable.Range(1, 100)
    .Select(i => new Employee { Id = i, Name = $"Name{i}", Age = 20 + i % 40 })
    .AsPriorityQueue(e => e.Age);

AtCoderとかで標準入力から読み込む場合はこんな感じです。

var q = Console.ReadLine().Split().Select(int.Parse).AsPriorityQueue();

実装

PriorityQueue<T>

public class PriorityQueue<T> : IEnumerable<T>
{
    private readonly List<T> _data = new List<T>();
    private readonly IComparer<T> _comparer;
    private readonly bool _isDescending;

    public PriorityQueue(IComparer<T> comparer, bool isDescending = true)
    {
        _comparer = comparer;
        _isDescending = isDescending;
    }

    public PriorityQueue(Comparison<T> comparison, bool isDescending = true)
        : this(Comparer<T>.Create(comparison), isDescending)
    {
    }

    public PriorityQueue(bool isDescending = true)
        : this(Comparer<T>.Default, isDescending)
    {
    }

    public void Enqueue(T item)
    {
        _data.Add(item);
        var childIndex = _data.Count - 1;
        while (childIndex > 0)
        {
            var parentIndex = (childIndex - 1) / 2;
            if (Compare(_data[childIndex], _data[parentIndex]) >= 0)
                break;
            Swap(childIndex, parentIndex);
            childIndex = parentIndex;
        }
    }

    public T Dequeue()
    {
        var lastIndex = _data.Count - 1;
        var firstItem = _data[0];
        _data[0] = _data[lastIndex];
        _data.RemoveAt(lastIndex--);
        var parentIndex = 0;
        while (true)
        {
            var childIndex = parentIndex * 2 + 1;
            if (childIndex > lastIndex)
                break;
            var rightChild = childIndex + 1;
            if (rightChild <= lastIndex && Compare(_data[rightChild], _data[childIndex]) < 0)
                childIndex = rightChild;
            if (Compare(_data[parentIndex], _data[childIndex]) <= 0)
                break;
            Swap(parentIndex, childIndex);
            parentIndex = childIndex;
        }
        return firstItem;
    }

    public T Peek()
    {
        return _data[0];
    }

    private void Swap(int a, int b)
    {
        var tmp = _data[a];
        _data[a] = _data[b];
        _data[b] = tmp;
    }

    private int Compare(T a, T b)
    {
        return _isDescending ? _comparer.Compare(b, a) : _comparer.Compare(a, b);
    }

    public int Count => _data.Count;

    public IEnumerator<T> GetEnumerator()
    {
        return _data.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

PriorityQueue<TKey, TValue>

public class PriorityQueue<TKey, TValue> : IEnumerable<TValue>
{
    private readonly List<KeyValuePair<TKey, TValue>> _data = new List<KeyValuePair<TKey, TValue>>();
    private readonly bool _isDescending;
    private readonly Func<TValue, TKey> _keySelector;
    private readonly IComparer<TKey> _keyComparer;

    public PriorityQueue(Func<TValue, TKey> keySelector, bool isDescending = true)
        : this(keySelector, Comparer<TKey>.Default, isDescending)
    {
    }

    public PriorityQueue(Func<TValue, TKey> keySelector, IComparer<TKey> keyComparer, bool isDescending = true)
    {
        _keySelector = keySelector;
        _keyComparer = keyComparer;
        _isDescending = isDescending;
    }

    public void Enqueue(TValue item)
    {
        _data.Add(new KeyValuePair<TKey, TValue>(_keySelector(item), item));
        var childIndex = _data.Count - 1;
        while (childIndex > 0)
        {
            var parentIndex = (childIndex - 1) / 2;
            if (Compare(_data[childIndex].Key, _data[parentIndex].Key) >= 0)
                break;
            Swap(childIndex, parentIndex);
            childIndex = parentIndex;
        }
    }

    public TValue Dequeue()
    {
        var lastIndex = _data.Count - 1;
        var firstItem = _data[0];
        _data[0] = _data[lastIndex];
        _data.RemoveAt(lastIndex--);
        var parentIndex = 0;
        while (true)
        {
            var childIndex = parentIndex * 2 + 1;
            if (childIndex > lastIndex)
                break;
            var rightChild = childIndex + 1;
            if (rightChild <= lastIndex && Compare(_data[rightChild].Key, _data[childIndex].Key) < 0)
                childIndex = rightChild;
            if (Compare(_data[parentIndex].Key, _data[childIndex].Key) <= 0)
                break;
            Swap(parentIndex, childIndex);
            parentIndex = childIndex;
        }
        return firstItem.Value;
    }

    public TValue Peek()
    {
        return _data[0].Value;
    }

    private void Swap(int a, int b)
    {
        var tmp = _data[a];
        _data[a] = _data[b];
        _data[b] = tmp;
    }

    private int Compare(TKey a, TKey b)
    {
        return _isDescending ? _keyComparer.Compare(b, a) : _keyComparer.Compare(a, b);
    }

    public int Count => _data.Count;

    public IEnumerator<TValue> GetEnumerator()
    {
        return _data.Select(r => r.Value).GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
10
8
4

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
10
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?