模板
注:这段代码来自于百度百科。(删除注释)
void sort(int *a, int left, int right)
{
if(left >= right)return;
int i = left;
int j = right;
int key = a[left];
while(i < j)
{
while(i < j && key <= a[j])
j--;
a[i] = a[j];
while(i < j && key >= a[i])
i++;
a[j] = a[i];
}
a[i] = key;
sort(a, left, i - 1);
sort(a, i + 1, right);
解读
1.冒泡排序
在百度百科中有这么一句话:“快速排序(Quicksort)是对冒泡排序的一种改进”。
那么首先我们可以回顾一下冒泡排序的算法:
void bubble(int *a,int len)
{
int i,j,flag;
for(i=0;i<len-1;i++)
{
flag=0;
for(j=0;j<len-i-1;j++)
if(a[j+1]>a[j])
{
swap(a+j+1,a+j);//这个函数中包含了三个元操作
flag=1;
}
if(!flag)return;
}
}
冒泡排序的核心思想就是相邻两个元素之间比较(两两比较),在两两比较的过程中,我总是把大的元素放在前面,小的元素放在后面,那么经过N次比较,一次遍历过后,最小的元素就被推到了最后面,实际遍历通过下标j,i用于记录已经遍历过的次数(每遍历一次就有一个”极值“被推到某一正确位置,就像气泡一样,大的气泡把小的气泡顶上去。)例下,从小到大排序数组A[5]={5,4,3,2,1}
j/i | i=1 | i=2 | i=3 | i=4 |
---|---|---|---|---|
j=1 | 4,5,3,2,1 | 3,4,2,1,5 | 2,3,1,4,5 | 1,2,3,4,5 |
j=2 | 4,3,5,2,1 | 3,2,4,1,5 | 2,1,3,4,5 | |
j=3 | 4,3,2,5,1 | 3,2,1,4,5 | ||
j=4 | 4,3,2,1,5 |
不难看出,冒泡排序的最小子问题就是两个元素之间的排序。譬如数组A[4,3,2,1],我想把元素4往上顶,那么按照冒泡排序的算法我得先和元素3进行比较和交换,元素4再和元素2比较和交换,以此类推。在元素4被推向第四个位置时,需要进行3次交换,元素3推向第三个位置需要2次交换,数组A排完序一共需要3+2+1=6次交换。
2.分治思想
既然冒泡排序的最小子问题是两两比较,我们不妨就从两两比较入手,考虑一般排序中最坏的情况——逆序。按照冒泡算法我们需要进行n*(n-1)/2次交换,把这n个数二分,分成两个n/2的逆序列,那么需要2*(n/2)*(n/2-1)/2+n/2次数据交换。我们可以得出结论:在逆序情况下,冒泡排序的事件复杂度是O(n ^ 2),做一次二分后的冒泡排序的时间复杂度是O((n/2)^2 )。同理如果我们做两次二分,那么逆序情况下的时间复杂度还会降低,如果我们一直二分,最后变成两个数之间的排序,此时逆序情况下的时间复杂度最低。那么逆序的二分法有如下图解
在这里,深色为分组,浅色表示排好序。最后其实是把有序的组[4,5],[3],[1,2]在进行排序得到[1,2,3,4,5]。如果把数组A的元素3改成4.5得到一个新的数组B[5,4,4.5,2,1],那么这个上面的排序过程还能解决数组B的排序问题嘛?熟悉排序的人一眼就能看出,在最后排序[4,5],[4.5],[1,2]的时候会有问题,因为4.5应该在4和5的中间,而按分组排序的情况在两两比较时必须满足:i分组中最大元素比ii分组中最小的元素小或者i分组中最小的元素比ii分组中最大的元素大。 显然,第一个逆序的例子是个特殊情况,在乱序的情况下,需要我们对二分的过程进行改进。所以,为了满足刚刚说的条件,我们可以把采用选取中间值的方法,以某一元素为中间值,把大于中间值的数分成一组,把小于等于中间值的数分成另一组。
快排步骤
1.确定首尾,确定关键值
2.以关键值为分界线分成两组
3.重复步骤1,2(确定首尾的值相等或者首小于尾时回溯)