forked from TheAlgorithms/C-Sharp
-
Notifications
You must be signed in to change notification settings - Fork 0
/
MinMaxHeap.cs
380 lines (337 loc) · 11.1 KB
/
MinMaxHeap.cs
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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
using System;
using System.Collections.Generic;
using System.Linq;
namespace DataStructures.Heap;
/// <summary>
/// This class implements min-max heap.
/// It provides functionality of both min-heap and max-heap with the same time complexity.
/// Therefore it provides constant time retrieval and logarithmic time removal
/// of both the minimum and maximum elements in it.
/// </summary>
/// <typeparam name="T">Generic type.</typeparam>
public class MinMaxHeap<T>
{
private readonly List<T> heap;
/// <summary>
/// Initializes a new instance of the <see cref="MinMaxHeap{T}" /> class that contains
/// elements copied from a specified enumerable collection and that uses a specified comparer.
/// </summary>
/// <param name="collection">The enumerable collection to be copied.</param>
/// <param name="comparer">The default comparer to use for comparing objects.</param>
public MinMaxHeap(IEnumerable<T>? collection = null, IComparer<T>? comparer = null)
{
Comparer = comparer ?? Comparer<T>.Default;
collection ??= Enumerable.Empty<T>();
heap = collection.ToList();
for (var i = Count / 2 - 1; i >= 0; --i)
{
PushDown(i);
}
}
/// <summary>
/// Gets the <see cref="IComparer{T}" />. object that is used to order the values in the <see cref="MinMaxHeap{T}" />.
/// </summary>
public IComparer<T> Comparer { get; }
/// <summary>
/// Gets the number of elements in the <see cref="MinMaxHeap{T}" />.
/// </summary>
public int Count => heap.Count;
/// <summary>
/// Adds an element to the heap.
/// </summary>
/// <param name="item">The element to add to the heap.</param>
public void Add(T item)
{
heap.Add(item);
PushUp(Count - 1);
}
/// <summary>
/// Removes the maximum node from the heap and returns its value.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown if heap is empty.</exception>
/// <returns>Value of the removed maximum node.</returns>
public T ExtractMax()
{
if (Count == 0)
{
throw new InvalidOperationException("Heap is empty");
}
var max = GetMax();
RemoveNode(GetMaxNodeIndex());
return max;
}
/// <summary>
/// Removes the minimum node from the heap and returns its value.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown if heap is empty.</exception>
/// <returns>Value of the removed minimum node.</returns>
public T ExtractMin()
{
if (Count == 0)
{
throw new InvalidOperationException("Heap is empty");
}
var min = GetMin();
RemoveNode(0);
return min;
}
/// <summary>
/// Gets the maximum value in the heap, as defined by the comparer.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown if heap is empty.</exception>
/// <returns>The maximum value in the heap.</returns>
public T GetMax()
{
if (Count == 0)
{
throw new InvalidOperationException("Heap is empty");
}
return heap[GetMaxNodeIndex()];
}
/// <summary>
/// Gets the minimum value in the heap, as defined by the comparer.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown if heap is empty.</exception>
/// <returns>The minimum value in the heap.</returns>
public T GetMin()
{
if (Count == 0)
{
throw new InvalidOperationException("Heap is empty");
}
return heap[0];
}
/// <summary>
/// Finds maximum value among children and grandchildren of the specified node.
/// </summary>
/// <param name="index">Index of the node in the Heap array.</param>
/// <returns>Index of the maximum descendant.</returns>
private int IndexOfMaxChildOrGrandchild(int index)
{
var descendants = new[]
{
2 * index + 1,
2 * index + 2,
4 * index + 3,
4 * index + 4,
4 * index + 5,
4 * index + 6,
};
var resIndex = descendants[0];
foreach (var descendant in descendants)
{
if (descendant >= Count)
{
break;
}
if (Comparer.Compare(heap[descendant], heap[resIndex]) > 0)
{
resIndex = descendant;
}
}
return resIndex;
}
/// <summary>
/// Finds minumum value among children and grandchildren of the specified node.
/// </summary>
/// <param name="index">Index of the node in the Heap array.</param>
/// <returns>Index of the minimum descendant.</returns>
private int IndexOfMinChildOrGrandchild(int index)
{
var descendants = new[] { 2 * index + 1, 2 * index + 2, 4 * index + 3, 4 * index + 4, 4 * index + 5, 4 * index + 6 };
var resIndex = descendants[0];
foreach (var descendant in descendants)
{
if (descendant >= Count)
{
break;
}
if (Comparer.Compare(heap[descendant], heap[resIndex]) < 0)
{
resIndex = descendant;
}
}
return resIndex;
}
private int GetMaxNodeIndex()
{
return Count switch
{
0 => throw new InvalidOperationException("Heap is empty"),
1 => 0,
2 => 1,
_ => Comparer.Compare(heap[1], heap[2]) > 0 ? 1 : 2,
};
}
private bool HasChild(int index) => index * 2 + 1 < Count;
private bool IsGrandchild(int node, int grandchild) => grandchild > 2 && Grandparent(grandchild) == node;
/// <summary>
/// Checks if node at index belongs to Min or Max level of the heap.
/// Root node belongs to Min level, its children - Max level,
/// its grandchildren - Min level, and so on.
/// </summary>
/// <param name="index">Index to check.</param>
/// <returns>true if index is at Min level; false if it is at Max Level.</returns>
private bool IsMinLevelIndex(int index)
{
// For all Min levels, value (index + 1) has the leftmost bit set to '1' at even position.
const uint minLevelsBits = 0x55555555;
const uint maxLevelsBits = 0xAAAAAAAA;
return ((index + 1) & minLevelsBits) > ((index + 1) & maxLevelsBits);
}
private int Parent(int index) => (index - 1) / 2;
private int Grandparent(int index) => ((index - 1) / 2 - 1) / 2;
/// <summary>
/// Assuming that children sub-trees are valid heaps, pushes node to lower levels
/// to make heap valid.
/// </summary>
/// <param name="index">Node index.</param>
private void PushDown(int index)
{
if (IsMinLevelIndex(index))
{
PushDownMin(index);
}
else
{
PushDownMax(index);
}
}
private void PushDownMax(int index)
{
if (!HasChild(index))
{
return;
}
var maxIndex = IndexOfMaxChildOrGrandchild(index);
// If smaller element are put at min level (as result of swaping), it doesn't affect sub-tree validity.
// If smaller element are put at max level, PushDownMax() should be called for that node.
if (IsGrandchild(index, maxIndex))
{
if (Comparer.Compare(heap[maxIndex], heap[index]) > 0)
{
SwapNodes(maxIndex, index);
if (Comparer.Compare(heap[maxIndex], heap[Parent(maxIndex)]) < 0)
{
SwapNodes(maxIndex, Parent(maxIndex));
}
PushDownMax(maxIndex);
}
}
else
{
if (Comparer.Compare(heap[maxIndex], heap[index]) > 0)
{
SwapNodes(maxIndex, index);
}
}
}
private void PushDownMin(int index)
{
if (!HasChild(index))
{
return;
}
var minIndex = IndexOfMinChildOrGrandchild(index);
// If bigger element are put at max level (as result of swaping), it doesn't affect sub-tree validity.
// If bigger element are put at min level, PushDownMin() should be called for that node.
if (IsGrandchild(index, minIndex))
{
if (Comparer.Compare(heap[minIndex], heap[index]) < 0)
{
SwapNodes(minIndex, index);
if (Comparer.Compare(heap[minIndex], heap[Parent(minIndex)]) > 0)
{
SwapNodes(minIndex, Parent(minIndex));
}
PushDownMin(minIndex);
}
}
else
{
if (Comparer.Compare(heap[minIndex], heap[index]) < 0)
{
SwapNodes(minIndex, index);
}
}
}
/// <summary>
/// Having a new node in the heap, swaps this node with its ancestors to make heap valid.
/// For node at min level. If new node is less than its parent, then it is surely less then
/// all other nodes on max levels on path to the root of the heap. So node are pushed up, by
/// swaping with its grandparent, until they are ordered correctly.
/// For node at max level algorithm is analogical.
/// </summary>
/// <param name="index">Index of the new node.</param>
private void PushUp(int index)
{
if (index == 0)
{
return;
}
var parent = Parent(index);
if (IsMinLevelIndex(index))
{
if (Comparer.Compare(heap[index], heap[parent]) > 0)
{
SwapNodes(index, parent);
PushUpMax(parent);
}
else
{
PushUpMin(index);
}
}
else
{
if (Comparer.Compare(heap[index], heap[parent]) < 0)
{
SwapNodes(index, parent);
PushUpMin(parent);
}
else
{
PushUpMax(index);
}
}
}
private void PushUpMax(int index)
{
if (index > 2)
{
var grandparent = Grandparent(index);
if (Comparer.Compare(heap[index], heap[grandparent]) > 0)
{
SwapNodes(index, grandparent);
PushUpMax(grandparent);
}
}
}
private void PushUpMin(int index)
{
if (index > 2)
{
var grandparent = Grandparent(index);
if (Comparer.Compare(heap[index], heap[grandparent]) < 0)
{
SwapNodes(index, grandparent);
PushUpMin(grandparent);
}
}
}
private void RemoveNode(int index)
{
SwapNodes(index, Count - 1);
heap.RemoveAt(Count - 1);
if (Count != 0)
{
PushDown(index);
}
}
private void SwapNodes(int i, int j)
{
var temp = heap[i];
heap[i] = heap[j];
heap[j] = temp;
}
}