排序算法:快速排序(C实现)

目录
        三数取中
        Hoare法(左右指针法)
        挖坑法
        前后指针法
        用栈实现非递归快排
        特性总结

基本思想

快速排序是指在待排序列中选取一个基准(key值),然后把不比该基准大的放在基准的前面,把不比该基准小的放在基准的后面,这样待排序列就被基准划分成了两个序列,对这两个序列再递归进行这样的操作,直到基准两边没有或者只有一个数字,最终就完成了排序。快排主要用到的算法思想是分治思想。常用的快排方法有:Hoare法(左右指针法)、挖坑法、前后指针法

对下面的数据进行排序:
在这里插入图片描述

三数取中:

首先我们需要选定一个 key值,通常情况下是直接用 arr[begin] 或者 arr[end] 作为 key值的。但是在一些特殊情况下还不是很好,因为选取的 key值可能偏大或者偏小。因此可以用三数取中法选择 key值,判断 begin、end、middle的大小,从上图中可以看到 begin的值适中,适合作为 key值。则交换 begin 和 end 所在位置的元素,然后选取 arr[end]作为key值。
在这里插入图片描述
选定好 key值以后,就可以开始进行快速排序的步骤了。后面介绍的Hoare法(左右指针法)、挖坑法、前后指针法三种方法都是要先用到三数取中。

过程分析

Hoare法(左右指针法)

我们来对三数取中后的数据进行排序:
在这里插入图片描述

  1. 从 begin开始依次向后找,直到找到比 key大的元素停止;从 end开始依次向前找,直到找到比 key小的元素停止
    在这里插入图片描述
    在这里插入图片描述
  2. 交换 begin 和 end 所在位置的元素
    在这里插入图片描述
  3. begin继续依次向后找,直到找到比 key大的元素停止;end继续依次向前找,直到找到比 key小的元素停止
    在这里插入图片描述
  4. 继续交换 begin 和 end 所在位置的元素
    在这里插入图片描述
  5. begin继续依次向后找,直到找到比 key大的元素停止;end继续依次向前找,直到找到比 key小的元素停止
    在这里插入图片描述
  6. 继续交换 begin 和 end 所在位置的元素
    在这里插入图片描述
  7. begin继续依次向后找,此时 begin和 end相遇,结束 begin和 end的遍历交换。只有当 begin < end时才可以继续遍历。

在这里插入图片描述

  1. 把begin所在位置的元素和 key值交换
    在这里插入图片描述
    从上图可以发现 key左边的元素都是小于等于6的,key右边的元素都是大于6的,这样我们就完成了第一次预排序。然后再分别对左区间和右区间的元素重新用三数取中法选定 key值,重复上述步骤,就完成了快速排序。
    在这里插入图片描述
    代码实现
void Swap(int* x, int* y)
{
    int tmp = *x;
    *x = *y;
    *y = tmp;
}

int GetMiddleKey(int* arr, int begin, int end)
{
    int middle = (begin + end) / 2;
    if (arr[begin] > arr[middle])
    {
        if (arr[middle] > arr[end])
        {
            return middle;
        }
        else if(arr[begin] > arr[end]) //arr[middle] < arr[end]
        {
            return end;
        }
        else
        {
            return begin;
        }
    }
    else //arr[begin] < arr[middle]
    {
        if (arr[end] > arr[middle])
        {
            return middle;
        }
        else if (arr[begin] > arr[end]) //arr[end] < arr[middle]
        {
            return end;
        }
        else
        {
            return begin;
        }
    }
}

//Hoare法(左右指针法)
int Hoare(int* arr, int begin, int end)
{
    //三数取中
    int middle_key = GetMiddleKey(arr, begin, end);
    Swap(&arr[middle_key], &arr[end]);

    //依据key进行排序确定子区间
    int key = arr[end];
    int keyi = end;
    while (begin < end)
    {
        while (begin < end && arr[begin] <= key)
        {
            ++begin;
        }

        while (begin < end && arr[end] >= key)
        {
            --end;
        }
        Swap(&arr[begin], &arr[end]);
    }
    Swap(&arr[begin], &arr[keyi]);
    return begin;
}

void InQuickSort(int* arr, int begin, int end)
{
    if (begin >= end)
    {
        return;
    }
    int key = Hoare(arr, begin, end);
    InQuickSort(arr, begin, key - 1); //左区间
    InQuickSort(arr, key + 1, end); //右区间
}

void QuickSort(int* arr, int n)
{
    int begin = 0;
    int end = n - 1;
    InQuickSort(arr, begin, end);
}

测试用例:

void Test()
{
    int arr[] = { 6, 1, 2, 7, 9, 3, 4, 5, 10, 8 };
    int length = sizeof(arr) / sizeof(arr[0]);
    QuickSort(arr, length);
    for (int i = 0; i < length; i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

在这里插入图片描述

挖坑法

同样我们还是来对三数取中后的数据进行排序:
在这里插入图片描述

  1. 选定key值为arr[end],且让 end所在位置为坑
    在这里插入图片描述
  2. 从 begin开始依次向后找,直到找到比 key大的元素停止,将 begin所在的位置和坑交换
    在这里插入图片描述
  3. 从 end开始依次向前找,直到找到比 key小的元素停止,将 end所在的位置和坑交换
    在这里插入图片描述
  4. begin继续依次向后找,直到找到比 key大的元素停止,将 begin所在的位置和坑交换
    在这里插入图片描述
  5. end继续依次向前找,直到找到比 key小的元素停止,将 end所在的位置和坑交换
    在这里插入图片描述
  6. begin继续依次向后找,直到找到比 key大的元素停止,将 begin所在的位置和坑交换
    在这里插入图片描述
  7. end继续依次向前找,直到找到比 key小的元素停止,将 end所在的位置和坑交换
    在这里插入图片描述
  8. begin继续依次向后找,此时 begin和 end相遇,结束遍历交换。只有当 begin < end时才可以继续遍历。

在这里插入图片描述

  1. 结束遍历交换后,将 key值填入到坑中
    在这里插入图片描述
    从上图可以发现 key左边的元素都是小于等于6的,key右边的元素都是大于6的,这样我们就完成了第一次预排序。然后再分别对左区间和右区间的元素重新用三数取中法选定 key值和坑,重复上述步骤,就完成了快速排序。
    在这里插入图片描述
    代码实现
void Swap(int* x, int* y)
{
    int tmp = *x;
    *x = *y;
    *y = tmp;
}

int GetMiddleKey(int* arr, int begin, int end)
{
    int middle = (begin + end) / 2;
    if (arr[begin] < arr[middle])
    {
        if (arr[end] > arr[middle])
        {
            return middle;
        }
        else if (arr[begin] > arr[end]) //arr[end] < arr[middle]
        {
            return begin;
        }
        else
        {
            return end;
        }
    }
    else //arr[begin] > arr[middle]
    {
        if (arr[middle] > arr[end])
        {
            return middle;
        }
        else if (arr[begin] > arr[end])
        {
            return end;
        }
        else
        {
            return begin;
        }
    }
}

int DigPitMethod(int* arr, int begin, int end)
{
    int key_middle = GetMiddleKey(arr, begin, end);
    Swap(&arr[key_middle], &arr[end]);

    int key = arr[end];
    while (begin < end)
    {
        while (begin < end && arr[begin] <= key)
        {
            ++begin;
        }
        arr[end] = arr[begin];

        while (begin < end && arr[end] >= key)
        {
            --end;
        }
        arr[begin] = arr[end];
    }
    arr[begin] = key;
    return begin;
}

void InQuickSort(int* arr, int begin, int end)
{
    if (begin >= end)
    {
        return;
    }

    int key = DigPitMethod(arr, begin, end);
    InQuickSort(arr, begin, key - 1);
    InQuickSort(arr, key + 1, end);
}

void QuickSort(int* arr, int n)
{
    int begin = 0;
    int end = n - 1;
    InQuickSort(arr, begin, end);
}

测试用例:

void Test()
{
    int arr[] = { 6, 1, 2, 7, 9, 3, 4, 5, 10, 8 };
    int length = sizeof(arr) / sizeof(arr[0]);
    QuickSort(arr, length);
    for (int i = 0; i < length; i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

在这里插入图片描述

前后指针法

同样我们还是来对三数取中后的数据进行排序:
在这里插入图片描述
整体思路:cur初始位置是begin,prev初始位置是 begin-1。cur先依次向后遍历找比 key小的元素,当 cur < key时,++prev,然后交换 prev 和 cur所在位置的元素。重复前面的步骤,直到 cur遇到 key时终止。
在这里插入图片描述

  1. cur开始找比 key小的元素,找到后 ++prev,然后交换 prev 和 cur所在位置的元素
    在这里插入图片描述
  2. cur继续找比 key小的元素,找到后 ++prev,然后交换 prev 和 cur所在位置的元素
    在这里插入图片描述
  3. cur继续找比 key小的元素,找到后 ++prev,然后交换 prev 和 cur所在位置的元素
    在这里插入图片描述
  4. cur继续找比 key小的元素,找到后 ++prev,然后交换 prev 和 cur所在位置的元素
    在这里插入图片描述
  5. cur继续找比 key小的元素,找到后 ++prev,然后交换 prev 和 cur所在位置的元素
    在这里插入图片描述
  6. cur继续找比 key小的元素,此时 cur和 key相遇,然后 ++prev,交换 prev和 key所在位置的元素
    在这里插入图片描述
    通过对上图观察可以发现左区间都是小于等于6的元素,右区间都是大于6的元素。然后再对左区间和右区间分别重复上述步骤排序。
    在这里插入图片描述

代码实现

void Swap(int* x, int* y)
{
    int tmp = *x;
    *x = *y;
    *y = tmp;
}

int GetMiddleKey(int* arr, int begin, int end)
{
    int middle = (begin + end) / 2;
    if (arr[begin] < arr[middle])
    {
        if (arr[end] > arr[middle])
        {
            return middle;
        }
        else if (arr[begin] > arr[end]) //arr[end] < arr[middle]
        {
            return begin;
        }
        else
        {
            return end;
        }
    }
    else //arr[begin] > arr[middle]
    {
        if (arr[middle] > arr[end])
        {
            return middle;
        }
        else if (arr[begin] > arr[end])
        {
            return end;
        }
        else
        {
            return begin;
        }
    }
}

//前后指针法
int PrevCurMethod(int* arr, int begin, int end)
{
    //三数取中
    int key_middle = GetMiddleKey(arr, begin, end);
    Swap(&arr[key_middle], &arr[end]);

    int key = arr[end];
    int cur = begin;
    int prev = begin - 1;
    while (cur < end)
    {
        if (arr[cur] < key && ++prev != cur)
        {
            Swap(&arr[prev], &arr[cur]);
        }
        ++cur;
    }
    ++prev;
    Swap(&arr[prev], &arr[end]);
    return prev;
}

void InQuickSort(int* arr, int begin, int end)
{
    if (begin >= end)
    {
        return;
    }

    int key = PrevCurMethod(arr, begin, end);
    InQuickSort(arr, begin, key - 1);
    InQuickSort(arr, key + 1, end);
}

void QuickSort(int* arr, int n)
{
    int begin = 0;
    int end = n - 1;
    InQuickSort(arr, begin, end);
}

测试用例:

void Test()
{
    int arr[] = { 6, 1, 2, 7, 9, 3, 4, 5, 10, 8 };
    int length = sizeof(arr) / sizeof(arr[0]);
    QuickSort(arr, length);
    for (int i = 0; i < length; i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

在这里插入图片描述

用栈实现非递归快排(C++实现)

#include <iostream>
#include <stack>
using namespace std;

void Swap(int* x, int* y)
{
    int tmp = *x;
    *x = *y;
    *y = tmp;
}

int GetMiddleKey(int* arr, int begin, int end)
{
    int middle = (begin + end) / 2;
    if (arr[begin] < arr[middle])
    {
        if (arr[end] > arr[middle])
        {
            return middle;
        }
        else if (arr[begin] > arr[end]) //arr[end] < arr[middle]
        {
            return begin;
        }
        else
        {
            return end;
        }
    }
    else //arr[begin] > arr[middle]
    {
        if (arr[middle] > arr[end])
        {
            return middle;
        }
        else if (arr[begin] > arr[end])
        {
            return end;
        }
        else
        {
            return begin;
        }
    }
}

//前后指针法
int PrevCurMethod(int* arr, int begin, int end)
{
    //三数取中
    int key_middle = GetMiddleKey(arr, begin, end);
    Swap(&arr[key_middle], &arr[end]);

    int key = arr[end];
    int cur = begin;
    int prev = begin - 1;
    while (cur < end)
    {
        if (arr[cur] < key && ++prev != cur)
        {
            Swap(&arr[prev], &arr[cur]);
        }
        ++cur;
    }
    ++prev;
    Swap(&arr[prev], &arr[end]);
    return prev;
}

//用栈实现非递归快排
void QuickSortNonR(int* arr, int begin, int end)
{
    stack<int> st;
    st.push(begin);
    st.push(end);
    while (!st.empty())
    {
        int right = st.top();
        st.pop();
        int left = st.top();
        st.pop();
        int key = PrevCurMethod(arr, left, right);

        if (left < key - 1)
        {
            st.push(left);
            st.push(key - 1);
        }

        if (key + 1 < right)
        {
            st.push(key + 1);
            st.push(right);
        }
    }
}

void QuickSort(int* arr, int n)
{
    int begin = 0;
    int end = n - 1;
    QuickSortNonR(arr, begin, end);
}

测试用例:

void Test()
{
    int arr[] = { 6, 1, 2, 7, 9, 3, 4, 5, 10, 8 };
    int length = sizeof(arr) / sizeof(arr[0]);
    QuickSort(arr, length);
    for (int i = 0; i < length; i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

在这里插入图片描述

特性总结

  1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(logN),真正消耗空间的是递归调用,因为每次递归就要保持一些数据
  4. 稳定性:不稳定
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值