数据结构 快速排序

思想

对于一般的排序算法,复杂度几乎都是o(n^2),排序算法需要遍历一遍整个数组,时间复杂度是从o(n)开始的,这一遍遍历几乎无法优化,优化的办法在于嵌套的第二层遍历。

快速排序由C. A. R.Hoare在1962年提出,它是一种基于二叉树结构的交换排序算法,它采用了一种分治的策略,通常称其为分治法。
该方法的基本思想是:先从数列中取出一个数作为基准数,然后将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边,
再对左右区间重复第二步,直到各区间只有一个数。快速排序是一种不稳定的排序方法,但是他的效率非常快,比肩希尔和堆排序也略胜一筹。

快速排序是一种分而治之的思想,借助一个基准,将数组的所有数据拆分成2组,拆分的2组数据个数如果相同,那么对这两个小组的排序时间复杂度就会变成o(n/2),
反之拆分2组数据个数是1:n-1,那么对这两个小组排序的时间复杂度就是o(n-1),接近于o(n)。由此可知快速排序的时间复杂度跟基数的选取有莫大关系。

源码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>


#define GTC_ARRAY_SIZE 10

static void build(int* arr, int num);
static void show(int* arr, int num);
static void quick_sort(int* arr, int num);


void test()
{
    // 定义数组
    int arr[GTC_ARRAY_SIZE];

    // 构造随机数组元素
    build(arr, GTC_ARRAY_SIZE);

    // 打印数组信息
    show(arr, GTC_ARRAY_SIZE);

    // 快速排序
    quick_sort(arr, GTC_ARRAY_SIZE);

    // 打印数组信息
    show(arr, GTC_ARRAY_SIZE);
}


int main(int argc,char*argv[])
{
    test();
    return 0;
}


static void show(int* arr, int num)
{
    int i = 0;

    for (i = 0; i < num; i++)
    {
        printf("%5d", *(arr + i));
    }
    printf("\n");
}


static void build(int* arr, int num)
{
    time_t ts;
    int i;

    srand((unsigned int)time(&ts));

    for (i = 0; i < 10; i++)
    {
        arr[i] = (int)(rand() % 100);
    }

}


static void quick_sort(int* arr, int num)
{
    int pivot, i, last, idx;
    int ele;

    if (num < 2)
    {
        return;
    }

    idx = 0;
    last = num - 1;
    pivot = arr[last];

    // 遍历时,刨除最后一个元素
    for (i = 0; i < last; i++)
    {
        if (arr[i] <= pivot)
        {
            // 交换位置
            ele = arr[idx];
            arr[idx] = arr[i];
            arr[i] = ele;

            idx++;
        }
    }

    // 更新基准值
    ele = arr[idx];
    arr[idx] = pivot;
    arr[last] = ele;

    quick_sort(arr, idx);

    quick_sort(arr + idx + 1, num - idx - 1);

}

关于快速排序,首先是按照一个基准值,将数组中的元素拆分成两部分
为了节省内存,打算使用同一块内存来完成该功能

基准值为啥需要放在两部分中间位置?
如果基准值不移动,假如选中的基准值本来就是最小的(或者最大的),就会陷入死循环

对于基准值的选择
a.基准值选择下标0
    当基准值使用arr[0]时,arr[0]的值是无效的,最终进行基准值替换时,使用大于基准值的数据替换arr[0]是有问题的
b.基准值选择中间下标
    问题同上
c.基准值选择末尾
    顺序遍历,那么无效值位置一直在末尾,移动元素的过程中也不会涉及该元素,
   分组完成后,做替换操作将会很容易

 

static void quick_sort(int* arr, int num) 
{
    int i, j, pivot;
    int temp;
    int first, last;

    first = 0;
    last = num - 1;

    if (first >= last)
    {
        return;
    }

    pivot = first;
    i = first;
    j = last;
    while (i < j) 
    {
        // arr[pivot]的位置实际上不会发生变化
        while (arr[i] <= arr[pivot] && i < last)
            i++;

        while (arr[j] > arr[pivot])
            j--;

        if (i < j) 
        {
            temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
        }
    }

    // 至此,arr[j]是小于或者等于arr[pivot]

    // 这一步交换位置才是精华
    temp = arr[pivot];
    arr[pivot] = arr[j];
    arr[j] = temp;

    quick_sort(arr, j);
    quick_sort(arr + j + 1, num - j - 1);

}

对于基准值的选择
a.基准值选择下标0
    这里只能选择arr[0],因为选择arr[0]才能保证pivot是固定且唯一的,选择其他位置pivot都可能发生变化

a.基准值选择末尾

  这里选择末尾,那么每一轮排序实际上就是选择出最大的元素作为基准值,跟冒泡排序没有区别

posted on 2016-08-07 10:30  寒魔影  阅读(362)  评论(0)    收藏  举报

导航