找最小的第k个数 k min (Selection Algorithm 与 Median of Medians Algorithm)

题目很简单:要求一个算法能在一个长度为n的无序数组中找到第k小的数(k从0开始)

例如:4, 1, 2, 5 序列的第2小的数应该是4. 因为原数列排序后为1,2,4,5 所以第0小的数是1,第1小的数是2,第2小的数是4,第3小的数是5.


解法:

1)排序:

最容易想到的解法就是先把整个数组从小到大排序一遍,然后想要找哪一个数从头开始找即可。这种解法时间复杂度:O(nlogn)


2)多次查找:

第一遍遍历先找到最小的值,并标记为已访问过。第二遍再找剩下没访问过的最小值,即第二小的数。。。这样遍历k轮就能找到第k小的数。时间复杂度:O(n*k)


3)Partition算法:

Partition算法是快速排序QuickSort中的一部分,思想是选定一个值作为pivot,然后通过swap,使得最终pivot左边的数都小于pivot,pivot右边的数都大于pivot。

利用Partition算法,再结合递归,类似二分查找的分治。

即:返回pivot的index正好和k相等,则找到了第k小的数。

如果返回pivot的index小于k,则在pivot的右半段递归查找。

如果返回pivot的index大于k,则在pivot的做半段递归查找。


时间复杂度:平均:O(nlogk) 优于排序解法,但最差时(如:递减数列时)退化成 O(n^2)

具体实现看下面代码。。。


一些写的不错的文章:

http://algorithmsandme.blogspot.com/2013/08/find-kth-smallest-element-application.html


4)Median of Medians 算法:最差:O(n)

1. Divide the array into N/C columns of elements,for small odd C.
2. Find the median of each column by sorting it.
3. Take only the medians and repeat steps 1-2 recursivelyuntil only one value remains. That value is picked as the pivot.
4. Iterate through the array and count number of elementsstrictly smaller than the pivot (S), larger than the pivot (L)and equal to the pivot (E=N-S-L).
5. If K<S, move all values smaller than the pivotto the beginning of the array and recursively runthe whole algorithm on that sub-array.
6. If K<N-L, conclude that Kth element is equalto the current pivot so return the pivot valueand terminate the algorithm.
7. Otherwise, move all values larger than the pivotto the beginning of the array and recursively runthe whole algorithm on that sub-array.


参考这幅图:




一些写的不错的文章:

http://functionspace.org/articles/19

http://cs.indstate.edu/~spitla/abstract2.pdf

http://www.sysexpand.com/?path=exercises/kth-smallest


先附上快排算法:

package K_Min;

import java.util.*;

public class QuickSort{

	/**
     * Partition the array into two halves and return the index
     * of the pivot.
     *
     * @param array the array of values to be partitioned
     * @param start the starting index in the array to partition
     * @param end the ending index in the array to partition
     */
     private static int partition(int array[], int start, int end) {
        // set the pivot to the first element in the array
        int pivot = array[start];

        while (start < end) {
            // look for an element smaller than the pivot from the end
            while (start < end && array[end] >= pivot) {
                end--;
            }
            if (start < end) {  // found a smaller array
                array[start] = array[end];

                // now find an element larger than the pivot from
                // the start
                while (start < end && array[start] <= pivot) {
                    start++;
                }

                if (start < end) {  // found a larger array
                    array[end] = array[start];
                }
            }
        }

        // done, move the pivot back into the array
        array[start] = pivot;
        return start;
    }


    /**
     * Sort an array using quick sort.
     *
     * @param array the array of values to be sorted
     * @param low the start of the sorting region
     * @param high the end of the sorting region
     */
    public static void quickSort(int array[], int low, int high) {
        if (low < high) {
            // partition the array into two halves (in place)
            // and get the index of the pivot (middle)
            int pivot = partition(array, low, high);
            quickSort(array, low, pivot-1);
            quickSort(array, pivot + 1, high);
        }
    }


    /**
     * Main method.
     *
     * @param args command line arguments.
     */
    public static void main(String args[]) {
        
        int arrays[] = {1,0,-1,0,0};

        // Print out the initial array
        System.out.print("Initial: ");
        System.out.println(Arrays.toString(arrays));

        // Quicksort the array
        quickSort(arrays, 0, arrays.length-1);

        // Print out the sorted array
        System.out.print("Sorted: ");
        System.out.println(Arrays.toString(arrays));
    }
}


下面是借用QuickSort中的partition得到的求k最小值:

package K_Min;


public class KMin_SelectionAlgorithm {

	// Average Time Complexity: O(nlogk)
	// Worst Time Complexity: O(n^2)  如:递减数列时
	// 区间为前闭后闭,即 [start, end]
	public static void getKMin(int[] A, int start, int end, int K) {
		if (start <= end) {
			int pivot = partition(A, start, end);
			
			if (pivot == K) {				// pivot位于第K小的位置(K从1开始,即第1小)
				System.out.println(K + "th smallest element :" + A[pivot]);
			} else if (pivot > K) {		// pivot过大,所以在前半段继续搜索(排除pivot位置)
				getKMin(A, start, pivot-1, K);
			} else{								// pivot过小,所以在后半段继续搜索(排除pivot位置)
				getKMin(A, pivot + 1, end, K);
			}
		}
	}
	
	
	/**
     * Partition the array into two halves and return the index
     * of the pivot.
     *
     * @param array the array of values to be partitioned
     * @param start the starting index in the array to partition
     * @param end the ending index in the array to partition
     */
     private static int partition(int array[], int start, int end) {
        // set the pivot to the first element in the array
        int pivot = array[start];

        while (start < end) {
            // look for an element smaller than the pivot from the end
            while (start < end && array[end] >= pivot) {
                end--;
            }
            if (start < end) {  // found a smaller array
                array[start] = array[end];

                // now find an element larger than the pivot from
                // the start
                while (start < end && array[start] <= pivot) {
                    start++;
                }

                if (start < end) {  // found a larger array
                    array[end] = array[start];
                }
            }
        }

        // done, move the pivot back into the array
        array[start] = pivot;
        return start;
    }

	public static void main(String[] args) {
		int[] A = { 4, 2, 1, 7, 5, 3, 8, 10, 9, 6 };
//		int[] A = { 4, 1, 2, 5};
		for (int K = 0; K < A.length; K++) {
			getKMin(A, 0, A.length-1, K);
		}
	}

}


最后是Median of Medians算法:

package K_Min;

public class KMin_MedianOfMedians {

	// Median of Medians Algorithm, Time complexity: O(n)
	public static int findKthSmallest(int[] A, int k) {
		int value = 0;
		int n = A.length;
		int c = 5; 		// Constant used to divide the array into columns

		while (true) {
			// Extract median of medians and take it as the pivot
			int pivot = findPivot(A, n, c);

			// Now count how many smaller and larger elements are there
			int smallerCount = 0;
			int largerCount = 0;

			int[] data = new int[2];

			// CountElements(a, n, pivot, out smallerCount, out largerCount);
			CountElements(A, n, pivot, data);

			smallerCount = data[0];
			largerCount = data[1];

			// Finally, partition the array
			if (k < smallerCount) {
				n = Partition(A, n, pivot, true);
			} else if (k < n - largerCount) {
				value = pivot;
				break;
			} else {
				k -= n - largerCount;
				n = Partition(A, n, pivot, false);
			}
		}
		return value;
	}

	private static int findPivot(int[] A, int n, int c) {
		while (n > 1) {
			int pos = 0;
			int tmp = 0;

			for (int start = 0; start < n; start += c) {
				int end = start + c;
				if (end > n){ 	// Last column may have
					end = n; 	// less than c elements
				}
				
				// Sort the column
				for (int i = start; i < end - 1; i++){
					for (int j = i + 1; j < end; j++){
						if (A[j] < A[i]) {
							tmp = A[i];
							A[i] = A[j];
							A[j] = tmp;
						}
					}
				}

				// Pick the column's median and promote it
				// to the beginning of the array
				end = (start + end) / 2; // Median position
				tmp = A[end];
				A[end] = A[pos];
				A[pos++] = tmp;

			}
			n = pos; // Reduce the array and repeat recursively
		}

		return A[0]; // Last median of medians is the pivot
	}

	// static void CountElements(int[] a, int n, int pivot, out int
	// smallerCount, out int largerCount)
	private static void CountElements(int[] a, int n, int pivot, int[] values) {
		for (int i = 0; i < n; i++) {
			if (a[i] < pivot)
				values[0]++;
			if (a[i] > pivot)
				values[1]++;
		}
	}

	private static int Partition(int[] a, int n, int pivot, boolean extractSmaller) {
		int pos = 0;
		for (int i = 0; i < n; i++) {
			if ((extractSmaller && a[i] < pivot)
					|| (!extractSmaller && a[i] > pivot)) {
				int tmp = a[i];
				a[i] = a[pos];
				a[pos++] = tmp;
			}
		}
		n = pos;
		return n;
	}

	public static void main(String[] args) {
		int[] a = { 4, 2, 1, 7, 5, 3, 8, 10, 9, 6 };

		for (int k = 0; k < a.length; k++) {
			int value = findKthSmallest(a, k);
			System.out.println(k + "th smallest value is " + value);
		}
	}

}


PS:如果要求一个无序数组的第二小的数,可以不用上面那么麻烦,保持两个变量(最小,次小)然后遍历数组,不断更新这两个值,最后即可得。


还有一些参考:http://www.cs.rit.edu/~sps/Courses/CS3/20083/Sorting/QuickSort.java




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值