排序 ---- 快排(C语言)


  • 思想:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都要比另一部分的所有数据要小,然后再按此方法对这两部分数据分别进行快排,整个过程可以递归进行,以此达到整个数据变成有序序列。

  • 时间复杂度:最优情况O(nlgn) 最差情况O(n2)

  • 空间复杂度:O(1)
  • 稳定性: 快排是不稳定排序算法

快排版本

快排有很多版本,但是最终目的都是为了排序

三数取中法:

由于每次取到的是序列的最右端的元素,取到极值的可能性是很大的,于是我们对他进行优化,每次选序列最左、最右和中间的元素,然后找出其中的中间元素,并把它和最右端的元素相交换,这样的话可以提高快排的效率。
代码实现:

 // 三数取中法
int GetMidIndex(int* array, int left, int right)
{
    int mid = left + (right - left) / 2;

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

1.先看提出快排思想的hoare版本

这里写图片描述
这里写图片描述


 // 分治思想
 // [left, right] 左闭右闭区间
void QuickSort(Datatype* array, int left, int right)
{
    int mid = 0;
    int div = 0;
    Datatype key = 0;
    int begin = 0;
    int end = 0;

    // 解释一下这里 left 是有可能大于 right 的    边界细节 
    // 举个例子 如果此时序列是 1 2 3                   
    // 以3为基准 左边划分没有问题 重点看一下右边 
    // 递归往右边走的时候 left指向3后面的元素(下标为3的元素)right 指向3(下标为2)
    // 那么此时就找不到递归出口(就会Stack overflow)  
    // 所以这里递归出口的判断条件必须是 ">="  或者写成 !(left < right)
    if (left >= right)
    {
        return;
    }

    // 这是我们的一种优化  三数取中法
    // 如果每次取序列最优端的那个元素 极易取到极值
    // 我们在序列中选择了三个数
    // 拿到这三个数的中间值 如果他不是最右边的元素就把他交换到最右边
    // 以此来提高快排的性能
    mid = GetMidIndex(array, left, right);
    if(mid != right)
    {
        _swap(&array[mid], &array[right]);
    }
    key = array[right]; // 这是选的基准
    begin = left;
    end = right;

    // begin从前往后找第一个比基准值大的元素
    // end从后往前找第一个比基准值小的元素
    // 然后交换他们指向的元素 这样小数就在前面 大数就调到后面了 
    while (begin < end)
    {
        // begin 找大
        while (begin < end && array[begin] <= key)
        {
            ++begin;
        }
        // end 找小
        while (begin < end && array[end] >= key)
        {
            --end;
        }
        if (begin < end)
        {
            _swap(&array[begin], &array[end]);
        }
    }

    // 当begin与end相遇的时候 begin这个位置记录的元素应该是大于基准值的
    // 将begin位置元素与序列最右边元素交换
    _swap(&array[begin], &array[right]);

    // begin记录分割边界 div之前元素都比 div之后的元素小
    div = begin;  

    // 递归排序列的左半部分
    QuickSort(array, left, div - 1);
    // 递归排序列的右半部分
    QuickSort(array, div + 1, right);
}

测试:

#include "Sort.h"

void Print(Datatype* array, int size);

void TestQSort()
{
    Datatype array[] = { 12, 2, 5, 37, 34, 88, 3, 77, 9, 21, 37 };
    int size = sizeof(array) / sizeof(array[0]);
    Print(array, size);
    QuickSort(array, 0, size-1); // 因为是[left, right]
    Print(array, size);

}

int main()
{
    TestQSort();
    system("pause");
    return 0;
}

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

测试结果:
这里写图片描述
2.挖坑法
这种方法是最容易理解的
这里写图片描述

 // 时间复杂度O(nlgn)
 // 空间复杂度O(1)
 // 快排
 // [ , ) 左闭右开区间
 // 挖坑
void QuickSort(Datatype* array, int left, int right)
{
    int mid = 0;
    int low = left;
    int high = right - 1;
    Datatype temp = 0;

    if (!(left < right))  // 解释一下这里 left 是有可能大于 right 的  边界细节 
    {                     // 举个例子 如果此时序列是 1 2 3                   
        return;           // 以3为基准 左边划分没有问题 重点看一下右边 
    }                     // 递归往右边走的时候 left指向3后面的元素(下标为3的元素)right 指向3(下标为2)
                          // 那么此时就找不到递归出口(就会Stack overflow)  
                          // 所以这里递归出口的判断条件必须是 ">="  或者写成 !(left < right)

    mid = GetMidIndex(array, left, right - 1);
    if(mid != right - 1)
    {
        _swap(&array[mid], &array[right - 1]);
    }
    temp = array[high]; // 这是挖的第一个"坑" 把原来"坑"里的值用temp保存一份 原来的数据不会丢失 覆盖了也没啥事
    while (low < high)
    {
        while (temp >= array[low] && low < high) // 这里有两个陷阱 一是 ">="   二是 low < high
        {
            low++;
        }
        if (low < high)
        {
            array[high] = array[low]; // 填"坑"  并且low这个位置又是一个新的坑
        }

        while (temp <= array[high] && low < high)
        {
            high--;
        }
        if (low < high)
        {
            array[low] = array[high]; // 再填"坑"
        }

    }
    array[low] = temp; // 最后把最后一个"坑"填上

    QuickSort(array, left, low);
    QuickSort(array, low + 1, right);
}

接下来我们来测试一下”挖坑法“版本的快排

void Print(Datatype* array, int size);

void TestQSort()
{
    Datatype array[] = { 12, 2, 5, 37, 34, 88, 3, 77, 9, 21, 37 };
    int size = sizeof(array) / sizeof(array[0]);
    Print(array, size);
    QuickSort(array, 0, size-1);
    Print(array, size);

}

int main()
{
    TestQSort();
    system("pause");
    return 0;
}

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

测试结果:
这里写图片描述
3.下来我们来介绍一下”前后指针法“
前后两个指针,起始的时候前指针Cur在下标为left位置,后指针Pre在left-1的位置

 // 前后指针
 // [ , ]
void QuickSort(Datatype* array, int left, int right)
{
    int Pre = 0;
    int Cur = 0;
    Datatype key = 0;
    int mid = 0;
    int div = 0;

    if (!(left < right))
    {
        return;
    }

    Pre = left - 1;
    Cur = left;
    key = array[right];

    mid = GetMidIndex(array, left, right);
    if(mid != right)
    {
        _swap(&array[mid], &array[right]);
    }

    while (Cur < right)
    {
        if (array[Cur] < key && ++Pre != Cur)
        {
            _swap(&array[Pre], &array[Cur]);
        }

        ++Cur;
    }

    _swap(&array[++Pre], &array[right]);
    div = Pre;

    QuickSort(array, left, div - 1);
    QuickSort(array, div + 1, right);
}

测试:

void Print(Datatype* array, int size);

void TestQSort()
{
    Datatype array[] = { 12, 2, 5, 37, 34, 88, 3, 77, 9, 21, 37 };
    int size = sizeof(array) / sizeof(array[0]);
    Print(array, size);
    QuickSort(array, 0, size-1);
    Print(array, size);

}

int main()
{
    TestQSort();
    system("pause");
    return 0;
}

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

测试结果:
这里写图片描述


如有不正,还请指出,有劳!

猜你喜欢

转载自blog.csdn.net/Eric_qiushui/article/details/80585230