Skip to content
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

Open
wants to merge 45 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
9e5cb32
Adding Skeleton
JAPNITSINGH May 28, 2024
9ff4e85
Understanding Heap Sort
JAPNITSINGH May 29, 2024
69b24a8
Implement merge sort
JAPNITSINGH May 30, 2024
6de4059
Smooth Sort impl, needs cleaning.
JAPNITSINGH Jun 14, 2024
d6a14b6
Added test cases, improved code coverage.
JAPNITSINGH Jun 17, 2024
384f814
Refactorization of code
JAPNITSINGH Jun 18, 2024
29252e1
Merge branch 'TheAlgorithms:master' into Branch_Smooth_Sort
JAPNITSINGH Jun 18, 2024
4f0fd70
Fixed brackets and ran mvn checkstyle:checkstyle to ensure no more ch…
JAPNITSINGH Jun 18, 2024
0cdcba2
Fixed a tab space issue
JAPNITSINGH Jun 18, 2024
d32e435
Removed useless parenthesis.
JAPNITSINGH Jun 18, 2024
712868e
Re work, fix bugs, names etc.
JAPNITSINGH Jun 24, 2024
368db8a
Merge branch 'TheAlgorithms:master' into Branch_Smooth_Sort
JAPNITSINGH Jun 24, 2024
5f9d2e7
Merge branch 'Branch_Smooth_Sort' of github.com:JAPNITSINGH/Java into…
JAPNITSINGH Jun 24, 2024
990b183
Forgot to add test file
JAPNITSINGH Jun 24, 2024
0eda98e
Improved code coverage yo 99.9%
JAPNITSINGH Jun 24, 2024
aed3358
Removed the break statement to improve code coverage.
JAPNITSINGH Jun 24, 2024
ab905bf
Removed comments and used SortUtils
JAPNITSINGH Jun 25, 2024
65ca159
Merge branch 'TheAlgorithms:master' into Branch_Smooth_Sort
JAPNITSINGH Jun 25, 2024
d5e3b95
Merge branch 'Branch_Smooth_Sort' of github.com:JAPNITSINGH/Java into…
JAPNITSINGH Jun 25, 2024
ec02a6c
Use SingleBitOperations class.
JAPNITSINGH Jun 26, 2024
b19bd38
Conditional changes on when to call max_heapify.
JAPNITSINGH Jun 28, 2024
5d21701
Skeleton of LeonardoHeap.java
JAPNITSINGH Jun 29, 2024
951653f
Leonardo Heap implementeaton , TODO: Test cases
JAPNITSINGH Jul 4, 2024
79a5d20
Slight modifications to code
JAPNITSINGH Jul 5, 2024
9fe137c
Merge branch 'TheAlgorithms:master' into Branch_Smooth_Sort
JAPNITSINGH Jul 5, 2024
7aa1421
Merge branch 'Branch_Smooth_Sort' of github.com:JAPNITSINGH/Java into…
JAPNITSINGH Jul 5, 2024
645b29c
variable name modifications.
JAPNITSINGH Jul 8, 2024
89d9f51
Merge branch 'TheAlgorithms:master' into Branch_Smooth_Sort
JAPNITSINGH Jul 8, 2024
1adbd15
Added LeonardoHeapTest.java
JAPNITSINGH Jul 8, 2024
c249579
Merge branch 'Branch_Smooth_Sort' of github.com:JAPNITSINGH/Java into…
JAPNITSINGH Jul 8, 2024
edbf08a
UMTP_UNBOUND_METHOD_TEMPLATE_PARAMETER mvn spotbug fix
JAPNITSINGH Jul 8, 2024
cf1ce78
Merge branch 'master' into Branch_Smooth_Sort
JAPNITSINGH Jul 12, 2024
369cbeb
Merge branch 'master' into Branch_Smooth_Sort
JAPNITSINGH Jul 16, 2024
01ba460
Rename members and add tests
JAPNITSINGH Jul 18, 2024
fa0797f
pmd errors fix
JAPNITSINGH Jul 18, 2024
ba730d0
spotbug fix
JAPNITSINGH Jul 18, 2024
a8a5835
Added LeonardoHeapHelper.java
JAPNITSINGH Jul 19, 2024
5093f86
Merge branch 'TheAlgorithms:master' into Branch_Smooth_Sort
JAPNITSINGH Jul 19, 2024
761b848
Merge branch 'Branch_Smooth_Sort' of github.com:JAPNITSINGH/Java into…
JAPNITSINGH Jul 19, 2024
37c794b
Removed Helper Class, formatted to make items final.
JAPNITSINGH Jul 22, 2024
c0b8737
Rework on formatting.
JAPNITSINGH Jul 22, 2024
ad4c540
Reworking LeonardoHeap: shiftRootAndRestoreHeap
JAPNITSINGH Jul 24, 2024
0edabcd
Lint issues in LeonardoHeap
JAPNITSINGH Jul 24, 2024
6b4c415
style: use predecrement operator
vil02 Aug 6, 2024
174de3f
tests: add more checks into tests
vil02 Aug 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
vil02 marked this conversation as resolved.
Show resolved Hide resolved
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() {
Copy link
Member

Choose a reason for hiding this comment

The 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?

Copy link
Author

Choose a reason for hiding this comment

The 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.

Copy link
Member

Choose a reason for hiding this comment

The 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;
}
}
30 changes: 30 additions & 0 deletions src/main/java/com/thealgorithms/sorts/SmoothSort.java
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added some more assertEquals here. A test, which does not assert can lead to confusion. Please have a look if it makes sense to parametrize these tests.

The LeonardoHeap misses a method isEmpty(). There is simply no way to know if user cans still removeElement.

Copy link
Author

Choose a reason for hiding this comment

The 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.

Copy link
Member

Choose a reason for hiding this comment

The 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 isEmpty() method makes sense.

Copy link
Author

Choose a reason for hiding this comment

The 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.

}
8 changes: 8 additions & 0 deletions src/test/java/com/thealgorithms/sorts/SmoothSortTest.java
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();
}
}