快速排序总结_legend

来源:互联网 发布:php云人才系统破解版 编辑:程序博客网 时间:2024/06/15 23:44


       快速排序

(一) 简介 :

 快速排序是由Hoare提出的,采用了分治法(Divide-and-ConquerMethod)。
分治法一般分成三个组成部分:分解,解决,合并。
快速排序不需要合并,只有分解 ,以及解决(递归)两个步骤。
(二) 基本思想 :(挖坑填数+分治法)

 1.先从数列中取出一个数作为基准数。
 2.将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边,得到基准数的 下标 I 。
 3.再对左右区间(左区间[left , i-1], 右区间[ i+1, right ]  )重复第二步,直到各区间只有一个数。

(三) 步骤:

 (挖坑填数+分治法):

(1)挖坑填数图文解析 :

以一个数组作为示例,取区间第一个数为基准数。
0   1   2   3   4   5   6   7   8  9
72 6 57 88 60 42 83 73 48 85
初始时,i = 0;  j = 9;   X = a[i] = 72,基准数为左边元素,双向扫描。
由于已经将a[0]中的数保存到X中,可以理解成在数组a[0]上挖了个坑,可以将其它数据填充到这来。
从j开始向前找一个比X小或等于X的数。当j=8,符合条件,将a[8]挖出再填到上一个坑a[0]中。a[0]=a[8]; ;  这样一个坑a[0]就被搞定了,但又形成了一个新坑a[8],这怎么办了?简单,再找数字来填a[8]这个坑。这次从i开始向后找一个大于X的数,当i=3,符合条件,将a[3]挖出再填到上一个坑中a[8]=a[3]; 
 
数组变为:
0   1   2   3   4   5   6   7   8  9
48 6 57 88 60 42 83 73 88 85
 i = 3;   j = 7;   X=72
再重复上面的步骤,先从后向前找,再从前向后找。
从j开始向前找,当j=5,符合条件,将a[5]挖出填到上一个坑中,a[3] = a[5]; i++;
从i开始向后找,当i=5时,由于i==j退出。
此时,i = j = 5,而a[5]刚好又是上次挖的坑,因此将X填入a[5]。
 
数组变为:
0   1   2  3   4   5   6   7   8   9
48 6 57 42 60 72 83 73 88 85
可以看出a[5]前面的数字都小于它,a[5]后面的数字都大于它。因此再对a[0…4]和a[6…9]这二个子区间重复上述步骤就可以了。

(2) 挖坑填数总结 :

1.i =L; j = R; 将基准数挖出形成第一个坑a[i] , 即将a[i] 的值保存到temp 中。
(注意保存的是基准值,而并不是基准值的下标。)

2.由后向前找比基准数小的数,找到后挖出此数填前一个坑a[ i ]中,即a[ j] 的值赋给a[ i ]

3.由前向后找大于等于基准数的数,找到后也挖出此数填到前一个坑a[j]中,即将a[ i] 的值赋值给 a[ j ]。

4.再重复执行2,3二步,直到i==j,将基准数填入a[i] 这个坑中。
5.  结果为返回基准数的下标 i .


(刚开始时是将array[left]赋值给temp ,所以要从右边找数填坑。
即左边挖坑,右边找数填坑;
右边挖坑,左边找数 填坑。)

(3) 实现代码 :

int partition(int s[], int left, int right) //分解函数,返回调整后基准数的位置
{
       int leftIndex = left, rightIndex = right;
       int x = s[left]; //s[l]即s[i]就是第一个坑
       while (leftIndex < rightIndex)
       {
              // 从右向左找小于x的数来填s[i]
              while(leftIndex < rightIndex && s[rightIndex] >= x)
                     rightIndex--;
              if(leftIndex < rightIndex)
              {
                     s[leftIndex] = s[rightIndex]; //将s[j]填到s[i]中,s[j]就形成了一个新的坑
                                   }
 
              // 从左向右找大于或等于x的数来填s[j]
              while(leftIndex < rightIndex && s[leftIndex] < x)
                     leftIndex++;
              if(leftIndex <rightIndex)
              {
                     s[rightIndex] = s[leftIndex]; //将s[i]填到s[j]中,s[i]就形成了一个新的坑
                                  }
       }
       //退出时,i等于j。将x填到这个坑中。
       s[leftIndex] = x;
 
       return leftIndex;
}
 (
注意: 此中x保存的是基准值,而不是基准值的下标,因为在后来的比较中,通过下标找到的基准值可能已经发生了替换。

再写分治法的代码:
void quick_sort1(int s[], int left, int right)
{
       if (left < right)
    {
              int pivot = partition(s, left, right);//先成挖坑填数法调整s[]
              quick_sort1(s, left, pivot - 1); // 递归调用
              quick_sort1(s, pivot + 1, right);
       }
}


(四) 快速排序扩展 :

(1) 算法描述 :
(2) 时间复杂度分析 :
(3) 具体实现细节 :
3.1)划分 :
 3.1.1 选取基准数(枢纽点)
  1. 左边元素
  2. 随机元素
  3. 三数取中

 3.1.2 分割
  1. 单向扫描
  2. 双向扫描
  3. Hoare的双向扫描
  4. 改进的双向扫描
  5. 双向扫描的其他问题
3.2)分治 :
 3.2  尾递归

(4) 时间复杂度分析:
快速排序最佳运行时间O(nlogn),最坏运行时间O(N^2)。
两点很重要:
1. 选取枢纽元的不同, 决定了快排算法时间复杂度的数量级;
2. 划分方法的划分方法总是O(n), 所以其具体实现的不同只影响算法时间复杂度的系数。

(5) 选取基准值 :select_pivot( ElementType  A[], int left, int right)(枢纽元)
1. 左边元素
2. 随机元素
3. 三数取中
1) 左边元素:
对于完全随机的数据,枢纽元的选取不是很重要,往往直接取左端的元素作为枢纽元。

 ElementType selecet_pivot(ElementType A[ ] , int left ,int right ){
     Return A[left ];
}

问题:但是实际应用中,数据往往是部分有序的,如果仍用两端的元素最为枢纽元,则会产生很不好的划分,使算法退化成O(n^2)。

所以要采用一些手段避免这种情况,我知道的有“随机选取法”和“三数取中法”。

2)随机元素 :

 ElementType select_pivot(ElementType A[ ] ,int left ,int right ){
Int i=randInt(left ,right );
Swap(&A[left ] ,& A[ i ]);
Return A[left ];
}

分析:此中获取的基准值始终是A[ left ] ,这样 选择不同 基准值,后面的代码不用改变 。

(3) 三数取中 :

1) ElementType select_pivot(ElementType A[ ] ,int left ,int right ){
Int mid=(left+right)/2;


// swap to ensure A[mid ]<=A[left ]<=A[ right ]


If(A[left] < A[mid] ) swap(&A[left], &A[mid]);
If(A[right]<A[left])  swap(&A[left], &A[right]);
If(A[right]<A[mid])   swap(&A[left] ,& A[right]);
Return A[left ];


}

(5)  改进快速排序 :


按照上面的方法,递归会持续到分区只有一个元素。而事实上,当分割到一定大小后,继续分割的效率比插入排序要差。

所以 改进为 :

Void  quicksort( ElementType A [ ] ,int left ,int right){
If (q-p>cutoff ) // cutoff  is constant
{
Int pivot=partition(A,left ,right);
quicksort(A, left ,pivot-1);
quicksort(A,pivot+1,right);
}
Else
insertSort(A,left ,right);
}

分析: 本来快速排序的递归条件是if (left < right) ,
此中将其改为 if( right-left>cutoff) ,说明在前面一部分是用快速排序,
到后面一部分直接是用插入排序 来处理更加小的数组。
(6) 总结 :
1. 快速排序 (分治法中的划分,解决):
1. 选取基准值(左边元素,随机元素,三数取中)
2. 划分函数 partition 之 挖坑填数,返回枢纽点下标(中心点下标)。
3. 递归


0 0