本文主要记录优先队列的基础实现(C#)。
代码
要求泛型实现 IComparable<T> 接口,这样才可以对容器内的对象进行比较。
1 2 3 4
| public class PriorityQueue<T> where T : IComparable<T> { }
|
字段
底层容器为数组,比较器可以使用 IComparer<T> 接口实现,也可以使用 Comparison 委托实现。
从规范的角度来讲,使用 IComparer<T> 接口可能会更好(将比较器留给泛型类自行实现),这样在构造优先队列的时候既不需要传入比较器,也可以对泛型类进行一定的限制。
如果对比较器有要求也可以在构造时自定义比较器,后面的实际应用会提到如何传入自定义的比较器。
1 2 3 4
| private const int _DEFAULT_CAPACITY = 0x10;
private readonly IComparer<T> _comparer; private T[] _heap;
|
属性
1 2 3 4 5
| public int Capacity => _heap.Length;
public int Count { get; private set; }
public bool IsEmpty => Count == 0;
|
方法
构造方法
1 2 3 4 5 6 7 8
| public PriorityQueue(IComparer<T> comparer) : this(_DEFAULT_CAPACITY, comparer) { }
public PriorityQueue(int capacity = _DEFAULT_CAPACITY, IComparer<T> comparer = null) { _heap = new T[capacity]; _comparer = comparer ?? Comparer<T>.Default; }
|
扩容 Expand
使用 Array 类的静态方法 Resize 进行扩容,将当前队列的容量扩容为之前的两倍。
1 2 3 4
| private void Expand() { Array.Resize(ref _heap, Capacity * 2); }
|
缩容 Shrink
使用 Array 类的静态方法 Resize 进行缩容,将当前队列的容量缩容为之前的一半。
1 2 3 4
| private void Shrink() { Array.Resize(ref _heap, Capacity / 2); }
|
获取头元素 Peek
判断队列是否为空,是则抛出异常,否则返回堆的头元素。
1 2 3 4 5 6 7 8
| public T Peek() { if (IsEmpty) { throw new InvalidOperationException("优先队列为空,获取头元素失败。"); }
return _heap[0]; }
|
入队 Enqueue
判断队列容量是否已满,是则进行扩容;
然后将新元素放在队列的末尾,同时队列实际长度 +1;
最后对队列进行自底向上的调整。
1 2 3 4 5 6 7 8 9
| public void Enqueue(T item) { if (Count == Capacity) { Expand(); }
_heap[Count++] = item; HeapAdjustFromBottom(); }
|
自底向上调整堆 HeapAdjustFromBottom
将新入堆的元素上升到其该在的位置,详细见注释。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| private void HeapAdjustFromBottom() { int child = Count - 1; T value = _heap[child]; for (int parent = (child - 1) / 2; parent >= 0 && child > 0; parent = (parent - 1) / 2) { if (_comparer.Compare(value, _heap[parent]) <= 0) { break; } _heap[child] = _heap[parent]; child = parent; } _heap[child] = value; }
|
出队 Dequeue
判断队列是否为空,是则抛出异常;
然后将头元素出队,队列实际长度 -1,并用队列的尾元素补上头元素;
若出队后,实际的元素数量小于等于容量的一半,则进行一次缩容;
最后对队列进行自顶向下的调整。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public T Dequeue() { if (IsEmpty) { throw new InvalidOperationException("优先队列为空,出队失败。"); }
T item = _heap[0]; _heap[0] = _heap[--Count]; HeapAdjustFromTop(); if (Count * 2 <= Capacity) { Shrink(); } return item; }
|
自顶向下调整堆 HeapAdjustFromTop
将新的头元素下沉到其该在的位置,详细见注释。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| private void HeapAdjustFromTop() { int parent = 0; T value = _heap[0]; for (int child = parent * 2 + 1; child < Count; child = child * 2 + 1) { if (child < Count - 1 && _comparer.Compare(_heap[child], _heap[child + 1]) < 0) { ++child; } if (_comparer.Compare(_heap[child], value) <= 0) { break; } _heap[parent] = _heap[child]; parent = child; } _heap[parent] = value; }
|
完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
| public class PriorityQueue<T> where T : IComparable<T> { private const int _DEFAULT_CAPACITY = 0x10;
private readonly IComparer<T> _comparer; private T[] _heap;
public PriorityQueue(int capacity = _DEFAULT_CAPACITY, IComparer<T> comparer = null) { _heap = new T[capacity]; _comparer = comparer ?? Comparer<T>.Default; }
public int Capacity => _heap.Length;
public int Count { get; private set; }
public bool IsEmpty => Count == 0;
private void Expand() { Array.Resize(ref _heap, Capacity * 2); }
private void Shrink() { Array.Resize(ref _heap, Capacity / 2); }
public void Enqueue(T item) { if (Count == Capacity) { Expand(); }
_heap[Count++] = item; HeapAdjustFromBottom(); }
public T Dequeue() { if (IsEmpty) { throw new InvalidOperationException("优先队列为空,出队失败。"); }
T item = _heap[0]; _heap[0] = _heap[--Count]; HeapAdjustFromTop(); if (Count * 2 <= Capacity) { Shrink(); } return item; }
private void HeapAdjustFromTop() { int parent = 0; T value = _heap[0]; for (int child = parent * 2 + 1; child <= Count - 1; child = child * 2 + 1) { if (child < Count - 1 && _comparer.Compare(_heap[child], _heap[child + 1]) < 0) { ++child; } if (_comparer.Compare(_heap[child], value) <= 0) { break; } _heap[parent] = _heap[child]; parent = child; } _heap[parent] = value; }
private void HeapAdjustFromBottom() { int child = Count - 1; T value = _heap[child]; for (int parent = (child - 1) / 2; parent >= 0 && child > 0; parent = (parent - 1) / 2) { if (_comparer.Compare(value, _heap[parent]) <= 0) { break; } _heap[child] = _heap[parent]; child = parent; } _heap[child] = value; }
public T Peek() { if (IsEmpty) { throw new InvalidOperationException("优先队列为空,获取头元素失败。"); }
return _heap[0]; } }
|
实际应用
自定义比较器
下面以 int 作为例子,分别向优先队列中传入升序(其实 int 本身的比较器就是升序)和降序的比较器。
分别创建 int 对应的升序和降序类,实现 IComparer<T> 接口,T 为 int。
在接口方法中实现对应的升序和降序比较。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public class Test { private class Asc : IComparer<int> { public int Compare(int x, int y) { return y - x; } }
private class Desc : IComparer<int> { public int Compare(int x, int y) { return x - y; } } private readonly PriorityQueue<int> _btmHeap; private readonly PriorityQueue<int> _topHeap; public Test() { _btmHeap = new PriorityQueue<int>(new Asc()); _topHeap = new PriorityQueue<int>(new Desc()); } }
|
LeetCode
LeetCode-295-困难-数据流的中位数
该题使用了两个优先队列,分别对应大根堆和小根堆。
代码
这里只放上 MedianFinder 类的内容,省略了优先队列类,提交时需要加上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| public class MedianFinder { private readonly PriorityQueue<int> _topHeap; private readonly PriorityQueue<int> _btmHeap;
public MedianFinder() { _topHeap = new PriorityQueue<int>(new Desc()); _btmHeap = new PriorityQueue<int>(new Asc()); }
private int Count => _topHeap.Count + _btmHeap.Count;
public void AddNum(int num) { if (Count % 2 == 0) { _topHeap.Enqueue(num); _btmHeap.Enqueue(_topHeap.Dequeue()); } else { _btmHeap.Enqueue(num); _topHeap.Enqueue(_btmHeap.Dequeue()); } }
public double FindMedian() { if (Count % 2 == 0) { return (_topHeap.Peek() + _btmHeap.Peek()) / 2.0; } return _btmHeap.Peek(); }
private class Asc : IComparer<int> { public int Compare(int x, int y) { return y - x; } }
private class Desc : IComparer<int> { public int Compare(int x, int y) { return x - y; } } }
|
执行结果