快排
废话不说,先贴上代码,代码在《c程序设计语言》p74:
void swap(int v[],int i,int j)
{
int temp;
temp = v[i];
v[i] = v[j];
v[j] = temp;
}
void qsort(int v[],int left,int right)
{
int i,last;
void swap(int v[],int i,int j);
if(left >= right)
return;
swap(v,left,(left + right)/2);
last = left;
for(i = left+1;i<=right;i++)
if(v[i] < v[left])
swap(v,++last,i);
swap(v,left,last);
qsort(v,left,last-1);
qsort(v,last+1,right);
}
假定初始给定:v[7]
0 | 1 | 2 | 3 | 4 | 5 | 6 |
7 | 6 | 8 | 3 | 2 | 5 | 9 |
①swap( v, left, ( left+right ) / 2 )
本例swap(v, 0 , 3),即将 首元素 ↔中间元素
0 | 1 | 2 | 3 | 4 | 5 | 6 |
3 | 6 | 8 | 7 | 2 | 5 | 9 |
②last = left
last = left = 0
③for(i = left + 1;i <= right;i++)
if ( v[i] < v[left])
swap(v, ++last, i)
本例:for( i = 1; i <= 6; i++),将数组v[7]从v[1]到v[6]扫描,
当满足条件:v[i] < v[left] = v [0] = 3,也就是,比首元素3还要小的话,
那么执行:swap(v,++last,i),执行的是交换元素的操作。
结合上面的实例,i = 1, v[1] = 6 > v[left] = 3,不执行交换操作;
i = 2, v[2] = 8 > v[left] = 3,不执行交换操作;
i = 3, v[3] = 7 > v[left] = 3,不执行交换操作;
i = 4, v[4] = 2 < v[left] = 3,满足if的条件,第一次执行swap操作,将v[4]与v[++last]交换,last之前为0,++执行完结果为last = 1,那么就是交换v[4]和v[1],结果:
0 | 1 | 2 | 3 | 4 | 5 | 6 |
3 | 2 | 8 | 7 | 6 | 5 | 9 |
i = 5, v[5] = 5 > v[left] = 3,不执行交换操作;
i = 6, v[6] = 9 > v[left] = 3,不执行交换操作;
那么这个for循环结束,只进行了1次swap交换,效果是将小于3的元
④swap(v ,left ,last)
swap(v , 0,1)交换v[0]和v[1]:
0 | 1 | 2 | 3 | 4 | 5 | 6 |
2 | 3 | 8 | 7 | 6 | 5 | 9 |
⑤qsort(v , left,last - 1)
递归调用快排qsort,这时,qsort(v ,0,0),无需进行。
⑥qsort(v ,last+1,right)
qsort(v ,2 ,6),对v[2] ~ v[6]重复上述步骤,继续。
根据刚刚总结的,执行:
1. 首元素↔ 中间元素,这里left = 2 , right = 6
0 | 1 | 2 | 3 | 4 | 5 | 6 |
2 | 3 | 6 | 7 | 8 | 5 | 9 |
2. last = left = 2
3. for循环,操作的目的是:
范围:v[3] ~ v[6]
条件:小于v[left] = 6
操作:v[i] ↔ v[++last]
在v[3] ~ v[6]里,只有v[5]比v[left]小,所以只要swap一次即可,同时last = 3,v[5] ↔ v[3],。
0 | 1 | 2 | 3 | 4 | 5 | 6 |
2 | 3 | 6 | 5 | 8 | 7 | 9 |
4.swap(v, 2, 3)
0 | 1 | 2 | 3 | 4 | 5 | 6 |
2 | 3 | 5 | 6 | 8 | 7 | 9 |
5.qsort(v, 2, 2)
因为for循环里,也只执行了一次swap操作,故这一步不需要操作,已经有序。
6.qsort(v, 4, 6)
0 | 1 | 2 | 3 | 4 | 5 | 6 |
2 | 3 | 5 | 6 | 8 | 7 | 9 |
6.1 首元素 ↔ 中间元素,left = 4, right = 6
0 | 1 | 2 | 3 | 4 | 5 | 6 |
2 | 3 | 5 | 6 | 7 | 8 | 9 |
6.2 last = left = 4
6.3 for循环:
范围:v[4] ~ v[6]
条件:小于v[left] = 7
操作:v[i] ↔ v[++last]
发现,没有比v[left] = 7还小的数,所以不执行操作。不过此时,我们发现,顺序好像已经排好了。当然接下来的操作也没有意义了。
总结
通观整个算法思想,利用了递归,减少了代码量,不过理解起来也稍加费力。打完上面的例子,才发现不好,因为每次for循环之多只执行了一次,没有完全展现出“分治”的思想来。不妨再来一个,简单说明一下。
0 | 1 | 2 | 3 | 4 | 5 | 6 |
8 | 4 | 7 | 6 | 3 | 2 | 9 |
初始:left = 0, right = 6
step1. 将中间元素与首元素交换,即把a[3] = 6选为key,放到首位。同时将last 赋值为left的值,last = 0.因为left不能随意改变,last可以记录交换的位置。
0 | 1 | 2 | 3 | 4 | 5 | 6 |
6 | 4 | 7 | 8 | 3 | 2 | 9 |
step2. for循环:
范围:v[1] ~ v[6]
条件:小于v[0] = 6
操作:交换v[i]↔ v[++last]
i = 1, v[1] = 4 < v[0] = 6,++last = 1 ,v[1] ↔ v[1],不需要交换。
i = 2, v[2] = 7 > v[0] = 6,无需操作。
i = 3, v[3] = 8 > v[0] = 6,无需操作。
i = 4, v[4] = 3 < v[0] = 6,++last = 2 ,v[4] ↔ v[2]。
0 | 1 | 2 | 3 | 4 | 5 | 6 |
6 | 4 | 3 | 8 | 7 | 2 | 9 |
i = 5, v[5] = 2 < v[0] = 6,++last = 3 ,v[5] ↔ v[3]。
0 | 1 | 2 | 3 | 4 | 5 | 6 |
6 | 4 | 3 | 2 | 7 | 8 | 9 |
i = 6, v[6] = 9 > v[0] = 6,无需操作。
可以看到for循环的效果是:将比6小的数依次从1开始排,而last记录了最后一个比6小的数。
step3. 将v[left] ↔ v[last]
0 | 1 | 2 | 3 | 4 | 5 | 6 |
2 | 4 | 3 | 6 | 7 | 8 | 9 |
step4. qsort(v, left, last - 1)
qsort(v, last + 1, right)
递归再次快排,将之前划分的两个子集再次排序,即v[0] ~ v[2] ,v[4] ~ v[6],只要两个子集顺序排好,自然连在一起是有序的。
当left >= right时候,即数组包含的元素的个数少于两个的时候,那么就意味着不能再划分了,已经有序了,不执行操作,return即可。