-
Notifications
You must be signed in to change notification settings - Fork 20k
Implement Smooth Sort #5236
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement Smooth Sort #5236
Changes from all commits
9e5cb32
9ff4e85
69b24a8
6de4059
d6a14b6
384f814
29252e1
4f0fd70
0cdcba2
d32e435
712868e
368db8a
5f9d2e7
990b183
0eda98e
aed3358
ab905bf
65ca159
d5e3b95
ec02a6c
b19bd38
5d21701
951653f
79a5d20
9fe137c
7aa1421
645b29c
89d9f51
1adbd15
c249579
edbf08a
cf1ce78
369cbeb
01ba460
fa0797f
ba730d0
a8a5835
5093f86
761b848
37c794b
c0b8737
ad4c540
0edabcd
6b4c415
174de3f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
vil02 marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,203 @@ | ||
package com.thealgorithms.datastructures.heaps; | ||
|
||
import com.thealgorithms.bitmanipulation.SingleBitOperations; | ||
import com.thealgorithms.maths.LeonardoNumber; | ||
import java.util.ArrayList; | ||
import java.util.Collections; | ||
import java.util.List; | ||
|
||
/** | ||
* Wikipedia: https://en.wikipedia.org/wiki/Smoothsort | ||
*/ | ||
public class LeonardoHeap<T extends Comparable<T>> { | ||
|
||
private int levelTracker = 0; | ||
private final List<T> heap = new ArrayList<>(); | ||
|
||
public LeonardoHeap() { | ||
} | ||
|
||
private void decreaseLevelTracker() { | ||
final int lastTreeLevel = getRightMostTree(); | ||
levelTracker = SingleBitOperations.clearBit(levelTracker, lastTreeLevel); | ||
if (lastTreeLevel != 0 && lastTreeLevel != 1) { | ||
levelTracker = SingleBitOperations.setBit(levelTracker, lastTreeLevel - 1); | ||
levelTracker = SingleBitOperations.setBit(levelTracker, lastTreeLevel - 2); | ||
} | ||
} | ||
|
||
private void increaseLevelTracker() { | ||
final List<Integer> consecutiveTreeIndices = findConsecutiveTreeIndices(levelTracker); | ||
if (consecutiveTreeIndices.get(0) != -1) { | ||
// if 0th or 1st index is -1 that implies there are no concequtive trees | ||
levelTracker = SingleBitOperations.clearBit(levelTracker, consecutiveTreeIndices.get(0)); | ||
levelTracker = SingleBitOperations.clearBit(levelTracker, consecutiveTreeIndices.get(1)); | ||
levelTracker = SingleBitOperations.setBit(levelTracker, consecutiveTreeIndices.get(1) + 1); | ||
} else if ((levelTracker & 2) == 0) { | ||
levelTracker = SingleBitOperations.setBit(levelTracker, 1); | ||
} else { | ||
levelTracker = SingleBitOperations.setBit(levelTracker, 0); | ||
} | ||
} | ||
|
||
private void maxHeapifyTree(int rootNodeIndex, int currentLevel) { | ||
// A leonardo tree of level n is just 1 node(the root) plus the leonardo tree of n-1 level(left child) plus leonardo tree of n-2 level(right child) | ||
// To maxheapify a leonardo tree we need to compare the current root and roots of it's left and right subtree | ||
// We recursively hepify the left and right subtrees using the currentLevel | ||
|
||
// BASE CASE | ||
if (currentLevel == 0 || currentLevel == 1) { | ||
return; // Trees with one node are in already max-heapified. | ||
} | ||
|
||
final int currentRootNodeIndex = rootNodeIndex; | ||
final int rightChildIndex = rootNodeIndex - 1; | ||
final int leftChildIndex = rootNodeIndex - LeonardoNumber.leonardoNumber(currentLevel - 2) - 1; | ||
final int childIndexForSwap = (heap.get(rightChildIndex).compareTo(heap.get(leftChildIndex)) >= 0) ? rightChildIndex : leftChildIndex; | ||
|
||
if (heap.get(childIndexForSwap).compareTo(heap.get(currentRootNodeIndex)) > 0) { | ||
swap(currentRootNodeIndex, childIndexForSwap); | ||
if (childIndexForSwap == rightChildIndex) { | ||
maxHeapifyTree(rightChildIndex, currentLevel - 2); | ||
} else { // swap happened with the left child | ||
maxHeapifyTree(leftChildIndex, currentLevel - 1); | ||
} | ||
} | ||
} | ||
|
||
private void shiftRootAndRestoreHeap() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you see some change to chop this method into few smaller ones? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might be possible, I'll take a look into using some form of recursion instead of the while loop I am using. Recursion might give a bit more readability. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would suggest to refactor this method into a shorter one. |
||
|
||
if (heap.isEmpty()) { | ||
return; | ||
} | ||
|
||
final Integer[] currentTreeLevels = findAllTreeIndices(levelTracker); | ||
ArrayList<Integer> rootNodeIndices = getRootNodeIndices(currentTreeLevels); | ||
|
||
int rootNodeIndexForHeapify = rootNodeIndices.getLast(); | ||
int treeLevelforHeapify = currentTreeLevels[currentTreeLevels.length - 1]; | ||
|
||
for (int i = 1; i < rootNodeIndices.size(); i++) { | ||
|
||
int currentRootNodeIndex = rootNodeIndices.get(i); | ||
int prevRootNodeIndex = rootNodeIndices.get(i - 1); | ||
int j = i; | ||
while (compareRoots(currentRootNodeIndex, prevRootNodeIndex)) { | ||
final int currentLevel = currentTreeLevels[j]; | ||
boolean swapRequired = compareChildren(currentRootNodeIndex, prevRootNodeIndex, currentLevel); | ||
if (swapRequired) { | ||
// compare child and swap | ||
swap(prevRootNodeIndex, currentRootNodeIndex); | ||
rootNodeIndexForHeapify = prevRootNodeIndex; | ||
treeLevelforHeapify = currentTreeLevels[j - 1]; | ||
} else { | ||
maxHeapifyTree(currentRootNodeIndex, currentLevel); | ||
} | ||
--j; | ||
|
||
if (j == 0) { | ||
// We arrived at the left most tree. Do a maxheapifyTree if a swap had occurred | ||
if (swapRequired) { | ||
maxHeapifyTree(rootNodeIndexForHeapify, treeLevelforHeapify); | ||
} | ||
break; | ||
} | ||
currentRootNodeIndex = rootNodeIndices.get(j); | ||
prevRootNodeIndex = rootNodeIndices.get(j - 1); | ||
} | ||
} | ||
|
||
maxHeapifyTree(rootNodeIndexForHeapify, treeLevelforHeapify); // In case of insert and no swap. | ||
} | ||
|
||
private int getRightMostTree() { | ||
// Isolate the rightmost set bit | ||
int isolatedBit = levelTracker & -levelTracker; | ||
int position = 0; | ||
|
||
while (isolatedBit > 1) { | ||
isolatedBit >>= 1; | ||
position++; | ||
} | ||
|
||
return position; | ||
} | ||
|
||
private void swap(int i, int j) { | ||
Collections.swap(heap, i, j); | ||
} | ||
|
||
public void addElement(T element) { | ||
increaseLevelTracker(); | ||
heap.add(element); | ||
shiftRootAndRestoreHeap(); | ||
} | ||
|
||
public T removeElement() { | ||
decreaseLevelTracker(); | ||
final T element = heap.removeLast(); | ||
shiftRootAndRestoreHeap(); | ||
|
||
return element; | ||
} | ||
|
||
private static List<Integer> findConsecutiveTreeIndices(int num) { | ||
int prevOneIndex = -1; | ||
int currentLevel = 0; | ||
|
||
List<Integer> answer = new ArrayList<>(); | ||
answer.add(-1); | ||
answer.add(-1); | ||
|
||
for (int i = 0; num > 0; i++) { | ||
currentLevel = num & 1; | ||
if (currentLevel == 1) { | ||
if (prevOneIndex != -1) { | ||
answer.set(0, prevOneIndex); | ||
answer.set(1, i); | ||
} | ||
prevOneIndex = i; | ||
} else { | ||
prevOneIndex = -1; | ||
} | ||
num >>>= 1; | ||
} | ||
return answer; | ||
} | ||
|
||
private static Integer[] findAllTreeIndices(int num) { | ||
List<Integer> setBitIndexes = new ArrayList<>(); | ||
for (int i = Integer.SIZE - 1; i >= 0; i--) { | ||
if ((num & (1 << i)) != 0) { | ||
setBitIndexes.add(i); | ||
} | ||
} | ||
return setBitIndexes.toArray(new Integer[0]); | ||
} | ||
|
||
private boolean compareChildren(int currentRootNodeIndex, int prevRootNodeIndex, int currentLevel) { | ||
if (currentLevel <= 1) { | ||
// if there are no children to compare (L1 or L0 tree) return true | ||
// because we already know that element at prevRootNodeIndex is greater than element at currentRootNodeIndex | ||
// so a swap will be needed | ||
return true; | ||
} | ||
final int rightChildIndex = currentRootNodeIndex - 1; | ||
final int leftChildIndex = currentRootNodeIndex - 1 - LeonardoNumber.leonardoNumber(currentLevel - 2); | ||
return heap.get(prevRootNodeIndex).compareTo(heap.get(rightChildIndex)) > 0 && heap.get(prevRootNodeIndex).compareTo(heap.get(leftChildIndex)) > 0; | ||
} | ||
|
||
private boolean compareRoots(int currentRootNodeIndex, int prevRootNodeIndex) { | ||
return heap.get(prevRootNodeIndex).compareTo(heap.get(currentRootNodeIndex)) > 0; | ||
} | ||
|
||
private ArrayList<Integer> getRootNodeIndices(Integer[] currentTreeLevels) { | ||
int previousTreeSizeCumulative = 0; | ||
ArrayList<Integer> rootNodeIndices = new ArrayList<>(); | ||
for (int i = 0; i < currentTreeLevels.length; i++) { | ||
rootNodeIndices.add(previousTreeSizeCumulative + LeonardoNumber.leonardoNumber(currentTreeLevels[i]) - 1); | ||
previousTreeSizeCumulative = previousTreeSizeCumulative + LeonardoNumber.leonardoNumber(currentTreeLevels[i]); | ||
} | ||
return rootNodeIndices; | ||
} | ||
} |
vil02 marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package com.thealgorithms.sorts; | ||
|
||
import com.thealgorithms.datastructures.heaps.LeonardoHeap; | ||
|
||
/** | ||
* Wikipedia: https://en.wikipedia.org/wiki/Smoothsort | ||
*/ | ||
public final class SmoothSort implements SortAlgorithm { | ||
|
||
public SmoothSort() { | ||
} | ||
|
||
private static <T extends Comparable<T>> void smoothSort(T[] array) { | ||
LeonardoHeap<T> leonardoHeap = new LeonardoHeap<T>(); | ||
|
||
for (final var element : array) { | ||
leonardoHeap.addElement(element); | ||
} | ||
|
||
for (int i = 0; i < array.length; i++) { | ||
array[array.length - i - 1] = leonardoHeap.removeElement(); | ||
} | ||
} | ||
|
||
@Override | ||
public <T extends Comparable<T>> T[] sort(T[] array) { | ||
smoothSort(array); | ||
return array; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
package com.thealgorithms.datastructures.heaps; | ||
|
||
import static org.junit.jupiter.api.Assertions.assertEquals; | ||
|
||
import org.junit.jupiter.api.Test; | ||
|
||
public class LeonardoHeapTest { | ||
|
||
@Test | ||
public void testAddElement() { | ||
LeonardoHeap<Integer> heap = new LeonardoHeap<>(); | ||
heap.addElement(5); | ||
heap.addElement(3); | ||
heap.addElement(8); | ||
|
||
assertEquals(8, heap.removeElement()); // Max element should be 8 | ||
assertEquals(5, heap.removeElement()); | ||
assertEquals(3, heap.removeElement()); | ||
} | ||
|
||
@Test | ||
public void testRemoveElement() { | ||
LeonardoHeap<Integer> heap = new LeonardoHeap<>(); | ||
heap.addElement(10); | ||
heap.addElement(20); | ||
heap.addElement(5); | ||
|
||
assertEquals(20, heap.removeElement()); | ||
assertEquals(10, heap.removeElement()); | ||
assertEquals(5, heap.removeElement()); | ||
} | ||
|
||
@Test | ||
public void testAddElementStrings() { | ||
LeonardoHeap<String> heap = new LeonardoHeap<>(); | ||
heap.addElement("z"); | ||
heap.addElement("a"); | ||
heap.addElement("x"); | ||
heap.addElement("b"); | ||
heap.addElement("y"); | ||
|
||
assertEquals("z", heap.removeElement()); // Max element should be z | ||
assertEquals("y", heap.removeElement()); | ||
assertEquals("x", heap.removeElement()); | ||
assertEquals("b", heap.removeElement()); | ||
assertEquals("a", heap.removeElement()); | ||
} | ||
|
||
@Test | ||
public void testRemoveElementString() { | ||
LeonardoHeap<String> heap = new LeonardoHeap<>(); | ||
heap.addElement("z"); | ||
heap.addElement("a"); | ||
heap.addElement("x"); | ||
|
||
assertEquals("z", heap.removeElement()); | ||
assertEquals("x", heap.removeElement()); | ||
assertEquals("a", heap.removeElement()); | ||
} | ||
|
||
@Test | ||
public void testAlwaysCurrentMaxElementIsRemoved() { | ||
LeonardoHeap<Integer> heap = new LeonardoHeap<>(); | ||
heap.addElement(5); | ||
heap.addElement(8); | ||
heap.addElement(7); | ||
heap.addElement(3); | ||
|
||
heap.addElement(4); | ||
heap.addElement(4); | ||
heap.addElement(4); | ||
heap.addElement(6); | ||
|
||
heap.addElement(8); | ||
heap.addElement(8); | ||
|
||
assertEquals(8, heap.removeElement()); | ||
assertEquals(8, heap.removeElement()); | ||
assertEquals(8, heap.removeElement()); | ||
assertEquals(7, heap.removeElement()); | ||
|
||
assertEquals(6, heap.removeElement()); | ||
assertEquals(5, heap.removeElement()); | ||
assertEquals(4, heap.removeElement()); | ||
assertEquals(4, heap.removeElement()); | ||
|
||
assertEquals(4, heap.removeElement()); | ||
assertEquals(3, heap.removeElement()); | ||
} | ||
|
||
@Test | ||
public void testForCompareChildAndSwap() { | ||
LeonardoHeap<Integer> heap = new LeonardoHeap<>(); | ||
Integer[] elements = {5, 33, 40, 28, 95, 29, 88, 94, 12, 84, 15, 33, 2, 52, 37, 62, 48, 13, 61, 59}; | ||
|
||
for (Integer element : elements) { | ||
heap.addElement(element); | ||
} | ||
|
||
assertEquals(95, heap.removeElement()); | ||
assertEquals(94, heap.removeElement()); | ||
assertEquals(88, heap.removeElement()); | ||
assertEquals(84, heap.removeElement()); | ||
assertEquals(62, heap.removeElement()); | ||
assertEquals(61, heap.removeElement()); | ||
assertEquals(59, heap.removeElement()); | ||
assertEquals(52, heap.removeElement()); | ||
assertEquals(48, heap.removeElement()); | ||
assertEquals(40, heap.removeElement()); | ||
assertEquals(37, heap.removeElement()); | ||
assertEquals(33, heap.removeElement()); | ||
assertEquals(33, heap.removeElement()); | ||
assertEquals(29, heap.removeElement()); | ||
assertEquals(28, heap.removeElement()); | ||
assertEquals(15, heap.removeElement()); | ||
assertEquals(13, heap.removeElement()); | ||
assertEquals(12, heap.removeElement()); | ||
assertEquals(5, heap.removeElement()); | ||
assertEquals(2, heap.removeElement()); | ||
} | ||
Comment on lines
+9
to
+120
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added some more The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @vil02 , I had a public heap size function earlier which was being used to check if the heap is empty (or heap size is 0). It was removed in one of the previous conversations. I can explicitly add an isEmpty() function instead of the heap size one. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @JAPNITSINGH what can I say: it is my bad. Sorry for that. But honestly: I think having a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Alright, I'll make the changes in a couple of days and update here. |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package com.thealgorithms.sorts; | ||
|
||
public class SmoothSortTest extends SortingAlgorithmTest { | ||
@Override | ||
SortAlgorithm getSortAlgorithm() { | ||
return new SmoothSort(); | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.