0

Note: This question was posted yesterday, late at night, and did not receive a sufficient answer. I've added some detail and reposted.


As an assignment, I've been tasked with creating a median of medians sort, that can also determine the nth smallest element of an array (ie index 0 of the sorted array is the 1st smallest). This sort is recursive, which is causing my issues understanding it as I have not worked with recursion extensively.

I've gotten extremely close to successfully creating the method through cobbling together tutorials and my studying of the algorithm; however, I'm currently returning the wrong value. An input of QS(A, 1) will return the value contained at index 1, when I want it to return the value at index 0:

public static int QS(int[] A, int k){

    List<Integer> AList = new ArrayList<Integer>();

    if(k < 1 || k >= A.length){
        System.out.println("Code called because k = "+k);
        return -1;
    }

    for (int i = 0; i < A.length; i++){
            AList.add(A[i]);
    }

    if (AList.size() < 14) {
        Collections.sort(AList);
        return AList.get(k);
    }

    ArrayList<Integer> medians = new ArrayList<Integer>();

    for (int i = 0; i < AList.size() - AList.size() % 7; i = i + 7){
        medians.add(medianFind(AList.subList(i, i + 7)));
    }

    int a = medianFind(medians);
    ArrayList<Integer> left = partitionFind(AList, a, true);
    ArrayList<Integer> right = partitionFind(AList, a, false);
    int[] leftArray = new int[left.size()];
    int[] rightArray = new int[right.size()];

    for(int i = 0; i < left.size(); i++){
        leftArray[i] = left.get(i);
    }

    for(int i = 0; i < right.size(); i++){
        rightArray[i] = right.get(i);
    }   

    if(left.size() + 1 == k){
        return a;
    }
    else if(left.size() > k){
        return QS(leftArray, k);
    }
    else{
        return QS(rightArray, k - left.size());
    }

}

/////////

public static int medianFind(List<Integer> AList) {
    Collections.sort(AList);
    return AList.get(AList.size() / 2);
}

/////////

public static ArrayList<Integer> partitionFind(List<Integer> AList, int b, boolean lessThan) {
    ArrayList<Integer> store = new ArrayList<Integer>();
    for (int element : AList)
        if (element < b && lessThan)
                store.add(element);
        else if (element >= b && !lessThan)
                store.add(element);
    return store;
}

I'm having a very hard time wrapping my head around the recursion in this algorithm in order to modify the index of the final return by -1. I can't simply make the following change:

    if (AList.size() < 14) {     //old 
        Collections.sort(AList);
        return AList.get(k);
    }

    if (AList.size() < 14) {     //new 
        Collections.sort(AList);
        return AList.get(k-1);
    }

As this sometimes results in return QS(rightArray, k - left.size()) being passed a parameter less than 1. I also don't think I can add an:

if(nothing left to sort){
    return AList.get(k-1) 
}

Statement due to the recursive nature of the algorithm. I've been at this for quite awhile with no success, and would greatly appreciate any indication of where I should be going with this problem.

MMMMMCK
  • 307
  • 3
  • 14
  • That could mean a couple of different things. It could mean that `k - left.size() == 0 ` when being called recursively in the else return statement. It could also, however, simply mean that the method has been asked to find the 0th smallest element of the array when initially called, which is illegal and should return -1. – MMMMMCK Oct 04 '17 at 21:45
  • if(k < 1 || k >= A.length){ System.out.println("Code called because k = "+k); return -1; } wouldn't you want to return `A[A.length-1]` in the case where `k=A.length`? – mm8511 Oct 04 '17 at 21:54
  • I'm not totally sure I follow. I do want to return `A[A.length-1]` eventually, but if `k == A.length` that just means it's looking 1 degree past the final element of the array. Why are those two concepts connected? – MMMMMCK Oct 04 '17 at 22:13

1 Answers1

1
//returns the k^th smallest element in the array A for 0<k<A.length  
public static int QuickSelect(int[] A, int k){

        List<Integer> AList = new ArrayList<Integer>();

        if(A.length<k||k<1){
            System.out.println("k out of range, got k: "+k +", but A.length: "+A.length);
            return -1;
        }

        for (int i = 0; i < A.length; i++){
                AList.add(A[i]);
        }

At this point we know that AList.size()>0, so get element k-1, which is the kth smallest element in the list

        if (AList.size() < 14) {
            Collections.sort(AList);
            return AList.get(k-1);
        }

        ArrayList<Integer> medians = new ArrayList<Integer>();


        for (int i = 0; i < AList.size() - AList.size() % 7; i = i + 7){
            medians.add(medianFind(AList.subList(i, i + 7)));
        }

***You forgot to find median of last AList.size()-( AList.size() % 7) elements

      int a = medianFind(medians);
            ArrayList<Integer> left = partitionFind(AList, a, true);
            ArrayList<Integer> right = partitionFind(AList, a, false);

added med variable to count number of occurrences of median

            int med=AList.size()-(left.size()+right.size())
            int[] leftArray = new int[left.size()];
            int[] rightArray = new int[right.size()];

        for(int i = 0; i < left.size(); i++){
            leftArray[i] = left.get(i);
        }

        for(int i = 0; i < right.size(); i++){
            rightArray[i] = right.get(i);
        }   

Not totally sure about the logic in the rest of this, but I think the following makes more sense (note that I modified PartitionFind)

        if(left.size() >= k){
            return QuickSelect(leftArray, k);
        }
        else if(left.size()+med<k){
            return QuickSelect(rightArray, k-(left.size()+med));
        }
        else{
            return a;
        }

    }

    /////////

    public static int medianFind(List<Integer> AList) {
        Collections.sort(AList);
        return AList.get(AList.size() / 2);
    }

    /////////

Note: I modified this so that the subproblem becomes strictly smaller (o.w. you could have b as your largest element in the list for example, and right array would still contain the entire list)

    public static ArrayList<Integer> partitionFind(List<Integer> AList, int b, boolean lessThan) {
        ArrayList<Integer> store = new ArrayList<Integer>();
        for (int element : AList)
            if (element < b && lessThan)
                    store.add(element);
            else if (element > b && !lessThan)
                    store.add(element);
        return store;
    }

let me know if this works

mm8511
  • 261
  • 2
  • 6
  • Hi mm8511, thank you very much for such a thorough and well-documented answer, I didn't think to make a special case for `k == 0` or to track with a `med` variable and it's made a world of difference. I'm still cleaning the code up and testing out datasets but so far there have been no issues, so I'm going to accept this as solved. Thanks again for such a great response! – MMMMMCK Oct 04 '17 at 23:46
  • @MMMMMCK no problem. Glad it helped. – mm8511 Oct 05 '17 at 15:41