javaalgorithmsortingtime-complexitymedian-of-medians

Modifying kth Element Found in Median of Median Quicksort


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.


Solution

  • //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