24*:排序算法7:快速排序(分区值,分而治之)

问题

 

目录

 

预备

快排利用了分治的思想,分而治之,分治算法的基本思想是将一个规模为N的问题,分解成K个规模较小的子问题,这些子问题相互独立且问题性质相同。

求解出子问题的解,合并得到原问题的解。拆分问题总不可能手脚并用一个个拆分,因此分治算法大多采用递归实现。 

正文

快速排序

1:算法描述

对A[p....r]进行快速排序的分治过程:

分解:选择一个枢轴(pivot)元素划分数组。将数组 A[p..r] 划分为两个子数组(可能为空) A[p..q-1] 和 A[q+1..r],使得 A[q] 大于等于A[p..q-1] 中的每个元素,小于 A[q+1..r] 中的每个元素。计算下标 q 的值也是划分过程的一部分;求解:通过递归调用快速排序,对子数组 A[p..q-1] 和 A[q+1..r] 分别排序;合并:子数组在原地排序,故无需合并操作,数组 A[p...r] 已经有序。

2:算法思想
每次选一个pivot(分区值),其他数依次和 pivot 比较,大的放右边小的放左边,然后再对左边和右边的两组数,分别选出pivot,重复比较,大的放左边,小的放右边;重复步骤,直到最后都变成单个元素,整个数组就成了有序的序列。

​ 首先,每次重复,pivot 一定会有序,这点和冒泡排序很像,冒泡排序也是每次遍历冒泡,都会有一个元素排序正确;再者,快排也是两两比较,交换位置,和冒泡排序也是相似的,快排的核心交换代码和冒泡神似,这些点可说快速排序是冒泡排序的变种。

3:动图演示

  • 明黄色代表此次遍历选定的 pivot,每次选数组第一个数作为 pivot,本数组 pivot 分别为3,38,19,4,26,27,46,47,50;

  • 暗黄色代表已经排好序的元素;

  • 绿色代表比基准值 pivot 小的数据,紫色代表比基准值 pivot 大的数据,绿色子数组和紫色子数组有会进行切割数组排序,直到子数组只有一个元素为止;

  • 每次遍历过后,至少有一个元素会是有序的, pivot 元素一定有序。

4:图解分析:

图解还是用上面数组里的数据做分析,动图里面元素太多了。

 

 

明黄色代表此分组选定的 pivot, 暗黄色代表已经排好序的,当分组内只有一个元素不需要再排序。

可知,每次遍历之后,pivot 是一定有序的,其他元素如果组内只剩下一个元素也有序。

5:代码实现

  • 每次取数组第一个元素作为基准值 pivot,设定数组基准值 pivot 右边第一个数为mark用来存储比 pivot 小的元素,遍历数组;

  • 元素大于 pivot,正常,继续遍历;

  • 遍历元素小于 pivot,将此元素放入mark位置,即此元素与mark所指元素互换,选择mark后面一个元素位置继续存储小于pivot值的元素;

  • 一趟遍历下来,mark存储了所有比第一个元素pivot小的元素,将pivot与mark最后一个元素交换,使得pivot去中间,pivot左边的都比它小,右边的都比它大

public class QuickSort {
    public static int[] quickSort(int[] arr) {
        return sort(arr, 0, arr.length-1);
    }

    public static int[] sort(int[] arr, int left, int right) {
        // 递归终止条件,子数组长度为1
        if (left >= right) {
            return arr;
        }
        //获取中轴元素所处的位置,即 pivot 的值
        int pivot = partition(arr, left, right);
        //切割,递归先排左边数组
        arr = sort(arr, left, pivot - 1);
        //切割,递归排好右边数组
        arr = sort(arr, pivot + 1, right);
        return arr;
    }

    private static int partition(int[] arr, int left, int right) {
        // pivot 选取数组第一个元素
        int pivot = arr[left];
        // mark 初始选取 pivot 右边第一个元素,存储小于 pivot 的元素, mark 最后指的元素就是小于 pivot 的数组最后一个值
        int mark = left + 1;
        for (int i = mark; i <= right; i++) {
            // 如果遍历的元素小于 pivot 元素,和 mark 元素交换
            if (arr[i] < pivot) {
                int temp = arr[i];
                arr[i] = arr[mark];
                arr[mark] = temp;
                // mark 元素向右挪一位
                mark ++;
            }
        }
        // 交换到最后 mark 多加了1,需减去;
        mark -= 1;
        // 如果left==mark,表示没有交换过,没有比pivot小的值,这个pivot即为一组,直接返回 mark
        if (left < mark) {
            // pivot 所指第一个元素和 mark元素交换,即pivot元素和小于pivot的最后一个元素互换,使得 pivot 左边元素全部小于pivot
            arr[left] = arr[mark];
            arr[mark] = pivot;
        }
        return mark;
    }

}

 

稳定性分析

不稳定。

 

​ 还是3 1 1 2数组分析,第一次分区 [2,1,1], [3],第二次分区[1,1,2],[3],这里两个 1 就调换了位置

 

时间复杂度分析

最好的情况下,如果每次分区操作,都能正好把数组分成大小接近相等的两个小区间,用递归树来表示如下图:

 

第1层是n次,

第2层有2次递归,每次n/2次,共n次操作,

第3层有4次递归,每次n/4次,共n次操作,

……

(最后一层)第k层有k次递归,每次n/2^(k-1)次,共n次操作

由于递归结束的条件是只有一个元素,所以这里的**n/2^(k-1)=1**  =>  **k=logn+1** 

即递归树的**深度为logn**

时间复杂度=每层的操作次数*树的深度=nlogn 即:O(nlogn);
最坏情况下,如果数组中的数据原来已经是有序的了,比如1,3,5,6,8。如果我们每次选择最后一个元素作为pivot,那每次分区得到的两个区间都是不均等的。我们需要进行大约n次分区操作,才能完成快排的整个过程。每次分区我们平均要扫描大约n/2个元素,这种情况下,快排的时间复杂度就 从O(nlogn)退化成了O(n2)。
 
 
OC代码
原理:从数列中挑出一个元素,称为 "基准", 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分割之后,再以该基准为分界,分左右两边,分别挑选基准递归的进行排序。

时间复杂度:最差O(n^2),平均O(nlogn)

空间复杂度:O(nlogn)

-(void)quickSequence:(NSMutableArray *)arr andleft:(int)left andright:(int)right
{
    if (left >= right) {//如果数组长度为0或1时返回
        return ;
    }
    int key = [arr[left] intValue];
    int i = left;
    int j = right;
    
    while (i<j){
        while (i<j&&[arr[j] intValue]>=key) {
            j--;
        }
        arr[i] = arr[j];
        
        while (i<j&&[arr[i] intValue]<=key) {
            i++;
        }
        arr[j] = arr[i];
    }
    arr[i] = [NSString stringWithFormat:@"%d",key];
    [self quickSequence:arr andleft:left andright:i-1];
    [self quickSequence:arr andleft:i+1 andright:right];
}

注意

 

引用

1:极客算法训练笔记(六),十大经典排序之希尔排序,快速排序

2:OC-排序算法总结(冒泡、快速、插入、选择、希尔、堆)

3:iOS 开发中常用的排序(冒泡、选择、快速、插入、希尔、归并、基数)算法

posted on 2020-12-16 19:07  风zk  阅读(286)  评论(0编辑  收藏  举报

导航