双向扫描的快速排序法及其优化

本文探讨快速排序中的中枢选取策略,包括最左端、随机和三数取中,并详细解析了双向扫描法在左右划分过程中的实现细节,包括扫描结束时的指针状态处理。此外,还介绍了递归与迭代版本的快排主体,并讨论了针对小数组的插入排序优化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


本文以升序排序为例讲解快速排序法。

快速排序是这样的一个过程——通过选取一个中枢,将数组排成为“左侧部分-中枢-右侧部分”的形式,左侧部分的元素皆小于中枢,右侧部分的元素皆大于中枢,然后对左右侧子数组进行相同的操作,直到数组不可划分为止。因此,快速排序的要点有三方面,一是中枢的选取,二是左右划分的方法,三是递归主体。本文对于“左右划分”的阐述只包括双向扫描法,并不包括单向扫描法和填坑法。

一、中枢选取

如果选取的中枢越接近数组的中位数,那划分效果将会更好,以下介绍几种中枢选取方式。

(一) 直接取最左端或最右端元素

这是最简单的方法,但容易遇上所选取中枢逼近数组最值的情况。

size_t
GetPivotIndex(int nums[], size_t start_index, size_t end_index) {
   
   
	if (start_index > end_index) {
   
   
        std::swap(start_index, end_index);
    }
    if (start_index == end_index || end_index == 1 + start_index) {
   
   
        return start_index;
    }
    
    return start_index;
}
size_t
GetPivotIndex(int nums[], size_t start_index, size_t end_index) {
   
   
	if (start_index > end_index) {
   
   
        std::swap(start_index, end_index);
    }
    if (start_index == end_index || end_index == 1 + start_index) {
   
   
        return start_index;
    }
    
    return end_index - 1;
}

(二) 随机选取

size_t
GetPivotIndex(int nums[], size_t start_index, size_t end_index) {
   
   
	if (start_index > end_index) {
   
   
        std::swap(start_index, end_index);
    }
    if (start_index == end_index || end_index == 1 + start_index) {
   
   
        return start_index;
    }
    
    return rand() % (end_index - start_index + 1) + start_index;
}

(三) 三数取中

三数取中是指选取数组的第一个数,最后一个数和最中间的那个数,然后取出这三个数的中位数作为中枢。

size_t // 思路更简洁的版本
GetPivotIndex(int nums[], size_t start_index, size_t end_index) {
   
   
    if (start_index > end_index) {
   
   
        std::swap(start_index, end_index);
    }
    if (start_index == end_index || end_index == 1 + start_index) {
   
   
        return start_index;
    }
    
    size_t left_index = start_index;
    size_t right_index = end_index - 1;
    size_t pivot_index = left_index + (right_index - left_index) / 2;
    
    if (nums[left_index] > nums[right_index]) {
   
   
        std::swap(nums[left_index], nums[right_index]);
    }
    if (nums[left_index] > nums[pivot_index]) {
   
   
        std::swap(nums[left_index], nums[pivot_index]);
    }
    if (nums[pivot_index] > nums[right_index]) {
   
   
        std::swap(nums[pivot_index], nums[right_index]);
    }
    std::swap(nums[left_index], nums[pivot_index]);
    pivot_index = left_index;
    return pivot_index;
}

在上述代码中,三个 if 代码块是有顺序讲究的——必须先比较两端的元素,再比较相邻的元素,否则交换后结果有可能出错。


二、左右划分:双向扫描法

(一) 扫描结束时左右指针重合

若想让左右指针在扫描结束时重合,则需要注意几点。

第一,若中枢为最左端元素,则必须让右指针先移动;若中枢为最右端元素,则必须让左指针先移动。

第二,扫描时,左右指针至少要有一个和中枢相等时也能继续移动,否则会是排序出现错误。

第三,扫描完毕后,若中枢为最左端元素,则只有当左指针指向的元素小于中枢时才能交换两者位置;若中枢为最右端元素亦同理。

第四,第二点提及的交换位置完毕后还要更新中枢索引值,并将其返回。

以下为示例程序,笔者选定 0 为中枢的初始索引,即中枢为最左端元素。

size_t
Partition(int nums[], size_t start_index, size_t end_index) {
   
   
    if (start_index + 1 == end_index || start_index == end_index) {
   
   
        return start_index;
    }
    size_t pivot_index = start_index;
    size_t left_index = start_index + 1;
    size_t right_index = end_index - 1;
    while (left_index < right_index) {
   
   
    	// 右指针在和中枢相等时选择继续移动,对应第二个注意点
        while (left_index < right_index && nums[pivot_index] <= nums[right_index]) {
   
   
            right_index--;
        }
        // 左指针在和中枢相等时选择暂时停止移动,跳出循环
        while (left_index < right_index && nums[left_index] < nums[pivot_index]) {
   
   
            left_index++;
        }
        std::swap(nums[left_index], nums[right_index]);
    }
    if (nums[left_index] < nums[pivot_index
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值