分享

快速排序实现之三路划分, 三元中值法和插入排序处理小子文件(three-way-partition, mean pivot and insertion for small file of Quick...

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

算法代码:

void quickSort_3(int *array, int l, int r) {
        /**
         * 由于三路划分中有可能有两个不同的划分点,所以不能
         * 使用函数直接返回,这里将partition和quickSort驱动
         * 程序结合成一个方法;
         * */
        if(l>=r) return;
        /**
         * 选择pivot划分元素,并将其与array[r]交换
         * */
        int pivot, temp;
        pivot=l+rand()%(r-l+1);
        temp=array[pivot];
        array[pivot]=array[r];
        array[r]=temp;
        /**
         * 双向扫描:
         * left和right都为主动移动
         * lflag和rflag为被动移动
         * */
        int left=l, lflag=l;
        int right=r-1, rflag=r-1;
        while(true) {

                while(array[left]<=array[r] && left                        /**
                         * 如果array[left]与pivot相等,则将其交换到
                         * lflag当前指向的元素
                         * */
                        if(array[left]==array[r]) {
                                temp=array[left];
                                array[left]=array[lflag];
                                array[lflag]=temp;
                                lflag++;
                        }
                        left++;
                }

                while(array[right]>=array[r] && right>=l) {
                        /**
                         * 如果array[right]与pivot相等,则将其交换到
                         * rflag当前指向的元素
                         * */
                        if(array[right]==array[r]) {
                                temp=array[right];
                                array[right]=array[rflag];
                                array[rflag]=temp;
                                rflag--;
                        }
                        right--;
                }

                if(left>=right) break;
                /**
                 * 将左边大于pivot的元素与右边小于pivot的元素进行
                 * 交换
                 * */
                temp=array[left];
                array[left]=array[right];
                array[right]=temp;

                left++;right--;
        }
        /**
         * 由于left和lflag指向的当前元素都是即将需要处理的元素,
         * 也就是当处理结束之后,他们都需要左移一步才是已经处理好的
         * 元素; 将等于pivot的元素交换到中间
         * */
        lflag--;left--;
        while(lflag>=l) {
                temp=array[left];
                array[left]=array[lflag];
                array[lflag]=temp;
                left--;lflag--;
        }
        /**
         * 由于right和rflag指向的当前元素都是即将需要处理的元素,
         * 也就是当处理结束之后,他们都需要右移一步才是已经处理好的
         * 元素;将等于pivot的元素交换到中间
         * 注意:由于pivot本身也需要移动到中间,所以这里的判断条件
         * 包含r
         * */
        rflag++;right++;
        while(rflag<=r) {
                temp=array[right];
                array[right]=array[rflag];
                array[rflag]=temp;
                right++;rflag++;
        }
        /**
         * 最终递归处理左右子序列部分
         * */
        quickSort_3(array, l, left);
        quickSort_3(array, right, r);
}

int main() {
        int array[]={2,5,8,2,1,6};
        quickSort_3(array,0,5);
        for(int i=0;i<6;i++)
                printf("%d,",array[i]);
        return 1;
}

算法说明:
即使没有重复键,最后的移动开销就不需要,即使有也是线性问题;较好的处理了划分元素的相等值问题,没有多余的性能损耗,所以运行时间为线性N。
此程序将序列划分成三个部分:小于划分元素的元素(放置于a[l]到a[left]);等于划分元素的元素(放置于a[left+1]到a[right-1]);大于划分元素的元素(放置于a[right]到a[r]);由于直接进行如此划分不是特别方便,所以首先将left和right指针分别遇到的等于pivot的元素放置到最左边和最右边,当处理完所有元素之后才将两边等于pivot的元素拷贝到序列中间,并且分别递归处理left左边的序列和right右边的序列;

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

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

算法代码:

void insertSort(int *array, int l, int r);

int partition_4(int *array, int l, int r) {

        int temp;
        int pivot;
        /**
         * 获取array[l],array[(l+r)/2], array[r]
         * 的中间值
         * */
        if(array[l]<=array[(l+r)/2]<=array[r] ||
                        array[r]<=array[(l+r)/2]<=array[l])
                pivot=array[(l+r)/2];
        else if(array[(l+r)/2]<=array[r]<=array[l] ||
                        array[l]<=array[r]<=array[(l+r)/2])
                pivot=array[r];
        else if(array[(l+r)/2]<=array[l]<=array[r] ||
                        array[r]<=array[l]<=array[(l+r)/2])
                pivot=array[l];

        temp=array[pivot];
        array[pivot]=array[r];
        array[pivot]=temp;

        /**
         * 双向扫描:
         * left从array的左边l处开始向右处理,直到r-1
         * right从array的右边r-1处开始向左处理,直到l
         * left和right都是主动移动
         * */
        int left=l, right=r-1;
        while(true) {
                /**
                 * left左边的元素为小于等于array[r]的元素
                 * 并注意array[r]为最大值的情况,left会一直
                 * 移动到r
                 * */
                while(array[left]<=array[r] && left<>
                        left++;
                /**
                 * right右边的元素为大于array[r]的元素
                 * 并注意array[r]为最小值的情况,right会一直
                 * 移动到l-1
                 * 这里仅使用大于的逻辑关系还可以避免当array
                 * 都是相同元素的情况时指针交叉的发生
                 * */
                while(array[right]>array[r] && right>=l)
                        right--;
                /**
                 * 有四种序列情况:
                 * 1. 一般情况:left和right在序列中间的某个元素交叉
                 * 2. array[r]是最大值情况:left移动到r,right在r-1
                 * 3. array[r]是最小值情况:left在l,right移动到l-1
                 * 4. array所有元素为同一个值:left移动到r,right在r-1
                 * */
                if(left>=right)
                        break;
                /**
                 * 交换元素
                 * */
                temp=array[left];
                array[left]=array[right];
                array[right]=temp;

                left++;right--;
        }
        /**
         * 最终将array[r]的pivot元素与array[left]进行交换
         * 由于此时的array[right]比array[r]小,所以只能交换
         * array[left]
         * */
        temp=array[left];
        array[left]=array[r];
        array[r]=temp;
        return left;
}

void quickSort_4(int *array, int l, int r) {
        /**
         * 当序列大小小于11的时候,假设此时系统堆栈的开销已经
         * 大于处理递归序列本身的开销,则递归调用具有低效率,
         * 所以直接使用插入排序进行排序
         * */
        if(l-r>11) {
                insertSort(array, l, r);
                return;
        }
        /**
         * 利用partition方法获取划分元素
         * */
        int pivot=partition_4(array, l, r);
        /**
         * 划分元素已经到达最终位置,所以不用参与进一步处理
         * 分别递归处理左右部分的元素
         * */
        quickSort_4(array, l, pivot-1);
        quickSort_4(array, pivot+1, r);
}

int main() {
        int array[]={2,5,8,2,1,6};
        quickSort_4(array,0,5);
        for(int i=0;i<6;i++)
                printf("%d,",array[i]);
        return 1;
}

算法说明:
优势:当系统递归栈的开销所占递归处理本身的比例较高时,快速排序性能较低,从而可以使用直接排序而非递归处理小子文件;
三元中值划分法表示一种抽样估算的思想,也就是使得划分元素尽量接近序列的中间值,具体的抽样可不限于三个,并且在代码实现的时候,需要考虑到循环内部操作的优化,
并且在实现过程中,为了优化栈操作,有的较小序列直接使用插入排序,可以从系统的角度提升算法的性能。
时间:运行时间可以在N㏒N的基础上减少10%。小子文件大小在5-25之间的性能改善相似

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多