算法练习——快速排序

 主要参考资料 : 算法导论

1:时间复杂度介绍

对于包含n个数的输入数组来说,快速排序是一种最坏情况时间复杂度为O(n2)的排序算法.虽然最坏情况时间复杂度很差,但是快速排序通常是实际排序应用中最好的选择,因为它的平均性能非常好:它的期望时间复杂度是O(nlgn).而且O(nlgn)中隐含的常数因子非常小。另外,它还能够进行原址排序,甚至在虚存环境中也能很好地工作。

2:基本思路

(1)分解:

数组A[p ... r]被划分为两个(可能为空)子数组A[p...q-l]和A[q+1 ... r],使得A[p ... q-l]中的每一个元素都小于等于A[q]
而A[q]也小于等于A[q+1...r]中的每个元素。其中,计算下标 q 也是划分过程的一部分。

(2)解决:通过递归调用快速排序,对子数组A[p ... q-l]和A[q+1...r] 进行排序.
(3)合并:因为子数组都是原址排序的,所以不需要合并操作:数组A[p...r]已经有序.

 

 下面给出快速排序的核心代码分析:

如何实现上面的数组划分过程:

 1 //定义函数用于主要的划分 p表示数组的下标,r表示数组的上标
 2     public int partition(int[] a ,int p, int r){
 3         int x = a[r];
 4         int i = p-1;
 5         for (int j = p; j <= r-1; j++) {
 6             if (a[j] <= x) {
 7                 i++;
 8                 swap(a,i,j);
 9             }
10         }
11         swap(a, i+1, r);
12         return i+1;
13     }

下面分析如何对数组进行划分:

(1)首先选择一个  x = A[r] 元素作为主元 (或者“哨兵”)围绕该元素进行划分数组。小于x   放在一起,大于 x 的放在一起。

(2)对于5-10行  循环体的每一轮迭代开始时,对于任意教组下标k,有:

1.若 p ≤  k ≤  i,则A[k]  ≤  x。
2.若 i+1 ≤ k ≤  j-l,则A[k ] >  x。
3.若 k = r,则 A[k]  = x。

 

上图表示PARTITION操作过程.数组项A[r]是主元x。浅阴影部分的数组元素都在划分的第一部分,其值都不大于x
深阴影部分的元素都在划分的第二部分,其值都大于x.无阴影的元素则是还未分人这两个部分中的任意一个。最后的白色元素就是主元x。
(a)初始的数组和变最设置.数组元素均未被放人前两个部分中的任何一个.
(b) 2与它自身进行交换,并被放入了元素值较小的那个部分。
(c)~(d)8和7被添加到元素值较大的那个部分中.
(e)l和8进行交换,数值较小的部分规模增加.
(f)数值3和7进行交换,数值较小的部分规模增加.
(g)~(h)5和6被包含进较大部分,循环结束.
(i)在第7~8行中,主元被交换,这样主元就位于两个部分之间

 

然后分别递归调用   quickSort(A ,  p, q-1 )  以及   quickSort(A , q+1, r)

 具体的代码实现如下:

 1 package com.hone.heapsort;
 2 
 3 
 4 public class QucikSort {
 5     
 6     //这里面使用一个变量q怎么解决执行第一个递归的时候造成的q值的变化,不在
 7     //等于第一次返回的中值
 8     public void quickSort(int[] A,int m,int n){
 9         if(m < n){
10             int q = partition(A, m, n);
11             quickSort(A, m, q-1);
12             quickSort(A, q+1, n);
13         }
14     }
15     
16     
17     //定义函数用于主要的划分 p表示数组的下标,r表示数组的上标
18     public int partition(int[] a ,int p, int r){
19         int x = a[r];
20         int i = p-1;
21         for (int j = p; j <= r-1; j++) {
22             if (a[j] <= x) {
23                 i++;
24                 swap(a,i,j);
25             }
26         }
27         swap(a, i+1, r);
28         return i+1;
29     }
30 
31     private void swap(int[] a, int i, int j) {
32         int temp = a[j];
33         a[j] = a[i];
34         a[i] = temp;
35     }
36 
37     public void printMy(int[] a){
38         for (int i = 0; i < a.length; i++) 
39             System.out.print(a[i]+" ");
40         System.out.println();
41     }
42     
43     public static void main(String[] args) {
44         int[] a = {2,8,7,1,3,5,6,4};
45         int m = 0;
46         int n = a.length-1;
47         QucikSort qsort = new QucikSort();
48         System.out.print("原数组: ");
49         qsort.printMy(a);
50         qsort.quickSort(a, m, n);
51         System.out.print("排序后: ");
52         qsort.printMy(a);
53     }
54 }

4:时间复杂度分析:

最坏情况:当每次划分都产生  n-1  个元素  以及  0个元素的时候,最坏情况就产生了。

T(n)  =  T(n-1)+T(0)+O(n)

也就是说,如果算法每次递归一次的时候都产生极大的不平衡性,则算法的最坏情况就产生了,并且时间复杂度为  O(n2)。

最好情况:在可能的最平衡的划分中,PARTITION得到的两个子问题的规模都不大于n/2。在这种情况下,快速排序的性能非常好.此时,算法运行时间的递归式为:

                        T(n) = 2T(n/2)+O(n)
     在上式中,可以得到时间复杂度为 O(nlogn)

并且在一般情况下,之后每次二分的比例为常数倍,算法的时间复杂度总是为   O(nlogn)

原文地址:https://www.cnblogs.com/xiaxj/p/7416650.html