八大排序——快速排序/快排优化

八大排序——快速排序/快排优化

目录

一、快速排序定义以及实现

1.1 定义

1.2 代码

1.3 快速排序特点

二、快速排序的优化

2.1 三数取中法

2.2 随机数法

2.3 调用冒泡/直接插入

三、非递归怎么实现快排


一、快速排序定义以及实现

1.1 定义

分治的思想。每一趟将待排序的第一个值看作基准值,通过一趟排序,可以将排序数据分成俩半,左边这一半都小于等于基准值,右边这一半都大于基准值,然后对左右俩半分别递归进行排序,直接数据全部有序(基准值俩边划分好之后,自身是有序的,他就在最终排序好所在的位置上)

1. 选择第一个值作为基准值 从右向左找比基准值小的数  谁不空挪谁

2.  从右向左找比基准值小的数,往左扔,86大于基准值不动,right向前一位,47比基准值小,挪到空位里

3. 右边有空位,从左往右找比基准值大的数往右扔。

4. 只要指针没有相遇就一直这样操作。直到指针相遇,把基准值tmp中的值放到当前空位中

1.2 代码

Partition函数实现方法

int Partition(int arr[], int left, int right)
{
    int tmp = arr[left];

    while (left < right)//没相遇
    {
        //从右向左找,找一个小于等于基准值的值
        while (left<right && arr[right] > tmp)
        {
            right--;
        }
        //while结束 有两种可能:1.两个指针相遇了
        //                      2.找到一个小于等于tmp的值
        if (left == right)//处理情况1
        {
            break;
        }
        arr[left] = arr[right];

        //从左向右找,找大于基准值的值
        while (left<right && arr[left] <= tmp)
        {
            left++;
        }
        //while结束 有两种可能:1.两个指针相遇了
        //                      2.找到一个大于tmp的值
        if (left == right)//处理情况1
        {
            break;
        }
        arr[right] = arr[left];
    }

    //当最外层while结束,代表两个指针相遇

    arr[left] = tmp;//== arr[right] = tmp;
    return left;//== return right
}
void Quick(int arr[], int left, int right)
{
    int par = Partition(arr, left, right);//3,找第一个找到的基准值下标

    //最少两个值才需要递归处理
    if (left < par - 1)//保证左半边至少两个值
    {
        Quick(arr, left, par - 1);
    }
    if (par + 1 < right)//保证右半边至少两个值
    {
        Quick(arr, par + 1, right);
    }
    
}

1.3 快速排序特点

 数据越乱越有序

二、快速排序的优化

快速排序在最坏情况下(例如数组已经有序)时间复杂度会退化为 O(n2),为了降低这种最坏情况出现的概率,可采用以下方法来选择基准元素。

优化思路:把数据打乱,越乱越好,越乱越有可能以基准值为中心均分

2.1 三数取中法

最左端/中间值/最右端,找其中不大不小的那个值放到最左端,最大值放最右端。

三数取中法是指在进行分区操作前,从数组的起始位置、中间位置和末尾位置选取三个元素,然后对这三个元素进行排序,取排序后中间的那个元素作为基准元素。这种方法能让基准元素更接近数组的中位数,使分区后的两个子数组规模更均衡,进而减少最坏情况发生的可能性。

#include <stdio.h>

void Swap(int* pa, int* pb)
{
    int tmp = *pa;
    *pa = *pb;
    *pb = tmp;
}

void Three_Nums_Get_Mid(int arr[], int left, int right)
{
    //arr[left]    arr[mid]    arr[right]
    int mid = (left + right) / 2;

    if (arr[left] > arr[mid])//保证前两个位置的较大的值放到中间中间
    {
        Swap(&arr[left], &arr[mid]);
    }

    if (arr[mid] > arr[right])//再比较前两个位置的较大值和最右端的值进行比较,目的是让三个值里面的最大值,放到最右端去
    {
        Swap(&arr[mid], &arr[right]);
    }

    if (arr[left] < arr[mid])//此时,不大不小的那个值,肯定在前两个位置
    {
        Swap(&arr[left], &arr[mid]);
    }
}


int Partition(int arr[], int left, int right)
{
    int tmp = arr[left];

    while (left < right)//没相遇
    {
        //从右向左找,找一个小于等于基准值的值
        while (left<right && arr[right] > tmp)
        {
            right--;
        }
        //while结束 有两种可能:1.两个指针相遇了
        //                      2.找到一个小于等于tmp的值
        if (left == right)//处理情况1
        {
            break;
        }
        arr[left] = arr[right];

        //从左向右找,找大于基准值的值
        while (left<right && arr[left] <= tmp)
        {
            left++;
        }
        //while结束 有两种可能:1.两个指针相遇了
        //                      2.找到一个大于tmp的值
        if (left == right)//处理情况1
        {
            break;
        }
        arr[right] = arr[left];
    }

    //当最外层while结束,代表两个指针相遇

    arr[left] = tmp;//== arr[right] = tmp;
    return left;//== return right
}
void Quick(int arr[], int left, int right)
{
    if (left < right) {
        Three_Nums_Get_Mid(arr, left, right);
        int par = Partition(arr, left, right);

        //最少两个值才需要递归处理
        if (left < par - 1)//保证左半边至少两个值
        {
            Quick(arr, left, par - 1);
        }
        if (par + 1 < right)//保证右半边至少两个值
        {
            Quick(arr, par + 1, right);
        }
    }
}

// 测试代码
int main() {
    int arr[] = {3, 6, 8, 10, 1, 2, 1};
    int n = sizeof(arr) / sizeof(arr[0]);
    Quick(arr, 0, n - 1);
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
    return 0;
}

1. swap函数

此函数的作用是交换两个整数的值。它借助一个临时变量 tmp 来完成交换操作

2. Three_Nums_Get_Mid 函数

该函数实现了三数取中法。它从数组的起始位置 left、中间位置 mid 和末尾位置 right 选取三个元素,经过一系列比较和交换操作,把这三个元素中的中位数放到 arr[left] 位置,从而将其作为后续分区操作的基准元素。

3. Partition 函数

此函数实现了分区操作。它把 arr[left] (也就是三数取中法选出的基准元素)作为基准,通过双指针法,从数组的两端向中间遍历,把小于等于基准的元素放到左边,大于基准的元素放到右边,最后将基准元素放到正确的位置,并返回该位置的索引。

4. Quick 函数

该函数是快速排序的核心递归函数。它先调用 Three_Nums_Get_Mid 函数选择基准元素,接着调用 Partition 函数进行分区操作,最后递归地对分区后的左右子数组进行排序。 

2.2 随机数法

2.3 调用冒泡/直接插入

      开始时就:当数据量不大时,可以在快速排序中进行判断,转头直接调用冒泡/直接插入

      进行partition函数后:判断基准值左右俩部分的数据个数,如果小于一定量,转头直接调用冒泡/直接插入

三、非递归怎么实现快排

#include <iostream>
#include <stack>

// 分区函数
int Partition(int arr[], int left, int right)
{
    int tmp = arr[left];

    while (left < right) // 没相遇
    {
        // 从右向左找,找一个小于等于基准值的值
        while (left < right && arr[right] > tmp)
        {
            right--;
        }
        // while结束 有两种可能:1.两个指针相遇了
        //                      2.找到一个小于等于tmp的值
        if (left == right) // 处理情况1
        {
            break;
        }
        arr[left] = arr[right];

        // 从左向右找,找大于基准值的值
        while (left < right && arr[left] <= tmp)
        {
            left++;
        }
        // while结束 有两种可能:1.两个指针相遇了
        //                      2.找到一个大于tmp的值
        if (left == right) // 处理情况1
        {
            break;
        }
        arr[right] = arr[left];
    }

    // 当最外层while结束,代表两个指针相遇
    arr[left] = tmp; // == arr[right] = tmp;
    return left; // == return right
}

// 快速排序非递归函数
void Quick_No_Recursion(int arr[], int left, int right)
{
    std::stack<int> st;
    st.push(left);
    st.push(right);

    while (!st.empty())
    {
        int tmp_right = st.top();
        st.pop();
        int tmp_left = st.top();
        st.pop();

        int par = Partition(arr, tmp_left, tmp_right);
        // 正确使用当前子数组的边界
        if (tmp_left < par - 1) 
        {
            st.push(tmp_left);
            st.push(par - 1);
        }
        if (par + 1 < tmp_right) 
        {
            st.push(par + 1);
            st.push(tmp_right);
        }
    }
}

// 打印数组函数
void printArray(int arr[], int size)
{
    for (int i = 0; i < size; i++)
    {
        std::cout << arr[i] << " ";
    }
    std::cout << std::endl;
}

int main()
{
    int arr[] = {3, 6, 8, 10, 1, 2, 1};
    int n = sizeof(arr) / sizeof(arr[0]);

    std::cout << "排序前的数组: ";
    printArray(arr, n);

    Quick_No_Recursion(arr, 0, n - 1);

    std::cout << "排序后的数组: ";
    printArray(arr, n);

    return 0;
}

 快速排序非递归函数 Quick_No_Recursion

  • 功能:该函数使用栈来模拟递归调用的过程,实现快速排序的非递归版本。
  • 实现步骤
    1. 创建一个栈st,并将整个数组的左右边界leftright压入栈中。
    2. 当栈不为空时,从栈中弹出两个元素,分别作为当前子数组的左右边界tmp_lefttmp_right
    3. 调用Partition函数对当前子数组进行分区操作,得到基准值的最终位置par
    4. 如果基准值左边的子数组长度大于 1,则将其左右边界压入栈中。
    5. 如果基准值右边的子数组长度大于 1,则将其左右边界压入栈中。
    6. 重复步骤 2 - 5,直到栈为空。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值