- /* 直接插入排序 */
- void insert_sort(int v[],int n){
- register int i,j,temp;
- for(i=1; i<n; ++i){ /* 0~i-1为已排序部分,i~n-1为未排序部分 */
- temp=v[i]; /* 保存当前需要排序的元素 */
- for(j=i-1;j>=0 && v[j]>temp;--j) /* 在已排序部分中找出正确的插入位置 */
- v[j+1]=v[j]; /* 元素后移一位,最后j+1为空出的插入位置 */
- v[j+1]=temp; /* 插入元素 */
- }
- }
2、shell排序:将要排序的一组数按某个增量g分成若干组,每组中记录的下标相差g。对每组中全部元素进行直接插入排序,然后缩小增量g,在每组中再进行排序。当增量减到1时,整个要排序的数被分成一组,排序完成。shell排序是不稳定的,算法时间复杂度可改进到O(n^1.25)左右。
- /* shell排序 */
- void shell_sort(register int v[],int n){
- register int gap,i,j,temp;
- gap=1;
- /* gap初始化为不大小n的序列(1、4、13、40、121、...)中的最小值 */
- do (gap=3*gap+1); while(gap<=n);
- for(gap/=3;gap>0;gap/=3) /* 缩小增量进采用除3的策略 */
- for(i=gap;i<n;++i){ /* 对当前组进行直接插入排序 */
- temp=v[i];
- /* 在当前组的已排序部分中找到正确的插入位置 */
- for(j=i-gap;(j>=0)&&(v[j]>temp);j-=gap)
- v[j+gap]=v[j]; /* 元素后移,以空出插入位置 */
- v[j+gap]=temp; /* 插入元素 */
- }
- }
注意一般的shell排序实现都是gap初始化为n/2,并采用除2的缩小增量策略。这里初始化为不大小n的序列(1、4、13、40、121、...)中的最小值,并采用除以3的缩小增量策略,这可使排序加快20%~30%。内循环中的赋值从3次减少到1次。这里增加了register修饰符,使这些变量存储在寄存器中,在有些实现中,register声明可以大大提高性能(有的高达40%)。
3、冒泡排序:对前面未排序部分每相邻的两个数进行比较和调整,让较大的数上浮到后面已排序部分的开头。冒泡排序是稳定的,算法时间复杂度O(n^2)。
- /* 冒泡排序 */
- void bubble_sort(int b[],int n){
- register int i,j,temp,flag;
- for(i=n-1;i>=1;--i){ /* 0~i为未排序部分,i+1~n-1为已排序部分 */
- flag=0;
- for(j=0;j<i;++j) /* 对未排序部分每相邻的两个数进行比较和调整 */
- if(b[j]>b[j+1]){ /* 大的上浮 */
- temp=b[j];
- b[j]=b[j+1];
- b[j+1]=temp;
- flag=1; /* 如果进行了交换,说明最初始的元素序列并不是有序的,flag设为1 */
- }
- if(flag==0) break; /* 如果flag没有被设为1,说明第一轮扫描中没有作任何交换,这表明最初的元素序列
- 是有序的,无需再排序了,直接break返回 */
- }
- }
这里设置了一个flag,当元素序列本身就是有序时,直接返回,这可以加快排序过程。
4、快速排序:扫描一次元素并不断地交换,使右边部分的各个数都比左边部分大。然后用同样的方法处理这两个子序列(用递归调用)。快速排序是不稳定的,最理想情况算法时间复杂度O(nlgn),最坏为O(n^2),空间复杂度需要O(lgn)。
- /* 快速排序 */
- void quick_sort(int x[],int low,int high){
- register int i,j,temp;
- if (low < high){ /* 要排序的元素起止下标,保证小的放在左边,大的放在右边。这里以下标为low的元素为基准点 */
- i=low;
- j=high;
- temp=*(x+low); /* 暂存初始基准点的数 */
- while (i<j){ /*循环扫描*/
- while (i<j && *(x+j)>temp) /* 在右边的只要比基准点大仍放在右边 */
- j--; /* 前移一个位置 */
- if (i<j){
- *(x+i)=*(x+j); /* 上面的循环退出:即出现比基准点小的数,替换基准点的数 */
- i++; /* 基准点后移一个位置 */
- }
- while (i<j && *(x+i)<=temp) /* 在左边的只要小于等于基准点仍放在左边 */
- i++; /* 后移一个位置 */
- if (i<j){
- *(x+j)=*(x+i); /* 上面的循环退出:即出现比基准点大的数,放到右边 */
- j--; /* 前移一个位置 */
- }
- }
- *(x+i)=temp; /* 一遍扫描完后,把初始基准点的值放到适当位置 */
- quick_sort(x,low,i-1); /* 对基准点左边的数再执行快速排序 */
- quick_sort(x,i+1,high); /* 对基准点右边的数再执行快速排序 */
- }
- }
注意实现时要找一个基准点的数,扫描完一遍后,左边部分都会小于基准点数,右边部分都会大于基准点数。因此开始时要暂存基准点数,扫描完一遍后把基准点插入到中间恰当的位置,这样就分出了左边部分和右边部分,然后递归调用即可。
5、选择排序:在后面未排序的部分中选择最小的数插入到前面已排序部分的末尾。直到倒数第二个数和最后一个数比较为止。选择排序是不稳定的,算法复杂度O(n^2)。
- /* 选择排序 */
- void select_sort(int x[], int n){
- register int i,j,min,temp;
- for(i=0; i<n-1; ++i){ /* 0~i为已排序部分,i+1~n-1为未排序部分 */
- min=i; /* 假设当前下标为i的数最小,比较后再调整 */
- for(j=i+1; j<n; ++j) /* 找出最小的数的下标 */
- if(*(x+j) < *(x+min))
- min=j;
- if (min != i){ /* 如果min在循环中改变了,就需要交换数据 */
- temp=*(x+i);
- *(x+i)=*(x+min); /* 把最小数交换到已排序部分的末尾 */
- *(x+min)=temp;
- }
- }
- }
6、堆排序:是对选择排序的改进。把数组看作是二叉树,b[k]的两个子树为元素x[2*k]与x[2*k+1]。堆是一棵树,每个节点的数值不小于其后代节点的数值。初始时把要排序的数的序列看作是一棵顺序存储的二叉树,调整它们的存储顺序,使之成为一个堆,这时堆的根节点的数最大。然后将根节点与堆的最后一个节点交换。然后对前面n-1个数重新调整使之成为堆。依此类推,直到只有两个节点的堆,并对它们作交换,最后得到有n个节点的有序序列。堆排序是不稳定的,算法时间复杂度O(nlgn)。
- /* 堆排序 */
- /* 调整成为堆 */
- void adjust(int x[], int n, int s){
- register int temp,k,j;
- temp=*(x+s); /* 暂存开始元素 */
- k=s; /* 开始元素下标 */
- j=2*k+1; /* 右子树元素下标 */
- while (j<n){
- if (j<n-1 && *(x+j) < *(x+j+1)) /* 判断是否满足堆的条件:满足就继续下一轮比较,否则调整 */
- j++;
- if (temp<*(x+j)){ /* 找到更大的数:调整上来 */
- *(x+k)=*(x+j);
- k=j; /* 调整后,开始元素也随之调整 */
- j=2*k+1;
- }else
- break; /* 没有需要调整了,已经是个堆了,退出循环 */
- }
- *(x+k)=temp; /* 开始元素放到它正确位置 */
- }
- /* 排序函数 */
- void heap_sort(int x[], int n){
- register int i,k,temp;
- for (i=n/2-1; i>=0; i--) /* 扫描整个数组来建立一个初始堆 */
- adjust(x,n,i);
- for (k=n-1; k>=1; k--){
- temp=*(x+0); /* 堆顶放到最后 */
- *(x+0)=*(x+k);
- *(x+k)=temp;
- adjust(x,k,0); /* 剩下的数再调整为堆 */
- }
- }
注意堆排序需要两个过程,一是建立堆,二是堆顶与堆的最后一个元素交换位置。所以堆排序有两个函数组成,一是建堆的调整函数,二是反复调用实现排序的函数。
7、归并排序:设两个有序的子序列(相当于输入堆)放在同一向量中相邻的位置上R[low..m],R[m+1..high],先将它们合并到一个局部的暂存向量R1(相当于输出堆)中,待合并完成后将R1复制回R[low..high]中。归并排序是稳定的,算法时间复杂度为O(nlgn),空间复杂度需要O(n)。
- /* 归并排序 */
- /* 合并两个有序子序列 */
- void merge(int array[],int flow,int middle,int high){
- /* flow~middle和middle+1~high为数组array的两个有序子序列 */
- register int i,j,k;
- int n1=middle-flow+1,n2=high-middle;
- int temp1[n1]; int temp2[n2];
- for (i=0; i<n1; ++i) /* 把子序列flow~middle拷贝到temp1中 */
- temp1[i]=array[flow+i];
- for (i=0; i<n2; ++i) /* 把子序列iddle+1~high拷贝到temp2中 */
- temp2[i]=array[middle+i+1];
- for (j=0,k=0,i=flow; i<=high; ++i){
- if (j<n1&&k<n2){ /* 从大到小的顺序把两个子序列写入到array中 */
- if (temp1[j]>=temp2[k])
- array[i]=temp2[k++];
- else
- array[i]=temp1[j++];
- }
- else{ /* 一个子序列写入完后,另一个子序列可能还有剩余元素,把剩余元素也写入到array中 */
- if(j>=n1&&k<n2)
- array[i]=temp2[k++];
- if(j<n1&&k>=n2)
- array[i]=temp1[j++];
- }
- }
- }
- /* 排序函数 */
- void merge_sort(int array[],int s,int t){
- register int m;
- if (s<t) {
- m=(s+t)/2; /* 将数组分成两个子序列 */
- merge_sort(array,s,m); /* 对左边子序列递归使用归并排序 */
- merge_sort(array,m+1,t); /* 对右边子序列也递归使用归并排序 */
- merge(array,s,m,t); /* 合并排好序的两个子序列 */
- }
- }
8、测试代码。
- #include <stdio.h>
- #define MAX 8
- /* 这里放各个排序算法的实现函数 */
- /* 输入要排序的测试数据 */
- void input(int a[],int k){
- register int i;
- printf("Input %d number for sorting: /n",k);
- for( i=0;i<k;++i)
- scanf("%d",a++);
- printf("/n");
- }
- /* 打印数组中的各个元素 */
- void print(int b[],int k){
- int i;
- for(i=0;i<k;++i)
- printf("%d ",b[i]); /* 一个元素占4个字符的宽度 */
- printf("/n");
- }
- int main(){
- int *p,a[MAX];
- /* 输入测试数据 */
- p=a;
- input(p,MAX);
- /* 测试直接插入排序 */
- /*
- p=a;
- insert_sort(p,MAX);
- print(a,MAX);
- */
- /* 测试shell排序 */
- /*
- p=a;
- shell_sort(p,MAX);
- print(a,MAX);
- */
- /* 测试冒泡排序 */
- /*
- p=a;
- insert_sort(p,MAX);
- print(a,MAX);
- */
- /* 测试快速排序 */
- /*
- p=a;
- quick_sort(p,0,MAX-1);
- print(a,MAX);
- */
- /* 测试选择排序 */
- /*
- p=a;
- select_sort(a,10);
- print(a,MAX);
- */
- /* 测试堆排序 */
- /*
- p=a;
- heap_sort(p,MAX);
- print(a,MAX);
- */
- /* 测试归并排序 */
- p=a;
- merge_sort(p,0,MAX-1);
- print(a,MAX);
- return 0;
- }