目录
-
【稳定排序】冒泡排序
-
普通冒泡
#include <stdio.h> #define N 5 int f[10001],i,j,sum; // sum:统计交换次数 int main(){ srand(time(NULL)); //产生种子 for(i=1;i<=N;i++) f[i]=rand()%100; //随机产生100范围内的随机数 for(i=1;i<=N;i++){ int flag=0; //设置标志位,防止输入的数据既是有序的 for(j=1;j<=N-i;j++){ //或者for(j=1;j<=i;j++) if(f[j]>f[j+1]){ f[j]=f[j]+f[j+1];f[j+1]=f[j]-f[j+1];f[j]=f[j]-f[j+1]; // a=a+b;b=a-b;a=a-b; // f[j]=f[j]^f[j+1];f[j+1]=f[j]^f[j+1];f[j]=f[j]^f[j+1]; // a=a^b;b=a^b;a=a^b; flag=1; sum++; //用于计数,既交换次数 } } if(flag==0) break; } for(i=1;i<=N;i++) printf("%d ",f[i]); printf("\nsum=%d",sum); return 0; }
#include <stdio.h> #define N 5 int f[10001],i,j,sum; // sum:统计交换次数 int main(){ srand(time(NULL)); //产生种子 for(i=0;i<=N;i++) f[i]=rand()%100; //随机产生100范围内的随机数 for(i=0;i<N;i++){ int t=f[i],j=i; while((j>0)&&(f[j-1]>t)){ f[j]=f[j-1]; j--; sum++; //用于计数,既交换次数 } f[j]=t; } for(i=0;i<N;i++) printf("%d ",f[i]); printf("\nsum=%d",sum); return 0; }
-
-
【稳定排序】插入排序
-
直接插入排序
#include <stdio.h> #define N 5 int f[10000],i,j,sum; // sum:统计交换次数 int main(){ srand(time(NULL)); // 产生种子 for(i=0;i<N;i++) f[i]=rand()%100; // 随机产生100范围内的随机数 for(i=1;i<N;i++){ // 从第二项开始比较 int t=f[i],flag=0; // t落地标识,防止待插入的数最小 for(j=0;j<=i-1;j++){ // 不断遍历,不断移位 if(t<f[j]) f[j+1]=f[j]; else{ f[j+1]=t; flag=1;break; } } if(!flag) f[0]=t; } for(i=0;i<N;i++) printf("%d ",f[i]); printf("\nsum=%d",sum); return 0; }
样例;89 45 54 29 90 34 68
89 45 54 29 90 34 68
45 89 54 29 90 34 68
45 54 89 29 90 34 68
29 45 54 89 90 34 68
29 45 54 89 90 34 68
29 34 45 54 89 90 68
29 34 45 54 68 89 90
-
优化直接插入排序:设置哨兵位【小规模数组】
#include <stdio.h> // 根据上一次的位置,简化下一次定位 #define N 5 int f[10000],i,j; int main(){ srand(time(NULL)); // 产生种子 for(i=1;i<=N;i++) f[i]=rand()%100; // 随机产生100范围内的随机数 int sc=f[0]; // 记录上一个插入值的数值 int sz=0; // 记录上一个插入值的位置 for(i=1;i<N;i++){ // 循环插入 int t=f[i]; int st=i-1; if(t<sc){ // 根据上一个值,定位开始遍历的位置 st=sz; for(j=i-1;j>st-1;j--) f[j+1]=f[j]; // 插入值后面的值向后移位 } int flag=0; for(j=st;j>=0;j--){ // 剩余情况是:sz 位置的数字,和其下一个坐标位置是相同的 if(t<f[j]) f[j+1]=f[j]; // 循环判断+插入 else{ f[j+1]=t; sc=t; sz=j+1; flag=1; break; } } if(!flag) f[0]=t; } for(i=0;i<N;i++) printf("%d ",f[i]); return 0; }
优化方法分析:
记录上一次插入的值和位置,与当前插入值比较。
若当前值小于上个值,将上个值插入的位置之后的数
全部向后移位,从上个值插入的位置作为比较的起点;
反之,仍然从有序数组的最后一位开始比较。
-
优化直接插入排序:二分查找法【大规模数组】【重点记忆】
#include <stdio.h> #define N 5 int f[10000]; int main(){ int i,j; srand(time(NULL)); // 产生种子 for(i=0;i<N;i++) f[i]=rand()%100; // 随机产生100范围内的随机数 for(i=1;i<N;i++){ // 从第二项开始比较 int t=f[i]; // 待插入到前面有序序列的值 int l=0,r=i-1,m=0; // 有序序列的左右侧及中间值 while(l<=r){ m=(l+r)>>1; // 用位操作就行除2操作,计算中间位置 if(t<f[m]) r=m-1; // 应插至序列左半侧 【条件在此处添加】 else l=m+1; // 否则插至序列右半侧 } for(j=i-1;j>=l;j--) f[j+1]=f[j]; // 从i-1到l依次向后移动一位,等待t值插入 if(l!=i) f[l]=t; } for(i=0;i<N;i++) printf("%d ",f[i]); return 0; }
-
-
【不稳定排序】希尔排序
希尔排序,又称缩小增量排序
其重点显然在于初始增量 d 的选取,
以及每次增量 d 缩小的额度。
一般来说,初始增量设为数组长度的一半,
同时每次增量减半,直至 d=1,可以满足大多数的需求。基本思想:
*记录按下标的一定增量分组,对每组进行直接插入排序。
*不断地缩小增量,对每组进行直接插入排序,直至增量为1。#include <stdio.h> #define N 5 int f[10000]; int main(){ int i,j,k; srand(time(NULL)); // 产生种子 for(i=1;i<=N;i++) f[i]=rand()%100; // 随机产生100范围内的随机数 int d=N>>1; // 初始增量 while(d>0){ // d 次直接插入排序 for(i=0;i<d;i++){ // 组内进行,相隔增量 d 项的直接插入排序 for(j=i+d;j<N;j+=d){ int t=f[j]; int flag=0; for(k=j-d;k>=0;k-=d){ if(t<f[k]) f[k+d]=f[k]; else{ f[k+d]=t;flag=1;break; } } if(!flag) f[i]=t; } } d>>=1; // 每次增量减半 } for(i=0;i<N;i++) printf("%d ",f[i]); return 0; }
测试数据:
初始数组:
89 45 54 29 90 34 68第一步:增量 d=3
29 45 34 68 90 54 89第二步:增量 d=1
29 34 45 54 68 89 90
-
【不稳定排序】桶排序
#include <stdio.h> #define N 5 int f[10000]; int main(){ int n,i,x; srand(time(NULL)); for(i=1;i<=N;i++){ // 随机产生 100 个数放入桶中 x=rand()%100; f[x]++; // 将等于x的值全部装入第x个桶中 } for(i=0;i<=10000;i++) // 输出排序结果 while(f[i]>0){ // 相同的整数,要重复输出 printf("%d ",i); f[i]--; // 输出一个整数后,个数减一 } return 0; }
-
【不稳定排序】快速排序
-
使用自定义函数实现
#include <stdio.h> #define N 5 int f[10000]; int qs(int l,int r){ int i,j,m,t; i=l;j=r; m=f[(l+r)>>1]; // 将当前序列中间位置数定为分隔数 do{ while(f[i]<m) i++; // 在左半部分寻找比中间数大的数 若找到则退出 while(f[j]>m) j--; // 在右半部分寻找比中间数小的数 若找到则退出 if(i<=j){ // 若找到一组与排序目标不一致的数,既交换 t=f[i];f[i]=f[j];f[j]=t; i++;j--; // 否则继续找 } }while(i<=j); if(l<j) qs(l,j); // 若未到两个数的边界,则递归搜索左右区间 if(i<r) qs(i,r); } void sort1(int *a, int l){ if(l<=1) return; // 数组长度等于一或者等于零的时候,不必排序 // a为每一组的第一个元素的地址,len为每一组的长度 int i=0,j=l-1,t=a[0]; // 初始化i指向数组开头,初始化j指向数组结尾,temp为标准值 while(i<j) { // 只要i,j不相遇,就一直扫描 while(a[j]>t) j--;a[i]=a[j]; // 后面的数只要大于前面的数就不管,往前扫描,直到小于或等于标准值的时候,把a[j]赋值给前面的a[i] // 注意,此时a[j]有两份而a[i]原来的值被覆盖,且a[j]是没有用的,因为它小于标准值,它应该在前面a[i]的位置 while(a[i]<=t) i++;a[j]=a[i]; // 前面的数小于等于标准值就继续扫描 // 注意此时在上面的那一份多余a[j]被替换掉了,但是多了一份a[i],它大于标准值,它应该此刻j对应的位置. // 这个循环又会走到上面消去这个无用的a[i]。 } // 执行完上面这个循环,在i的位置上会多一个无用的a[i],因为最后一次多余的a[i]不会被替换掉 // 而且可以发现在进入循环时,无端消去了一个并不是多余的a[i],因为是第一次的时候,所以是a[0]。 // 而temp=a[0],并且可以发现a[i]前面的数必定小于temp,a[i]后面的数必定大于temp,所以可以令a[i] = temp,这样数组的元素就没有变化,并且完成了把数分成两组; a[i]=t; // 对前一组,后一组 数分别继续进行这种操作 sort(a,i); // 0~i-1长度为i sort(a+i+1,l-i-1); return; } void sort2(int *a, int l){ if(l<=1) return; int i=0,j=l-1,t=a[0]; while(i<j) { while(a[j]>t) j--;a[i]=a[j]; while(a[i]<=t) i++;a[j]=a[i]; } a[i]=t; sort(a,i); sort(a+i+1,l-i-1); return; } void sort(int *a,int l) { if(l<=1) return; int i=0,j=l-1,t=a[0]; while(i<j){ while(a[j]>t&&j>i) j--; while(a[i]<=t&&i<j) i++; if(i!=j) { // i等于j时交换没有意义,否则使小数到前面,大数到后面 t=a[i]; a[i]=a[j]; a[j]=a[i]; } } // 由于是先从后面往前扫描,所以无论何时,从后往前扫描停止时,当时的a[j]必定不大于temp,则退出时a[i]必定不大于temp // 使标准值到中间形成 小数数组 标准值 大数数组 a[0]=a[i]; a[i]=t; sort2(a,i); sort2(a+i+1,l-i-1); return; } int main(){ int n,i; srand(time(NULL)); // 产生种子 for(i=0;i<N;i++) f[i]=rand()%100; // 随机产生100范围内的随机数 qs(0,N-1); for(i=0;i<N;i++) printf("%d ",f[i]); return 0; }
-
使用库函数实现
快排库函数使用:
1 待排序数组首地址 数组名
2 数组中待排序元素数量
3 各元素的占用空间大小
4 指向函数的指针,用于确定排序的顺序#include <stdio.h> #define N 5 int f[1000],i; int cmp(const void *a,const void *b){ int *x=(int *)a; int *y=(int *)b; return (*x>*y); //函数返回值,如果为真1,则交换,为0则不交换 } int main(){ srand(time(NULL)); //产生种子 for(i=0;i<=N;i++) f[i]=rand()%100; //随机产生100范围内的随机数 qsort(f,N,sizeof(int),cmp); for(i=0;i<N;i++) printf("%d ",f[i]); return 0; }
-
案例试题
-
【二分查找】成绩排序
#include <stdio.h> //二分结构体 #include <string.h> typedef struct{ char name[20]; double score; }STU; STU f[21]; int main(){ int n,k,i,j; scanf("%d",&n); for(i=0;i<n;i++) scanf("%s %lf",&f[i].name,&f[i].score); for(i=1;i<=n;i++){ STU t=f[i]; int l=0,r=i-1,m; while(l<=r){ m=(l+r)>>1; if(t.score>f[m].score||(t.score==f[m].score&&strcmp(f[m].name,t.name)>0)) r=m-1;//strcmp(f[m].name,t.name)>0字典升序 else l=m+1; } for(j=i-1;j>=l;j--) f[j+1]=f[j]; if(l!=i) f[l]=t; } for(i=0;i<n;i++) printf("%s %g\n",f[i].name,f[i].score); return 0; }
-
【二分查找】奖学金
#include <stdio.h> //二分结构体 typedef struct{ int num; //学号 double a; //语文 double b; //数学 double c; //英语 double d; //总分 }STU; STU f[301]; int main(){ int n,k,i,j; scanf("%d",&n); for(i=0;i<n;i++){ scanf("%lf%lf%lf",&f[i].a,&f[i].b,&f[i].c); //输入语文,数学,英语三门学科成绩 f[i].num=i+1;f[i].d=f[i].a+f[i].b+f[i].c; //为当前学生录入学号并计算总分 } for(i=1;i<=n;i++){ //二分排序 从第二项开始 STU t=f[i]; int l=0,r=i-1,m; while(l<=r){ m=(l+r)>>1; if(t.d>f[m].d||(t.d==f[m].d&&t.a>f[m].a)) r=m-1; //总分递减; 总分相同 语文高者优先 总分语文均相同,学号小者优先 else l=m+1; } for(j=i-1;j>=l;j--) f[j+1]=f[j]; if(l!=i) f[l]=t; } for(i=0;i<5;i++) printf("%d %g\n",f[i].num,f[i].d); return 0; }
-
【二分查找】分数线划定
#include <stdio.h> typedef struct{ int num; double a; }STU; STU f[5001]; int main(){ int n,m,i,j; scanf("%d%d",&n,&m); for(i=0;i<n;i++) scanf("%d%lf",&f[i].num,&f[i].a); for(i=1;i<=n;i++){ STU t=f[i]; int l=0,r=i-1,m; while(l<=r){ m=(l+r)>>1; if(t.a>f[m].a||(t.a==f[m].a&&t.num<f[m].num)) r=m-1; //笔试成绩减序或者成绩相同报名号升序 else l=m+1; } for(j=i-1;j>=l;j--) f[j+1]=f[j]; if(l!=i) f[l]=t; } int k=m*1.5-0.5; //确定总录取名额 "-0.5"向下取整 int count=0,len=0; //达线人数 double s; //缓存最底分数线 既所有达线人员最后一名的成绩 while(count++<k){ s=f[len].a; //循环确定分数线s,既最后一名学生成绩 len++; //统计进入面试的人数 } while(f[len++].a==s){} //处理刚好达到分数线的同学是否有重分,如有则应该加上该同学 printf("%g %d\n",s,len-1); for(i=0;i<len-1;i++) printf("%d %g\n",f[i].num,f[i].a); return 0; }
-
【简单排序】整数奇偶排序
#include <stdio.h> int a[20],b[20],m,n,t,i,j; int main(){ for(i=0;i<10;i++){ int x; scanf("%d",&x); if(x%2==1) a[m++]=x; else b[n++]=x; } for(i=0;i<m;i++) for(j=i+1;j<m;j++) if(a[i]<a[j]){t=a[i];a[i]=a[j];a[j]=t; } for(i=0;i<n;i++) for(j=i+1;j<n;j++) if(b[i]>b[j]){t=b[i];b[i]=b[j];b[j]=t; } for(i=0;i<m;i++) printf("%d ",a[i]); for(i=0;i<n;i++) printf("%d ",b[i]); return 0; }
-
合影效果
#include <stdio.h> //二分结构体 #include <string.h> int n,i,j,p,q; double a[50],b[50],d,t; int main(){ scanf("%d",&n); for(i=0;i<n;i++){ char s[10]; scanf("%s %lf",s,&d); if(strcmp(s,"male")==0) a[p++]=d; else b[q++]=d; } for(i=0;i<p;i++) for(j=i+1;j<p;j++) if(a[i]>a[j]){ t=a[i];a[i]=a[j];a[j]=t; } for(i=0;i<q;i++) for(j=i+1;j<q;j++) if(b[i]<b[j]){ t=b[i];b[i]=b[j];b[j]=t; } for(i=0;i<p;i++) printf("%.2lf ",a[i]); for(i=0;i<q;i++) printf("%.2lf ",b[i]); return 0; }
-
【桶排】明明的随机数
#include <stdio.h> int f[10000]; int main(){ int n,i,x,sum=0; scanf("%d",&n); for(i=1;i<=n;i++){ scanf("%d",&x); if(f[x]==0) sum++; //等于零表示该位之前并没有重复值 f[x]++; } printf("%d\n",sum); for(i=0;i<=10000;i++) if(f[i]>0) printf("%d ",i); return 0; }
- 病人排队
#include <stdio.h> #include <stdlib.h> typedef struct{ char a[11]; int b,id; }STU; STU a[100],b[100]; int n,p,q,i,j; int main(){ scanf("%d",&n); for(i=0;i<n;i++){ STU x; scanf("%s %d",&x.a,&x.b); if(x.b>=60){ a[p]=x;a[p].id=i;p++; }else{ b[q]=x;b[q].id=i;q++; } } for(i=1;i<p;i++) //>=60 for(j=0;j<p;j++){ if(a[i].b>a[j].b||(a[i].b==a[j].b&&a[i].id<a[j].id)){ STU t=a[i];a[i]=a[j];a[j]=t; } } for(i=1;i<q;i++) //<60 for(j=0;j<q;j++){ if(b[i].id<b[i].id){ STU t=b[i];b[i]=b[j];b[j]=t; } } for(i=0;i<p;i++) printf("%s\n",a[i].a); for(i=0;i<q;i++) printf("%s\n",b[i].a); return 0; }
-
单词排序
#include <stdio.h> #include <string.h> char f[101][51],t[51]; int num,i,j,k; int main(){ while(~scanf("%s",&f[num])){ int flag=0; for(i=0;i<num;i++){ if(strcmp(f[i],f[num])==0){ flag=1;break; } } if(!flag) num++; } for(i=1;i<num;i++){ strcpy(t,f[i]); int l=0,r=i-1,m=0; while(l<=r){ m=(l+r)>>1; if(strcmp(t,f[m])<0) r=m-1; else l=m+1; } for(j=i-1;j>=l;j--) strcpy(f[j+1],f[j]); if(l!=i) strcpy(f[l],t); } for(i=0;i<num;i++) printf("%s\n",f[i]); return 0; }
-
【桶排序】出现次数超过一半的数
#include <stdio.h> int f[105],n,i,flag; int main(){ scanf("%d",&n); for(i=0;i<n;i++){ int x; scanf("%d",&x); f[x+50]++; } for(i=1;i<100;i++){ if(2*f[i]>n){ flag=1; printf("%d",i-50); break; } } if(flag==0) printf("no"); return 0; }
-
【桶排序】统计字符数
#include <stdio.h> #include <string.h> int i,loc,f[27]; char a[1001]; int main(){ int len=strlen(gets(a)); for(i=0;i<len;i++) f[a[i]-97]++; for(i=1;i<27;i++) if(f[loc]<f[i]) loc=i; printf("%c %d",97+loc,f[loc]); return 0; }
总结比较
一、稳定性:
插入,冒泡,二叉树,二路归并排序及其他线性排序是稳定排序;
选择,希尔,快速,堆排序是不稳定排序。既有跨度的交换都会导致不稳定;
二、时间复杂性:
插入,冒泡,选择的时间复杂性为O(n2);
快速,归并,堆排序的时间复杂性为O(long2n);
桶排序的时间复杂性为O(n);
若从最好情况考虑,则直接插入排序和冒泡排序的时间复杂度最好,为O(n),其他算法的最好情况同平均情况相同;
若从最坏情况考虑,则快速排序的时间复杂度为O(n2),直接插人排序和冒泡排序虽然平均情况相同。
但系数大约增一倍,所以运行速度将降低一半,最坏情况对直接选择排序、堆排序和归并排序影响不大。
由此可知,在最好情况下,直接插人排序和冒泡排序最快;在平均情况下,快速排序最快;在最坏情况下堆排序和归并排序最快。
三、辅助空间
桶排序、二路归并排序的辅助空间为0(n);
快速排序的辅助空间为O(log2n),最坏情况为O(n);
其他排序的辅助空间为0(1);
四、其他
- 插人、冒泡排序的速度较慢,但参加排序的序列局部或整体有序时,这种排序能达到较快的速度。而在这种情况下,快速排序反而慢了。
- 当n较小时,对稳定性不作要求时宜用选择排序,对稳定性有要求时宜用插人或冒泡排序。
- 若待排序的记录的关健字在一个明显有限范围内时,且空间允许时,宜选用桶排序。
- 当n较大时,关键字元素比较随机,对稳定性没要求宜用快速排序。
- 当n较大时,关键字元素可能出现本身是有序的,对稳定性没有要求时宜用堆排序。
- 快速排序是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短。
- 堆排序所需的辅助空间少于快速排序,并且不会出现快速排序可能出现的最坏情况,这两种排序都是不稳定的。