C++编写算法 (四)——排序问题进阶,快速排序

快速排序,是应用最广泛的排序方法。快速排序流行的原因是它实现简单、适用于各种不同的输入数据且在一般应用中比其他排序算法要快得多。 -------《算法》

一、快速排序

快速排序也是一种分治的思想。它需要将数组分为两个、四个、八个等子数组,通过递归的方法,对每个子数组进行排序。因此,快速排序的第一个步骤就是需要找到一个能将数组划分成子数组的分割点。分割点的产生与划分,需要一个简单的分割算法(Partition)来实现。

分割算法的思想为:

①先在数组中找到一个幸运数字(Lucky Number, LN)。
②将比LN小的数字(后统一称作小值)置于LN前,将比LN大的数字(后统一称作大值)置于LN之后。
首先,为代码简便,幸运数字通常可以选择数组首个元素。确定幸运数字后,需要将小值全部移动到LN前,将大值移动到LN后。因此,我们需要两个index来对数组的元素进行搜索。

具体步骤

Partition函数就是用来实现子数组的分割,需要注意的是,它的工作只限于分割,而分割后的子数组有可能仍然处于乱序当中。分割算法的具体实现:需要将两个index(我们在后面称作哨兵i和j)分别指向数组的首部和尾部,哨兵i从首部开始搜寻比LN大的元素,哨兵j从尾部开始搜寻比LN小的元素。哨兵i与j分别搜索到相应的元素之后,将这两个元素的位置进行交换。交换后,哨兵i与哨兵j继续进行相应的搜索。直到哨兵i与哨兵j相遇,分割工作的第一步就完成了。此时,我们选出的LN还位于数组的首位,需要将LN放置到数组合适的位置中去,以完成Partition函数的分割工作。在哨兵i与哨兵j相遇后,我们将LN与在哨兵j的位置的数进行交换,便可得到分割后的数组了。

Partition Function

 int Partition(int *arr, int start, int end)
{
    int i, j;
    i = start;
    j = end;
    int temp1, temp2;
    int LK_num = arr[i];
    while (true)
    {
        while (arr[i] <= LK_num)
        {
            i++;
            if (i > end)
                break;
        }
        while (arr[j] > LK_num)
        {
            j--;
            if (j < start)
                break;
        }
        if (i >= j)
            break;
        temp1 = arr[i];
        arr[i] = arr[j];
        arr[j] = temp1;
    }
    temp2 = LK_num;
    arr[start] = arr[j];
    arr[j] = LK_num;
    return j;
}

此外,分割函数中还存在一个问题。为了保持随机性,我们将数组通过Shuffle函数进行顺序的打乱。这样就可以随机的选择一个元素进行切分了。

Shuffle Function

void Shuffle(int *arr)
{
    srand(time(NULL));
    int temp;
    for (int i = 0; i < SIZE; i++)
    {
        int value = rand() % (SIZE);
        temp = arr[i];
        arr[i] = arr[value];
        arr[value] = temp;
    }
}

在这里,也碰到了一个小问题。在使用rand()函数时,每一次运行程序后,数组的打乱顺序基本一致。这个问题在第二篇扑克牌排序的文章中也写到了。

rand()函数产生的随机数时伪随机数,是计算机按照某种规律产生的数字。因此rand()产生的数字并不是真正意义上的随机数。因此我们需要使用srand()函数来产生一个随机数种子。

因此,通过使用Partition函数,数组可达到部分有序,也被划分为两个子数组,通过对两个子数继续进行规律的分割,最后实现排序,快速排序如是而已。

#include <iostream>
#include <vector>
#include<ctime>
const int SIZE = 15;
using namespace std;
void Shuffle(int *arr);
void SORT(int *arr, int lo, int hi);
//void SORT(int *arr, int begin, int end);
int Partition(int *arr, int start, int end);
int main()
{
    int *arr = new int[SIZE];
    for (int i = 0; i < SIZE; i++)
    {
        cout << "第" << i + 1 << "个数为:";
        cin >> arr[i];
    }
    cout << "数组为:";
    for (int j = 0; j < SIZE; j++)
    {
        cout << arr[j] << " ";
    }
    cout << endl;
    Shuffle(arr);
    SORT(arr, 0, SIZE-1);
    cout << "排序后的数组为: ";
    for (int i = 0; i < SIZE; i++)
        cout << arr[i] << " ";
    cout << endl;
    delete[] arr;
    system("pause");
    return 0;
}

void Shuffle(int *arr)
{
    srand(time(NULL));
    int temp;
    for (int i = 0; i < SIZE; i++)
    {
        int value = rand() % (SIZE);
        temp = arr[i];
        arr[i] = arr[value];
        arr[value] = temp;
    }
}

int Partition(int *arr, int start, int end)
{
    int i, j;
    i = start;
    j = end;
    int temp1, temp2;
    int LK_num = arr[i];
    while (true)
    {
        while (arr[i] <= LK_num)
        {
            i++;
            if (i > end)
                break;
        }
        while (arr[j] > LK_num)
        {
            j--;
            if (j < start)
                break;
        }
        if (i >= j)
            break;
        temp1 = arr[i];
        arr[i] = arr[j];
        arr[j] = temp1;
    }
    temp2 = LK_num;
    arr[start] = arr[j];
    arr[j] = LK_num;
    return j;
}

void SORT(int *arr, int lo, int hi)
{
    if (lo >= hi)
        return;
    int LK_located = Partition(arr, lo, hi);
    cout << endl;
    SORT(arr, LK_located + 1, hi);
    SORT(arr, lo, LK_located - 1);  
}

快速排序切分方法的内循环会用一个递增的索引将数组元素和一个定值比较。这种简洁性也是快速排序的一个优点。快速排序拥有最短小的内循环。归并排序和希尔排序一般都比快速排序要慢,其原因就是他们还在内循环中移动数据。

二、快速排序算法的改进

1、和之前提及的归并排序相似,快速排序也是采用了递归的思想进行排序,改进这种递归排序的方法可以基于以下两点:
-对于分割的小数组,可以采用插入排序算法。对于小型数组,插入排序比快速排序快。
-因为快速排序采用了递归的思想,在很小的数组中,也会自己调用自己。
因此,改进的基本思想与归并算法的思想是一致的:提前结束递归,在小数组中采用更快的排序算法。

#include <iostream>
#include <vector>
#include<ctime>
const int SIZE = 16;
using namespace std;
void Shuffle(int *arr);
void SORT(int *arr, int lo, int hi);
void InsertSort(int * arr, int lo, int hi);
int Partition(int *arr, int start, int end);
int main()
{
    int *arr = new int[SIZE];
    for (int i = 0; i < SIZE; i++)
    {
        cout << "第" << i + 1 << "个数为:";
        cin >> arr[i];
    }
    cout << "数组为:";
    for (int j = 0; j < SIZE; j++)
    {
        cout << arr[j] << " ";
    }
    cout << endl;
    Shuffle(arr);
    SORT(arr, 0, SIZE-1);
    cout << "排序后的数组为: ";
    for (int i = 0; i < SIZE; i++)
        cout << arr[i] << " ";
    cout << endl;
    delete[] arr;
    system("pause");
    return 0;
}

void Shuffle(int *arr)
{
    srand(time(NULL));
    int temp;
    for (int i = 0; i < SIZE; i++)
    {
        int value = rand() % (SIZE);
        temp = arr[i];
        arr[i] = arr[value];
        arr[value] = temp;
    }
}

int Partition(int *arr, int start, int end)
{
    int i, j;
    i = start;
    j = end;
    int temp1, temp2;
    int LK_num = arr[i];
    while (true)
    {
        while (arr[i] <= LK_num)
        {
            i++;
            if (i > end)
                break;
        }
        while (arr[j] > LK_num)
        {
            j--;
            if (j < start)
                break;
        }
        if (i >= j)
            break;
        temp1 = arr[i];
        arr[i] = arr[j];
        arr[j] = temp1;
    }
    temp2 = LK_num;
    arr[start] = arr[j];
    arr[j] = LK_num;
    return j;
}

void SORT(int *arr, int lo, int hi)
{
    if (lo + 4 >= hi)   //提前结束递归,采用插入排序
    {
        InsertSort(arr, lo, hi);
        return;
    }
    int LK_located = Partition(arr, lo, hi);
    cout << endl;
    SORT(arr, LK_located + 1, hi);
    SORT(arr, lo, LK_located - 1);  
}
//引入插入排序算法
void InsertSort(int *arr, int lo, int hi)
{
    int temp;
    for(int i = lo; i <= hi; i++)
    {
        for (int j = i; j > lo; j--)
        {
            if (arr[j] < arr[j - 1])
            {
                temp = arr[j];
                arr[j] = arr[j-1];
                arr[j-1] = temp;
            }
        }
    }
}

2、如果数组中存在较多相同元素时,可以采用三向切分快速排序,其思想是将比LN小的数字归于左部分,比LN大的数字归于右部分,中间部分的数是未确定部分。三向切分快速排序需要三个指针来执行操作。但总体的代码量比传统快速排序要小。

#include <iostream>
#include <vector>
#include<ctime>
const int SIZE = 8;
using namespace std;
void SORT(int *arr, int lo, int hi);
int main()
{
    int *arr = new int[SIZE];
    for (int i = 0; i < SIZE; i++)
    {
        cout << "第" << i + 1 << "个数为:";
        cin >> arr[i];
    }
    cout << "数组为:";
    for (int j = 0; j < SIZE; j++)
    {
        cout << arr[j] << " ";
    }
    cout << endl;
    SORT(arr, 0, SIZE-1);
    cout << "排序后的数组为: ";
    for (int i = 0; i < SIZE; i++)
        cout << arr[i] << " ";
    cout << endl;
    delete[] arr;
    system("pause");
    return 0;
}

void SORT(int *arr, int lo, int hi)
{
    int lt = lo;
    int gt = hi;
    int i = lo + 1;
    int v = arr[lo];
    int temp;
    if (lo >= hi)
        return;
    while (i <= hi)
    {
        if (arr[i] < v)
        {
            temp = arr[i];
            arr[i] = arr[lt];
            arr[lt] = temp;
            i++;
            lt++;
        }
        if (arr[i] > v)
        {
            temp = arr[i];
            arr[i] = arr[gt];
            arr[gt] = temp;
            gt--;
        }
        if (arr[i] == v)
        {
            i++;
        }
    }
    SORT(arr, lo, lt - 1);
    SORT(arr, gt + 1, hi);
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,951评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,606评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,601评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,478评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,565评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,587评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,590评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,337评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,785评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,096评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,273评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,935评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,578评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,199评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,440评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,163评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,133评论 2 352

推荐阅读更多精彩内容

  • 概述 排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    蚁前阅读 5,178评论 0 52
  • 概述:排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    每天刷两次牙阅读 3,729评论 0 15
  • 1.插入排序—直接插入排序(Straight Insertion Sort) 基本思想: 将一个记录插入到已排序好...
    依依玖玥阅读 1,250评论 0 2
  • 今天是主题班会,班主任马琳老师带领我们总结了这次期中考试的成绩。这次成绩有很多同学进步很大,王雪洁同学,王玺同学,...
    卫校一五助产阅读 261评论 0 0
  • 记得已逝诗人汪国真,有这么一首诗,其中有一句是:最美的风景,在远方。当时,看到这句话时,心里立即就被触动了,就像琴...
    一泓夜雨阅读 458评论 0 0