快速排序C语言讲解——递归版本

8 篇文章 0 订阅
6 篇文章 0 订阅

在学习快速排序之前 首先 我们需要明白什么是快速排序呢?它的逻辑是怎样的?

下面代码配有注释 可以边看边思考

#include<stdio.h>
#include<assert.h>

void Swap(int* p1, int* p2)
{
    int tmp = * p1;
    *p1 = *p2;
    *p2 = tmp;
}

void Print(int* arr, int n)
{
    assert(arr);
    for (int i = 0; i < n; i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int GetMidIndex(int* arr, int left, int right)
{
    int mid = left + ((right - left) >> 1);

    //arr[left]>arr[mid]
    if (arr[left] > arr[mid])
    {
        if (arr[mid] > arr[right])
        {
            return mid;
        }
        else if (arr[right] > arr[left])
        {
            return left;
        }
        else
        {
            return right;
        }
    }
    //arr[left]<arr[mid]
    else
    {
        if (arr[right] > arr[mid])
        {
            return mid;
        }
        else if (arr[right] < arr[left])
        {
            return left;
        }
        else
        {
            return right;
        }
    }

}


//三数取中 —— 三数取中的意义在哪呢 避免arr[keyi]值过大 降低快排的效率
//快排就是确定一个值(尽可能将数组由大到小(由小到大)平分的值 就是中间值)
//通过左右寻找比arr[keyi]大 或 小的值进行交换 将大的值放到一侧将小的值放到另一侧 
//最终确定 arr[keyi]的真正位置 
//再利用递归  将keyi 两侧的区间进行 处理 
//怎样处理的呢 也是和上面一样的操作 
//将区间中的arr[keyi]放到它该去的位置 如此反复 将所有区间中的数都放在该呆的位置 再不断的返回排好的区间


// 8     ,5,2,6   ,4,      9,7,3,      1 
// left            mid                  right
// 三数取中 进行交换
// 4     5 2 6      8      9 7 3       1
// left                               right
// keyi  
//寻找大小值进行交换  右边先走 然后左边再走 找到比keyi小的 和大的就进行交换 接着找 循环结束之后
//keyi两侧一侧是大的 一侧是小的
//4      5     2 6       8     9   7    3        1
//keyi
//      left                                     right
//
//4      1    2    6       8     9   7    3        5
//keyi
//      left                            right
//
// 4     1      2    6      8      9    7    3        5 
// keyi    
//                                          right
//                  left       
// 
// 4     1      2    3      8      9    7    6        5  一次交换
// keyi    
//                                         right
//                  left       
// 4     1      2    3      8      9    7    6        5  一次交换
// keyi    
//                  right
//                  left (3<= 4 没进入if语句 所以left没++)跳出循环 进行交换 arr[keyi] 和 arr[left]
// 
//  3     1      2    4      8      9    7    6        5  一次交换
// keyi    
//                  right
//                  left   (这样4就位于它该呆的位置)
// 3     1      2 |  4   |  8      9    7    6        5  一次交换
// keyi    
//                  right
//                  left 
// 
// [left ,keyi - 1]   keyi(最终Partion返回的是left下标 keyi接受)      [keyi+1,left]  
// 
//hoare
//[left,right]


//快排的最初设计

int Partion1(int* arr, int left, int right)
{
    // 三数取中 -- 面对有序最坏情况,变成选中位数做key,变成最好情况
    //三数取中 —— 三数取中的意义在哪呢 避免arr[keyi]值过大 降低快排的效率

    int min = GetMidIndex(arr, left, right);
    Swap(&arr[left], &arr[min]);

    int keyi = left;

    while (left < right)
    {
        if (left <= right && arr[right] < arr[keyi])
            right--;
        if (left <= right && arr[left] > arr[keyi])
            left++;
        Swap(&arr[right], &arr[left]);
    }
    Swap(&arr[keyi], &arr[left]);

    return left;
}


//因为快排的最初设计有许多人可能没理解 所以就有大佬 设计了下面的“挖坑法” 
//字面上理解就是将一个数字挖掉 该位置变成了一个空位置 谁都可以来这个坑位 而且还不会影响数组中的元素个数
//当然被挖掉的数字不会被丢掉 被丢掉那还怎么排 它会保存在另一个位置 等到有新的坑位再将它放进去 
//如果前面理解清楚了 这个就很好理解 第一个被挖掉的数字 就相当于最初设计快排思想 里面的 arr[keyi] 
//它是最后一个放进去的

//这个挖坑法大家可以通过上面的例子去画一画这个函数的运行逻辑

//挖坑法
int Partion2(int* arr, int left, int right)
{
    int min = GetMidIndex(arr, left, right);
    Swap(&arr[left], &arr[min]);

    int key = arr[left];
    int pivot = left;

    while (left < right)
    {
        while (left < right && arr[right] >= key)
            --right;
        arr[pivot] = arr[right];
        pivot = right;
        while (left < right && arr[left] <= key)
            ++left;
        arr[pivot] = arr[left];
        pivot = left;
    }
    //最后将刚开始存在key里面的值放在它该待在的位置
    arr[pivot] = key;
    return pivot;//这两种写法都可以 最后一定是返回pivot或left 前提是右边先走
    //return right; //试了一下返回这两种都可以
    //return left;

}


//还有种快排思想 相比于前两种 这种方法确实不同 它是通过快慢指针去寻找大于或小于 目标值(arr[keyi])
//例子演示

//8,5,2,6,4,9,7,3,1 
//三数取中处理之后
//4,      5,2,6,8,9,7,3,1 
//keyi   cur
//prev
// 
//4,      5, 2,6,8,9,7,3,1 
//keyi      cur (没进去最里面while) 只进行了cur++  又因为左边先为假 所以不进行右边的条件判断你 prev就没有加
//prev
// 
//4,      5,   2,6,8,9,7,3,1 
//keyi        cur
//       prev(满足条件 进入while循环 进行Swap交换)
// 
//4,      2,  5,  6,8,9,7,3,1 
//keyi       cur
//       prev
// 省略中间的cur++ 步骤
//4,      2,  5,  6,   8,9,7,   3,   1 
//keyi                         cur
//           prev
// 交换
//4,      2,  3,  6,   8,9,7,   5,   1 
//keyi                         cur
//           prev
// 
//4,      2,  3,  6,   8,9,7,   5,   1 
//keyi                              cur
//              prev
// 交换
//4,      2,  3,  1,   8,9,7,   5,   6 
//keyi                              cur
//              prev
// 最后将arr[prev] 和 arr[keyi]交换
//1,      2,  3,  4,   8,9,7,   5,   6 
//keyi                              cur
//                prev
//[left, prev-1]  prev   [ prev+1 ,right] 最终只能返回prev
// 
// 
//快慢指针
int Partion3(int* arr, int left, int right)
{
    //三数取中
    int min = GetMidIndex(arr, left, right);
    Swap(&arr[min], &arr[left]);

    int keyi = left;
    int prev = left;
    int cur = prev + 1;
    while (cur <= right)
    {
        while (arr[keyi] >= arr[cur] && ++prev != cur)
            Swap(&arr[prev], &arr[cur]);
        cur++;
    }
    Swap(&arr[keyi], &arr[prev]);
    return prev;
}



//快速选择排序
//递归版本
void QuickSort(int* arr, int left, int right)
{
    //终止条件
    if (left >= right)
        return;
    else
    {
        //int keyi = Partion1(arr, left, right);
        int keyi = Partion2(arr, left, right);
        //int keyi = Partion3(arr, left, right);


        //[left,keyi-1] keyi [keyi+1,right]
        QuickSort(arr, left, keyi - 1);
        QuickSort(arr, keyi + 1, right);
    }
}

int main()
{
    int arr[] = { 8,5,2,6,4,9,7,3,1 };
    int sz = sizeof(arr) / sizeof(arr[0]);

    int left = 0, right = sz - 1;

    Print(arr, sz);
    QuickSort(arr, left, right);
    Print(arr, sz);

    

以上代码有什么 问题 留言私信~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

挣扎的泽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值