算法<基于三路划分的快速排序>

当待排序元素序列中有大量的重复排序码时,简单的快速排序算法的效率将会降到非常之低。一种直接的想法就是将待排序列分成三个子序列:一部分是排序码比基准元素排序码小的;一部分是与基准元素排序码等值的;一部分是比基准元素排序码大的,如下图所示:
这里写图片描述

但是,如果我们直接据此思想去编写实现算法的话,会让我们面临很大的困难。与基准元素等值的元素到底有多少?以及如何最快速有效地确定划分的边界?所以,完成这样的三路划分是非常困难的,甚至比两路划分过程更加复杂。
我们可以基于以下的思想实现三路划分:在划分的过程中,扫描时将遇到的左子序列中与基准元素排序码等值的元素放到序列的最左边,将遇到的右子序列中与基准元素排序码等值的元素放到序列的最右边。这样,我们会得到如下所示的序列划分图:

这里写图片描述

这里我们用到了四个指针,这里四个指针的含义:

leftLabelPtr(左子序列下一个与基准元素相等元素的下标),
rightLabelPtr(右子序列下一个与基准元素相等元素的下标), 
leftScanPtr(左子序列的扫描指针), 
rightScanPtr(右子序列的扫描指针)


当我们在左子序列中找到比基准元素大,且在右子序列中找到比基准元素小的元素的时候(此时左右扫描指针记录着这两和 元素),将两个元素进行交换,然后扫描指针接着扫描(左指针往右扫描,右指针往左扫描)。
直到左扫描指针大于等于右扫描指针的时候,我们跳出循环,然后将左边与基准元素相等的元素交换到中间,将右边与基准元素相等的元素交换到中间。然后递归地在左右子序列中调用快排。

下面是{25, 15, 30, 10, 50, 3, 5, 30}数组基于三路划分的快速排序的示意图:
这里写图片描述

代码实现:

/**
 * 三路划分的快速排序
 */

public class QuickSort_Three_way_division {
    public static void main(String[] args) {
        int[] arra = {25, 15, 30, 10, 50, 3, 5, 30};
        QuickSort_Three_way_division t = new QuickSort_Three_way_division();
        t.quickSort(arra, 0, arra.length - 1);
        for (int i = 0; i < arra.length; i++) {
            System.out.print(arra[i] + " ");
        }
    }

    private void quickSort(int[] nums, int left, int right) {
        if (right <= left)
            return;
        int leftLabelPtr, rightLabelPtr, leftScanPtr, rightScanPtr;
        int pivot;// 锚点
        leftScanPtr = leftLabelPtr = left;
        rightScanPtr = rightLabelPtr = right - 1;
        pivot = nums[right];
        while (true) {
            while (leftScanPtr < right && nums[leftScanPtr] <= pivot) {
                if (nums[leftScanPtr] == pivot) {
                    swap(nums, leftScanPtr, leftLabelPtr);
                    leftLabelPtr++;
                }
                leftScanPtr++;
            }
            while (left <= rightScanPtr && nums[rightScanPtr] >= pivot) {
                if (nums[rightScanPtr] == pivot) {
                    swap(nums, rightScanPtr, rightLabelPtr);
                    rightLabelPtr--;
                }
                rightScanPtr--;
            }
            if (leftScanPtr >= rightScanPtr)
                break;
        /*
        * 将左边大于pivot的元素与右边小于pivot元素进行交换
        */
            swap(nums, leftScanPtr, rightScanPtr);
            leftScanPtr++;
            rightScanPtr--;
        }
    /*
    * 因为工作指针i指向的是当前需要处理元素的下一个元素
    * 故而需要退回到当前元素的实际位置,然后将等于pivot元素交换到序列中间
    */
        leftScanPtr--;
        leftLabelPtr--;
        while (leftLabelPtr >= left) {
            swap(nums, leftScanPtr, leftLabelPtr);
            leftScanPtr--;
            leftLabelPtr--;
        }
    /*
    * 因为工作指针j指向的是当前需要处理元素的上一个元素
    * 故而需要退回到当前元素的实际位置,然后将等于pivot元素交换到序列中间
    */
        rightScanPtr++;
        rightLabelPtr++;
        while (rightLabelPtr <= right) {
            swap(nums, rightScanPtr, rightLabelPtr);
            rightScanPtr++;
            rightLabelPtr++;
        }

    /*
    * 递归遍历左右子序列
    */
        quickSort(nums, left, leftScanPtr);
        quickSort(nums, rightScanPtr, right);
    }
    public void swap(int[] nums, int i, int j) {
        if (i != j) {
            nums[i] = nums[i] ^ nums[j];
            nums[j] = nums[i] ^ nums[j];
            nums[i] = nums[i] ^ nums[j];
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
冒泡排序算法快速排序算法是两种不同的排序算法,它们在效率和实现上有一些明显的区别。 冒泡排序是一种简单的比较排序算法,它通过依次比较相邻的元素并交换位置来排序。具体步骤如下: 1. 从第一个元素开始,依次比较相邻的两个元素,如果顺序错误则交换位置。 2. 重复上述步骤,直到没有需要交换的元素。 冒泡排序的时间复杂度是O(n^2),其中n是待排序元素的数量。它是一种稳定的排序算法,因为相等元素之间的顺序不会改变。但是冒泡排序在最坏情况下需要进行n次遍历,因此效率较低。 快速排序是一种基于分治思想的排序算法,它通过递归地将数组划分为较小和较大的两个子数组,并对这两个子数组分别进行排序。具体步骤如下: 1. 选择一个基准元素,并将其他元素与基准元素比较,将较小的元素放到基准元素的左边,较大的元素放到右边。 2. 对基准元素左右两侧的子数组递归进行快速排序快速排序的时间复杂度平均为O(nlogn),最坏情况下为O(n^2)。它是一种不稳定的排序算法,因为在交换过程中相等元素的顺序可能改变。快速排序通常比冒泡排序快得多,尤其是在大规模数据集上。 因此,虽然冒泡排序和快速排序都是经典的排序算法,但在效率和实现上存在明显的差异。如果对于效率要求较高的排序任务,快速排序通常是更好的选择。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值