Skip to content

Commit e120d12

Browse files
committed
Add Heap-Based PriorityQueue
1 parent cabbd32 commit e120d12

File tree

1 file changed

+128
-0
lines changed

1 file changed

+128
-0
lines changed
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import 'package:test/test.dart';
2+
3+
import '../../../../utils/list_extension.dart';
4+
5+
/// Note: While priority queues are often implemented using heaps, they are conceptually
6+
/// distinct from heaps. A priority queue is an abstract data structure like a list
7+
/// or a map; just as a list can be implemented with a linked list or with an array,
8+
/// a priority queue can be implemented with a heap or another method such as an unordered array.
9+
///
10+
/// Pseudocode:
11+
/// - Write a Min Binary Heap - lower number means higher priority.
12+
/// - Each Node has a val value a priority. Use the priority to build the heap.
13+
/// - Enqueue method accepts a value and priority, makes a new node,
14+
/// and puts it in the right spot based off of its priority.
15+
/// - Dequeue method removes root element, returns it, and rearranges heap using priority.
16+
17+
class Node<T> {
18+
Node(this.value, this.priority);
19+
20+
T value;
21+
int priority;
22+
}
23+
24+
class PriorityQueue<T> {
25+
final List<Node<T>> _heap = [];
26+
27+
void enqueue(T value, int priority) {
28+
final newNode = Node(value, priority);
29+
_heap.add(newNode);
30+
_siftUp();
31+
}
32+
33+
_siftUp() {
34+
int childIndex = _heap.length - 1;
35+
36+
while (childIndex > 0) {
37+
final parentIndex = (childIndex - 1) ~/ 2;
38+
if (_heap[childIndex].priority >= _heap[parentIndex].priority) break;
39+
40+
_heap.swap(childIndex, parentIndex);
41+
childIndex = parentIndex;
42+
}
43+
}
44+
45+
T? dequeue() {
46+
if (_heap.isEmpty) return null;
47+
48+
_heap.swap(0, _heap.length - 1);
49+
final oldRoot = _heap.removeLast();
50+
_siftDown();
51+
return oldRoot.value;
52+
}
53+
54+
_siftDown() {
55+
int parentIndex = 0;
56+
57+
while (true) {
58+
final leftChildIndex = parentIndex * 2 + 1;
59+
final rightChildIndex = leftChildIndex + 1;
60+
61+
if (leftChildIndex >= _heap.length) break;
62+
63+
final minChildIndex = rightChildIndex < _heap.length &&
64+
_heap[rightChildIndex].priority < _heap[leftChildIndex].priority
65+
? rightChildIndex
66+
: leftChildIndex;
67+
68+
if (_heap[minChildIndex].priority >= _heap[parentIndex].priority) break;
69+
70+
_heap.swap(parentIndex, minChildIndex);
71+
parentIndex = minChildIndex;
72+
}
73+
}
74+
}
75+
76+
void main() {
77+
group('enqueue', () {
78+
test('should enqueue items and sift-up based on the priority', () {
79+
final queue = PriorityQueue<String>();
80+
// 1
81+
// 2 5
82+
// 6 3
83+
queue.enqueue('some5', 5);
84+
queue.enqueue('some6', 6);
85+
queue.enqueue('some3', 3);
86+
queue.enqueue('some1', 1);
87+
queue.enqueue('some2', 2);
88+
89+
expect(queue._heap.map((e) => e.priority).toList(), [1, 2, 5, 6, 3]);
90+
});
91+
});
92+
93+
group('dequeue', () {
94+
test(
95+
'should dequeue root item after swapping with last item, then sift-down',
96+
() {
97+
final queue = PriorityQueue<String>();
98+
// 1
99+
// 2 5
100+
// 6 3
101+
queue.enqueue('some5', 5);
102+
queue.enqueue('some6', 6);
103+
queue.enqueue('some3', 3);
104+
queue.enqueue('some1', 1);
105+
queue.enqueue('some2', 2);
106+
107+
// 2
108+
// 3 5
109+
// 6
110+
expect(queue.dequeue(), 'some1');
111+
expect(queue._heap.map((e) => e.priority).toList(), [2, 3, 5, 6]);
112+
// 3
113+
// 6 5
114+
expect(queue.dequeue(), 'some2');
115+
expect(queue._heap.map((e) => e.priority).toList(), [3, 6, 5]);
116+
expect(queue.dequeue(), 'some3');
117+
expect(queue.dequeue(), 'some5');
118+
expect(queue.dequeue(), 'some6');
119+
expect(queue.dequeue(), isNull);
120+
});
121+
122+
test('should return null if the queue is empty', () {
123+
final queue = PriorityQueue();
124+
125+
expect(queue.dequeue(), isNull);
126+
});
127+
});
128+
}

0 commit comments

Comments
 (0)