排序算法六:快速排序——冒泡算法2.0

快速排序(Quick Sort)
快速排序的基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,已达到整个序列有序。

算法描述:
快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下:

  • 从数列中挑出一个元素,称为“基准”(pivot)
  • 分区(partition)操作:重新排序数列,所以元素比基准小的放在基准前面,所有元素比基准值大的放在基准后面(相同的任意一边)。在这个分区退出之后,该基准就处于数列的中间位置;
  • 递归地(recursive)把小于基准元素的子数列和大于基准元素的子数列排序。

动画演示:在这里插入图片描述
(不知道有没有人和我一样,光看图还是看不懂)
其实很简单,我们不是要按顺序吗?一种思路是把大的放一块,然后小的放另一块,这样这两块自己内部再分,就像以前的分班一样,有普通班和重点班两个班,高考550以上重点班,550以下普通班,这样最终如果要统计排名,肯定重点班自己排好序,普通班排好序后直接加在重点班最后一名后面就好了。

这里的550就是基准,当然在快速排序算法里,我们一般选数组的第一个数字为基准,也可以随机找个基准,或者下面的三数取中选个基准。基准的作用可大可小,但选个好基准能节省时间是真的。你要是选个600就是跟计算机过不去,能让计算机越快跑完越好。时间就是金钱。

选好基准之后我们怎么把基准放在合适的位置呢,即怎么保证让基准的位置刚好它的左边全是比它小的,它的右边全是比它大的?

其实就是这样一种思想,数组就像一座桥,桥的两端分别有个哨兵巡游,一个从前往后走,一个从后往前走。

前面的哨兵遇到靠前的比基准大的数字,把它放到后面,遇到靠后的比基准小的数字,把它放到前面。 那么要怎么放呢?比如说前面走的哨兵发现了一个数字 520 比 550要小,把这个数字放到哪呢,毫无疑问当然是应该把这个数字放到靠后,后面往前走的哨兵就可以起到接应的效果了,给谁不是给呢,后面哨兵的位置又是已知的,交换一下两个哨兵所在位置的数字就好了。

将基准放到合适的位置与数据的分布息息相关,依据现在学到的知识来看,我们还是很难预测基准的位置的,即使利用三数取中法我们也只能得到一个不是那么极端的基准(或者太大,或者太小), 我们普遍是希望基准是中位数的。

哨兵的位移也是有学问的,一般是一个哨兵遇到一个冲突的数字(比基准大却靠前或比基准小却靠后)而和另一个方向的哨兵交换数字后,另一个方向的哨兵再开始位移检测冲突的数字。比如靠前位置的哨兵发现一个数字,和靠后位置的哨兵交换数字,靠后位置的哨兵再开始走。这样做好像更适合基准是中间数吧,所以希望两个哨兵都是相同进度往中间走的,而不是一个走向另一个的尽头。

二者汇合时意味着什么? 意味着他们所在位置的左边都是比基准小的,他们所在位置的右边都是比基准大的,因为他们只有在检测完冲突的数据之后才能位移,现在他们已经没有冲突的数据可以检测了。那么这个位置,放上基准数字就是画龙点睛了。

然后基准两侧的区域各自重复上述步骤。直到有序。

快速排序优化

  1. 优化选取基准
    2如果我们选取的 pivotkey 是处于整个序列的中间位置,那么我们可以将整个序列分成小数集合和大数集合了,但注意,如果 pivotkey 不是中间数。即总是固定选取第一个关键字或是其他位置的关键字,这是一种不合理的做法。
  • 三数取中法: 取三个关键字先进行排序,将中间数作为枢轴,一般是取左端、右端和中间三个数。
    在这里插入图片描述
    图片来源:https://www.cnblogs.com/chengxiao/p/6262208.html
package sortdemo;

import java.util.Arrays;

/**
 * Created by chengxiao on 2016/12/14.
 * 快速排序
 */
public class QuickSort {
    public static void main(String[] args) {
        int[] arr = {9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
        quickSort(arr, 0, arr.length - 1);
        System.out.println("排序结果:" + Arrays.toString(arr));
    }

    /**
     * @param arr
     * @param left  左指针
     * @param right 右指针
     */
    public static void quickSort(int[] arr, int left, int right) {
        if (left < right) {
            //获取枢纽值,并将其放在当前待处理序列末尾
            dealPivot(arr, left, right);
            //枢纽值被放在序列末尾
            int pivot = right - 1;
            //左指针
            int i = left;
            //右指针
            int j = right - 1;
            while (true) {
                while (arr[++i] < arr[pivot]) {
                }
                while (j > left && arr[--j] > arr[pivot]) {
                }
                if (i < j) {
                    swap(arr, i, j);
                } else {
                    break;
                }
            }
            if (i < right) {
                swap(arr, i, right - 1);
            }
            quickSort(arr, left, i - 1);
            quickSort(arr, i + 1, right);
        }

    }

    /**
     * 处理枢纽值
     *
     * @param arr
     * @param left
     * @param right
     */
    public static void dealPivot(int[] arr, int left, int right) {
        int mid = (left + right) / 2;
        if (arr[left] > arr[mid]) {
            swap(arr, left, mid);
        }
        if (arr[left] > arr[right]) {
            swap(arr, left, right);
        }
        if (arr[right] < arr[mid]) {
            swap(arr, right, mid);
        }
        swap(arr, right - 1, mid);
    }

    /**
     * 交换元素通用处理
     *
     * @param arr
     * @param a
     * @param b
     */
    private static void swap(int[] arr, int a, int b) {
        int temp = arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值