順序統(tǒng)計
問題場景:給定具有n個元素的數(shù)組,已知數(shù)組是無序的,請找到第k小的元素并返回該元素(TOP K問題)。
根據(jù)之前所學(xué)的算法我們可以得出一個原始方案:
使用運行時間為Θ (nlgn)的排序算法(堆排序、歸并排序)進(jìn)行排序后返回數(shù)組索引為K的元素。
該問題當(dāng)K為如下特殊值的時候的情形:
- K=1 : 最小值
- K=n : 最大值
-
K=(n-1)/2: 中值
k值表示的不同意義
隨機(jī)化分治法
- 隨機(jī)化選擇法
用到了前面講的隨機(jī)化快速排序的子操作randomizedPartition。
方法的大致運行流程是這樣的:
如果開始位置和結(jié)束位置一樣時算法視為找到,返回開始位置的元素;否則進(jìn)行隨機(jī)化分割,當(dāng)分割后的主元位置和需要查找的第index小的位置對應(yīng)時視為找到,當(dāng)前主元就是需要查找的元素。否則,將問題劃分為更小規(guī)模的問題,如果主元位置p>index那么在[left,p]范圍內(nèi)的數(shù)據(jù)進(jìn)行隨機(jī)化選擇操作;否則從主元的右側(cè)進(jìn)行隨機(jī)話選擇操作。方法介紹完畢,需要注意的是不同的分割導(dǎo)致index的變化是一個需要關(guān)注的點。
package me.ziuo.ai.intro;
import java.util.Random;
/**
*
* {48,6,57,88,60,42,83,73,49,85,99,1424,35,242,6567,34,2,5};
*
* ------------------------------------------------
* 隨機(jī)化選擇第 index 小的元素
* top 1 is 2.
* top 2 is 5.
* top 3 is 6.
* top 4 is 34.
* top 5 is 35.
* top 6 is 42.
* top 7 is 48.
* top 8 is 49.
* top 9 is 57.
* top 10 is 60.
* top 11 is 73.
* top 12 is 83.
* top 13 is 85.
* top 14 is 88.
* top 15 is 99.
* top 16 is 242.
* top 17 is 1424.
* top 18 is 6567.
*
* @author eboy
*
* @description 隨機(jī)化的選擇算法,用于求解top k 問題
**/
public class RandomizedSelect {
/**
* @return 隨機(jī)化主元分割點位置
*
* @param src
* 待劃分的數(shù)組,分割后的結(jié)果保證,mid前的元素都是小于等于它的,右側(cè)的元素都是大于等于它的
* @param left
* 左邊界
* @param right
* 右邊界
*/
private static int randomizedPartition(int[] src, int left, int right) {
/*隨機(jī)選取主元元素*/
Random random = new Random();
int random_index = random.nextInt(right-left+1)+left;
//感興趣的話,可以打印一下主元的位置
// System.out.println("random_index="+random_index);
/**
* 交換
*/
int temp = src[random_index];
src[random_index] = src[left];
src[left]=temp;
int key = src[left];//挖坑
int i = left;
int j = right;
while (i < j) {
while (i < j && src[j] > key) {
j--;
}
if (i < j) {
src[i++] = src[j];// 挖坑填數(shù)
}
while (i < j && src[i] < key) {
i++;
}
if (i < j) {
src[j--] = src[i];// 挖坑填數(shù)
}
}
src[i] = key;//填空
// 這種情況下第一趟結(jié)束 i的坐標(biāo)都比他小i的右邊都比他大
return i;
}
/**
*
* @param src
* @param left
* @param right
* @param index
* @return 返回 第 index 小的元素
*/
public static int randomizedSelect(int[] src, int left, int right, int index) {
if(left==right) return src[left];//待分割的邊界到只有一個元素時,該元素作為結(jié)果返回
int mid =randomizedPartition(src, left, right);//隨機(jī)化分割后,主元所在位置
int key=mid-left+1;
if(index==key) return src[mid];
else if(index<key) return randomizedSelect(src, left, mid-1, index);
else return randomizedSelect(src, mid+1, right, index-key);//top i問題分解成top i-key
}
}
該算法的最壞情況的運行時間是Θ(n2);期望運行時間為Θ(n)。
雖然,隨機(jī)化選擇算法的期望運行時間是線性的,但是出現(xiàn)最壞的情況的話效率很低,盡管出現(xiàn)最壞的情況的概率是極其低的。
最終人類還是創(chuàng)造出最壞情況為線性的選擇算法。
最壞情況為線性的選擇算法
選擇算法概述
該算法是對隨機(jī)化選擇主元的優(yōu)化版本,最壞情況的運行時間是線性的即O(n)。和隨機(jī)化的選擇排序一樣,快速選擇算法通過對輸入數(shù)組的遞歸劃分找出所需的元素,但是該算法那能給個保證得到對數(shù)組的一個好的劃分。快速選擇算法使用的也是來自快速排序的確定性劃分算法PARTITION,做了部分修改,將劃分的主元也作為輸入?yún)?shù)。
代碼如下:
package me.ziuo.ai.intro;
import java.util.Random;
/**
*
* {7,9,8,6,3,5,2,4,1
,18,10,15,17,10,12,19,14,13
,34,58,79,21,16,53,23,22,11
,23,48,29,16,55,37,28,51,50
,22,18,27,35,53,27,96,88,101}
*
* ------------------------------------------------
* 最壞情況運行時間為線性的選擇第 index 小的元素
* top 1 is 1.
* top 2 is 2.
* top 3 is 3.
* top 4 is 4.
* top 5 is 5.
* top 6 is 6.
* top 7 is 7.
* top 8 is 8.
* top 9 is 9.
* top 10 is 10.
* top 11 is 10.
* top 12 is 11.
* top 13 is 12.
* top 14 is 13.
* top 15 is 14.
* top 16 is 15.
* top 17 is 16.
* top 18 is 16.
* top 19 is 17.
* top 20 is 18.
* top 21 is 18.
* top 22 is 19.
* top 23 is 21.
* top 24 is 22.
* top 25 is 22.
* top 26 is 23.
* top 27 is 23.
* top 28 is 27.
* top 29 is 27.
* top 30 is 28.
* top 31 is 29.
* top 32 is 34.
* top 33 is 35.
* top 34 is 37.
* top 35 is 48.
* top 36 is 50.
* top 37 is 51.
* top 38 is 53.
* top 39 is 53.
* top 40 is 55.
* top 41 is 58.
* top 42 is 79.
* top 43 is 88.
* top 44 is 96.
* top 45 is 101.
*
* @author eboy
*
* @description 確定性劃分算法的選擇算法,用于求解top k 問題
**/
public class Select {
int[] midianArray;//中位數(shù)數(shù)組
/**
* @return 隨機(jī)化主元分割點位置
*
* @param src
* 待劃分的數(shù)組,分割后的結(jié)果保證,mid前的元素都是小于等于它的,右側(cè)的元素都是大于等于它的
* @param left
* 左邊界
* @param right
* 右邊界
*/
private int partition(int[] src, int left, int right,int pivotIndex) {
/**
* 交換
*/
int temp = src[pivotIndex];
src[pivotIndex] = src[left];
src[left]=temp;
int key = src[left];//挖坑
int i = left;
int j = right;
while (i < j) {
while (i < j && src[j] > key) {
j--;
}
if (i < j) {
src[i++] = src[j];// 挖坑填數(shù)
}
while (i < j && src[i] < key) {
i++;
}
if (i < j) {
src[j--] = src[i];// 挖坑填數(shù)
}
}
src[i] = key;//填空
// 這種情況下第一趟結(jié)束 i的坐標(biāo)都比他小i的右邊都比他大
return i;
}
/**
* 最壞情況運行時間為線性的選擇排序算法的入口方法
* @param src
* @param left
* @param right
* @param key
* @return
*/
public int qSelect(int[] src, int left, int right, int key){
midianArray=new int[src.length/5 + 1];
return select(src, left, right, key);
}
/**
*
* @param src
* @param left
* @param right
* @param key
* @return 返回 第 index 小的元素
*/
private int select(int[] src, int left, int right, int key) {
/**
* 1.尋找中位數(shù)的中位數(shù)
* 2.進(jìn)行遞歸找到第k小的元素
*/
int median=findMedian(src,left,right);
// System.out.println("median is "+median);
int medianIndex=findMedianIndex(src, left, right, median);
// System.out.println("medianIndex is "+medianIndex);
int pivotIndex =partition(src, left, right,medianIndex);//根據(jù)主元進(jìn)行劃分,返回主元劃分后的位置
int index=pivotIndex-left+1;
if(key==index) return src[pivotIndex];
else if(key<index) return select(src, left, pivotIndex-1, key);
else return select(src, pivotIndex+1, right, key-index);//top i問題分解成top i-key
}
private int findMedianIndex(int[] src ,int left,int right,int median){
for(int i=left;i<=right;i++){
if(src[i]==median)
return i;
}
return -1;
}
private int findMedian(int[] src, int left, int right) {
if(left==right) return src[left];//范圍內(nèi)只有一個元素時,該元素作為結(jié)果返回
/**
* 1.元素分組:將輸入數(shù)組中的n個元素劃分為【n/5】個中位數(shù),且至多只有一組由剩下的n mod 5個元素組成。
* 2.尋找【n/5】個中位數(shù):首先對沒組元素進(jìn)行插入排序,然后確定每組元素的中位數(shù)。
* 3.找到中位數(shù):找出中位數(shù)的中位數(shù)。
*/
//1.元素分組
//2.尋找【n/5】個中位數(shù)
int index;
for(index=left;index<right-5;index+=5){//分組整除部分的數(shù)據(jù)
insertSort(src,index,4);
int num=index - left;
midianArray[num/5]=src[index+2];//中位數(shù)賦值
}
//分組余下的元素
int remainNum=right-index+1;
if(remainNum>0){
insertSort(src,index,remainNum-1);
int num=index - left;
midianArray[num/5]=src[index+remainNum/2];//中位數(shù)賦值
}
//3. 遞歸找到中位數(shù)的中位數(shù)
int elemAuxArray=(right-left)/5-1;
if((right-left)%5!=0) elemAuxArray++;
if(elemAuxArray==0)
return midianArray[elemAuxArray];
else
return findMedian(midianArray,0,elemAuxArray);
}
/**
* 子數(shù)組插入排序
* @param src 母數(shù)組
* @param left 開始為止
* @param loopLength 步長
*/
private void insertSort(int[] src, int left, int loopLength) {
for(int j=left; j<left+loopLength;j++){
int key=src[j];
int i=j-1;
while(i>left && src[i]>key){
src[i+1]=src[i];
i--;
}
src[i+1]=key;
}
}
}
測試代碼如下:
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] arr ={48,6,57,88,60,42,83,73,49,85};
/**
* random_index=9
* random_index=3
* random_index=1
* random_index=2
* random_index=6
* =4
* random_index=5
* sorted is {6,42,48,49,57,60,73,83,85,88}
*/
System.out.println("------------------------------------------------");
System.out.println("隨機(jī)化選快排的運算過程");
RandomizedQuickSort.randomizedQuickSort(arr,0, arr.length-1);
for(int i=0;i<arr.length;i++){
if(i==arr.length-1) System.out.print(arr[i]);
else System.out.print(arr[i]+",");
}
System.out.println("");
System.out.println("------------------------------------------------");
System.out.println("隨機(jī)化選擇第 index 小的元素");
/**
* 隨機(jī)化選擇第 index 小的元素
*/
int[] arr2 ={48,6,57,88,60,42,83,73,49,85,99,1424,35,242,6567,34,2,5};
for(int i=0;i<arr2.length;i++){
int indexMin=RandomizedSelect.randomizedSelect(arr2, 0, arr2.length-1, i+1);
System.out.println("top "+(i+1)+" is "+indexMin+".");
}
System.out.println("------------------------------------------------");
System.out.println("最壞情況運行時間為線性的選擇第 index 小的元素");
/**
* 最壞情況運行時間為線性的選擇第 index 小的元素
*/
int[] arr3 ={7,9,8,6,3,5,2,4,1
,18,10,15,17,10,12,19,14,13
,34,58,79,21,16,53,23,22,11
,23,48,29,16,55,37,28,51,50
,22,18,27,35,53,27,96,88,101};
for(int i=0;i<arr3.length;i++){
Select select=new Select();
int indexMin=select.qSelect(arr3, 0, arr3.length-1, i+1);
System.out.println("top "+(i+1)+" is "+indexMin+".");
}
}
控制臺打印
------------------------------------------------
隨機(jī)化選快排的運算過程
random_index=4
random_index=1
random_index=3
random_index=2
random_index=2
random_index=8
random_index=8
6,42,48,49,57,60,73,83,85,88
------------------------------------------------
隨機(jī)化選擇第 index 小的元素
top 1 is 2.
top 2 is 5.
top 3 is 6.
top 4 is 34.
top 5 is 35.
top 6 is 42.
top 7 is 48.
top 8 is 49.
top 9 is 57.
top 10 is 60.
top 11 is 73.
top 12 is 83.
top 13 is 85.
top 14 is 88.
top 15 is 99.
top 16 is 242.
top 17 is 1424.
top 18 is 6567.
------------------------------------------------
最壞情況運行時間為線性的選擇第 index 小的元素
top 1 is 1.
top 2 is 2.
top 3 is 3.
top 4 is 4.
top 5 is 5.
top 6 is 6.
top 7 is 7.
top 8 is 8.
top 9 is 9.
top 10 is 10.
top 11 is 10.
top 12 is 11.
top 13 is 12.
top 14 is 13.
top 15 is 14.
top 16 is 15.
top 17 is 16.
top 18 is 16.
top 19 is 17.
top 20 is 18.
top 21 is 18.
top 22 is 19.
top 23 is 21.
top 24 is 22.
top 25 is 22.
top 26 is 23.
top 27 is 23.
top 28 is 27.
top 29 is 27.
top 30 is 28.
top 31 is 29.
top 32 is 34.
top 33 is 35.
top 34 is 37.
top 35 is 48.
top 36 is 50.
top 37 is 51.
top 38 is 53.
top 39 is 53.
top 40 is 55.
top 41 is 58.
top 42 is 79.
top 43 is 88.
top 44 is 96.
top 45 is 101.
算法系列文章,因為水平所限,只做記錄,不做過多介紹。
以上,謝謝閱讀,希望你有所收獲!
以上,謝謝閱讀,希望你有所收獲!
算法導(dǎo)論公開課筆記(一)算法分析與設(shè)計
算法導(dǎo)論公開課筆記(二)快速排序和隨機(jī)化算法
算法導(dǎo)論公開課筆記(三)線性時間排序
算法導(dǎo)論公開課筆記(四)順序統(tǒng)計、中值
動態(tài)規(guī)劃算法