快速排序

来源:互联网 发布:邮箱的域名是什么 编辑:程序博客网 时间:2024/05/29 10:31

    • 一简介
    • 二分区过程
      • 1 两头法
      • 2 快慢指针
    • 三递归实现

一、简介

快速排序是基于 分治法 思想的一种排序算法,由于排序效率在同为 O(N*logN) 的几种排序方法中效率较高,因此经常被采用。

该方法的基本思想是:

  • 从数列 arr[left,right] 中取出一个数作为 基准数。(在此以 pivot 标记基准所在的下标)
  • 分区。将小于或等于 arr[pivot] 的数全放到它的左边,大于 arr[pivot] 的数全放到它的右边。由此形成以 arr[pivot] 为分界线的左右区间,[left,pivot)全是小于等于 arr[pivot] 的数,(pivot,right] 全是大于 arr[pivot] 的数。
  • 再对左右区间重复第二步,直到各区间只有一个数。


二、分区过程

假设要对 [ left , right ] 指定范围的数列arr 进行升序排序。根据快速排序的基本思想,首先需要选取一个基准 arr[pivot]在此假定选取 arr[left] 为基准。

2.1 两头法

基本思路:

  • 首先,从 right 起始,向左遍历,找到第一个小于 arr[pivot] 的数,即为arr[right]
  • 然后,从 left 起始, 向右遍历,找到第一个大于 arr[pivot] 的数,即为arr[left]
  • 交换这两个元素的值 swap(arr[right],arr[left])
  • 重复上述步骤,直至 left < right不成立。
  • 此时,以 left 为界限的左边,将全是小于等于 arr[pivot] 的元素;右边则全是大于 arr[pivot] 的元素。而 arr[pivot] 还在 left 的左边,所以此时,只要将 arr[pivot]arr[left] 交换,就能使得原始序列以 arr[pivot] 为界限。

上述 查找/交换 的过程,是为了将小于 arr[pivot] 的值通过交换,交换到左边;以及将大于 arr[pivot] 的值通过交换,交换到右边。

举例说明:

以序列 {7,8,4,5,1,9,6} 为例,进行升序排序。下图是一次 partition 过程。

经过一次 partition 之后,原始序列将被分为以基准(7)为界限的两个区间。

partition 的完整代码:

int partition1(int left,int right){    int pivot = left; // 选择 arr[left] 为基准    while(left < right)    {        while(left < right && arr[right] >= arr[pivot])        {            right--;        }        while(left < right && arr[left] <= arr[pivot])        {            left++;        }        if(left < right)        {            swap(arr[left],arr[right]); // 将小数交换到左,大数交换到右。        }    }    swap(arr[left],arr[pivot]); // 将基准放到合适位置。    return left;}


2.2 快慢指针

基本思路:

  • 快指针由右向左遍历,每找到一个大于 arr[pivot] 的值,就交换到 slow 指定的位置,然后 slow 前移一位。slow 的含义是下次找到满足条件的值 val 时,val 该交换到 slow 指定的位置。
  • 当遍历完一遍之后,所有满足条件的元素,都会被交换到 slow 的右边。而此时,arr[pivot] 还在slow的左边;因此,接下来只要将 arr[pivot]arr[slow] 交换位置,就可以使得原始序列以基准为分界线,分为两个区间。

举例说明:

以序列 {7,8,4,5,1,9,6} 为例,进行升序排序。下图是一次 partition 过程。

经过一次 partition 之后,原始序列将被分为以基准(7)为界限的两个区间。

partition 的完整代码:

int partition2(int left, int right){    int pivot = left;     // 选择 arr[left] 为基准    int slow = right;   // 慢指针    int quick = right;  // 快指针    while(quick > left)    {           // 找到大于 arr[pivot] 的元素,交换到 slow 指定的位置。        if(arr[quick] > arr[pivot])        {            swap(arr[quick],arr[slow]);            slow--;  // slow 右边全是大于 arr[pivot] 的元素。        }        quick--;    }    swap(arr[slow],arr[pivot]);    return slow;}


三、递归实现

在第二小结中,给出了两种 partition 的思路。任何一种方法,都能够将序列分为以基准为界限的两个区间,左边区间的所有元素都将小于等于基准值;右边区间的所有元素都将大于等于基准值。

再回头看看,快速排序的基本思想:

  • 从数列 arr[left,right] 中取出一个数作为 基准数。(在此以 pivot 标记基准所在的下标)
  • 分区。将小于或等于 arr[pivot] 的数全放到它的左边,大于 arr[pivot] 的数全放到它的右边。由此形成以 arr[pivot] 为分界线的左右区间,[left,pivot)全是小于等于 arr[pivot] 的数,(pivot,right] 全是大于 arr[pivot] 的数。
  • 再对左右区间重复第二步,直到各区间只有一个数。

根据快排的基本思想,每次 partition 都能将待排序序列分为两个区间。所以,快排函数需要做的就是对这两个区间,继续 partition。显然,递归是解决快排问题的一种方法。

递归实现:

#include <iostream>#include <algorithm>using namespace std;int partition1(int arr[],int left,int right){    int pivot = left; // 选择 arr[left] 为基准    while(left < right)    {        while(left < right && arr[right] >= arr[pivot])        {            right--;        }        while(left < right && arr[left] <= arr[pivot])        {            left++;        }        if(left < right)        {            swap(arr[left],arr[right]); // 将小数交换到左,大数交换到右。        }    }    swap(arr[left],arr[pivot]); // 将基准放到合适位置。    return left;}int partition2(int arr[],int left, int right){    int pivot = left;    int slow = right;   // 慢指针    int quick = right;  // 快指针    while(quick > left)    {        if(arr[quick] >= arr[pivot])        {            swap(arr[quick],arr[slow]);            slow--;        }        quick--;    }    swap(arr[slow],arr[pivot]);    return slow;}void quick_sort(int arr[],int left,int right){    if(left < right)    {        int pivot = partition1(arr,left,right); // 此处可以选择不同的 partition 方法。        quick_sort(arr,left,pivot-1);        quick_sort(arr,pivot+1,right);    }}int main(){    int arr[] = {7,8,4,5,1,9,6};    quick_sort(arr,0,sizeof(arr)/sizeof(int) - 1);    for(int i = 0; i < sizeof(arr)/sizeof(int); i++)     {         cout << arr[i] << " ";     }     cout << endl;    return 0;}
0 0
原创粉丝点击