[算法学习] 排序算法(二)——快速排序与优化

这里继续整理常见的排序算法.
本文介绍快速排序以及对快速排序的优化
type right by Thomas Alan 光风霁月023 .XDU

1. 常规

//1. 对arr[l...r]部分进行partition操作
// rtn p, arr[l, p - 1] < arr[p]; arr[p + 1, r] > arr[p]
template 
TINT32 __partition(T arr[], TINT32 l, TINT32 r)
{
    swap(arr[l], arr[rand() % (r - l + 1) + l]); // 增加标准值的随机性
    T v = arr[l];   // 选取标准值
    // arr[l+1...j] < v, arr[j+1...i) > v, 而arr[j+i]是当前考察的元素
    
    
    TINT32 idj = l;
    // 注意, (l...idj] 位置的元素时刻 < v, idj初始为l, 这样, arr的(l...idj]范围的元素为空
    for (TINT32 idx = l + 1; idx <= r; idx++)
    {
        // 如果 arr[idx] >= v, 那么索引向后移动就可以
        if (arr[idx] < v)
        {
            swap(arr[idj + 1], arr[idx]);
            idj++;

            // 上面两行也可以用 swap(arr[++idj], arr[idx]); 代替, 
            // 但是为了看起来方便, 不需要炫技
            // 将arr 的 idj + 1位置(一定 > v)的元素和当前考察元素交换位置
        }
    }

    swap(arr[l], arr[idj]);
    return idj;
}

//2. 对arr[l...r]部分进行快速排序
template 
void __quickSort(T arr[], TINT32 l, TINT32 r)
{
    // if (l >= r)
    // {
    //     return;
    // }

    if (r - l <= 15)
    {
        // 老样子, 数据少就用插入排序
        insertionSort(arr, l, r);
        return;
    }

    TINT32 p = __partition(arr, l, r);  // 得到数组arr的[l, p - 1]部分 < arr[p], [p + 1, r]部分 > arr[p]
    __quickSort(arr, l, p - 1);
    __quickSort(arr, p + 1, r);
}

// 3. 入口
template 
void quickSort(T arr[], TINT32 num)
{
    srand(time(NULL)); // 设置随即数种子
    __quickSort(arr, 0, num - 1);
}

其中, 如果快速排序选取的标准值过于特殊, 会导致分割成的两个部分失衡, 比如对一个近乎有序(升序)的数组进行快速排序, 会导致每次选取的arr[l]这个标准数是最小的数, 数组在partition后, 右边的组就会极其的长, 快速排序就会退化为O(n^2)级别的算法. 所以需要选取一个随机位置的数作为标准数, 增加性, 使其时间效率接近O(nlogn).

2. 改进的快速排序算法

在常规, 我们可以看到, arr数组在partition过程中, [l+1...idj] 部分总是 < v, 而 [idj + 1 ... idx - 1]的部分总是 >= v. 因此, 如果数组中存在着大量重复元素的时候, 数组很容易会被分割为极不平衡的两个部分, 快速排序也会退化为O(n^2)级别的算法. 因此我们可以这样:
存在两个索引idx, idj, 从数组的l端和r端同时开始扫描, 数组的l端存放 < v 的元素, r端存放 > v的元素, 当扫描到不符合要求的元素交换位置, 然后继续扫描, 直到二者重合后错位即idx > idy 后, 停止扫描, 返回中间位置(idj).

template 
// 改进后的partition算法
TINT32 __partition2(T arr[], TINT32 l, TINT32 r)
{
    swap(arr[l], arr[rand() % (r - l + 1) + l]);
    T v = arr[l];

    // arr[l+1, idx] <= v; arr(idj...r] >= v
    TINT32 idx = l + 1, idj = r;
    while (true)
    {
        // idx不可越界, 如果arr[idx] < v, 那么跳过, 跳过所有 < v 的元素
        while (idx <= r && arr[idx] < v)
        {
            idx++;
        }

        // idj不可越界, 如果arr[idj] > v, 那么跳过, 跳过所有 > v 的元素
        while (idj >= l + 1 && arr[idj] > v)
        {
            idj--;
        }

        // idx 和 idj 重合后, 结束
        // 由于前两个while的条件是互斥的, 因此在这里进行判断是可以的, 在大while开头不需要判断
        if (idx > idj)
        {
            break;
        }

        // 交换左右不符合条件的元素, 那么就符合要求了, 同时对索引 ++, --. 
        swap(arr[idx], arr[idj]);
        idx++;
        idj--;
    }
    // 最后将标准值换到最中间

    swap(arr[l], arr[idj]);

    // 此时arr[idx]是 >= v的值, 可能 > v, arr[idj] == v
    return idj;
}

3. 三路快速排序

在2.中, 我们考虑到了 == v 的情况, 在三路快速排序中, 我们把数组分为三个部分, l端部分 < v, r端部分 > v, 中间部分 == v. 这样, 我们只需要考虑 < 和 > v的部分继续递归的快速排序就可以. 这样可以避免对相等的v进行重复操作.

template 
// 1. 三路快速排序的算法
void __quickSort3Ways(T arr[], TINT32 l, TINT32 r)
{
    // if (l >= r)
    // {
    //     return;
    // }

    if (r - l <= 15)
    {
        insertionSort(arr, l, r);
        return;
    }

    // partition
    swap(arr[l], arr[rand() % (r - l + 1) + l]);
    T v = arr[l];

    TINT32 lt = l;      // 小于v的最后一个元素的索引, arr[l+1...lt] < v
    TINT32 gt = r + 1;  // 大于v的最后一个元素的索引, arr[gt...r] > v

    TINT32 idx = l + 1; // arr[lt+1...idx] == v;

    while (idx < gt)
    {
        if (arr[idx] < v)
        {
            // 如果arr[idx] < v, 那么移动到r端, 同时维护lt, arr[lt + 1] == v, 所以只需要交换他和arr[idx]就可以
            swap(arr[idx], arr[lt + 1]);
            lt++;
            idx++;
        }
        else if (arr[idx] > v)
        {
            // 如果arr[idx] > v, 那么移动到r端, 同时维护gt
            // idx位置的元素换成末尾的元素, 将继续考察这个元素, 所以没有idx++
            swap(arr[idx], arr[gt-1]);
            gt--;
        }
        else 
        {
            // 如果arr[idx] == v, 跳过即可
            idx++;
        }
    }

    // 将arr[1]这个标准值和arr[lt]这个 < v 的最后一个元素交换位置即可
    swap(arr[l], arr[lt]);

    // 分别对 < v 和 > v 部分进行递归处理
    __quickSort3Ways(arr, l, lt - 1);
    __quickSort3Ways(arr, gt, r);

}

template 
// 2. 入口
void quickSort3Ways(T arr[], TINT32 num)
{
    srand(time(NULL));
    __quickSort3Ways(arr, 0, num - 1);
}

你可能感兴趣的:([算法学习] 排序算法(二)——快速排序与优化)