介绍三种时间复杂度为 N^2的三种排序及实现:
选择排序:
一堆扑克牌摊开,
第一次从第二张牌开始之后的每一张牌都与第一张牌比较,假如比第一张牌小,那么就和第一张牌交换,直到比较到最后一张牌,那么本次就产生了最小的一张牌,且这张牌就是放在第一位的那张。也就是说假如数组arr[10],那么第一次从arr[1]开始,和arr[0]比较,小于arr[0]就交换,直到arr[9]。一轮下来就确定了arr[0]是最小的,下一轮就从arr[2]开始,和arr[1]比较,又可以确定arr[1]是第二小的。。。。
代码实现如下:
void select_sort(int *arr){
for(int i = 0; i < arrsize; i++){
for(int j = i+1; j < arrsize; j++){
if(arr[j] < arr[i]) swap(arr,j,i);
}
}
}
void swap(int*arr, int i, int j){
arr[i] = arr[i] ^ arr[j];
arr[j] = arr[i] ^ arr[j];
arr[i] = arr[i] ^ arr[j];
}
因为每次选一个最小的都得遍历一次数组,所以时间复杂度为N^2,其实仔细想一下,选择排序过程会出现重复比较两个树的情况,所以造成了不必要的浪费。就比如说 一个数组就3个数 5 6 3,现在被比较的数是5,而6比他大,不会交换。 下一个数是3 那么就会交换,导致原本 5 6 3 变成了3 6 5。然后又要用5和6比较,变成3 5 6;而实际上5和6已经比较过,这里就多了一次比较。
冒泡排序:
同样一堆扑克牌,盖着没有摊开。
翻开第一张扑克牌,放到arr[0]位置,再翻开第二张扑克牌,和第一张比较,把小的那张放到一个空的新牌堆(这里只是便于理解,实际上不需要额外的空间,用原来的数组就行),再从原牌堆翻开一张和大的比较,同样,谁小谁放到新牌堆。这样下来,原牌堆拿完,你手里的就是最大的那张牌。 然后从新牌堆继续这个过程找出第二大的。。。
void maopao_sort(int *arr){
for(int j = arrsize - 1; j > 0; j--)
{
for(int i = 0; i < j; i++){
if( arr[i+1] < arr[i]) swap(arr,i+1,i);
}
}
}
同样,冒泡排序也是要遍历整个牌堆,而且要遍历N次,一次只能找出一个最大值,所以也是时间复杂度为N^2的算法,在比较过程中同样出现重复比较的情况。比如本来就是顺序的3个数,3 5 6,
那么
3和5比较,不用交换;
5和6比较,不用交换;
确定6最大;
3和5比较,确定5;
最后确定顺序3 5 6;
所以在这个过程中3和5其实是比较了两次,重复了。
插入排序:
一堆扑克牌,
拿第一张,那么到手里因为只有一张,所以是有序的,然后拿第二张,比较第二张和第一张的大小,
如果 第二张>第一张 那么插在第一张的后面,否则插在第一张的前面,至此手上的两张牌也有序,
拿第三张,和你手上最大的那张牌比较,直到找到合适的位置插入,循环直到把牌拿完,手上的牌自然就有序了。
void insert_sort(int *arr){
for(int i = 1; i < arrsize; i++){
for(int j = i - 1; j >= 0; j--)
{
if(arr[j+1] < arr[j]) {
swap(arr,j+1,j);
}else {
break;
}
}
}
}
插入排序和选择排序以及冒泡排序不同的是,它的比较是不重复的,因为之前比较过的已经在手上形成了一个有序的牌型,每次拿一个新牌去比较都只是比较有序排序最大的,直到找到自己合适的位置。所以插入排序相对于上面两种排序要更好一些,那么为什么插入排序的时间复杂度依然是N^2呢,是因为有最差的情况比如 9 8 7 6 这四个数。
第一次拿出来9,第二次拿出来8需要和9比较,形成8 9;
第三次拿出来7,需要和9比较,需要和8比较,形成7 8 9;
第四次拿出来6,需要和9比较,需要和8比较,需要和7比较,形成6 7 8 9;
会发现如果是一个从大到小排好序的数组,用上述插入排序算法,需要比较的次数也是等差数列,自然也是N^2级别的。
下一篇文章再讲述归并排序,快排等时间复杂度N*LOGN的排序算法。