《算法导论》 — Chapter 7 高速排序

高速排序(QuickSort)也是一种排序算法,对包括n个数组的输入数组。最坏情况执行时间为O(n^2)。

尽管这个最坏情况执行时间比較差。可是高速排序一般是用于排序的最佳有用选择。这是由于其平均性能相当好。期望的执行时间为O(nlgn)。且O(nlgn)中隐含的常数因子非常小。另外它还能够进行就地排序在虚拟环境中也能非常好的工作。

GitHub chapter 7 程序代码下载

原理

高速排序也和合并排序一样,基于分治法,分为分解、解决、合并三个步骤。

分解:数组array[low…high]被分为两个(可能空)子数组array[low…temp-1]和array[temp+1…high]。使得array[low…temp-1]中的每个元素都小于等于array[temp],而array[temp+1…high]中的每个元素都大于array[temp],下标temp也是在这个过程中被计算出来;

解决:通过递归的调用高速排序。对子数组array[low…temp-1],array[temp+1…high]进行排序;

合并:由于两个子数组是就地排序的。将他们的合并不须要操作,整个数组array[low…high]是已经排好序的。

本章介绍了高速排序算法的原理、程序实现(包括随机化版本号)及其性能分析。

快排算法实现

#include <iostream>
#include <ctime>
#include <cstdlib>
#define N 10

using namespace std;

//高速排序的递归算法
void quickSort(int * array, int low, int high);
//求切割点
int partition(int * array, int low, int high);
//交换两个变量的值
void exchange(int &a, int &b);

int main()
{
    //声明一个待排序数组
    int array[N];
    //设置随机化种子,避免每次产生同样的随机数
    srand(time(0));
    for (int i = 0; i<N; i++)
    {
        array[i] = rand() % 101;//数组赋值使用随机函数产生1-100之间的随机数
    }
    cout << "排序前:" << endl;
    for (int j = 0; j<N; j++)
    {
        cout << array[j] << "  ";
    }
    cout << endl << "排序后:" << endl;
    //调用高速排序函数对该数组进行排序
    quickSort(array, 0, N - 1);
    for (int k = 0; k<N; k++)
    {
        cout << array[k] << "  ";
    }
    cout << endl;
    return 0;
}//main

void quickSort(int * array, int low, int high)
{
    if (low < high)
    {
        int temp = partition(array, low, high);
        quickSort(array, low, temp - 1);
        quickSort(array, temp + 1, high);
    }
}

int partition(int * array, int low, int high)
{
    int i = low - 1;
    //默认将划分段的最后一个元素为主元
    int x = array[high];

    for (int j = low; j<high; j++)
    {
        if (array[j] <= x)//在array[i]左边都是小于x即array[high]的数,右边均是大于它的数
        {
            i += 1;
            exchange(array[i], array[j]);
        }
    }
    exchange(array[i + 1], array[high]);
    return i + 1;//所以循环完成后。i+1就是该数组的切割点
}
void exchange(int &a, int &b)
{
    int temp = a;
    a = b;
    b = temp;
}

高速排序的随机化版本号

在上面介绍的高速排序算法实现中,Partition(A , p , r)总是默认A[r]为主元,作为比較标准。假设能够採用随机取样的随机化技术的话。将会使得分析更加简单。以下是随机化版本号的高速排序算法实现:

#include <iostream>
#include <ctime>
#include <cstdlib>
#define N 10

using namespace std;

//高速排序的递归算法
void quickSort(int * array, int low, int high);
//求切割点
int partition(int * array, int low, int high);

//以low ~ high 之间的一个随机元素作为主元 , 求切割点
int randomPartition(int *array, int low, int high);

//交换两个变量的值
void exchange(int &a, int &b);

int main()
{
    //声明一个待排序数组
    int array[N];
    //设置随机化种子,避免每次产生同样的随机数
    srand(time(0));
    for (int i = 0; i<N; i++)
    {
        array[i] = rand() % 101;//数组赋值使用随机函数产生1-100之间的随机数
    }
    cout << "排序前:" << endl;
    for (int j = 0; j<N; j++)
    {
        cout << array[j] << "  ";
    }
    cout << endl << "排序后:" << endl;
    //调用高速排序函数对该数组进行排序
    quickSort(array, 0, N - 1);
    for (int k = 0; k<N; k++)
    {
        cout << array[k] << "  ";
    }
    cout << endl;

    system("pause");

    return 0;
}//main

void quickSort(int * array, int low, int high)
{
    if (low < high)
    {
        int temp = randomPartition(array, low, high);
        quickSort(array, low, temp - 1);
        quickSort(array, temp + 1, high);
    }
}

int partition(int * array, int low, int high)
{
    int i = low - 1;
    //默认将划分段的最后一个元素为主元
    int x = array[high];

    for (int j = low; j<high; j++)
    {
        if (array[j] <= x)//在array[i]左边都是小于x即array[high]的数。右边均是大于它的数
        {
            i += 1;
            exchange(array[i], array[j]);
        }
    }
    exchange(array[i + 1], array[high]);
    return i + 1;//所以循环完成后,i+1就是该数组的切割点
}

int randomPartition(int *array, int low, int high)
{
    //找到low ~ high 之间的一个随机位置
    int i = rand() % (high - low + 1) + low;

    //交换该随机主元至尾部,
    exchange(array[i], array[high]);

    return partition(array, low, high);
}

void exchange(int &a, int &b)
{
    int temp = a;
    a = b;
    b = temp;
}

随机版本号的快排与普通快排差别并非非常大,修改的不过求切割点步骤中的主元选取,也就是添加了randomPartition函数,选定好主元元素下标i后。将该元素交换至段尾,依旧调用partition函数求切割点。

高速排序性能分析

高速排序的执行时间与划分是否对称有关。而后者又与选择了哪一个元素进行划分有关。假设划分是对称的,那么本算法在渐近意义上与合并排序一样快。假设划分是不正确称的那么本算法在渐进意义上与插入排序一样慢。以下分别讨论高速排序的最坏情况划分、最佳情况划分、平衡的划分。

最坏情况划分:高速排序的最坏情况划分行为发生在划分过程中产生的两个区域分别包括n-1个元素和0个元素的时候。假设算法每次递归调用都出现了这样的不正确称划分。划分的时间代价为O(n)。由于对一个大小为0的数组进行递归调用后,返回了T(n)=O(1),故算法的执行时间可递归的表示为:

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

从直观上来看。假设将每一层递归的代价加起来,就能够得到一个算术级数(等式(array,2)其和值的量极为O(n^2))利用代换法能够比較直接的证明递归式 T(n) = T(n-1) + O(n)的解为 T(n) = O(n^2)。

因此假设在算法的每一层递归上,划分都是最大程度不正确称的。那么算法的执行时间为O(n^2),亦即高速排序算法的最坏情况执行时间不如插入排序的好。

此外当输入数组全然排好序时,高速排序的执行时间是O(n^2),而插入排序的执行时间为O(n)。

最佳情况划分:在Partition可能做的最平衡划分中,得到的两个子问题的大小都不可能大于[n/2],由于若当中一个子问题的大小为[n/2]。则另外一个子问题的大小必定为[n/2]-1。在这样的情况下。高速排序的执行速度要快得多。这时表达其执行时间的递归式为:

T(n) <= 2T(n/2) + O(n)

解该递归式可得T(n) = O(nlgn)。由于在每一层递归划分的两边都是对称的。因此从渐进意义上来看。算法执行的就更快了。

平衡的划分: 高速排序的平均情况执行时间与其最佳情况执行时间非常接近,而不是非常接近与其最坏情况执行时间(证明原因具体參考《算法导论》原书第二版P88),由于不论什么一种按常数比例进行划分都会产生深度为O(lgn)的递归树,当中每一层的代价都是O(n),因而每当依照常数比例进行划分时,总的执行时间都是O(nlgn)。

时间: 2024-11-08 23:20:11

《算法导论》 — Chapter 7 高速排序的相关文章

算法导论22.4拓扑排序 练习总结 (转载)

22.4-1 给出算法 TOPOLOGICAL-SORT 运行于图 22-8 上时所生成的结点次序.这里的所有假设和练习 22.3-2 一样. ANSWER:   22.4-2 请给出一个线性时间的算法,算法的输入为一个有向无环图 G = (V, E) 以及两个结点 s 和 t,算法的输出是从结点 s 到结点 t 之间的简单路径的数量.例如,对于图 22-8 所示的有向无环图,从结点 p 到结点 v 一共有 4 条简单路径,分别是 pov.poryv.posryv 和 psryv.(本题仅要求计

[一周一算法]算法导论学习之计数排序

计数排序是一种线性时间的排序,同时也是一种非比较排序 代码如下: 1 void CountingSort(int *data, int k, int num) // A ~ data[], B ~ aimArray[], C ~ tempArray[] 2 { 3 int *aimArray = new int[num]; 4 int *tempArray = new int[k + 1]; 5 for (int i = 0; i <= k; i++) 6 tempArray[i] = 0; 7

算法导论01 几种排序以及其时间复杂度01

冒泡排序 #include<stdio.h> int main() { int a[10],i,j,t; for(i=0;i<10;i++) scanf("%d",&a[i]); for(i=0;i<10;i++) { for(j=i+1;j<10;j++) { if(a[i]>a[j]) { t=a[i]; a[i]=a[j]; a[j]=t; } } } for(i=0;i<10;i++) printf("%3d"

高速排序算法

高速排序算法 作者 July  二零一一年一月四日------------------------------------------ 写之前,先说点题外话.每写一篇文章,我都会遵循下面几点原则:一.保持版面的尽量清晰,力保排版良好.二.力争所写的东西,清晰易懂,图文并茂三.尽最大可能确保所写的东西精准,有实用价值. 由于,我认为,你既然要把你的文章,发布出来,那么你就一定要为你的读者负责.不然,就不要发表出来.一切,为读者服务. ok,闲不多说.接下来,咱们立马进入本文章的主题,排序算法.众所

算法系列之图--拓扑排序

本文介绍使用深度先搜索对向无环图(DAG)进行拓扑排序. 对于一个有向无环图G=(V,E)来说,其拓扑排序是G中所有结点的一种线性次序,该次序满足如下条件:如果G包含边(u,v)则结点u在拓扑排序中处于结点v的前面(若图G包含一个环路则不可能排出一个线性次序).可将图中的拓扑排序看成是将图的所有结点在一条水平线上排开,图中所有边都从左指向右. 给一个拓扑图如下示: 拓扑排序算法与DFS相似,但是在拓扑排序的过程中,每个结点都是后与其临接链表里的结点而放入Stack中. 具体代码如下示: 1 #i

算法导论 第2章

本章主要是算法知识的基础讲解,介绍了循环不变式,几个简单的排序算法,递归分治算法等内容. 1.循环不变式 循环不变式主要用来说明算法的正确性,那么什么是循环不变式呢,其实就是在循环过程中,一些元素数据必须保持的一些性质,例如在插入排序中,数组为A,必须保证三个性质: (1) 初始化:在循环开始之前,循环不变式是成立的,即:A[0]是有序的,A[1...n-1]是无序的. (2) 保持:在循环的某一次迭代开始之前,循环不变式是成立的,那么在此次迭代结束后依然应该是成立的,即:A[0...i]是有序

排序算法的c++实现——计数排序

任何比较排序算法的时间复杂度的上限为O(NlogN), 不存在比o(nlgN)更少的比较排序算法.如果想要在时间复杂度上超过O(NlogN)的时间复杂度,肯定需要加入其它条件.计数排序就加入了限制条件,从而使时间复杂度为O(N). 计数排序的核心思想(来自算法导论):计数排序要求待排序的n个元素的大小在[0, k]之间,并且k与n在一个数量级上,即k=O(n).对于每一个输入元素x, 确定小于等于x的个数为i.利用这一信息,就可以把元素x放到输出数组的正确位置,即把元素x放到输出数组下标为i-1

算法导论 第7章 高速排序

高速排序在最坏情况下的时间复杂度为O(n^2),尽管在最坏情况下执行时间比較差,可是高速排序一般是用于排序的最佳选择.由于其平均性能相当好,期望的执行时间为O(nlgn),且在O(nlgn)的记号中隐含的常数因子非常小. 高速排序和合并排序有相似之处,都是须要划分序列,在合并排序中.划分的过程非常easy.直接选择元素序列的中间位划分位置,排序是在合并的过程中实现的,所以合并排序的合并过程非常重要.相比合并排序,高速排序就没有合并的过程.仅仅有划分,高速排序的划分过程非常重要,排序是在划分的过程

排序算法之高速排序(Java)

//高速排序 public class Quick_Sort { // 排序的主要算法 private int Partition(int[] data, int start, int end) { int mid_data = data[end];// 选取最后最个数作为中间值哨兵,从開始进行遍历,每一个数与之比較 int index = start; // 记录比哨兵小的数字在左端的位置或个数 // 注意要考虑start = 0的情况,和<算法导论>中的伪码有差别 for (int i =