Skip to content

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

Closed
Closed
Show file tree
Hide file tree
Changes from 16 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
227 changes: 227 additions & 0 deletions src/main/java/com/thealgorithms/sorts/SmoothSort.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
package com.thealgorithms.sorts;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;

/**
* Wikipedia: https://en.wikipedia.org/wiki/Smoothsort
*/
public final class SmoothSort implements SortAlgorithm {

public SmoothSort() {
}

private static Integer[] getLeonardoNumbers() {
Integer[] leonardoNumbers = {1, 1, 3, 5, 9, 15, 25, 41, 67, 109, 177, 287, 465, 753, 1219, 1973, 3193, 5167, 8361, 13529, 21891, 35421, 57313, 92735, 150049, 242785, 392835, 635621, 1028457, 1664079, 2692537, 4356617, 7049155, 1405773, 18454929, 29860703, 48315633, 78176337, 126491971,
204668309, 331160281, 535828591};

return leonardoNumbers;
}

private static <T extends Comparable<T>> void smoothSort(T[] array) {
int length = array.length;
int leonardoHeapSize = 0; // start with size 0
int leonardoLevelTracker = 0; // No leonardo tree present initially

while (leonardoHeapSize < length) {
// if two trees with consequtive level
// combine them to get new tree
// else if there is no Level 1, add the node as level 1
// else add the node as Level 0
// perform shiftRoot and restore heap property

Integer[] consecutiveTreeIndices = findConsecutiveLeonardoTreeIndices(leonardoLevelTracker);
if (consecutiveTreeIndices[0] != -1) {
// if 0th or 1st index is -1 that implies there are no concequtive trees
leonardoLevelTracker = leonardoLevelTracker & ~(1 << consecutiveTreeIndices[0]);
leonardoLevelTracker = leonardoLevelTracker & ~(1 << consecutiveTreeIndices[1]);
leonardoLevelTracker = leonardoLevelTracker | (1 << consecutiveTreeIndices[1] + 1);
} else if ((leonardoLevelTracker & 2) == 0) {
leonardoLevelTracker = leonardoLevelTracker | (1 << 1);
} else {
leonardoLevelTracker = leonardoLevelTracker | (1 << 0);
}
leonardoHeapSize++;
shiftRootAndRestoreHeap(leonardoLevelTracker, leonardoHeapSize, array);
}

while (leonardoHeapSize > 0) {
// destroy the current level
// if level is not L1 or L0
// create two smaller sublevels
// perform shiftRoot and restore heap property

int lastTreeLevel = getRightMostTree(leonardoLevelTracker); // getting the right most tree

leonardoLevelTracker = leonardoLevelTracker & ~(1 << lastTreeLevel);
if (lastTreeLevel != 0 && lastTreeLevel != 1) {
leonardoLevelTracker = leonardoLevelTracker | (1 << lastTreeLevel - 1);
leonardoLevelTracker = leonardoLevelTracker | (1 << lastTreeLevel - 2);
}

leonardoHeapSize--;
shiftRootAndRestoreHeap(leonardoLevelTracker, leonardoHeapSize, array);
}
}

private static int getRightMostTree(int leonardoLevelTracker) {
// Isolate the rightmost set bit
int isolatedBit = leonardoLevelTracker & -leonardoLevelTracker;
int position = 0;

while (isolatedBit > 1) {
isolatedBit >>= 1;
position++;
}

return position;
}

private static Integer[] findConsecutiveLeonardoTreeIndices(int num) {
int prevOneIndex = -1;
int currentLevel;

Integer[] answer = new Integer[] {-1, -1};
for (int i = 0; num > 0; i++) {
currentLevel = num & 1;
if (currentLevel == 1) {
if (prevOneIndex != -1) {
answer[0] = prevOneIndex;
answer[1] = i;
}
prevOneIndex = i;
} else {
prevOneIndex = -1;
}
num >>>= 1;
}
return answer;
}

private static Integer[] findAllLeonardoTreeIndices(int num) {
int setBitCount = 0;
for (int i = 0; i < Integer.SIZE; i++) {
if ((num & (1 << i)) != 0) {
setBitCount++;
}
}

Integer[] setBitIndexes = new Integer[setBitCount];
int index = 0;
for (int i = 0; i < Integer.SIZE; i++) {
if ((num & (1 << i)) != 0) {
setBitIndexes[index++] = i;
}
}
return setBitIndexes;
}

private static <T extends Comparable<T>> void shiftRootAndRestoreHeap(int lenardoLevelTracker, int leonardoHeapSize, T[] array) {

if (leonardoHeapSize == 0) {
return;
}

Integer[] currentLeonardoTreeLevels = findAllLeonardoTreeIndices(lenardoLevelTracker);
int previousTreeSizeCumulative = 0;
ArrayList<Integer> rootNodeIndices = new ArrayList<Integer>();
Collections.reverse(Arrays.asList(currentLeonardoTreeLevels)); // To get the Levels in decreasing order of levels

// The number of roots are going to be same the the number of levels
// iterate over the currentLeonardoTreeLevels and get roots

for (int i = 0; i < currentLeonardoTreeLevels.length; i++) {
rootNodeIndices.add(previousTreeSizeCumulative + getLeonardoNumbers()[currentLeonardoTreeLevels[i]] - 1);
previousTreeSizeCumulative = previousTreeSizeCumulative + getLeonardoNumbers()[currentLeonardoTreeLevels[i]];
}

int rootNodeIndexForHeapify = rootNodeIndices.getLast();
int leonardoTreeLevelforHeapify = currentLeonardoTreeLevels[currentLeonardoTreeLevels.length - 1];

for (int i = 0; i < rootNodeIndices.size(); i++) {
if (i == 0) {
continue;
}

int currentRootNodeIndex = rootNodeIndices.get(i);
int prevRootNodeIndex = rootNodeIndices.get(i - 1);
int j = i;
while (array[prevRootNodeIndex].compareTo(array[currentRootNodeIndex]) > 0) {
int currentLeonardoLevel = currentLeonardoTreeLevels[j];
if (currentLeonardoLevel > 1) {
// compare child and swap

int indexOfRightChild = rootNodeIndices.get(j) - 1; // right child is of level n-2
int indexOfLeftChild = rootNodeIndices.get(j) - 1 - getLeonardoNumbers()[currentLeonardoLevel - 2];
if (array[prevRootNodeIndex].compareTo(array[indexOfRightChild]) > 0 && array[prevRootNodeIndex].compareTo(array[indexOfLeftChild]) > 0) {
Copy link
Member

Choose a reason for hiding this comment

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

As far as I see there is no test with array[prevRootNodeIndex].compareTo(array[indexOfRightChild]) > 0 evaluating to true and array[prevRootNodeIndex].compareTo(array[indexOfLeftChild]) > 0 to false.

To ensure that the logic is correct, please add a missing test.

Copy link
Author

Choose a reason for hiding this comment

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

Sure, I shall add a specific test case in SmoothSortTest.java.

Copy link
Author

@JAPNITSINGH JAPNITSINGH Jun 25, 2024

Choose a reason for hiding this comment

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

I see why this is happening and I shall try my best to explain in this comment.

Leonardo Heap has the following three properties that define it (Going through the array from left to right):

  1. The trees are in decreasing order of levels
  2. Root nodes of the trees are in increasing order
  3. Each tree is in a 'max-heap' state.

The Level-1 leonardo Tree(L1) and Level 0 leonardo Tree (L0) are the 'base case' trees as they have exactly one node.

At any given valid leonardo heap, if the heap contains both L1 and L0 tree then the element in L0 tree will always be greater than element at L1 tree, because of the second property mentioned above ( L0 >= L1 )

When L2 tree is being constructed, the level0 tree becomes the right child level1 tree becomes the left child. And hence it is not possible to have a condition such that level0 < prevRootNodeVal < level1 as it would be a voilation of the above.

This section of the wiki page and this article (under the imageAn erroneous swap ) mention that root needs to be bigger than the new element and the roots of its child nodes for a swap to happen. However this article(in page 6) mentions that the preRootValue should exceede its largest son.

I believe the statement in wiki is more genreic statement than a technical one and this condition can be optimized to just having array[prevRootNodeIndex].compareTo(array[indexOfLeftChild]) > 0. I shall however look around for more articles that suggest otherwise before I make changes to this.

swap(array, prevRootNodeIndex, currentRootNodeIndex);
rootNodeIndexForHeapify = prevRootNodeIndex;
leonardoTreeLevelforHeapify = currentLeonardoTreeLevels[j - 1];
} else {
maxHeapifyLeonardoTree(currentRootNodeIndex, currentLeonardoLevel, array);
}
} else {
// swap
swap(array, prevRootNodeIndex, currentRootNodeIndex);
rootNodeIndexForHeapify = prevRootNodeIndex;
leonardoTreeLevelforHeapify = currentLeonardoTreeLevels[j - 1];
}
j = j - 1;
if (j == i - 1) {
Copy link
Member

Choose a reason for hiding this comment

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

j == i - 1 is never false (among the existing test cases).

Copy link
Author

@JAPNITSINGH JAPNITSINGH Jun 25, 2024

Choose a reason for hiding this comment

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

I do have a test case (not currently committed) with around 900 entries where this evalueates to false during creation of the heap. I can try to reduce the entires in such a way that the condition still evaluates to true, I will for sure end up having an array of size 20-30 ( as i will need 3 trees of non consecutive levels .... L1 L3 and L5 at least ).

Would it be fine if I added a test case in SmoothSortTest.java with around 20-30 elements in the test? If yes, I shall work on it at the very end after resolving the other conversations.

Copy link
Author

@JAPNITSINGH JAPNITSINGH Jun 25, 2024

Choose a reason for hiding this comment

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

Update: Yes, this condition is never false. I belive what I intented to do was run maxHeapifyLeonardoTree if you reached the left most tree and further swaps are still present.

I am checking this as well.

maxHeapifyLeonardoTree(rootNodeIndexForHeapify, leonardoTreeLevelforHeapify, array);
}
}
}

maxHeapifyLeonardoTree(rootNodeIndexForHeapify, leonardoTreeLevelforHeapify, array); // for the last tree if needed
}

private static <T> void swap(T[] array, int idx, int idy) {
T swap = array[idx];
array[idx] = array[idy];
array[idy] = swap;
}

private static <T extends Comparable<T>> void maxHeapifyLeonardoTree(int rootNodeIndex, int currentLeonardoLevel, T[] array) {
// 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 currentLeonardoLevel

// BASE CASE
if (currentLeonardoLevel == 0 || currentLeonardoLevel == 1) {
return; // Trees with one node are in already max-heapified.
}

int currentRootNodeIndex = rootNodeIndex;
int rightChildIndex = rootNodeIndex - 1;
int leftChildIndex = rootNodeIndex - getLeonardoNumbers()[currentLeonardoLevel - 2] - 1;
int childIndexForSwap = -1;

// maxHeapifyLeonardoTree(rightChildIndex, currentLeonardoLevel - 2, array);
// maxHeapifyLeonardoTree(leftChildIndex, currentLeonardoLevel - 1, array);

if (array[rightChildIndex].compareTo(array[leftChildIndex]) >= 0) {
childIndexForSwap = rightChildIndex;
} else {
childIndexForSwap = leftChildIndex;
}

if (array[childIndexForSwap].compareTo(array[currentRootNodeIndex]) > 0) {
// swap(And keep on swapping I guess, I did not implement that which might be causing issue?)
swap(array, currentRootNodeIndex, childIndexForSwap);
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
swap(array, currentRootNodeIndex, childIndexForSwap);
SortUtils.swap(array, currentRootNodeIndex, childIndexForSwap);

if (childIndexForSwap == rightChildIndex) {
maxHeapifyLeonardoTree(rightChildIndex, currentLeonardoLevel - 2, array);
} else { // swap happened with the left child
maxHeapifyLeonardoTree(leftChildIndex, currentLeonardoLevel - 1, array);
}
}
}

@Override
public <T extends Comparable<T>> T[] sort(T[] unsorted) {
// TODO Auto-generated method stub
smoothSort(unsorted);
return unsorted;
}
}
8 changes: 8 additions & 0 deletions src/test/java/com/thealgorithms/sorts/SmoothSortTest.java
Copy link
Member

Choose a reason for hiding this comment

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

If the answer to the above questions is yes, then if should be enough to derive the SmoothSortTest from SortingAlgorithmTest.

Copy link
Author

Choose a reason for hiding this comment

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

I believe it is possible, I just have to use generic T[] in place of Integer[] modify the code a little bit and use .compareTo() and it should be good. I shall work on it and update this PR. I'll comment here once changes are ready.

Copy link
Author

@JAPNITSINGH JAPNITSINGH Jun 22, 2024

Choose a reason for hiding this comment

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

I have updated the code, haven't pushed it yet. The smooth sort algorithm I implemented seems to be failing for most arrays of size greater than 700. I guess the problem is with how I am calculating the left child of a node. I am having a look into it. I shall provide an update here once I am done with the fix

Copy link
Author

Choose a reason for hiding this comment

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

I have updated to code, derived the class from SortAlgorithm, removed unnecessary return statements , renamed variables to make the code more understandable.

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();
}
}