简单的说,排序就是将一组杂乱无章的数据按一定的规律顺次排列起来。
如果在元素序列中有两个元素R1和R2,它们的排序码也相等,且在排序之前R1和R2前面,如果排序之后,R1仍然也在R2之前,则称排序方法是稳定的,否则称这个排序算法是不稳定的。排序方法根据在排序过程中数据元素是否完全在内存,分为两大类:内排序和外排序。
排序算法的性能评估:
排序算法的执行时间是衡量算法好坏的最重要的参数。排序的时间开销可用算法执行中的数据比较次数和数据移动次数来衡量。算法的性能一般根据平均情况来计算,对于那些受元素排序码初始排列及元素个数影响较大的,需要按最好情况和最坏情况来进行估算。
插入排序的基本思想:每一步将一个待排序的元素,按其排序码的大小,插入到前面已经排好序的一组元素的适当位置上去,直到元素全部插入为止。
下面介绍三种排序算法:
直接插入排序:当插入第i个元素时,前面的i-1个元素已经是排好序,这时用第i个元素的排序码和前面的i-1个元素的排序码依次进行比较,找到插入位置然后将第i个元素插入,原来位置上的元素向后顺移。
直接插入排序算法: 1、先将第i个元素暂存; 2、比较第i个元素和前面的元素,顺移直到temp>某个元素; 3、顺移这些某个元素后面的这些元素。
编程经验:(重要) 1、手动在纸上画图,手动执行程序,找出规律,有助于理解。 2、单步调试容易发现程序的逻辑错误。
void Sort::InsertSort(int data[],int maxsize) { int temp=0; //临时交换元素 for(int i=0;i<maxsize;i++) { if(i==0) ; //如果i==0,则不做任何处理,数组下标从0开始 else { if(data[i]<data[i-1]) //data[i-1]隐含了对data[0]的比较 { temp=data[i]; int j=i-1; //前面i-1个元素的下标 //这里也是一个循环,其实也是每次将比temp小的顺移一个单位 while(j>=0&&temp<data[j]) { data[j+1]=data[j]; //向后顺移,由于j=i-1,所以不会对后面的数据产生覆盖等问题 j--; } data[j+1]=temp; //这里的j是要插入这个数的那个j } } } }
折半插入排序算法:
1、前i-1个元素在元素表中已经排好序,将第i分元素插入到元素表中时采用折半查找。
2、设置临时交换元素temp用于暂存这个数,一个整形low记录左边界,另外一个high记录右边界。
3、第一部分负责找到每个元素需要移动的位置,第二部分用于移动这些元素。
void Sort::HalfInsertSort(int data[],int maxsize) { int low=0,high=0; int j=0; int temp=0; int middle=0; //折半,需要取中点 for(int i=0;i<maxsize;i++) //二分插入排序,这个循环 //只是找到这个每个需要排序的元素的移动位置 { if(i==0) ; else { //1、设定low和high的初始值用于查找区间 //2、保存临时变量 temp=data[i]; j=i-1; low=0; //4、0的时候单独处理,错误点:v[0]可能比后面的元素大,所以low=1排除了v[0]参与比较的权利 high=j; while(low<=high) { middle=(low+high)/2; if(temp<data[middle]) //如果比中间值大,说明在middle右边 { //low=middle+1; //如果等于middle值,则上一个位置也符合要求 high=middle-1; //2、data[middle]小于temp,则middle-1的位置肯定符合 } else { //high=middle+1; //同上,错误 low=middle+1; } } while(j>=low) { data[j+1]=data[j]; //2、谨慎使用j++ 和j+1的不同 j--; } data[j+1]=temp; //1、不要忘记这一步,j多减了1, } } }
希尔排序的算法: 设待排序元素序列有n个元素,首先取出一个整数gap<n作为间隔,将全部元素分为 gap个子序列,所有距离为gap的元素放在同一个子序列中,在每一个子序列中分别 施行直接插入排序。然后缩小间隔gap。重复上述的子序列划分和排序工作,直到最 后gap==1,将所有元素放在同一个序列中排序为止。 1、定义gap,划分子序列,对子序列进行直接插入排序。 2、重复定义gap,对子序列排序。 3、直到gap=1
实际上是一个三重循环,代码如下:
void Sort::ShellSort(int data[],int maxsize) //希尔排序 { int temp=0; //临时变量 //int gap=maxsize/3+1; //定义最初的gap,gap也是步长,gap为1就是循环中止条件,没有理由再用for循环 int k=0; // for(int gap=maxsize/2;gap>0;gap=gap/2){// 排序到最后 //这里少一个循环次数,开始 //划分的次数是gap为1的次数,这里的k是错的,在这里进行直接插入排序 for(int n=0;n<gap;n++){ //每个gap的循环次数,这个改动很好,子序列的个数 //第gap个元素和第0个元素属于同一类,所以应为<,而非<= for(k=n+gap;k<maxsize;k=k+gap) { //循环步长和if语句也相当于一个循环 if(data[k]<data[k-gap]) //data[i-1]隐含了对data[0]的比较,k-gap这个需要比较的下标 { temp=data[k]; int j=k-gap; //前面i-1个元素的下标 //这里也是一个循环,其实也是每次将比temp小的顺移一个单位 while(j>=n&&temp<data[j]) //这里应为n,n已经变化了 { data[j+gap]=data[j]; //向后顺移,由于j=i-1,所以不会对后面的数据产生覆盖等问题,这里也是j+gap这个值 j=j-gap; } data[j+gap]=temp; //这里的j是要插入这个数的那个j } } } //改变循环值 ,这个应该放在用到gap值的外围 }
直接插入排序的时间复杂度为O(n^2),折半插入排序的时间复杂度为O(nlogn),希尔排序的时间复杂度比较难以准确度量,当n很大时,时间复杂度为n的1.25次方到1.6倍n的1.25次方,前提是子序列的排序方法利用了直接插入排序算法。