今天无意间使用到插入排序的时候,没有注意到一个小细节,导致出了一个意想不到的结果,然后分析,原来是一个很小很小的细节导致的,循环条件不成立就不继续循环了,继续执行循环之后的语句了,由于把第二表达式写错了,所以导致了这个错误,错误的代码如下(粗斜体即是错误地方):
#include <stdio.h>
void print(int *a)
{
for(int i = 0; i < 5 ;i ++)
printf("%d ",a[i]);
printf("\n");
}
void sort(int *a,int n)
{
int current,i,j;
for(i = 1;i < n;i ++)
{
current = a[i];
for(j = i - 1;j >= 0; j-- )
{
if(a[j] > current)
a[j + 1] = a[j];
}
a[j + 1] = current;
print(a);
}
}
int main(void)
{
int a[5],x;
for(int i = 0 ; i < 5; i ++)
scanf("%d",&a[i]);
x = sizeof(a)/sizeof(int);
sort(a,x);
print(a);
return 0;
}
如果是这样写的话,进入了循环体之后,只要if条件不成立,就继续进行循环,直到j成了-1,然后该数组的第一个元素总是会变成当前临时数组的current变量,结果截图如下(每一步的结果都打印出来了):
每一次的for循环导致第一个元素都会是当前的current
所以,依次分别是:
4 4 5 3 1(current = 4)
5 4 5 3 1(current = 5)
3 5 4 5 1(current = 3)
1 3 5 4 5(current = 1)
所以上面的写法是存在bug的,修改如下(粗斜体即是修改的地方):
这次用动态数组写出来的
#include <stdio.h>
#include <stdlib.h>
void print(int *a)
{
for(int i = 0; i < 5 ;i ++)
printf("%d ",a[i]);
printf("\n");
}
void sort(int *a,int n)
{
int current,i,j;
for(i = 1;i < n;i ++)
{
current = a[i];
for(j = i - 1;j >= 0 && a[j] > current; j-- )
a[j + 1] = a[j];
a[j + 1] = current;
print(a);
}
}
int main(void)
{
int *a;
a = (int *)malloc(5*sizeof(int));
for(int i = 0 ; i < 5; i ++)
scanf("%d",&a[i]);
sort(a,5);
print(a);
free(a);
return 0;
}
此时问题就解决了,因为一旦不满足这个表达式
j >= 0 && a[j] > current
该for循环就不会继续循环,j的值就停留在了该插入current变量的地方了,不会出现在了首元素的位置了
以下是结果截图 ,每一步的结果也打印出来了:
2 4 5 1 3(current = 4)
2 4 5 1 3(current = 5)
1 2 4 5 3(current = 3)
1 2 3 4 5(current = 1)
总结:虽然是一个很小的细节问题,但是不注意的话,真的是会出现很大的问题
插入排序虽然是以current来作为监视哨进行每一轮的循环,但是插入的位置要找好
不然的话,就会破坏掉数组本身的内容的,导致出现错误
还有就是for循环的第二表达式要理解透彻
插入排序无非就是在前面已经排序好的子序列找到合适的插入点,然后将待插入元素插入进去,然后插入点之后的元素向后移动一位,所以我们可以采取比上述(顺序查找)效率稍好一些的这般查找来找这个插入点,一次来提高算法的性能,代码实现如下:
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
//#define SWAP(a,b,c) ((c) = (a),(a) = (b),(b) = (c))
void print(int *a,int length)
{
for(int i = 0 ; i < length ; i ++ )
printf("%d ",a[i]);
printf("\n");
}
void insertion_sort(int *a,int length)
{
int current,low,high,mid;
for(int i = 2;i < length;i ++)
{
current = a[i];
low = 0,high = i - 1;
while(low <= high)//添加等号是为了防止边界元素在不知道大小的情况下进行了交换
{
mid = (low + high) >> 1;
if(current > a[mid])
low = mid + 1;
else
high = mid - 1;
}
for(int j = i;j > low ;j--)
a[j] = a[j - 1];
a[low] = current;
}
}
int main(void)
{
int a[10]={0};
srand(time(0));
for(int i = 0 ; i < 10 ; i ++ )
a[i] = rand() % 100 + 1;
print(a,10);
insertion_sort(a,10);
print(a,10);
return 0;
}
算法的复杂度分析:
简单插入排序:因为涉及到复杂度分析一般都是分析最坏情况(既要排序的逆序,比如结果是升序的,但是给定的表中关键字是降序排列,即5,4,3,2,1,)
所以此时外层控制的插入的次数还是n-1,内层的比较次数分别为0,1,2......,n-1,我们利用数学归纳法分析不难得知,最后结果是(n-1)*n/2,系数可以忽略掉,所以时间复杂度是O(n^2).
折半插入排序:此方法是由简答插入排序扩展而来的,因为简单插入排序是用的顺序查找关键字的方法来搜索待插入关键字的位置,所以在表很大的情况下,比较次数可想而知是很大的,所以我们可以利用二分法来进行比较找到插入位置,以此来减少比较次数,但是关键字移动的次数并没有减少,所以复杂度还是跟上面的一样,是O(n^2).