经典排序算法--快速排序

一、快速排序的基本思想:

  快速排序使用了分治的思想,通过一趟排序将待排序列分割成两部分,其中一部分记录的关键字均比另一部分记录的关键字小。之后分别对这两部分记录继续进行排序,以达到整个序列有序的目的。

二、快速排序的三个步骤

1) 选择基准:在待排序列中,按照某种方式挑出一个元素,作为 “基准”(pivot);

2) 分割操作:以该基准在序列中的实际位置,把序列分成两个子序列。如果为升序,则此时,在基准左边的元素都比该基准小,在基准右边的元素都比基准大;而基准则在排序后正确的位置上。

3) 递归地对两个序列进行快速排序,直到序列为空或者只有一个元素;

三、选择基准元的方式

对于分治算法,当每次划分时,算法若都能分成两个等长的子序列时,那么分治算法效率会达到最大。也就是说,基准的选择是很重要的。选择基准的方式决定了两个分割后两个子序列的长度,进而对整个算法的效率产生决定性影响。

最理想的方法是,选择的基准恰好能把待排序序列分成两个等长的子序列。

方法一:固定基准元(基本的快速排序)

  选取第一个元素或最后一个元素作为基准元。

 1 public static void sortCommon(int[] data,int low,int high){
 2         if(high>low){
 3             //进行一次排序,将基准元放置在正确顺序的位置上,并确定当前基准元位置
 4             int key = getStandard(data, low, high);
 5             //分别将基准元左侧和基准元右侧当做一个序列从新进行排序,知道序列的长度为1
 6             sortCommon(data, low, key-1);
 7             sortCommon(data, key+1, high);
 8         }
 9     }
10
11     public static int getStandard(int[] data,int low,int high){
12         //将基准元提取出来
13         int tmp = data[low];
14         while (low<high) {
15             //从右往左查询,查找第一个小于基准元的元素,并将其放置在data[low]位置
16             while(low<high&&data[high]>=tmp){
17                 high--;
18             }
19             data[low] = data[high];
20             //从左往右查询,查找第一个大于基准元的元素,并将其放置在data[high]位置
21             while(low<high&&data[low]<=tmp){
22                 low++;
23             }
24             data[high] = data[low];
25         }
26         //基准元归为
27         data[low] = tmp;
28         return low;
29     }

方法二:随机基准元

思想:取待排序列中任意一个元素作为基准元。

引入的原因:在待排序列是部分有序时,固定选取基准元使快排效率底下,要缓解这种情况,就引入了随机选取基准元。

 1     public static void sortRandom(int[] data,int low,int high){
 2         if(high>low){
 3             //进行一次排序,将基准元放置在正确顺序的位置上,并确定当前基准元位置
 4             getStandardRandom(data, low, high);
 5             int key = getStandard(data, low, high);
 6             //分别将基准元左侧和基准元右侧当做一个序列从新进行排序,知道序列的长度为1
 7             sortRandom(data, low, key-1);
 8             sortRandom(data, key+1, high);
 9         }
10     }
11
12     private static void getStandardRandom(int[] data, int low, int high) {
13         Random r = new Random();
14         int ran = low+r.nextInt(high-low);
15         //将基准元提取出来
16         swap(data, low, ran);
17     }

方法三:三数取中

引入的原因:虽然随机选取基准时,减少出现不好分割的几率,但是还是最坏情况下还是O(n^2),要缓解这种情况,就引入了三数取中选取基准。

分析:最佳的划分是将待排序的序列分成等长的子序列,最佳的状态我们可以使用序列的中间的值,也就是第N/2个数。可是,这很难算出来,并且会明显减慢快速排序的速度。这样的中值的估计可以通过随机选取三个元素并用它们的中值作为基准元而得到。事实上,随机性并没有多大的帮助,因此一般的做法是使用左端、右端和中心位置上的三个元素的中值作为基准元。显然使用三数中值分割法消除了预排序输入的不好情形,并且减少快排大约14%的比较次数。

举例:待排序序列为:8 1 4 9 6 3 5 2 7 0

左边为:8,右边为0,中间为6

我们这里取三个数排序后,中间那个数作为枢轴,则枢轴为6

注意:在选取中轴值时,可以从由左中右三个中选取扩大到五个元素中或者更多元素中选取,一般的,会有(2t+1)平均分区法(median-of-(2t+1),三平均分区法英文为median-of-three。

具体思想:对待排序序列中low、mid、high三个位置上数据进行排序,取他们中间的那个数据作为基准,并用0下标元素存储基准。

即:采用三数取中,并用0下标元素存储基准。

 1 public static void sortMiddleOfThree(int[] data,int low,int high){
 2         if(high>low){
 3             //三数去中,并将中间数换到low位置上
 4             middleOfThree(data, low, high);
 5             //进行一次排序,将基准元放置在正确顺序的位置上,并确定当前基准元位置
 6             int key = getStandard(data, low, high);
 7             //分别将基准元左侧和基准元右侧当做一个序列从新进行排序,知道序列的长度为1
 8             sortMiddleOfThree(data, low, key-1);
 9             sortMiddleOfThree(data, key+1, high);
10         }
11     }
12     private static void middleOfThree(int[] data, int low, int high) {
13         int middle = (high-low)/2+low;
14         if (data[middle] > data[high])
15         {
16             swap(data, middle, high);
17         }
18         if (data[low] > data[high])
19         {
20             swap(data, low, high);
21         }
22         if (data[middle] > data[low])
23         {
24             swap(data, middle, low);
25         }
26     }

四.  两种优化的方法

优化一:当待排序序列的长度分割到一定大小后,使用插入排序

原因:对于很小和部分有序的数组,快排不如插排好。当待排序序列的长度分割到一定大小后,继续分割的效率比插入排序要差,此时可以使用插排而不是快排。

截止范围:待排序序列长度N = 10,虽然在5~20之间任一截止范围都有可能产生类似的结果,这种做法也避免了一些有害的退化情形。

—-摘自《数据结构与算法分析》Mark Allen Weiness 著

 1 public static void sortThreeInsert(int[] data, int low, int high){
 2         if(high-low+1<10){
 3             insertSort(data);
 4             return;
 5         }else{
 6             //三数去中,并将中间数换到low位置上
 7             middleOfThree(data, low, high);
 8             //进行一次排序,将基准元放置在正确顺序的位置上,并确定当前基准元位置
 9             int key = getStandard(data, low, high);
10             //分别将基准元左侧和基准元右侧当做一个序列从新进行排序,知道序列的长度为1
11             sortThreeInsert(data, low, key-1);
12             sortThreeInsert(data, key+1, high);
13         }
14     }

优化二:在一次分割结束后,可以把与Key相等的元素聚在一起,继续下次分割时,不用再对与key相等元素分割

举例:

待排序序列 1 4 6 7 6 6 7 6 8 6

三数取中选取基准:下标为4的数6

转换后,待分割序列:6 4 6 7 1 6 7 6 8 6

基准key:6

本次划分后,未对与key元素相等处理的结果:1 4 6 6 7 6 7 6 8 6

下次的两个子序列为:1 4 6 和 7 6 7 6 8 6

本次划分后,对与key元素相等处理的结果:1 4 6 6 6 6 6 7 8 7

下次的两个子序列为:1 4 和 7 8 7

经过对比,我们可以看出,在一次划分后,把与key相等的元素聚在一起,能减少迭代次数,效率会提高不少

具体过程:在处理过程中,会有两个步骤

第一步,在划分过程中,把与key相等元素放入数组的两端

第二步,划分结束后,把与key相等的元素移到枢轴周围

 1 public static void sortThreeInsertGather(int[] data, int low, int high){
 2         if(high-low+1<10){
 3             insertSort(data);
 4             return;
 5         }else{
 6             //三数去中,并将中间数换到low位置上
 7             middleOfThree(data, low, high);
 8             //进行一次排序,确定基准元位置,并将和基准元相等的元素,一直数组的两侧
 9             //first 和last 用来计算基准元
10             int first = low;
11             int last = high;
12             //left和right 用来记录和基准元相等的元素
13             int left = low;
14             int right = high;
15             //leftLength和rightLength用来记录左右两侧各有多少和基准元相等的元素
16             int leftLength = 0;
17             int rightLength = 0;
18             int tmp = data[first];
19             while(first<last){
20                 while(first<last&&data[last]>=tmp){
21                     if(data[last]==tmp){
22                         swap(data, last, right);
23                         right--;
24                         rightLength++;
25                     }
26                     last--;
27                 }
28                 data[first] = data[last];
29                 while(first<last&&data[first]<=tmp){
30                     if(data[first]==tmp){
31                         swap(data, first, left);
32                         left++;
33                         leftLength++;
34                     }
35                     first++;
36                 }
37                 data[last] = data[first];
38             }
39             data[first] = tmp;
40
41             //一次排序完成,将两侧与基准元相等的元素移到基准元旁边
42             int i= first-1;
43             int j = low;
44             while (j < left && data[i] != tmp)
45             {
46                 swap(data, i, j);
47                 i--;
48                 j++;
49             }
50             i = last + 1;
51             j = high;
52             while (j > right && data[i] != tmp)
53             {
54                 swap(data, i, j);
55                 i++;
56                 j--;
57             }
58             sortThreeInsert(data, low, first-1-leftLength);
59             sortThreeInsert(data, first+1+rightLength, high);
60         }
61     }
62     

以下是全部的测试程序源码:

  1 package sortDemo;
  2
  3 import java.util.Random;
  4
  5 public class QuickSort {
  6
  7     public static void main(String[] args) {
  8         Random r = new Random();
  9         int[] data = new int[100];
 10         for (int i = 0; i < data.length; i++) {
 11             data[i] = r.nextInt(10);
 12         }
 13         Long startTime = System.currentTimeMillis();
 14         print(data);
 15         Long endTime = System.currentTimeMillis();
 16         //sortCommon(data, 0, data.length-1);
 17         //sortRandom(data, 0, data.length-1);
 18         //sortMiddleOfThree(data, 0, data.length-1);
 19         //sortThreeInsert(data, 0, data.length-1);
 20         sortThreeInsertGather(data, 0, data.length-1);
 21         print(data);
 22         System.out.println("排序用时:"+(endTime-startTime));
 23
 24
 25
 26     }
 27
 28     public static void sortCommon(int[] data,int low,int high){
 29         if(high>low){
 30             //进行一次排序,将基准元放置在正确顺序的位置上,并确定当前基准元位置
 31             int key = getStandard(data, low, high);
 32             //分别将基准元左侧和基准元右侧当做一个序列从新进行排序,知道序列的长度为1
 33             sortCommon(data, low, key-1);
 34             sortCommon(data, key+1, high);
 35         }
 36     }
 37
 38     public static int getStandard(int[] data,int low,int high){
 39         //将基准元提取出来
 40         int tmp = data[low];
 41         while (low<high) {
 42             //从右往左查询,查找第一个小于基准元的元素,并将其放置在data[low]位置
 43             while(low<high&&data[high]>=tmp){
 44                 high--;
 45             }
 46             data[low] = data[high];
 47             //从左往右查询,查找第一个大于基准元的元素,并将其放置在data[high]位置
 48             while(low<high&&data[low]<=tmp){
 49                 low++;
 50             }
 51             data[high] = data[low];
 52         }
 53         //基准元归为
 54         data[low] = tmp;
 55         return low;
 56     }
 57
 58     public static void sortRandom(int[] data,int low,int high){
 59         if(high>low){
 60             //进行一次排序,将基准元放置在正确顺序的位置上,并确定当前基准元位置
 61             getStandardRandom(data, low, high);
 62             int key = getStandard(data, low, high);
 63             //分别将基准元左侧和基准元右侧当做一个序列从新进行排序,知道序列的长度为1
 64             sortRandom(data, low, key-1);
 65             sortRandom(data, key+1, high);
 66         }
 67     }
 68
 69     private static void getStandardRandom(int[] data, int low, int high) {
 70         Random r = new Random();
 71         int ran = low+r.nextInt(high-low);
 72         //将基准元提取出来
 73         swap(data, low, ran);
 74     }
 75
 76     public static void sortMiddleOfThree(int[] data,int low,int high){
 77         if(high>low){
 78             //三数去中,并将中间数换到low位置上
 79             middleOfThree(data, low, high);
 80             //进行一次排序,将基准元放置在正确顺序的位置上,并确定当前基准元位置
 81             int key = getStandard(data, low, high);
 82             //分别将基准元左侧和基准元右侧当做一个序列从新进行排序,知道序列的长度为1
 83             sortMiddleOfThree(data, low, key-1);
 84             sortMiddleOfThree(data, key+1, high);
 85         }
 86     }
 87     private static void middleOfThree(int[] data, int low, int high) {
 88         int middle = (high-low)/2+low;
 89         if (data[middle] > data[high])
 90         {
 91             swap(data, middle, high);
 92         }
 93         if (data[low] > data[high])
 94         {
 95             swap(data, low, high);
 96         }
 97         if (data[middle] > data[low])
 98         {
 99             swap(data, middle, low);
100         }
101     }
102
103     public static void sortThreeInsert(int[] data, int low, int high){
104         if(high-low+1<10){
105             insertSort(data);
106             return;
107         }else{
108             //三数去中,并将中间数换到low位置上
109             middleOfThree(data, low, high);
110             //进行一次排序,将基准元放置在正确顺序的位置上,并确定当前基准元位置
111             int key = getStandard(data, low, high);
112             //分别将基准元左侧和基准元右侧当做一个序列从新进行排序,知道序列的长度为1
113             sortThreeInsert(data, low, key-1);
114             sortThreeInsert(data, key+1, high);
115         }
116     }
117
118     public static void sortThreeInsertGather(int[] data, int low, int high){
119         if(high-low+1<10){
120             insertSort(data);
121             return;
122         }else{
123             //三数去中,并将中间数换到low位置上
124             middleOfThree(data, low, high);
125             //进行一次排序,确定基准元位置,并将和基准元相等的元素,一直数组的两侧
126             //first 和last 用来计算基准元
127             int first = low;
128             int last = high;
129             //left和right 用来记录和基准元相等的元素
130             int left = low;
131             int right = high;
132             //leftLength和rightLength用来记录左右两侧各有多少和基准元相等的元素
133             int leftLength = 0;
134             int rightLength = 0;
135             int tmp = data[first];
136             while(first<last){
137                 while(first<last&&data[last]>=tmp){
138                     if(data[last]==tmp){
139                         swap(data, last, right);
140                         right--;
141                         rightLength++;
142                     }
143                     last--;
144                 }
145                 data[first] = data[last];
146                 while(first<last&&data[first]<=tmp){
147                     if(data[first]==tmp){
148                         swap(data, first, left);
149                         left++;
150                         leftLength++;
151                     }
152                     first++;
153                 }
154                 data[last] = data[first];
155             }
156             data[first] = tmp;
157
158             //一次排序完成,将两侧与基准元相等的元素移到基准元旁边
159             int i= first-1;
160             int j = low;
161             while (j < left && data[i] != tmp)
162             {
163                 swap(data, i, j);
164                 i--;
165                 j++;
166             }
167             i = last + 1;
168             j = high;
169             while (j > right && data[i] != tmp)
170             {
171                 swap(data, i, j);
172                 i++;
173                 j--;
174             }
175             sortThreeInsert(data, low, first-1-leftLength);
176             sortThreeInsert(data, first+1+rightLength, high);
177         }
178     }
179
180
181
182     public static void insertSort(int[] a){
183         //从下标为1开始比较,知道数组的末尾
184         for (int i = 1; i < a.length; i++) {
185             int j;
186             //将要比较的元素,拿出待比较过后再插入数组
187             int tmp = a[i];
188             //一次与前一元素比较,如果前一元素比要插入的元素大,则互换位置
189             for (j = i-1; j >=0&&a[j]>tmp; j--) {
190                 a[j+1] = a[j];
191             }
192             //将比较的元素插入
193             a[j+1] = tmp;
194         }
195     }
196
197     public static void print(int[] a){
198         for (int i = 0; i < a.length; i++) {
199             System.out.print(a[i]+" ");
200         }
201         System.out.println();
202     }
203
204     private static void swap(int[] data,int i,int j){
205         if(i==j){
206             return;
207         }
208         data[i] = data[i] + data[j];
209         data[j] = data[i] - data[j];
210         data[i] = data[i] - data[j];
211     }
212 }

学习整理自:http://www.cnblogs.com/HuoAA/p/4338424.html

时间: 2024-07-28 21:24:48

经典排序算法--快速排序的相关文章

经典排序算法 - 快速排序Quick sort

经典排序算法 - 快速排序Quick sort 原理,通过一趟扫描将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列 举个例子 如无序数组[6 2 4 1 5 9] a),先把第一项[6]取出来, 用[6]依次与其余项进行比较, 如果比[6]小就放[6]前边,2 4 1 5都比[6]小,所以全部放到[6]前边 如果比[6]大就放[6]后边,9比[6]大,放到[6

经典排序算法——快速排序

对于一个int数组,请编写一个快速排序算法,对数组元素排序. 给定一个int数组A及数组的大小n,请返回排序后的数组. 测试样例: [1,2,3,5,2,3],6 [1,2,2,3,3,5] class QuickSort { public: int* quickSort(int* A, int n) { // write code here if(A==NULL || n<2) return A; process(A,0,n-1); return A; } int* process(int*

经典排序算法

经典排序算法(via  kkun) 经典排序算法,以下文章参考了大量网上的资料,大部分都给出了出处 这一系列重点在理解,所以例子什么的都是最简单的情况,难免失误之处,多指教 大多数排序算法都给出了每一步的状态,以方便初学者更容易理解,通俗易懂,部分难以理解的排序算法则给出了大量的图示,也算是一个特色吧 经典排序算法 - 快速排序Quick sort 经典排序算法 - 桶排序Bucket sort 经典排序算法 -  插入排序Insertion sort 经典排序算法 - 基数排序Radix so

【转】经典排序算法

地址:http://www.cnblogs.com/kkun/archive/2011/11/23/2260312.html 大多数排序算法都给出了每一步的状态,以方便初学者更容易理解,通俗易懂,部分难以理解的排序算法则给出了大量的图示,也算是一个特色吧 经典排序算法 - 快速排序Quick sort 经典排序算法 - 桶排序Bucket sort 经典排序算法 -  插入排序Insertion sort 经典排序算法 - 基数排序Radix sort 经典排序算法 - 鸽巢排序Pigeonho

经典排序算法【转】

转自 还有多少青春可以挥霍 经典排序算法 - 快速排序Quick sort 经典排序算法 - 桶排序Bucket sort 经典排序算法 -  插入排序Insertion sort 经典排序算法 - 基数排序Radix sort 经典排序算法 - 鸽巢排序Pigeonhole sort 经典排序算法 - 归并排序Merge sort 经典排序算法 - 冒泡排序Bubble sort 经典排序算法 - 选择排序Selection sort 经典排序算法 - 鸡尾酒排序Cocktail sort 经

[经典排序算法][集锦]

http://www.cnblogs.com/kkun/archive/2011/11/23/2260312.html 经典排序算法 经典排序算法,以下文章参考了大量网上的资料,大部分都给出了出处 这一系列重点在理解,所以例子什么的都是最简单的情况,难免失误之处,多指教 大多数排序算法都给出了每一步的状态,以方便初学者更容易理解,通俗易懂,部分难以理解的排序算法则给出了大量的图示,也算是一个特色吧 经典排序算法 - 快速排序Quick sort 经典排序算法 - 桶排序Bucket sort 经

C#实现所有经典排序算法

C# 实现所有经典排序算法 1 .选择排序 选择排序 原理: 选择排序是从冒泡排序演化而来的,每一轮比较得出最小的那个值,然后依次和每轮比较的第一个值进行交换. 目的:按从小到大排序. 方法:假设存在数组:72, 54, 59, 30, 31, 78, 2, 77, 82, 72 第一轮依次比较相邻两个元素,将最小的一个元素的索引和值记录下来,然后和第一个元素进行交换. 如上面的数组中,首先比较的是72,54,记录比较小的索引是54的索引1.接着比较54和59,比较小的索引还是1.直到最后得到最

经典排序算法的PHP实现类

近期广受笔试摧残,对于各种排序也是晕头转向. 更坑爹的是貌似大多都是用C++.Java实现相关算法,让我搞PHP的情何以堪,更何况,PHP本身就有排序函数sort(),其实来说,是很简单的,这也可能是为什么不用PHP进行排序吧. 但考虑到PHP毕竟也是一门面向对象的语言吧,我们利用原生的语法,也是可以实现经典排序算法的,先不说性能如何,切不要妄自菲薄吧. 下面为具体的经典排序算法的PHP实现类. <?php /** * Author: helen * CreateTime: 2016/4/15

七种经典排序算法最全攻略

经典排序算法在面试中占有很大的比重,也是基础.包括冒泡排序,插入排序,选择排序,希尔排序,归并排序,快速排序,堆排序.希望能帮助到有需要的同学.全部程序采用JAVA实现. 本篇博客所有排序实现均默认从小到大. 一.冒泡排序 BubbleSort 介绍: 冒泡排序的原理非常简单,它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来. 步骤: 比较相邻的元素.如果第一个比第二个大,就交换他们两个. 对第0个到第n-1个数据做同样的工作.这时,最大的数就"浮"到了