Skip to content

Commit a78b15d

Browse files
authored
Enhance docs, add tests in MinHeap (TheAlgorithms#5985)
1 parent bc6ea1e commit a78b15d

File tree

5 files changed

+344
-52
lines changed

5 files changed

+344
-52
lines changed

DIRECTORY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -871,6 +871,7 @@
871871
* [LeftistHeapTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/heaps/LeftistHeapTest.java)
872872
* [MedianFinderTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/heaps/MedianFinderTest.java)
873873
* [MergeKSortedArraysTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/heaps/MergeKSortedArraysTest.java)
874+
* [MinHeapTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/heaps/MinHeapTest.java)
874875
* [MinPriorityQueueTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/heaps/MinPriorityQueueTest.java)
875876
* lists
876877
* [CircleLinkedListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/CircleLinkedListTest.java)

src/main/java/com/thealgorithms/datastructures/heaps/Heap.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,5 @@ public interface Heap {
4040
* @param elementIndex int containing the position in the heap of the
4141
* element to be deleted.
4242
*/
43-
void deleteElement(int elementIndex);
43+
void deleteElement(int elementIndex) throws EmptyHeapException;
4444
}

src/main/java/com/thealgorithms/datastructures/heaps/HeapElement.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,4 +167,8 @@ public int hashCode() {
167167
result += (additionalInfo != null) ? additionalInfo.hashCode() : 0;
168168
return result;
169169
}
170+
171+
public String getValue() {
172+
return additionalInfo.toString();
173+
}
170174
}

src/main/java/com/thealgorithms/datastructures/heaps/MinHeap.java

Lines changed: 197 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -4,122 +4,268 @@
44
import java.util.List;
55

66
/**
7-
* Heap tree where a node's key is higher than or equal to its parent's and
8-
* lower than or equal to its children's.
7+
* A Min Heap implementation where each node's key is lower than or equal to its children's keys.
8+
* This data structure provides O(log n) time complexity for insertion and deletion operations,
9+
* and O(1) for retrieving the minimum element.
10+
*
11+
* Properties:
12+
* 1. Complete Binary Tree
13+
* 2. Parent node's key ≤ Children nodes' keys
14+
* 3. Root contains the minimum element
15+
*
16+
* Example usage:
17+
* ```java
18+
* List<HeapElement> elements = Arrays.asList(
19+
* new HeapElement(5, "Five"),
20+
* new HeapElement(2, "Two")
21+
* );
22+
* MinHeap heap = new MinHeap(elements);
23+
* heap.insertElement(new HeapElement(1, "One"));
24+
* HeapElement min = heap.getElement(); // Returns and removes the minimum element
25+
* ```
926
*
1027
* @author Nicolas Renard
1128
*/
1229
public class MinHeap implements Heap {
1330

1431
private final List<HeapElement> minHeap;
1532

33+
/**
34+
* Constructs a new MinHeap from a list of elements.
35+
* Null elements in the input list are ignored with a warning message.
36+
*
37+
* @param listElements List of HeapElement objects to initialize the heap
38+
* @throws IllegalArgumentException if the input list is null
39+
*/
1640
public MinHeap(List<HeapElement> listElements) {
41+
if (listElements == null) {
42+
throw new IllegalArgumentException("Input list cannot be null");
43+
}
44+
1745
minHeap = new ArrayList<>();
46+
47+
// Safe initialization: directly add elements first
1848
for (HeapElement heapElement : listElements) {
1949
if (heapElement != null) {
20-
insertElement(heapElement);
50+
minHeap.add(heapElement);
2151
} else {
2252
System.out.println("Null element. Not added to heap");
2353
}
2454
}
55+
56+
// Heapify the array bottom-up
57+
for (int i = minHeap.size() / 2; i >= 0; i--) {
58+
heapifyDown(i + 1);
59+
}
60+
2561
if (minHeap.isEmpty()) {
2662
System.out.println("No element has been added, empty heap.");
2763
}
2864
}
2965

30-
// Get the element at a given index. The key for the list is equal to index value - 1
66+
/**
67+
* Retrieves the element at the specified index without removing it.
68+
* Note: The index is 1-based for consistency with heap operations.
69+
*
70+
* @param elementIndex 1-based index of the element to retrieve
71+
* @return HeapElement at the specified index
72+
* @throws IndexOutOfBoundsException if the index is invalid
73+
*/
3174
public HeapElement getElement(int elementIndex) {
3275
if ((elementIndex <= 0) || (elementIndex > minHeap.size())) {
33-
throw new IndexOutOfBoundsException("Index out of heap range");
76+
throw new IndexOutOfBoundsException("Index " + elementIndex + " is out of heap range [1, " + minHeap.size() + "]");
3477
}
3578
return minHeap.get(elementIndex - 1);
3679
}
3780

38-
// Get the key of the element at a given index
81+
/**
82+
* Retrieves the key value of an element at the specified index.
83+
*
84+
* @param elementIndex 1-based index of the element
85+
* @return double value representing the key
86+
* @throws IndexOutOfBoundsException if the index is invalid
87+
*/
3988
private double getElementKey(int elementIndex) {
4089
if ((elementIndex <= 0) || (elementIndex > minHeap.size())) {
41-
throw new IndexOutOfBoundsException("Index out of heap range");
90+
throw new IndexOutOfBoundsException("Index " + elementIndex + " is out of heap range [1, " + minHeap.size() + "]");
4291
}
43-
4492
return minHeap.get(elementIndex - 1).getKey();
4593
}
4694

47-
// Swaps two elements in the heap
95+
/**
96+
* Swaps two elements in the heap.
97+
*
98+
* @param index1 1-based index of first element
99+
* @param index2 1-based index of second element
100+
*/
48101
private void swap(int index1, int index2) {
49102
HeapElement temporaryElement = minHeap.get(index1 - 1);
50103
minHeap.set(index1 - 1, minHeap.get(index2 - 1));
51104
minHeap.set(index2 - 1, temporaryElement);
52105
}
53106

54-
// Toggle an element up to its right place as long as its key is lower than its parent's
107+
/**
108+
* Maintains heap properties by moving an element down the heap.
109+
* Used specifically during initialization.
110+
*
111+
* @param elementIndex 1-based index of the element to heapify
112+
*/
113+
private void heapifyDown(int elementIndex) {
114+
int smallest = elementIndex - 1; // Convert to 0-based index
115+
int leftChild = 2 * elementIndex - 1;
116+
int rightChild = 2 * elementIndex;
117+
118+
// Check if left child is smaller than root
119+
if (leftChild < minHeap.size() && minHeap.get(leftChild).getKey() < minHeap.get(smallest).getKey()) {
120+
smallest = leftChild;
121+
}
122+
123+
// Check if right child is smaller than smallest so far
124+
if (rightChild < minHeap.size() && minHeap.get(rightChild).getKey() < minHeap.get(smallest).getKey()) {
125+
smallest = rightChild;
126+
}
127+
128+
// If smallest is not root
129+
if (smallest != elementIndex - 1) {
130+
HeapElement swap = minHeap.get(elementIndex - 1);
131+
minHeap.set(elementIndex - 1, minHeap.get(smallest));
132+
minHeap.set(smallest, swap);
133+
134+
// Recursively heapify the affected sub-tree
135+
heapifyDown(smallest + 1); // Convert back to 1-based index
136+
}
137+
}
138+
139+
/**
140+
* Moves an element up the heap until heap properties are satisfied.
141+
* This operation is called after insertion to maintain heap properties.
142+
*
143+
* @param elementIndex 1-based index of the element to move up
144+
*/
55145
private void toggleUp(int elementIndex) {
146+
if (elementIndex <= 1) {
147+
return;
148+
}
149+
56150
double key = minHeap.get(elementIndex - 1).getKey();
57-
while (getElementKey((int) Math.floor(elementIndex / 2.0) + 1) > key) {
58-
swap(elementIndex, (int) Math.floor(elementIndex / 2.0));
59-
elementIndex = (int) Math.floor(elementIndex / 2.0);
151+
int parentIndex = (int) Math.floor(elementIndex / 2.0);
152+
153+
while (elementIndex > 1 && getElementKey(parentIndex) > key) {
154+
swap(elementIndex, parentIndex);
155+
elementIndex = parentIndex;
156+
parentIndex = (int) Math.floor(elementIndex / 2.0);
60157
}
61158
}
62159

63-
// Toggle an element down to its right place as long as its key is higher
64-
// than any of its children's
160+
/**
161+
* Moves an element down the heap until heap properties are satisfied.
162+
* This operation is called after deletion to maintain heap properties.
163+
*
164+
* @param elementIndex 1-based index of the element to move down
165+
*/
65166
private void toggleDown(int elementIndex) {
66167
double key = minHeap.get(elementIndex - 1).getKey();
67-
boolean wrongOrder = (key > getElementKey(elementIndex * 2)) || (key > getElementKey(Math.min(elementIndex * 2, minHeap.size())));
68-
while ((2 * elementIndex <= minHeap.size()) && wrongOrder) {
69-
// Check whether it shall swap the element with its left child or its right one if any.
70-
if ((2 * elementIndex < minHeap.size()) && (getElementKey(elementIndex * 2 + 1) < getElementKey(elementIndex * 2))) {
71-
swap(elementIndex, 2 * elementIndex + 1);
72-
elementIndex = 2 * elementIndex + 1;
73-
} else {
74-
swap(elementIndex, 2 * elementIndex);
75-
elementIndex = 2 * elementIndex;
168+
int size = minHeap.size();
169+
170+
while (true) {
171+
int smallest = elementIndex;
172+
int leftChild = 2 * elementIndex;
173+
int rightChild = 2 * elementIndex + 1;
174+
175+
if (leftChild <= size && getElementKey(leftChild) < key) {
176+
smallest = leftChild;
177+
}
178+
179+
if (rightChild <= size && getElementKey(rightChild) < getElementKey(smallest)) {
180+
smallest = rightChild;
181+
}
182+
183+
if (smallest == elementIndex) {
184+
break;
76185
}
77-
wrongOrder = (key > getElementKey(elementIndex * 2)) || (key > getElementKey(Math.min(elementIndex * 2, minHeap.size())));
186+
187+
swap(elementIndex, smallest);
188+
elementIndex = smallest;
78189
}
79190
}
80191

81-
private HeapElement extractMin() {
82-
HeapElement result = minHeap.get(0);
83-
deleteElement(0);
192+
/**
193+
* Extracts and returns the minimum element from the heap.
194+
*
195+
* @return HeapElement with the lowest key
196+
* @throws EmptyHeapException if the heap is empty
197+
*/
198+
private HeapElement extractMin() throws EmptyHeapException {
199+
if (minHeap.isEmpty()) {
200+
throw new EmptyHeapException("Cannot extract from empty heap");
201+
}
202+
HeapElement result = minHeap.getFirst();
203+
deleteElement(1);
84204
return result;
85205
}
86206

207+
/**
208+
* {@inheritDoc}
209+
*/
87210
@Override
88-
public final void insertElement(HeapElement element) {
211+
public void insertElement(HeapElement element) {
212+
if (element == null) {
213+
throw new IllegalArgumentException("Cannot insert null element");
214+
}
89215
minHeap.add(element);
90216
toggleUp(minHeap.size());
91217
}
92218

219+
/**
220+
* {@inheritDoc}
221+
*/
93222
@Override
94-
public void deleteElement(int elementIndex) {
223+
public void deleteElement(int elementIndex) throws EmptyHeapException {
95224
if (minHeap.isEmpty()) {
96-
try {
97-
throw new EmptyHeapException("Attempt to delete an element from an empty heap");
98-
} catch (EmptyHeapException e) {
99-
e.printStackTrace();
100-
}
225+
throw new EmptyHeapException("Cannot delete from empty heap");
101226
}
102227
if ((elementIndex > minHeap.size()) || (elementIndex <= 0)) {
103-
throw new IndexOutOfBoundsException("Index out of heap range");
104-
}
105-
// The last element in heap replaces the one to be deleted
106-
minHeap.set(elementIndex - 1, getElement(minHeap.size()));
107-
minHeap.remove(minHeap.size());
108-
// Shall the new element be moved up...
109-
if (getElementKey(elementIndex) < getElementKey((int) Math.floor(elementIndex / 2.0))) {
110-
toggleUp(elementIndex);
111-
} // ... or down ?
112-
else if (((2 * elementIndex <= minHeap.size()) && (getElementKey(elementIndex) > getElementKey(elementIndex * 2))) || ((2 * elementIndex < minHeap.size()) && (getElementKey(elementIndex) > getElementKey(elementIndex * 2)))) {
113-
toggleDown(elementIndex);
228+
throw new IndexOutOfBoundsException("Index " + elementIndex + " is out of heap range [1, " + minHeap.size() + "]");
229+
}
230+
231+
// Replace with last element and remove last position
232+
minHeap.set(elementIndex - 1, minHeap.getLast());
233+
minHeap.removeLast();
234+
235+
// No need to toggle if we just removed the last element
236+
if (!minHeap.isEmpty() && elementIndex <= minHeap.size()) {
237+
// Determine whether to toggle up or down
238+
if (elementIndex > 1 && getElementKey(elementIndex) < getElementKey((int) Math.floor(elementIndex / 2.0))) {
239+
toggleUp(elementIndex);
240+
} else {
241+
toggleDown(elementIndex);
242+
}
114243
}
115244
}
116245

246+
/**
247+
* {@inheritDoc}
248+
*/
117249
@Override
118250
public HeapElement getElement() throws EmptyHeapException {
119-
try {
120-
return extractMin();
121-
} catch (Exception e) {
122-
throw new EmptyHeapException("Heap is empty. Error retrieving element", e);
123-
}
251+
return extractMin();
252+
}
253+
254+
/**
255+
* Returns the current size of the heap.
256+
*
257+
* @return number of elements in the heap
258+
*/
259+
public int size() {
260+
return minHeap.size();
261+
}
262+
263+
/**
264+
* Checks if the heap is empty.
265+
*
266+
* @return true if the heap contains no elements
267+
*/
268+
public boolean isEmpty() {
269+
return minHeap.isEmpty();
124270
}
125271
}

0 commit comments

Comments
 (0)