1、什么是快速排序算法?
快速排序是由东尼·霍尔所发展的一种排序算法,犹如他的名字一样,速度快,效率高,也是实际
中最常用的一种算法,被称为20实际对世界影响最大的算法之一。
基本思想:
1): 从序列中挑出一个元素作为"基准"元素,一般是该序列的第一个元素或者是最后一个元素。
2): 把序列分成2个部分,其数值大于"基准"元素的元素放在"基准"元素的左边,否在放在"基准"元
素的右边,此时"基准"元素所在的位置就是正确的排序位置,这个过程被称为 partition(分区)。
3): 递归将"基准"元素左边的序列和"基准"元素右边的序列进行partition操作。
2、算法的演示
这个就是待排序的数组序列,第一个元素作为"基准"元素
给"基准"元素找到合适的位置,将比"基准"元素小的元素放在其左边,否则放在其右边
至此这个序列就成了这样了,这个过程成为partition
下面来看看partition的具体实现过程:
将"基准"元素用v表示,使用i作为遍历序列的索引值,j的位置
表示>v部分和<v部分的分界位置(也就是最后一个小于v的元
素所在位置)。
如果此时i指向的元素大于v,这个好处理,直接将i++即可,
也就表示大于v的元素多了一个
如果此时i指向的元素小于v,那么需要将i指向的元素与大于
v序列的第一个元素交换位置,即swap(arr[i], arr[j+1]),
然后再将i++,再将j++即可,表示小于v的元素多了一个。
如下图所示
进行swap(arr[i], arr[j+1])
j++
i++
由此可知,当遍历完成之后,就会出现这样的效果,然后我
们只需将元素v与j指向的元素交换位置即可
此时就出现了小于"基准"元素的元素在其左边,大于"基准"
元素的元素在其右边的分布情况
3、算法实现的动画效果
4、算法的实现(基于C++)
1 /*********************************** 随机化快速排序算法实现 ****************************************/ 2 // 对arr[left....right]进行partition操作 3 // 返回p,使得arr[left, p-1] < arr[p],arr[p+1, right] > arr[p] 4 template<typename T> 5 int __partition (T arr[], int left, int right) 6 { 7 T v = arr[left]; // 将第一个元素作为"基准"元素 8 int j = left; // 将<v部分的分界点位置j初始化为本序列中的第一个元素位置(也就是<v部分的最后一个元素位置) 9 10 // 将遍历序列的索引值i初始为第二个元素位置 11 for (int i = left + 1; i <= right; i++) { 12 if (arr[i] < v) { // 如果i指向的元素<v,那么将此元素与>v部分的第一个元素交换位置,然后j++,表示<v的元素又多了一个 13 j++; 14 std::swap(arr[j], arr[i]); // 这里采用了另一种那写法,因为j++之后指向的就是>v部分的第一个元素,交换位置之后其实类似于将>v的部分整体往右边移动了一个位置 15 } 16 } 17 18 std::swap(arr[left], arr[j]); // 遍历完成之后只需要将"基准"元素(也就是第一个元素)与当前j指向的位置交换位置即可 19 return j; // 因为"基准"元素并不属于<v的部分,所以交换之后此时j指向的元素就是"基准"元素 20 } 21 22 // 对arr[left...right]范围内数组序列进行快速排序操作 23 template<typename T> 24 void __quickSort (T arr[], int left, int right) 25 { 26 int p = __partition<T>(arr, left, right); // 对arr[left...right]区间元素进行partition操作,找到"基准"元素 27 __quickSort<T>(arr, left, p - 1); // 对基准元素之前的序列递归调用__quickSort函数 28 __quickSort<T>(arr, p + 1, right); // 对基准元素之后的序列递归调用__quickSort函数 29 } 30 31 template<typename T> 32 void quickSort (T arr[], int count) 33 { 34 __quickSort<T>(arr, 0, count - 1); // 调用__quickSort函数进行快速排序 35 } 36 /********************************************************************************************/
5、算法性能测试
与前面的2种归并排序算法相比较
测试数据量100000:
测试数据量1000000:
从上面可以知道,很明显快速排序要比归并排序快,这还是在快速排序没有优化的情况下。不同的
电脑由于配置不一样,可能得到的测试结果是不同的。
6、快速排序算法的优化
(1)第一种优化
在前面的学习过程中已经说到了,那就是对于几乎所有的高级算法都可以使用的一种优化方法,
当递归到元素个数很小时可以使用直接插入排序。
(2)第二种优化(随机化快速排序算法)
我们先来看一个例子,当待排序的数组序列接近为一个有序序列的时候,归并排序和快速排序的性能测试
测试数据量500000:
通过上面可以知道,当序列接近为有序状态的时候,快速排序慢得要死,这是为什么呢?请看下面的分析:
对于快速排序,因为我们是将第一个元素作为"基准"元素,但由于序列基本接近为有序,从而导致每一个
"基准"元素的左边没有一个元素,而全部
在他的右边,这样就会导致快速排序算法的高度为n,将退化为O(n^2)级别。
那么这种情况的解决办法就是: 尽可能的别让第一个元素成为"基准"元素,而最好使用中间位置的元素成为
"基准"元素,那如何做到这点呢?
解决办法就是"基准"元素随机产生,而不指定。请看下面的代码:
1 /*********************************** 随机化快速排序算法实现 ****************************************/ 2 // 对arr[left....right]进行partition操作 3 // 返回p,使得arr[left, p-1] < arr[p],arr[p+1, right] > arr[p] 4 template<typename T> 5 int __partition (T arr[], int left, int right) 6 { 7 std::swap(arr[left], arr[std::rand()%(right-left+1)+left]); // 随机产生"基准"元素所在位置,并与第一个元素交换位置 8 9 T v = arr[left]; // 将第一个元素作为"基准"元素 10 int j = left; // 将<v部分的分界点位置j初始化为本序列中的第一个元素位置(也就是<v部分的最后一个元素位置) 11 12 // 将遍历序列的索引值i初始为第二个元素位置 13 for (int i = left + 1; i <= right; i++) { 14 if (arr[i] < v) { // 如果i指向的元素<v,那么将此元素与>v部分的第一个元素交换位置,然后j++,表示<v的元素又多了一个 15 j++; 16 std::swap(arr[j], arr[i]); // 这里采用了另一种那写法,因为j++之后指向的就是>v部分的第一个元素,交换位置之后其实类似于将>v的部分整体往右边移动了一个位置 17 } 18 } 19 20 std::swap(arr[left], arr[j]); // 遍历完成之后只需要将"基准"元素(也就是第一个元素)与当前j指向的位置交换位置即可 21 return j; // 因为"基准"元素并不属于<v的部分,所以交换之后此时j指向的元素就是"基准"元素 22 } 23 24 // 对arr[left...right]范围内数组序列进行快速排序操作 25 template<typename T> 26 void __quickSort (T arr[], int left, int right) 27 { 28 if (right - left <= 40) { // 对归并到序列中元素个数较小时采用插入排序算法 29 __insertSortMG<T>(arr, left, right); 30 return; 31 } 32 33 int p = __partition<T>(arr, left, right); // 对arr[left...right]区间元素进行partition操作,找到"基准"元素 34 __quickSort<T>(arr, left, p - 1); // 对基准元素之前的序列递归调用__quickSort函数 35 __quickSort<T>(arr, p + 1, right); // 对基准元素之后的序列递归调用__quickSort函数 36 } 37 38 template<typename T> 39 void quickSort (T arr[], int count) 40 { 41 std::srand(std::time(NULL)); // 种下随机种子 42 __quickSort<T>(arr, 0, count - 1); // 调用__quickSort函数进行快速排序 43 } 44 /********************************************************************************************/
这次的改变在于: 在quickSort函数中种下了随机种子,然后在__partition函数中使用rand函数来产生随机的位置,
将此位置的元素作为"基准"元素,从而可以避免使用第一个元素作为"基准"。使用了随机化快速排序之后,虽然在排
序一般的序列时会比之前的快速排序算法要慢,但是之前的快速排序算法对于一个近乎有序的序列时就不行了,而随
机化快速排序就能够很好的解决这样的问题,所以随机化快速排序就能够兼顾这样两种不同的情况,而且还能够快速
的对序列进行排序。来看看与归并排序算法之间的性能比较:
一般序列数据量1000000:
近乎有序的序列数据量1000000:
从上面的测试得到的结果可以看出来,对于一般的序列使用随机化快速排序要比归并排序快,而对于近乎有序的序
列明显归并排序要快,这是归并排序的一个优势,之前说过;但是实际中出现近乎有序序列的概率是很低很低的,
所以完全可以认为随机化快速排序在总体上比归并排序快。