C++抽象编程——算法分析(6)——快速排序算法

即使本章前面介绍的合并排序算法在理论上表现良好,也具有O(N log N)的最差情况复杂度,实际上并没有太多的应用。 相反,目前使用的大多数排序程序都是基于由英国计算机科学家C.A.R.(Tony)Hoare开发的称为Quicksort的算法.

快速排序(Quicksort)

Quicksort和合并排序都采用分治法。在合并排序算法中,原始向量被分为两部分,每一个被独立排序。 然后将所得到的排序向量合并在一起以完成整个向量的排序操作。然而,假设你采取了不同的方法来分割向量,如果您通过初始化通过向量,改变元素的位置,使“小”值处于vector的开始,并且“大”值处于尾部时,会发生什么?这个时候怎么定义大跟小?
例如,假设要排序的原始vector如下,(我们在合并排序的讨论前面提到过):

这些元素中的一半大于50并且一半较小,因此在这种情况下我们可以将“small”的定义为小于50的数,将 “large”定义为大于50的数。 如果可以找到一种重新排列元素的方式,以便所有的“small”元素都在开头,所有的最“large”元素都在最后,那么将收到一个向量形式,它显示了许多元素之一 符合定义的可能排序:

  • 当以这种方式将元素划分为多个部分时,仍需完成的任务是使用对进行排序的函数的递归调用对每个部分进行排序。 由于边界线左侧的所有元素都小于右边的所有元素,因此最终结果将是完全排序的向量:
  • 如果你可以随时选择每一轮的小和大元素之间的最优边界,则该算法将每次将向量划分一半,最终证明与合并排序相同的定性特征。在实践中,Quicksort算法选择向量中的一些现有元素,并将该值用作小元素和大元素之间的分界线。例如,一个常见的方法是选择第一个元素,它是原始向量中的56个元素,并将其作为小元素和大元素之间的分界点。 当向量重新排序时,边界因此处于特定的下标位置而不是两个位置之间,如下所示:

从这一点来看,递归调用必须对位置0和3之间的向量进行排序,和将位置5和7之间的向量进行排序,将下标位置4留在它原来的位置。
在合并排序中,Quicksort算法的simple case是大小为0或1的向量,必须已经被排序。 Quicksort算法的递归部分包括以下步骤:

  1. 选择一个元素作为小元素和大元素之间的边界Choose an element to serve as the boundary between the small and large elements.)。这个元素传统上被称为枢轴(pivot)。 目前为此选择任何元素是足可行的,最简单的策略是选择向量中的第一个元素。
  2. 重新排列vector中的元素,使大的元素朝向vector的末端移动,使小的元素朝向vector的头部移动。Rearrange the elements in the vector so that large elements are moved toward the end of the vector and small elements toward the beginning) 这一步骤的目的是将边界位置附近的元素分开,使边界左边的所有元素都小于枢轴,右边的所有元素大于或等于枢轴。 这个处理称为分割vector(partitioning the vector).
  3. 对每个部分向量中的元素进行排序(Sort the elements in each of the partial vectors)。 因为枢轴边界左边的所有元素都严格小于右边的所有元素,所以对每个向量进行排序必须以排序的顺序保留整个向量。此外,由于该算法使用了分治策略,所以可以使用Quicksort的递归应用对这些较小的向量进行排序。

分割vector

在Quicksort算法的分区步骤中,目标是重新排列元素,所以将它们分为三个类:小于枢轴的类, 枢轴它本身,与枢轴至少一样大的元素。关于分区的棘手部分是重新排列元素,而不需要开辟任何额外的存储,(也就是不用开辟新的vector用于储存),这通常通过交换元素来完成。
Tony Hoare原来的划分方法很容易用英文解释。因为在启动算法的分区阶段时已经选择了枢纽值,所以我们可以立即判断一个值是相对于该轴的大还是小。为了使事情变得更容易,我们假设转换值存储在初始元素位置。 Hoare的分割算法进行如下:

  1. 暂时忽略索引位置0的pivot元素,并集中在其余的元素上。使用两个下标值lh和rh来记录vector其余部分中第一个和最后一个元素的下标位置,如下所示:
  2. 将rh下标向左移动,直到与lh重合,或指向相对于枢轴较小的值的元素。在这个例子中,位置7中的值30已经是一个很小的值(相对于56来说),所以rh下标不需要移动。
  3. 将lh下标向右移动,直到与rh重合,或指向包含大于或等于枢轴的值的元素。在此示例中,lh下标必须向右移动,直到它指向大于56的元素,这将导致以下配置:
  4. 如果lh和rh并未指向相同的位置,则在向量中交换那些位置的元素,使其看起来像这样:
  5. 重复步骤2到4,直到lh和rh位置重合。例如,在下一遍,在步骤4中的交换操作交换19和95.一旦发生这种情况,则下一步执行步骤2将rh下标移动到左侧,其结束匹配lh,如下:
  6. 除非所选的枢轴恰好恰好是整个向量中的最小元素(代码中包含对这种情况的特殊检查),否则lh和rh索引位置重合的点将是在向量中最右边的最小值。唯一剩下的步骤是在矢量的开始处使用pivot元素在该位置交换该值,如下所示:

请注意,此配置符合分区步骤的要求。枢轴值位于标记的边界位置,左侧的每个元素都比枢纽较小,右侧每个元素至少都大于等于枢纽。

实现快速排序

代码如下:

#include <iostream>
#include <vector>
using namespace std;
/*函数原型*/
void sort(std::vector<int> &vec);
void quickSort(std::vector<int> & vec, int start, int finish);
int partition(std::vector<int> & vec, int start, int finish);
/*主函数*/
int main(){
    vector<int> vec;
    for (int i = 0; i < 8; i++){
        int n;
        cin >> n;
        vec.push_back(n);
    }
    sort(vec);
    for(int k = 0; k < vec.size();k++){
        cout << vec[k] <<" ";
    }
    return 0;
}

/*
 *函数: quickSort
 *对下标位置之间的元素进行排序,包括开始位置和结束位置。 
 *Quicksort算法开始于“分割”后向量
 *使得小于指定的枢轴元素的所有元素都显示在边界的左侧,
 *并且所有相等或更大的值都显示在右侧。
 *将子vector排序到边界的左侧和右侧确保整个vector被排序。
 */ 
void quickSort(vector<int> & vec, int start, int finish) {
    if (start >= finish) return;
    int boundary = partition(vec, start, finish);
    quickSort(vec, start, boundary - 1);
    quickSort(vec, boundary + 1, finish);
}
/*
 *该函数重新排列vector的元素,使得小元素被分组在向量的左端,
 *而大元素分组在右端。通过比较每个元素与最初从vec [start]
 *获得的枢纽值来进行小和大的区别。当分区完成时,函数返回一
 *个边界索下标,使得0对于所有i <boundary,vec [i] <pivot,
 *对于vec [i] == pivot,i == boundary,对于i > pivot,
 *vec [i]> = pivot, 
 */ 
int partition(vector<int> & vec, int start, int finish){
    int pivot = vec[start];
    int lh = start + 1;
    int rh = finish;
    while(true){
        while(lh < rh && vec[rh] >= pivot) rh--;
        while(lh < rh && vec[lh] < pivot) lh++;
        if(lh == rh) break;
        int temp;
        int tmp = vec[lh];
        vec[lh] = vec[rh];
        vec[rh] = tmp;
    }
    if (vec[lh] >= pivot) return start;
    vec[start] = vec[lh];
    vec[lh] = pivot;
    return lh;
}
/*
 *用wrapper函数将函数调用变得更加简单*/
void sort(vector<int> &vec){
    quickSort(vec, 0, vec.size() - 1);
}

运行结果如下:

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值