《算法导论》 — Chapter 7 高速排序[通俗易懂]

《算法导论》 — 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)。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/116443.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 关于去掉Li标签前面的小圆点和距离/显示下划线

    关于去掉Li标签前面的小圆点和距离/显示下划线解决方法去掉 Li 标签前面的距离 nbsp nbsp 设置 ul nbsp nbsp padding 0px 去掉 Li 标签前面的小圆点 设置 li nbsp nbsp nbsp list style type none 显示下划线 nbsp nbsp nbsp text decoration underline

    2025年7月24日
    6
  • 算法导论在线阅读_英雄联盟韧性计算

    算法导论在线阅读_英雄联盟韧性计算都整理好了,看谁学得快!

    2022年9月1日
    4
  • hexo史上最全搭建教程[通俗易懂]

    花了几天搭建了个网站,先上链接,欢迎来访:fangzh的个人博客现在市面上的博客很多,如CSDN,博客园,简书等平台,可以直接在上面发表,用户交互做的好,写的文章百度也能搜索的到。缺点是比较不自由,会受到平台的各种限制和恶心的广告。而自己购买域名和服务器,搭建博客的成本实在是太高了,不光是说这些购买成本,单单是花力气去自己搭这么一个网站,还要定期的维护它,对于我们大多数人来说,实在是没…

    2022年4月8日
    222
  • BT Tracker服务器_bt服务器

    BT Tracker服务器_bt服务器有时候用BT软件下载文件的时候,经常会碰到没速度的情况,这个时候你就需要设置BTTracker服务器地址了。网上有人专门做了一个列表放在github上面,不定时更新。BitComet软件可以直接添加URL,然后设置启动时更新。项目地址:https://github.com/ngosang/trackerslist(长期更新)trackers_best(20tracker…

    2022年10月1日
    6
  • 从数字区间中选取数据

    从数字区间中选取数据

    2021年5月11日
    113
  • system.out.println()里面_println的意思

    system.out.println()里面_println的意思1.第一次见到该表达式的感受&amp;nbsp;&amp;nbsp;第一此次见到该表达式的时候,我还不知道什么是方法引用,当时真是一脸蒙圈,然后问了好多同事,给我的解释也是五花八门,但我还是感觉莫名其妙,有段时间想着就当一个特例记住就好了,不要再去深究了!!!但是我这个人,在这种时候就是很难说服自己,于是有了上篇文章,再回过头来看这个问题,其实就变得非常简单了。2.揭开System.out::print…

    2022年10月2日
    3

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号