package com.interview.algorithms.array;
import com.interview.utils.ConsoleReader;
/**
* This implements the "Median of median" selection algorithm to find the Kth small element in an array.
* The time complexity is O(n). Details can be found in page 112 of Introduction to Algorithms book 2nd edition
* .
* @author zouzhile
*
*/
public class C4_8_KthElement_MedianOfMedian {
private static final int GROUP_SIZE = 5;
private static C4_8_KthElement_MedianOfMedian SELECTOR = new C4_8_KthElement_MedianOfMedian();
/**
* Return the offset of the Kth smallest element in the sub array of "data" bounded by begin and end.
* The data array is changed by element switching during selection.
* @param data
* @param begin
* @param end
* @param K
* @return
*/
public int select(int[] data, int begin, int end, int K){
if(end - begin == 0)
return begin;
else if ((end - begin) == 1 && K == 1)
return begin;
else if ((end - begin) == 1 && K == 2)
return end;
int selectionOffset = begin;
int medianGroupAmount = (end - begin + 1) % GROUP_SIZE == 0 ? (end - begin + 1)/GROUP_SIZE : (end - begin + 1)/GROUP_SIZE + 1;
int[] medians = new int[medianGroupAmount];
int count = 0;
// find the median of all groups
for(int groupBeginIndex = begin; groupBeginIndex<= end; groupBeginIndex += GROUP_SIZE){
int groupEndIndex = end - groupBeginIndex >= GROUP_SIZE ? groupBeginIndex + GROUP_SIZE - 1 : end;
medians[count] = this.getGroupMedian(data, groupBeginIndex, groupEndIndex);
count ++;
}
// find the median of medians
int groupOfMedianOfMedians = this.select(medians, 0, medians.length - 1, medians.length/2);
int offsetMedianOfMedians = groupOfMedianOfMedians * GROUP_SIZE + 2;
for(int i = groupOfMedianOfMedians * GROUP_SIZE; i< groupOfMedianOfMedians * GROUP_SIZE + 5; i ++)
if(data[i] == medians[groupOfMedianOfMedians]) {
offsetMedianOfMedians = i;
break;
}
int offsetMedianOfMediansAfterPartition = this.partition(data, begin, end, offsetMedianOfMedians);
int KthSmallestElementOffset = begin + K - 1;
if(offsetMedianOfMediansAfterPartition == KthSmallestElementOffset)
selectionOffset = offsetMedianOfMediansAfterPartition;
else if (offsetMedianOfMediansAfterPartition < KthSmallestElementOffset)
selectionOffset = this.select(data, offsetMedianOfMediansAfterPartition + 1, end, K - offsetMedianOfMediansAfterPartition - 1);
else
selectionOffset = this.select(data, begin, offsetMedianOfMediansAfterPartition - 1, K);
return selectionOffset;
}
private int partition(int[] data, int begin, int end, int pivotOffset){
int value = data[pivotOffset];
this.switchElements(data, pivotOffset, end);
int i = begin - 1;
for(int j = begin; j < end; j++){
if(data[j] <= value) {
i++;
this.switchElements(data, i, j);
}
}
this.switchElements(data, i + 1, end);
return i + 1;
}
// DO NOT change the elements of data by switching elements.
private int getGroupMedian(int[] data, int groupBeginIndex, int groupEndIndex) {
int groupSize = groupEndIndex - groupBeginIndex + 1;
int groupMedianIndex = groupBeginIndex;
if(groupSize >= 3) { // group size is greater or equal than 3
int[] group = new int[groupSize];
int[] groupOffsets = new int[groupSize];
for(int i = groupBeginIndex; i<= groupEndIndex; i ++) {
group[i - groupBeginIndex] = data[i];
groupOffsets[i - groupBeginIndex] = i;
}
for(int i = 0 ; i< groupEndIndex - groupBeginIndex; i ++){
for(int j = i + 1; j <= groupEndIndex - groupBeginIndex; j ++) {
if(group[i] > group[j]) {
this.switchElements(group, i, j);
this.switchElements(groupOffsets, i, j);
}
}
}
groupMedianIndex = groupOffsets[(groupEndIndex - groupBeginIndex)/2]; // return low median index if the size of data is even
}
return data[groupMedianIndex];
}
private void switchElements(int[] data, int i, int j){
int temp = data[i];
data[i] = data[j];
data[j] = temp;
}
public static int select(int[] array, int k){
int offset = SELECTOR.select(array, 0, array.length - 1, k);
return array[offset];
}
/**
* @param args
*/
public static void main(String[] args) {
C4_8_KthElement_MedianOfMedian selector = new C4_8_KthElement_MedianOfMedian();
ConsoleReader reader = new ConsoleReader();
//int[] data = new int[]{ 2, 3, 5, 1, 9, 10, -22};
System.out.print("Please input array elements: ");
int[] data = reader.readIntItems();
System.out.print("Please input K: ");
int K = reader.readInt();
int offset = selector.select(data, 0, data.length - 1, K);
System.out.println("The Kth element: " + data[offset]);
}
}