首页 > 代码库 > 快速排序算法

快速排序算法

引言

快排采用分治法(Divide and Conquer)把一个list分为两个sub-lists。

算法步骤

1. 从数列中跳出一个元素,作为基准(pivot)。

2. 重新排序数列,所有比基准值小的元素(elements < pivot)放在基准值的前面,而所有比基准值大的元素(elements > pivot)放在基准值后面,与基准值相等的数可以放在任意一边。此操作即为分区(partition)操作。

3. 递归地把小于基准值元素的子数列和大于基准值元素的子数列进行排序。递归在数列长度为0或者1时退出,此时该子数列已经永久排序完成。

  1)如数列5 8 7 …,若此时5归位,那么其 left sub-list 的长度为0,退出递归。

  2)如数列1 2 8 4…,若此时2归位,那么其 left sub-list的长度为1,退出递归。

注意:该算法一定是可以退出的,因为在每次的迭代中,它至少会把一个元素放在它最后应该在的位置。下文将针对partition提出两种思路。

method 1 for partition

我们假定数列第一个元素即为我们的基准元素。索引 i 指向数列第一个元素,索引 j 指向数列最后一个元素。我们的要求如下:

pivot  =  key[0]

arr[i]  <  pivot           条件1

arr[j] >= pivot          条件2

注意:条件1和条件2是对立事件。满足一方,必定不满足另一方。

由于第一个元素就是基准元素,因此一定不会满足条件1,因此我们从索引 j 开始,找出第一个不满足条件2(那么必定满足条件1)的元素将其与基准元素交换,再从索引 i 开始找出第一个不满足条件1(那么必定满足条件2)的元素将其与基准元素交换。这样从后往前找一个交换,再从前往后找一个交换的好处在于,我们的基准元素的位置是在跟着变化的。最终可以处在正确的位置。

简而言之,当i与j相遇时,索引左边的元素一定小于基准元素,索引右边的元素也一定大于等于基准元素,而索引所在位置,为临界值,必然处于正确位置。

此交换元素的思想实际上在之前的博文中已经出现过,区别在于,之前博文的一次交换可以使交换的两个数均从错位位置归位到了正确的位置,而本文的每次交换始终是使一个处于不正确位置的元素(从前往后或从后往前)与基准元素交换,这样每次交换只能使得一个元素归位到正确位置,但是好处是使基准元素的位置跟着变化,当i与j相遇时,相遇位置便是基准元素位置应该在的地方。

完整代码如下:

#include <stdio.h>#include <stdlib.h>#include <string.h>static void swap(int *left, int *right){    int tmp = *left;    *left = *right;    *right = tmp;}
int partion(int *arr, int len){    int key = arr[0];    int low,high;    low = 0;    high = len - 1;    while(low < high)    {        while(low < high && arr[high] >= key)        {            high--;        }        if(low >= high)        {            break;        }else        {            swap(&arr[low],&arr[high]);        }        while(low < high && arr[low] < key)        {            low++;        }        if(low < high)//后面这个条件不满足。如果前面的条件不满足,反正外循环会判断。        {            swap(&arr[low],&arr[high]);        }    }    return low; //分界值}void quick_sort(int *arr, int len){    if(len == 0 || len == 1)        return;    int k;    k = partion(arr, len); // 0 ---- k-1     k+1 --- len-1    quick_sort(arr,k);    quick_sort(arr+k+1, len - k - 1) ;}void show(int *arr, int len){    int i;    for(i = 0; i < len; i++)    {        printf("%d ", arr[i]);    }    printf("\n");}int main(){    int arr[] = {2,3,4,5,6,8,7,9,1,10};    quick_sort(arr,10);    show(arr,10);    return 0;}

method 2 for partition

使用快慢指针。仍以数列第一个元素为基准元素。初始化时,慢指针指向基准元素,也就是数列第一个元素,快指针指向数列第二个元素。快指针用于遍历数列。当程序运行时,慢指针将指向比基准元素小的元素的最后一个位置。当快指针指向的元素小于满指针指向的元素时,才进行交换,其实就是将小于基准元素的元素的位置往前移(思想类似于去空格,参看此博文)。

partition代码如下:

int partion(int *arr, int len){    int last = 0;    int fast = 1;    int key = arr[0];    for(;fast < len; fast++)    {        if(arr[fast] < key)        {            swap(&arr[last+1], &arr[fast]);            last++;        }    }    swap(&arr[0], &arr[last]); //归位    return last;}

快速排序算法