快速排序详解

快速排序(英语:Quicksort),又称划分交换排序(partition-exchange sort),简称快排,一种排序算法,最早由东尼·霍尔提出。在平均状况下,排序n个项目要{\displaystyle \ O(n\log n)}(大O符号)次比较。在最坏状况下则需要{\displaystyle O(n^{2})}次比较,但这种状况并不常见。事实上,快速排序{\displaystyle \Theta (n\log n)}通常明显比其他算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地达成,在数据结构中算很重要的知识点,是基于分而治之的思想。快速排序一般情况下是面试的必备考点,很多面试需要手写快速排序。

算法:

快速排序使用分治法(Divide and conquer)策略来把一个序列(list)分为两个子序列(sub-lists)。

步骤为:

  1. 从数列中挑出一个元素,称为“基准”(pivot)。
  2. 重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(相同的数可以到任何一边)。在这个分割结束之后,该基准就处于数列的中间位置。这个称为分割(partition)操作。
  3. 递归地(recursively)把小于基准值元素的子数列和大于基准值元素的子数列排序。

递归到最底部时,数列的大小是零或一,也就是已经排序好了。这个算法一定会结束,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最终的位置上去。

算法伪代码:

 function quicksort(q)
     var list less, pivotList, greater
     if length(q) ≤ 1 {
         return q
     } else {
         select a pivot value pivot from q
         for each x in q except the pivot element
             if x < pivot then add x to less
             if x ≥ pivot then add x to greater
         add pivot to pivotList
         return concatenate(quicksort(less), pivotList, quicksort(greater))
     }

平均时间复杂度:

即使如果我们无法随机地选择基准数值,对于它的输入之所有可能排列,快速排序仍然只需要{\displaystyle O(n\log n)}时间。因为这个平均是简单地将输入之所有可能排列的时间加总起来,除以n这个因数,相当于从输入之中选择一个随机的排列。当我们这样作,基准值本质上就是随机的,导致这个算法与随机数快速排序有一样的运行时间。

更精确地说,对于输入顺序之所有排列情形的平均比较次数,可以借由解出这个递归关系式可以精确地算出来。C(n)=n-1+{\frac {1}{n}}\sum _{i=0}^{n-1}(C(i)+C(n-i-1))=2n\ln n=1.39n\log _{2}n

在这里,{\displaystyle n-1}是分割所使用的比较次数。因为基准值是相当均匀地落在排列好的数列次序之任何地方,总和就是所有可能分割的平均。

这个意思是,平均上快速排序比理想的比较次数,也就是最好情况下,只大约比较糟39%。这意味着,它比最坏情况较接近最好情况。这个快速的平均运行时间,是快速排序比其他排序算法有实际的优势之另一个原因。

空间复杂度:

被快速排序所使用的空间,依照使用的版本而定。使用原地(in-place)分割的快速排序版本,在任何递归调用前,仅会使用固定的額外空間。然而,如果需要产生{\displaystyle O(\log n)}嵌套递归调用,它需要在他们每一个存储一个固定数量的信息。因为最好的情况最多需要{\displaystyle O(\log n)}次的嵌套递归调用,所以它需要{\displaystyle O(\log n)}的空间。最坏情况下需要{\displaystyle O(n)}次嵌套递归调用,因此需要{\displaystyle O(n)}的空间。

然而我们在这里省略一些小的细节。如果我们考虑排序任意很长的数列,我们必须要记住我们的变量像是leftright,不再被认为是占据固定的空间;也需要{\displaystyle O(\log n)}对原来一个n项的数列作索引。因为我们在每一个堆栈框架中都有像这些的变量,实际上快速排序在最好跟平均的情况下,需要{\displaystyle O(\log _{2}n)}空间的比特数,以及最坏情况下{\displaystyle O(n\log n)}的空间。然而,这并不会太可怕,因为如果一个数列大部分都是不同的元素,那么数列本身也会占据{\displaystyle O(n\log n)}的空间字节。

非原地版本的快速排序,在它的任何递归调用前需要使用{\displaystyle O(n)}空间。在最好的情况下,它的空间仍然限制在{\displaystyle O(n)},因为递归的每一阶中,使用与上一次所使用最多空间的一半,且

\sum _{i=0}^{\infty }{\frac {n}{2^{i}}}=2n

它的最坏情况是很恐怖的,需要

\sum _{i=0}^{n}(n-i+1)=\Theta (n^{2})

空间,远比数列本身还多。如果这些数列元素本身自己不是固定的大小,这个问题会变得更大;举例来说,如果数列元素的大部分都是不同的,每一个将会需要大约{\displaystyle O(\log n)}为原来存储,导致最好情况是{\displaystyle O(n\log n)}和最坏情况是{\displaystyle O(n^{2}\log n)}的空间需求。

Java:

public class Application {

    public static void qSort(int[] arr, int head, int tail) {
        if (head >= tail || arr == null || arr.length <= 1) {
            return;
        }
        int i = head, j = tail, pivot = arr[(head + tail) / 2];
        while (i <= j) {
            while (arr[i] < pivot) {
                ++i;
            }
            while (arr[j] > pivot) {
                --j;
            }
            if (i < j) {
                int t = arr[i];
                arr[i] = arr[j];
                arr[j] = t;
                ++i;
                --j;
            } else if (i == j) {
                ++i;
            }
        }
        qSort(arr, head, j);
        qSort(arr, i, tail);
    }

    public static void main(String[] args) {
        int[] arr = new int[]{1, 4, 8, 2, 55, 3, 4, 8, 6, 4, 0, 11, 34, 90, 23, 54, 77, 9, 2, 9, 4, 10};
        qSort(arr, 0, arr.length - 1);
        String out = "";
        for (int digit : arr) {
            out += (digit + ",");
        }
        System.out.println(out);
    }
}

C语言:

//快速排序实现 
 
#include "stdio.h"
#include "stdlib.h"
#include "time.h"//用于获取程序运行时间  

void quick_sort(int s[],int l,int r)
{
    if(l < r)
    {
        int i=l,j=r,x=s[l];
        while(i<j)
        {
            while(i<j && s[j]>=x)//从右到左找到第一个小于x的数  
                j--;
            if(i<j)
                s[i++]=s[j];
            
            while(i<j && s[i]<=x)//从左往右找到第一个大于x的数  
                i++;
            if(i<j)
                s[j--]=s[i]; 
            
        }
        
        s[i]=x;//i = j的时候,将x填入中间位置  
        quick_sort(s,l,i-1);//递归调用 
        quick_sort(s,i+1,r);        
    }
}


int main()
{
    clock_t start,finish;
    double totaltime;
    start=clock();
    
    /****下面为需要运行的主程序****/ 
    
    int a[] = {1,8,44,77,35,65,78,12,25,455,20,15,45};
    int length = sizeof(a)/sizeof(int);//求数组的长度  
    
    printf("原序列为: ");
    for(int i=0;i<length;i++)
    {
        printf("%3d",a[i]);
    }
    
    quick_sort(a,0,length);
    
    printf("\n排序后的序列为:");
    for(int i=0;i<length;i++)
    {
        printf("%3d",a[i]);
    }

    /********************************/
       
   finish=clock();
   totaltime=(double)(finish-start)/CLOCKS_PER_SEC;
   
   printf("\n程序运行的时间为: %.5f 秒",totaltime);

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值