算法渣-排序-快速

简介: 没有一身好内功,招式再多都是空;算法绝对是防身必备,面试时更是不可或缺;跟着算法渣一起从零学算法

没有一身好内功,招式再多都是空;算法绝对是防身必备,面试时更是不可或缺;跟着算法渣一起从零学算法

定义

快速排序(Quicksort)是对冒泡排序的一种改进

快速排序由C. A. R. Hoare在1962年提出。

它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列

算法

设要排序的数组是A[0]……A[N-1],首先任意选取一个数据(通常选用数组的第一个数)作为关键数据,然后将所有比它小的数都放到它前面,所有比它大的数都放到它后面,这个过程称为一趟快速排序。值得注意的是,快速排序不是一种稳定的排序算法,也就是说,多个相同的值的相对位置也许会在算法结束时产生变动

一趟快速排序的算法是:

  1. 设置两个变量i、j,排序开始的时候:i=0,j=N-1;
  2. 以第一个数组元素作为关键数据,赋值给key,即key=A[0];
  3. 从j开始向前搜索,即由后开始向前搜索(j--),找到第一个小于key的值A[j],将A[j]和A[i]互换;
  4. 从i开始向后搜索,即由前开始向后搜索(i++),找到第一个大于key的A[i],将A[i]和A[j]互换;
  5. 重复第3、4步,直到i=j; (3,4步中,没找到符合条件的值,即3中A[j]不小于key,4中A[i]不大于key的时候改变j、i的值,使得j=j-1,i=i+1,直至找到为止。找到符合条件的值,进行交换的时候i, j指针位置不变。另外,i==j这一过程一定正好是i+或j-完成的时候,此时令循环结束)。

演示

上面的定义,算法都是很学术的表达,看得有点晕了,看一个示例:

对数组[2,10,8,22,34,5,12,28,21,11]进行快速排序

image.png

也可以通过动画更清晰了解整个排序过程

动画:

http://www.zhuxingsheng.com/tools/sort/sort.html

实现

快速排序有好些实现手法,左右指针法、挖坑法、前后指针(快慢指针)法

左右指针法

算法过程与上面的演示图差不多

先从数列中取出一个数作为中轴基数。
分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。
再对左右区间重复第二步,直到各区间只有一个数
/**
 * 分治法,从小到大排序
 * @param array
 */
public static int partition(int []array,int start,int end) {
    //取一位做为基数
    int pivot = array[end];//这儿取最后一位作为基数了
    int left = start;
    int right = end-1;//从倒数第二位开始比较
    //从两头向中间搜索,从小到大排序
    while (left < right) {
        //从left端开始搜索
        while(left < right && array[left] <= pivot) {
            left ++;
        }
        //从right端开始搜索
        while (left < right && array[right] >= pivot) {
            right -- ;
        }
        //两数交换,大的放到右边,小的放到左边
        if(left < right) {
            swap(array,left,right);
            left++;
            right--;
        }
    }
    //
    if(left != end && array[left] > array[end] ) {
        swap(array,left,end);
        return left;
    }
    //right == left
    return left+1;
}
/**
 * 分治法,从小到大排序
 * @param array
 * @param start
 * @param end
 */
private static void quicSort(int []array,int start,int end){
    if( start < end ){
        int partition = partition(array,start,end);
        print(array);
        quicSort(array,start,partition-1);
        quicSort(array,partition+1,end);
    }
}

挖坑法

挖坑填坑,也可以叫拆东墙补西墙

先演示一番,对[22,12,28,21,14]进行排序

index: 0 1 2 3 4
data: 22 12 28 21 14

第一步:挖最右边的数为坑

坑: pit=array[right]=14;左索引: left=0,右索引: right=3,

index: 0 1 2 3 4
data: 22 12 28 21 X

从left开始搜索,寻找比坑大的数,发现array[left=0]>14,那就用left=0 填 right=4的坑

array[right=4]=array[left=0];left++;

此时数组:

index: 0 1 2 3 4
data: X 12 28 21 22

left=1,right=3

第二步: 从right端开始找比pit小的数,发现array[1] < 14,那就用right=1 填 left=0的坑

array[left=0]= array[right=1];

此时数组:

index: 0 1 2 3 4
data: 12 X 28 21 22

left=1,right=0; left>right,那此回合结束,用pit填回坑array[left=1]

结束时:

index: 0 1 2 3 4
data: 12 14 28 21 22

发现14左都是小于它的数,右边都是大于它的数

以14为分界,两边数组进行递归下去,就行了

int partion(int arr[], int begin, int end)
{
    int pit = arr[end];//挖最右数,留一个坑
    int left = begin;
    int right = end;
    while (left < right)
    {
        //从最左开始搜索比坑大的数
        while (left < right && arr[left] <= pit)
            left++;
        if (left<right) {
            arr[right] = arr[left];//拿left去填坑,left成为新坑
        }
        while (left < right && arr[right] >= pit)
            right--;
        if (left < right) {
            arr[left] = arr[right];//right去填left坑,left成为新坑
        }
    }
    arr[left] = pit;//用key填坑
    return left;
}
void QuickSort(int arr[], int left, int right)
{
    if (left < right)
    {
        int mid = partion(arr, left, right);
        print(arr);
        QuickSort(arr, left, mid - 1);
        QuickSort(arr, mid + 1, right);
    }
}

前后指针法

大体思路:

  1. 定义两个指针,一前一后,cur(前)指向起始位置,prev(后)指向cur的前一个位置;选择数组最后一个元素作为key(right)
  2. cur找比key小的数,prev在cur没有找到的情况下,一直不动(即保证prev一直指向比key大的数);直到cur找到比key小的数(+ +prev && prev != cur时)然后++cur,prev仍然不动
  3. 直到cur走到right前一个位置,终止循环。最后++prev,交换prev和right的值。返回中间位置prev。最后再继续递归

image.png

int partion(int arr[], int left, int right)
{
    int key = arr[right];//取最后一位为key
    int cur = left;//当前指针
    int prev = left - 1;//前一个指针
    while (cur < right)
    {
        if(arr[cur] < key && ++prev != cur){//发现比key小的数
            swap(arr,cur,prev);
        }
        cur++;
    }
    //最后++prev交换
    swap(arr,++prev,cur);
    return prev;
}
void QuickSort(int arr[], int left, int right)
{
    if (left < right)
    {
        int mid = partion(arr, left, right);
        print(arr);
        QuickSort(arr, left, mid - 1);
        QuickSort(arr, mid + 1, right);
    }
}

分治

以上几个方法,不管怎样的思路,都是寻找一个标准数,让它成为一个分界,大于所有左边的数,小于所有右边的数,进行分而治之

复杂度

最好情况:最在最好的情况下,每次的划分都会恰好从中间将序列划分开来,那么只需要lgn次划分即可划分完成,是一个标准的分治算法Cn=2Cn/2+N,每一次划分都需要比较N次。时间复杂度O(nlogn)

最坏情况:在最坏的情况下,即序列已经排好序的情况下,每次划分都恰好把数组划分成了0,n两部分,那么需要n次划分,但是比较的次数则变成了n, n-1, n-2,….1, 所以整个比较次数约为n(n-1)/2~n2/2

比较和移动次数最少时间复杂度表示为O(nlogn);

比较和移动次数最多的时间复杂度表示为O(n^2);

使用的辅助存储空间最少为logn,最多为n^2;是不稳定的排序;

Best Average Worst Memory Stable
nlog(n) nlog(n) n^2 log(n) No



目录
相关文章
|
5月前
|
算法 C++
【洛谷 P1223】排队接水(贪心算法+结构体排序)
该问题要求安排$n$个人的排队顺序以最小化平均等待时间。每个人接水需时$T_i$,解决方案是让接水时间短的人优先。给定$n\leq1000$和$t_i\leq10^6$,代码示例使用C++实现,通过排序使时间从小到大排列,然后计算平均等待时间。样例输入为10个人的时间数组,输出为优化后的排队顺序及平均等待时间(291.90)。
53 0
|
3月前
|
算法
【算法】二分查找——在排序数组中查找元素的第一个和最后一个位置
【算法】二分查找——在排序数组中查找元素的第一个和最后一个位置
|
9天前
|
搜索推荐 算法 C语言
【排序算法】八大排序(上)(c语言实现)(附源码)
本文介绍了四种常见的排序算法:冒泡排序、选择排序、插入排序和希尔排序。通过具体的代码实现和测试数据,详细解释了每种算法的工作原理和性能特点。冒泡排序通过不断交换相邻元素来排序,选择排序通过选择最小元素进行交换,插入排序通过逐步插入元素到已排序部分,而希尔排序则是插入排序的改进版,通过预排序使数据更接近有序,从而提高效率。文章最后总结了这四种算法的空间和时间复杂度,以及它们的稳定性。
50 8
|
9天前
|
搜索推荐 算法 C语言
【排序算法】八大排序(下)(c语言实现)(附源码)
本文继续学习并实现了八大排序算法中的后四种:堆排序、快速排序、归并排序和计数排序。详细介绍了每种排序算法的原理、步骤和代码实现,并通过测试数据展示了它们的性能表现。堆排序利用堆的特性进行排序,快速排序通过递归和多种划分方法实现高效排序,归并排序通过分治法将问题分解后再合并,计数排序则通过统计每个元素的出现次数实现非比较排序。最后,文章还对比了这些排序算法在处理一百万个整形数据时的运行时间,帮助读者了解不同算法的优劣。
36 7
|
1月前
|
搜索推荐 Shell
解析排序算法:十大排序方法的工作原理与性能比较
解析排序算法:十大排序方法的工作原理与性能比较
49 9
|
1月前
|
算法 搜索推荐 Java
数据结构与算法学习十三:基数排序,以空间换时间的稳定式排序,速度很快。
基数排序是一种稳定的排序算法,通过将数字按位数切割并分配到不同的桶中,以空间换时间的方式实现快速排序,但占用内存较大,不适合含有负数的数组。
23 0
数据结构与算法学习十三:基数排序,以空间换时间的稳定式排序,速度很快。
|
1月前
|
算法
❤️算法笔记❤️-(每日一刷-83、删除排序链表中的重复项)
❤️算法笔记❤️-(每日一刷-83、删除排序链表中的重复项)
30 0
|
1月前
|
存储 算法 搜索推荐
算法进阶之路:Python 归并排序深度剖析,让数据排序变得艺术起来!
算法进阶之路:Python 归并排序深度剖析,让数据排序变得艺术起来!
70 0
|
3月前
|
搜索推荐 算法 Java
现有一个接口DataOperation定义了排序方法sort(int[])和查找方法search(int[],int),已知类QuickSort的quickSort(int[])方法实现了快速排序算法
该博客文章通过UML类图和Java源码示例,展示了如何使用适配器模式将QuickSort类和BinarySearch类的排序和查找功能适配到DataOperation接口中,实现算法的解耦和复用。
39 1
现有一个接口DataOperation定义了排序方法sort(int[])和查找方法search(int[],int),已知类QuickSort的quickSort(int[])方法实现了快速排序算法
|
3月前
|
算法 搜索推荐 Java
算法实战:手写归并排序,让复杂排序变简单!
归并排序是一种基于“分治法”的经典算法,通过递归分割和合并数组,实现O(n log n)的高效排序。本文将通过Java手写代码,详细讲解归并排序的原理及实现,帮助你快速掌握这一实用算法。
41 0