zoukankan      html  css  js  c++  java
  • 笔试算法题(55):快速排序实现之三路划分, 三元中值法和插入排序处理小子文件

    议题:快速排序算法实现之三(三路划分遍历,解决与划分元素相等元素的问题)

    分析:

    • 算法原理:使用三路划分策略对数组进行划分(也就是荷兰国旗问题,dutch national flag problem)。这个实现是对实现二的改进,它添加处理等于划分元素的值的逻辑,将所有等于划分元素的值集中在一起,并且以后都不会再对他们进行划分。 本算法中使用四个标示值进行操作。使用left和right同时向中间遍历时,当left遇见等于划分元素时,就与iflag指向的值进行交换 (iflag指向的当前值到最左端表示left在过程中遇见的等于划分元素的值部分),同理,右边也使用同样的逻辑完成对等于划分元素的处理。最后分别交 换左右部分的相等值(left和iflag对应交换,right和rflag对应交换),由于需要返回两个标记值,所以将partition和 quicksort合并成一个方法。
       

    • 即使没有重复键,最后的移动开销就不需要,即使有也是线性问题;较好的处理了划分元素的相等值问题,没有多余的性能损耗,所以运行时间为线性N;

    • 此程序将序列划分成三个部分:小于划分元素的元素(放置于a[l]到a[left]);等于划分元素的元素(放置于a[left+1]到a[right- 1]);大于划分元素的元素(放置于a[right]到a[r]);由于直接进行如此划分不是特别方便,所以首先将left和right指针分别遇到的等 于pivot的元素放置到最左边和最右边,当处理完所有元素之后才将两边等于pivot的元素拷贝到序列中间,并且分别递归处理left左边的序列和 right右边的序列;

    样例:

      1 void quickSort_3(int *array, int l, int r) {
      2         /**
      3          * 由于三路划分中有可能有两个不同的划分点,所以不能
      4          * 使用函数直接返回,这里将partition和quickSort驱动
      5          * 程序结合成一个方法;
      6          * */
      7         if(l>=r) return;
      8         /**
      9          * 选择pivot划分元素,并将其与array[r]交换
     10          * */
     11         int pivot, temp;
     12         pivot=l+rand()%(r-l+1);
     13         temp=array[pivot];
     14         array[pivot]=array[r];
     15         array[r]=temp;
     16         /**
     17          * 双向扫描:
     18          * left和right都为主动移动
     19          * lflag和rflag为被动移动
     20          * */
     21         int left=l, lflag=l;
     22         int right=r-1, rflag=r-1;
     23         while(true) {
     24 
     25                 while(array[left]<=array[r] && left<r) {
     26                         /**
     27                          * 如果array[left]与pivot相等,则将其交换到
     28                          * lflag当前指向的元素
     29                          * */
     30                         if(array[left]==array[r]) {
     31                                 temp=array[left];
     32                                 array[left]=array[lflag];
     33                                 array[lflag]=temp;
     34                                 lflag++;
     35                         }
     36                         left++;
     37                 }
     38 
     39                 while(array[right]>=array[r] && right>=l) {
     40                         /**
     41                          * 如果array[right]与pivot相等,则将其交换到
     42                          * rflag当前指向的元素
     43                          * */
     44                         if(array[right]==array[r]) {
     45                                 temp=array[right];
     46                                 array[right]=array[rflag];
     47                                 array[rflag]=temp;
     48                                 rflag--;
     49                         }
     50                         right--;
     51                 }
     52 
     53                 if(left>=right) break;
     54                 /**
     55                  * 将左边大于pivot的元素与右边小于pivot的元素进行
     56                  * 交换
     57                  * */
     58                 temp=array[left];
     59                 array[left]=array[right];
     60                 array[right]=temp;
     61 
     62                 left++;right--;
     63         }
     64         /**
     65          * 由于left和lflag指向的当前元素都是即将需要处理的元素,
     66          * 也就是当处理结束之后,他们都需要左移一步才是已经处理好的
     67          * 元素; 将等于pivot的元素交换到中间
     68          * */
     69         lflag--;left--;
     70         while(lflag>=l) {
     71                 temp=array[left];
     72                 array[left]=array[lflag];
     73                 array[lflag]=temp;
     74                 left--;lflag--;
     75         }
     76         /**
     77          * 由于right和rflag指向的当前元素都是即将需要处理的元素,
     78          * 也就是当处理结束之后,他们都需要右移一步才是已经处理好的
     79          * 元素;将等于pivot的元素交换到中间
     80          * 注意:由于pivot本身也需要移动到中间,所以这里的判断条件
     81          * 包含r
     82          * */
     83         rflag++;right++;
     84         while(rflag<=r) {
     85                 temp=array[right];
     86                 array[right]=array[rflag];
     87                 array[rflag]=temp;
     88                 right++;rflag++;
     89         }
     90         /**
     91          * 最终递归处理左右子序列部分
     92          * */
     93         quickSort_3(array, l, left);
     94         quickSort_3(array, right, r);
     95 }
     96 
     97 int main() {
     98         int array[]={2,5,8,2,1,6};
     99         quickSort_3(array,0,5);
    100         for(int i=0;i<6;i++)
    101                 printf("%d,",array[i]);
    102         return 1;
    103 }

    议题:快速排序算法实现之四(三元中值法,InsertSort处理小子文件,减少递归栈)

    分析:

    • 算法原理:此算法利用insert算法处理小子文件,从而有效减小系统栈空间的耗用;使用三元中值(Median-of-Three-Method)划分 策略在数组的左,中,右抽样,并使用他们的中值作为划分元素,然后使用小子文件(放弃对小序列使用递归调用)策略对序列元素小于11的子序列放弃使用递 归,而是使用插入排序。也就是将每一次递归之前进行判断的if(l>=r) return;修改为当l和r相差一定大小的时候就调用Insertion Sort。使用Probabilistic Algorithm(随机取值)获取的划分元素可以最高概率地获得好性能。Median-of-Three Method(取头,中,尾三处的中间大小元素)获取的划分元素可以更好地获取高性能(减少算法平均时间的10%);

    • 优势:当系统递归栈的开销所占递归处理本身的比例较高时,快速排序性能较低,从而可以使用直接排序而非递归处理小子文件;

    • 三元中值划分法表示一种抽样估算的思想,也就是使得划分元素尽量接近序列的中间值,具体的抽样可不限于三个,并且在代码实现的时候,需要考虑到循环内部操作的优化,并且在实现过程中,为了优化栈操作,有的较小序列直接使用插入排序,可以从系统的角度提升算法的性能;

    • 时间:运行时间可以在N㏒N的基础上减少10%。小子文件大小在5-25之间的性能改善相似;

    样例:

      1 void insertSort(int *array, int l, int r);
      2 
      3 int partition_4(int *array, int l, int r) {
      4 
      5         int temp;
      6         int pivot;
      7         /**
      8          * 获取array[l],array[(l+r)/2], array[r]
      9          * 的中间值
     10          * */
     11         if(array[l]<=array[(l+r)/2]<=array[r] ||
     12                         array[r]<=array[(l+r)/2]<=array[l])
     13                 pivot=array[(l+r)/2];
     14         else if(array[(l+r)/2]<=array[r]<=array[l] ||
     15                         array[l]<=array[r]<=array[(l+r)/2])
     16                 pivot=array[r];
     17         else if(array[(l+r)/2]<=array[l]<=array[r] ||
     18                         array[r]<=array[l]<=array[(l+r)/2])
     19                 pivot=array[l];
     20 
     21         temp=array[pivot];
     22         array[pivot]=array[r];
     23         array[pivot]=temp;
     24 
     25         /**
     26          * 双向扫描:
     27          * left从array的左边l处开始向右处理,直到r-1
     28          * right从array的右边r-1处开始向左处理,直到l
     29          * left和right都是主动移动
     30          * */
     31         int left=l, right=r-1;
     32         while(true) {
     33                 /**
     34                  * left左边的元素为小于等于array[r]的元素
     35                  * 并注意array[r]为最大值的情况,left会一直
     36                  * 移动到r
     37                  * */
     38                 while(array[left]<=array[r] && left<r)
     39                         left++;
     40                 /**
     41                  * right右边的元素为大于array[r]的元素
     42                  * 并注意array[r]为最小值的情况,right会一直
     43                  * 移动到l-1
     44                  * 这里仅使用大于的逻辑关系还可以避免当array
     45                  * 都是相同元素的情况时指针交叉的发生
     46                  * */
     47                 while(array[right]>array[r] && right>=l)
     48                         right--;
     49                 /**
     50                  * 有四种序列情况:
     51                  * 1. 一般情况:left和right在序列中间的某个元素交叉
     52                  * 2. array[r]是最大值情况:left移动到r,right在r-1
     53                  * 3. array[r]是最小值情况:left在l,right移动到l-1
     54                  * 4. array所有元素为同一个值:left移动到r,right在r-1
     55                  * */
     56                 if(left>=right)
     57                         break;
     58                 /**
     59                  * 交换元素
     60                  * */
     61                 temp=array[left];
     62                 array[left]=array[right];
     63                 array[right]=temp;
     64 
     65                 left++;right--;
     66         }
     67         /**
     68          * 最终将array[r]的pivot元素与array[left]进行交换
     69          * 由于此时的array[right]比array[r]小,所以只能交换
     70          * array[left]
     71          * */
     72         temp=array[left];
     73         array[left]=array[r];
     74         array[r]=temp;
     75         return left;
     76 }
     77 
     78 void quickSort_4(int *array, int l, int r) {
     79         /**
     80          * 当序列大小小于11的时候,假设此时系统堆栈的开销已经
     81          * 大于处理递归序列本身的开销,则递归调用具有低效率,
     82          * 所以直接使用插入排序进行排序
     83          * */
     84         if(l-r>11) {
     85                 insertSort(array, l, r);
     86                 return;
     87         }
     88         /**
     89          * 利用partition方法获取划分元素
     90          * */
     91         int pivot=partition_4(array, l, r);
     92         /**
     93          * 划分元素已经到达最终位置,所以不用参与进一步处理
     94          * 分别递归处理左右部分的元素
     95          * */
     96         quickSort_4(array, l, pivot-1);
     97         quickSort_4(array, pivot+1, r);
     98 }
     99 
    100 int main() {
    101         int array[]={2,5,8,2,1,6};
    102         quickSort_4(array,0,5);
    103         for(int i=0;i<6;i++)
    104                 printf("%d,",array[i]);
    105         return 1;
    106 }
  • 相关阅读:
    USACO Section 2.2 Subset Sums
    九度 1399 名侦探柯南
    九度 1416 猴子吃坚果
    pch文件的使用(原作者太逗了)
    线程同步
    extern "c"
    进程与线程
    排序算法代码汇总
    Linux Shell 常用命令与目录分区的学习总结 (开始学习linux)
    堆和栈
  • 原文地址:https://www.cnblogs.com/leo-chen-2014/p/3760527.html
Copyright © 2011-2022 走看看