读书笔记:"算法导论"之RANDOMIZED-SELECT(快速选择算法)

缘由

由于一道题需要使用这个方法处理,题目:
输入n 个整数,输出其中最小的k 个。
例如输入1,2,3,4,5,6,7 和8 这8 个数字,则最小的4 个数字为1,2,3 和4。

该算法,即RANDOMIZED-SELECT算法,可以以时间复杂度为O(n)的要求完成。
我不打算证明,需要看证明的请
此外,该题其他的解法请看:

原理

算法导论一书是: Introduction to Algorithms Third Edition(已上传网盘),其第二版的中文版广为人知: 算法导论

Quicksort

必须先简单的讲一下快速排序,因为RANDOMIZED-SELECT排序需要使用到快速排序。
直接看书上是如何讲解快速排序的:
(摘自7.1 Description of quicksort)

Quicksort, like merge sort (归并排序), applies the divide-and-conquer paradigm introduced in Section 2.3.1. Here is the three-step divide-and-conquer process for sorting a
typical subarray A[p...r]:
  1. Divide: Partition (rearrange,后一个词中文上更好理解) the array A[p...r] into two (possibly empty) subarrays A[p...q-1]  and A[q+1..r] such that each element of A[p...q-1] is less than or equal to A[q], which is, in turn, less than or equal to each element of A[q+1..r]. Compute the index q as part of this partitioning procedure.
  2. Conquer: Sort the two subarrays A[p...q-1]  and A[q+1..r] by recursive calls
  3. Combine: Because the subarrays are already sorted, no work is needed to combine them: the entire array A[p...r] is now sorted.
我认为可以强调几点:
  1. 基本思路就是选择一个元素作为中心,然后将比这个数小的元素放在这个中心的左边,比这个数大的元素放在这个中心的右边,然后对中心左边和右边的两个数组再递归调用快排算法。
  2. 中心一旦确定了左右两边的数,它就已经处于最终位置了。
伪代码如下,传入参数:QUICKSORT(A,1,A.length)也就是数组,第一个元素的下标,最后一个元素的下标(注意这个伪代码的数组是从1开始)

补充:
  • 很好的体现了递归
  • q是一个下标
对于上述过程,又抽象又难理解,请看下面用实际例子来体会:

The operation of PARTITION on a sample array. Array entry A[r] becomes the pivot element x. Lightly shaded array elements are all in the first partition with values no greater than x. Heavily shaded elements are in the second partition with values greater than x. The unshaded elements have not yet been put in one of the first two partitions, and the final white element is the pivot x.
  • (a) The initial array and variable settings. None of the elements have been placed in eitherof the first two partitions.
  • (b) The value 2 is “swapped with itself” and put in the partition of smaller values. 
  • (c)–(d) The values 8 and 7 are added to the partition of larger values. 
  • (e) The values 1 and 8 are swapped, and the smaller partition grows. 
  • (f) The values 3 and 7 are swapped, and the smaller partition grows. 
  • (g)–(h) The larger partition grows to include 5 and 6, and the loop terminates. 
  • (i) In lines 7–8, the pivot element is swapped so that it lies between the two partitions.

RANDOMIZED PARTITION

(摘自7.3 A randomized version of quicksort)

In exploring the average-case behavior(平均表现/一般表现) of quicksort, we have made an assumption that all permutations(排列) of the input numbers are equally likely(可能性相同). In an engineering situation, however, we cannot always expect this assumption to hold. (See Exercise 7.2-4.) As we saw in Section 5.3, we can sometimes add randomization to an algorithm in order to obtain good expected performance over all inputs. Many people regard the resulting randomized version of quicksort as the sorting algorithm of choice(作为选择)for large enough inputs.

In Section 5.3, we randomized our algorithm by explicitly permuting(明确地改变了) the input. We could do so for quicksort also, but a different randomization technique,called random sampling(随机采样), yields a simpler analysis. Instead of always using A[r] as the pivot, we will select a randomly chosen element from the subarray A[p...r].We do so by first exchanging element A[r] with an element chosen at random from A[p...r]. By randomly sampling the range p...r, we ensure that the pivot element x=A(r) is equally likely to be any of the r-p+1 elements in the subarray. Because we randomly choose the pivot element, we expect the split of the input array to be reasonably well balanced on average.

The changes to PARTITION and QUICKSORT are small. In the new partition procedure, we simply implement the swap before actually partitioning:

伪代码如下:


RANDOMIZED-SELECT

该算法其实为了对付一种情况产生的:在一个无序的数组中寻找第K小(大)的数。如果这个问题解决了,实际上也解决了文首的问题,只需要再遍历一遍就好了。

核心思想:我们发现快速排序算法,在完成一次计算之后,枢轴处于了最终位置,并且我们已经了左边都比它小,右边的都比它大。换句话,它就是第几个大的数,也可以说是第几小的数。所以可以用以应对上述情况。

(伪代码摘自9.2 Selection in expected linear time)
A为数组,p为数组第一个元素,r为最后一个元素,i为需要求的第几小的元素

  • 4句的k表示处于a[q]是第几小的数
  • 第7是关键:如果i<k,说明i在q的左边,否则就是在右边。所以递归使用RANDOMIZED-SELECT方法。
  • 注意第9句,因为i>k,所以i在q的右边,在右边的子数组中,所求的数就不再是第i小的了,而是第i-k小的。
特点:
  1. RANDOMIZED-SELECT只用对一边递归即可
  2. 快速排序的时间复杂度是O(n*logn),但是RANDOMIZED-SELECT的时间复杂度为:O(n)。证明请看:"Introduction to Algorithms Third Edition"的216页。

代码

  1. /** 
  2.  * 根据算法导论的伪代码,完成快速排序快速选择的代码。 
  3.  * @author zy 
  4.  * 
  5.  */  
  6. public class randomizedSelect {  
  7.   
  8.   
  9.     /** 
  10.      * @param args 
  11.      */  
  12.     public static void main(String[] args) {  
  13.          int a[]={2,5,3,0,2,3,0,3};  
  14.          a=quickSort(a,0,a.length-1);  
  15.          System.out.print("排序结果:");  
  16.          for(int i=0;i<a.length;i++){  
  17.              System.out.print(a[i]+" ");  
  18.          }  
  19.          int result=randomizedSelect(a,0,a.length-1,3);//产生第三小的数  
  20.          System.out.print("\n"+result);  
  21.     }  
  22.     private static int[] quickSort(int[] a,int p,int r){  
  23.         if(p<r){  
  24.             int q=partition(a,p,r);  
  25.             quickSort(a,p,q-1);  
  26.             quickSort(a,q+1,r);  
  27.         }  
  28.         return a;  
  29.     }  
  30.     private static int partition(int[] a, int p, int r) {  
  31.         int x=a[r];  
  32.         int i=p-1;  
  33.         for(int j=p;j<r;j++){  
  34.             if(a[j]<=x){  
  35.                 i=i+1;  
  36.                 swap(a, i, j);  
  37.             }  
  38.         }  
  39.         swap(a, i+1, r);  
  40.         return i+1;  
  41.     }  
  42.     private static int[] randomizedquickSort(int[] a,int p,int r){  
  43.         if(p<r){  
  44.             int q=randomizedPartition(a,p,r);  
  45.             randomizedPartition(a,p,q-1);  
  46.             randomizedPartition(a,q+1,r);  
  47.         }  
  48.         return a;  
  49.     }  
  50.     private static int randomizedPartition(int[] a,int p,int r){  
  51.         java.util.Random random = new java.util.Random();  
  52.         int i=Math.abs(random.nextInt() % (r-p+1)+p);//产生指定范围内的随机数  
  53.         swap(a,i,r);  
  54.         return partition(a,p,r);  
  55.     }  
  56.       
  57.       
  58.     /** 
  59.      *  
  60.      * @param a 数组 
  61.      * @param p 数组的第一个元素 
  62.      * @param r 数组的最后一个元素 
  63.      * @param i 需要求第几小的元素 
  64.      * @return 
  65.      */  
  66.     private static int randomizedSelect(int[] a,int p,int r,int i){  
  67.         if(p==r){  
  68.             return a[p];//这种情况就是数组内只有一个元素  
  69.         }  
  70.         int q=randomizedPartition(a,p,r);  
  71.         int k=q-p+1;//拿到上一句中作为枢纽的数是第几小的数  
  72.         if(i==k){  
  73.             return a[q];  
  74.         }else if(i<k){  
  75.             return randomizedSelect(a,p,q-1,i);  
  76.         }else{  
  77.             return randomizedSelect(a,q+1,r,i-k);  
  78.         }  
  79.           
  80.     }  
  81.   
  82.   
  83.       
  84.     private static void swap(int[] a, int i, int j) {  
  85.         int temp=a[i];  
  86.         a[i]=a[j];  
  87.         a[j]=temp;  
  88.     }  
  89.   
  90.   
  91. }  


执行结果:

排序结果:0 0 2 2 3 3 3 5 
2


另一份用java实现的代码请看:

优秀博客

源代码

与代码处一模一样:
  1. /** 
  2.  * 根据算法导论的伪代码,完成快速排序快速选择的代码。 
  3.  * @author zy 
  4.  * 
  5.  */  
  6. public class randomizedSelect {  
  7.   
  8.   
  9.     /** 
  10.      * @param args 
  11.      */  
  12.     public static void main(String[] args) {  
  13.          int a[]={2,5,3,0,2,3,0,3};  
  14.          a=quickSort(a,0,a.length-1);  
  15.          System.out.print("排序结果:");  
  16.          for(int i=0;i<a.length;i++){  
  17.              System.out.print(a[i]+" ");  
  18.          }  
  19.          int result=randomizedSelect(a,0,a.length-1,3);//产生第三小的数  
  20.          System.out.print("\n"+result);  
  21.     }  
  22.     private static int[] quickSort(int[] a,int p,int r){  
  23.         if(p<r){  
  24.             int q=partition(a,p,r);  
  25.             quickSort(a,p,q-1);  
  26.             quickSort(a,q+1,r);  
  27.         }  
  28.         return a;  
  29.     }  
  30.     private static int partition(int[] a, int p, int r) {  
  31.         int x=a[r];  
  32.         int i=p-1;  
  33.         for(int j=p;j<r;j++){  
  34.             if(a[j]<=x){  
  35.                 i=i+1;  
  36.                 swap(a, i, j);  
  37.             }  
  38.         }  
  39.         swap(a, i+1, r);  
  40.         return i+1;  
  41.     }  
  42.     private static int[] randomizedquickSort(int[] a,int p,int r){  
  43.         if(p<r){  
  44.             int q=randomizedPartition(a,p,r);  
  45.             randomizedPartition(a,p,q-1);  
  46.             randomizedPartition(a,q+1,r);  
  47.         }  
  48.         return a;  
  49.     }  
  50.     private static int randomizedPartition(int[] a,int p,int r){  
  51.         java.util.Random random = new java.util.Random();  
  52.         int i=Math.abs(random.nextInt() % (r-p+1)+p);//产生指定范围内的随机数  
  53.         swap(a,i,r);  
  54.         return partition(a,p,r);  
  55.     }  
  56.       
  57.       
  58.     /** 
  59.      *  
  60.      * @param a 数组 
  61.      * @param p 数组的第一个元素 
  62.      * @param r 数组的最后一个元素 
  63.      * @param i 需要求第几小的元素 
  64.      * @return 
  65.      */  
  66.     private static int randomizedSelect(int[] a,int p,int r,int i){  
  67.         if(p==r){  
  68.             return a[p];//这种情况就是数组内只有一个元素  
  69.         }  
  70.         int q=randomizedPartition(a,p,r);  
  71.         int k=q-p+1;//拿到上一句中作为枢纽的数是第几小的数  
  72.         if(i==k){  
  73.             return a[q];  
  74.         }else if(i<k){  
  75.             return randomizedSelect(a,p,q-1,i);  
  76.         }else{  
  77.             return randomizedSelect(a,q+1,r,i-k);  
  78.         }  
  79.           
  80.     }  
  81.   
  82.   
  83.       
  84.     private static void swap(int[] a, int i, int j) {  
  85.         int temp=a[i];  
  86.         a[i]=a[j];  
  87.         a[j]=temp;  
  88.     }  
  89.   
  90.   

  • 4
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
第一部分(Part I) 基础(Foundations) 第一章 计算中算法的角色(The Role of Algorithms in Computing) 第二章 开始(Getting Started) 第三章 函数的增长率(Growth of Functions) 第四章 递归(Recurrences) 第五章 概率分析与随机化算法(Probabilistic Analysis and Randomized Algorithms) 第二部分(Part II) 排序与顺序统计(Sorting and Order Statistics) 第六章 堆排序(Heapsort) 第七章快速排序(Quicksort) 第八章 线性时间中的排序(Sorting in Linear Time) 第九章 中值与顺序统计(Medians and Order Statistics) 第三部分(Part III) 数据结构(Data Structures) 第十章 基本的数据结构(Elementary Data Structures) 第十一章 散列表(Hash Tables) 第十二章 二叉查找树(Binary Search Trees) 第十三章 红-黑树(Red-Black Trees) 第十四章 扩充的数据结构(Augmenting Data Structures) 第四部分(Part IV) 高级的设计与分析技术(Advanced Design and Analysis Techniques) 第十五章 动态规划(Dynamic Programming) 第十六章 贪婪算法(Greedy Algorithms) 第十七章 分摊分析(Amortized Analysis) 第五部分(Part V) 高级的数据结构(Advanced Data Structures) 第十八章 B-树(B-Trees) 第十九章 二项式堆(Binomial Heaps) 第二十章 斐波纳契堆(Fibonacci Heaps) 第二十一章 不相交集的数据结构(Data Structures for Disjoint Sets) 第六部分(Part VI) 图算法(Graph Algorithms) 第二十二章 基本的图算法(Elementary Graph Algorithms) 第二十三章 最小生成树(Minimum Spanning Trees) 第二十四章单源最短路径(Single-Source Shortest Paths) 第二十五章 全对的最短路径(All-Pairs Shortest Paths) 第二十六章 最大流(Maximum Flow) 第七部分(Part VII) 精选的主题(Selected Topics) 第二十七章 排序网络(Sorting Networks) 第二十八章矩阵运算(Matrix Operations) 第二十九章 线性规划(Linear Programming) 第三十章 多项式与快速傅里叶变换(Polynomials and the FFT) 第三十一章 数论算法(Number-Theoretic Algorithms) 第三十二章 字符串匹配(String Matching) 第三十三章 计算几何学(Computational Geometry) 第三十四章 NP-完备性(NP-Completeness) 第三十五章 近似算法(Approximation Algorithms) 第八部分(Part VIII) 附录:数学背景(Mathematical Background) 索引(Index)

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值