Algorithm——一般陣列元素選擇問題(十一)
阿新 • • 發佈:2019-01-27
Algorithm——一般陣列元素選擇問題
《演算法導論》中引出了一個一般性的陣列元素選擇問題:即在一個元素各異的陣列A中,選擇其中第i小的元素(即如果i=1,則表明選擇最小的那個元素)。
該演算法的虛擬碼如下,它使用了之前介紹快速排序中的隨機子陣列劃分方法:
此演算法的Java實現如下:RANDOMIZED-SELECT(A, p, r, i) if p == r return A [p] q = RANDOMIZED-PARTITION(A, p, r) k = q - p + 1 if i == k return A[q] else if i < k return RANDOMIZED-SELECT(A, p, q - 1, i) else return RANDOMIZED-SELECT(A, q + 1, r, i - k)
完整的測試程式碼是:/** * * 在給定的陣列下標區間範圍內,根據選定的主元劃分子陣列 * * @param A * 待排序的主陣列 * @param p * 子陣列下標的左邊界 * @param r * 子陣列下標的右邊界 * @return 得到的子陣列劃分分界下標值 */ private int partition(int[] A, int p, int r) { int pivot = A[r];// pivot element int i = p - 1; for (int j = p; j < r; j++) { // 當pivot >= // A[j]時,下標為j的元素就是需要與下標為i的元素進行交換的元素;下表為i的元素是前面那些比pivot大的元素的下標;即,在發現一個元素比pivot小時,就將此元素與之前的比pivot元素大的元素進行交換;這樣比pivot小的元素總會出現在陣列左邊,並連續出現. if (A[j] <= pivot) { i++;// 下標自加1;每次自加1後,當前下標i指向的元素都比pivot大 exchange(A, i, j); } } // for迴圈結束後此時陣列A[p...r]的狀態是: // 對於任意下標k,如果k在[p,i]區間內,總有A[k] <= pivot; // 對於任意下標k,如果k在[i+1,j]區間內,總有A[k] > pivot; // 若下標k = r,則A[r] = pivot; exchange(A, i + 1, r);// 將A[i+1]與A[r]交換,此時會有小遠(i+1)下標的元素的值都小於等於pivot;大於(i+1)下標的元素值都大於pivot return i + 1; } // 用於隨機抽樣 private Random util = new Random(); /** * * 加入隨機抽樣的陣列劃分演算法 * * @param A * 待排序的陣列A * @param p * 待排序的子陣列下標左邊界 * @param r * 帶排序的子陣列下標右邊界 * @return 得到的子陣列劃分分界下標值 */ public int randomizedPartition(int[] A, int p, int r) { int i = util.nextInt(r - p + 1)/* 區間[0,r-p+1) */ + p;// 隨機生成一個在區間[p,r]之間的下標值 exchange(A, i, r);// 保證子陣列劃分所需要的pivot element是隨機從A[p...r]中選取的 return partition(A, p, r);// 呼叫常規子陣列劃分函式,進行子陣列劃分 } /** * 一種解決選擇問題的分治演算法:返回陣列A(不包含相同元素)中下標範圍[p...r]內的第i小的元素;i從1開始算,i=1則說明選擇A[p...r]中最小的那個元素 * */ public int randomizedSelect(int[] A, int p, int r, int i) { if (p == r) return A[p];//如果指定的下標範圍內只有一個元素,那當前元素就是所需要的第i小的元素 int q = randomizedPartition(A, p, r);//使用隨機子陣列劃分方法,藉助一個隨機主元去劃分子陣列(可能為空);返回的q的下標是當前主元的下標 //在上一步劃分完子陣列後,A[p...r]實際上可以看做被劃分成了三個子陣列區間:A[p...q-1] =< A[q] < A[q+1...r];此時主元A[q]在整個子陣列A[p...r]中已經是排好序的了 int k = q - p + 1;//計運算元陣列A[p...q]內的元素個數 if (i == k)//如果i==k,則說明此次的主元A[q]就是要選擇的第i小的元素,此時就直接返回;否則下面兩步就要確定要選的元素是在子陣列A[p...q-1]還是在A[q+1...r]中了 return A[q]; else if (i < k)//i < k,說明待選擇的元素在子陣列中A[p...q-1]中,隨後繼續遞迴查詢 return randomizedSelect(A, p, q - 1, i); else //i > k,說明待選擇的元素在子陣列A[q+1...r]中,隨後繼續遞迴查詢; return randomizedSelect(A, q + 1, r, i - k);//同時要注意,此時我們已知了A[p...q]中的元素(總共有k個)都是比待選擇的元素小的,那麼在子陣列A[q+1...r]中,待選擇的元素應該是第(i-k)小的了 }
public class SortAlgor { public static void main(String[] args) { int[] A = { 2, 5, 10, 8, 9, 12, 15, 4 }; SortAlgor algorithm = new SortAlgor(); System.out.println(algorithm.randomizedSelect(A, 0, A.length-1, 3));//返回5 } /** * * 在給定的陣列下標區間範圍內,根據選定的主元劃分子陣列 * * @param A * 待排序的主陣列 * @param p * 子陣列下標的左邊界 * @param r * 子陣列下標的右邊界 * @return 得到的子陣列劃分分界下標值 */ private int partition(int[] A, int p, int r) { int pivot = A[r];// pivot element int i = p - 1; for (int j = p; j < r; j++) { // 當pivot >= // A[j]時,下標為j的元素就是需要與下標為i的元素進行交換的元素;下表為i的元素是前面那些比pivot大的元素的下標;即,在發現一個元素比pivot小時,就將此元素與之前的比pivot元素大的元素進行交換;這樣比pivot小的元素總會出現在陣列左邊,並連續出現. if (A[j] <= pivot) { i++;// 下標自加1;每次自加1後,當前下標i指向的元素都比pivot大 exchange(A, i, j); } } // for迴圈結束後此時陣列A[p...r]的狀態是: // 對於任意下標k,如果k在[p,i]區間內,總有A[k] <= pivot; // 對於任意下標k,如果k在[i+1,j]區間內,總有A[k] > pivot; // 若下標k = r,則A[r] = pivot; exchange(A, i + 1, r);// 將A[i+1]與A[r]交換,此時會有小遠(i+1)下標的元素的值都小於等於pivot;大於(i+1)下標的元素值都大於pivot return i + 1; } // 用於隨機抽樣 private Random util = new Random(); /** * * 加入隨機抽樣的陣列劃分演算法 * * @param A * 待排序的陣列A * @param p * 待排序的子陣列下標左邊界 * @param r * 帶排序的子陣列下標右邊界 * @return 得到的子陣列劃分分界下標值 */ public int randomizedPartition(int[] A, int p, int r) { int i = util.nextInt(r - p + 1)/* 區間[0,r-p+1) */ + p;// 隨機生成一個在區間[p,r]之間的下標值 exchange(A, i, r);// 保證子陣列劃分所需要的pivot element是隨機從A[p...r]中選取的 return partition(A, p, r);// 呼叫常規子陣列劃分函式,進行子陣列劃分 } /** * 一種解決選擇問題的分治演算法:返回陣列A(不包含相同元素)中下標範圍[p...r]內的第i小的元素;i從1開始算,i=1則說明選擇A[p...r]中最小的那個元素 * */ public int randomizedSelect(int[] A, int p, int r, int i) { if (p == r) return A[p];//如果指定的下標範圍內只有一個元素,那當前元素就是所需要的第i小的元素 int q = randomizedPartition(A, p, r);//使用隨機子陣列劃分方法,藉助一個隨機主元去劃分子陣列(可能為空);返回的q的下標是當前主元的下標 //在上一步劃分完子陣列後,A[p...r]實際上可以看做被劃分成了三個子陣列區間:A[p...q-1] =< A[q] < A[q+1...r];此時主元A[q]在整個子陣列A[p...r]中已經是排好序的了 int k = q - p + 1;//計運算元陣列A[p...q]內的元素個數 if (i == k)//如果i==k,則說明此次的主元A[q]就是要選擇的第i小的元素,此時就直接返回;否則下面兩步就要確定要選的元素是在子陣列A[p...q-1]還是在A[q+1...r]中了 return A[q]; else if (i < k)//i < k,說明待選擇的元素在子陣列中A[p...q-1]中,隨後繼續遞迴查詢 return randomizedSelect(A, p, q - 1, i); else //i > k,說明待選擇的元素在子陣列A[q+1...r]中,隨後繼續遞迴查詢; return randomizedSelect(A, q + 1, r, i - k);//同時要注意,此時我們已知了A[p...q]中的元素(總共有k個)都是比待選擇的元素小的,那麼在子陣列A[q+1...r]中,待選擇的元素應該是第(i-k)小的了 } }