快速排序

blog_quickSort

1 快排分析 与 实现

 

1.1 原理

quick sort 与 merge sort 类似都是 分治策略. 都具有如下的执行时间递推关系: \(T(n) <= 2T(n/2) + cn\)
quick sort在理想情况下如此. T(n): 表示n规模上的最坏执行时间.
差异在于:

  1. quick sort 将cn花在拆分上, 而 merge sort 将cn花在合并上.
    • merge sort 花在 将2个有序数组合并
    • quick sort 花在 将1个无数数组 拆分为2个无序数组. 1个无数数组中所有元素, 都比 另一个数组中元素小.
  2. merge的递推关系是确定的, 而quick sort不是, 在理想情况下与merge sort相同.
  3. merge sort需要额外内存, 而 quick sort 不需要.

1.1.1 quick sort 最坏情况

最坏情况: 选出的划分元素, 产生极端情况, 只能1个集合为空, 剩余元素全在另一个集合. 具有递推关系: \(T(n) <= T(n-1) + cn\) 展开该递推关系得: \(T(n) <= cn + c(n-1) + c(n-2) +...+ 1 = \theta(n^2)\)

1.1.2 quick sort 期望运行时间证明

但是, quick sort 能得到期望运行时间 \(\theta(nlogn)\) 简单证明过程如下:

  1. 定义 中心划分元素 能够划分为1:3的两部分, 即为中心划分元素. 这样, 有1/2的元素, 都是中心元素.
  2. 定义子问题类型 类型j为, 元素个数为 \(n(3/4)^j\) 到 \(n(3/4)^(j+1)\)
  3. 定界 类型j子问题个数 用总数除以类型j的规模下界,即, \(n/(n(3/4)^(j+1)) = (4/3)^(j+1)\)
  4. 定界 类型j子问题的执行时间 \(O(n(3/4)^j)\)
  5. 将3,4相乘, 并求和得到处理一类需要 O(n), 而类型数O(logn) 层数 所以总时间为O(nlogn)

1.2 quick sort 实现

实现对比

void quickSort(int* nums, int numsSize) {
    if (numsSize <= 1) {
        return;
    }
    if (numsSize == 2)
    {
        if (nums[0] > nums[1]){
            int temp = nums[0];
            nums[0] = nums[1];
            nums[1] = temp;
        }
        return;
    }


    int spliterIndex = numsSize-1;

    int firstHalfIndex = 0;
    int secondHalfIndex = numsSize-2;

    int randIndex = rand()%numsSize; 
    int temp = nums[spliterIndex];
    nums[spliterIndex] = nums[randIndex];
    nums[randIndex] = temp;

    int spliter = nums[spliterIndex];  

    while (firstHalfIndex != secondHalfIndex) {
        if (firstHalfIndex < numsSize-2 && nums[firstHalfIndex] <= spliter) {
            firstHalfIndex++;
        } else if (secondHalfIndex > 0 && nums[secondHalfIndex] >= spliter) {
            //ERROR: 没有加else
            secondHalfIndex--;
        }

        if (nums[firstHalfIndex] > nums[secondHalfIndex]) {
            int temp = nums[firstHalfIndex];
            nums[firstHalfIndex] = nums[secondHalfIndex];
            nums[secondHalfIndex] = temp;
        }
    }
    if (nums[secondHalfIndex] > nums[spliterIndex]) {
        nums[spliterIndex] = nums[secondHalfIndex];
        nums[secondHalfIndex] = spliter;
        spliterIndex = secondHalfIndex;
    }

    quickSort(nums, spliterIndex);
    quickSort(&nums[spliterIndex+1], numsSize-spliterIndex-1);
}
void swap(int* a, int* b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}

void preprocess(int array[], int left, int right)
{
    if (left>=right) {
        return;
    }

    int randIndex = rand()%(right-left) + left;

    swap(&array[right], &array[randIndex]);
}

int partition(int array[], int left, int right)
{
    preprocess(array, left, right);

    int spliterIndex = right;
    int spliter = array[right];

    while(left < right) {
        while(left < right && array[left] <= spliter) {
            left++;
        }
        while(left < right && array[right] >= spliter) {
            right--;
        }

        swap(&array[left], &array[right]);
    }

    swap(&array[left], &array[spliterIndex]);
    return left;
}

void quickSort(int array[], int left, int right)
{
    int spliterIndex;

    if (left >= right) {
        return;
    }

    spliterIndex = partition(array, left, right);

    quickSort(array, left, spliterIndex-1);
    quickSort(array, spliterIndex+1, right);
}
  1. 使用数组下标更好. 减少len和下标之间的转换.
  2. 逻辑上拆分为多个函数, 更漂亮, 更好分析, 更好改进.
posted @ 2019-11-17 22:34  董庆然  阅读(168)  评论(0编辑  收藏  举报