排序
-
冒泡排序:
static void MP_sort(int[]array){ for(int i=0;i<array.length;i++) for(int j=i+1;j<array.length;j++) if(array[j]<array[i]){ int temp=array[i]; array[i]=array[j]; array[j]=temp; } }
-
插入排序:将数组分为两个部分,一边排好序的,一边是乱的,将乱一个个插入到有序数组合适的位置
-
性能与数组中的逆序对数量息息相关
-
通过交换相邻元素进行排序的任何算法平均都需要N^2
void insert_sort( int array[]){ int j; for(int i=1;i<array.length;i++){ int temp=array[i]; for (j=i;j>0&&temp<array[j-1];j--) array[j]=array[j-1]; array[j]=temp; } }
-
希尔排序:缩小增量排序,是对插入排序的一种改良
-
一趟一个增量,用增量进行分组,组内进行插入排序
-
可以理解为插入排序是增量1的希尔排序
-
因为插入排序的性能取决于逆序对数量,但每次交换只能抵消一个逆序对,希尔排序提高一次交换抵消逆序对的数量
-
至于增量大小的选择:array.length/2是作者shell建议的序列,但是不好
-
运行时间更依赖增量的选择:
-
shell的最坏是n^2;(1,2,4,8)
-
Hibbard的最坏是n^(3/2);(1,3,7,15,2^k-1))
-
Sedgewick的最坏是n^(4/3),平均是n^8/7;(1,5,19,41,109)或者(9*4^i-9*2^i+1)或(4^i-3*2^i+1)
-
编程简单特点使得他成为对适度地大量的输入数据经常选用
void shell_sort(int array[]) { for(int mid=array.length/2;mid>0;mid/=2) //mid表示增量的大小,每一次整除与2, for(int i=mid;i<array.length;i++)//分组,后一部分 { int temp=array[i]; int j=i; while(j>=0&&temp<a[j]) //对组内元素进行插入排序 { array[j]=array[j+mid]; j-=mid; } aarray[j]=temp; } }
-
堆排序:建立堆的时间是logN,每次删除堆顶需要n次,所以为N*logN
-
为了避免使用新数组存储被删除的数,我们将数插入在堆后面
-
当然为了方便,我们把堆建成大顶堆,这样容易得到从小到大的数组
-
稳定,它使用的比较平均只比最坏情形界指出的略少,最坏(2N*logN-O(N))
void heap_sort(int array[]){ for(int i= array.length/2-1;i>=0;i--)//建立堆 percDown(array,i, array.length); for(int i=array.length-1;i>0;i--){ int temp=array[0]; array[0]=array[i]; array[i]=temp; //交换,使得最大在最后 percDown(array,0, i);//调整 } } void percDown(int array[],int i,int n){ //i表示从该节点为根进行下滤调整,n表示堆的大小 int child=2*i+1;//左树 int temp=array[i]; while(child<n)//左子树边界,否则就是叶子节点 { if(child!=n-1&&array[child]<array[child+1]) child++;//比较左右子树那个大,大的位置在child if(array[child]>temp) array[i]=array[child]; else break; i=child;//由于是这个儿子升上去;所以下滤寻找temp适合的位置 child=2*i+1; } array[i]=temp; }
-
归并排序:核心操作就是合并两个排好序的数组
-
最坏时间O(nlogn),需要开辟辅助数组
-
分治法的经典策略
-
归并排序在其他流行排序算法中最少的比较次数,在java标准库中泛型排序所使用的算法
//初始化,开辟辅助空间 static void merge_sort(int array[]){ int []B=new int[array.length]; merge_sort(array,B,0,array.length-1); } //分割 static void merge_sort(int array[],int B[],int left,int right){ if(left<right){ int center=(left+right)>>1; merge_sort(array,B,left,center); merge_sort(array,B,center+1,right); merge(array,B,left,center+1,right);;//合并 } } //合并 static void merge(int array [],int B[],int left_p,int right_p,int right_end){ int left_end=right_p-1; int B_p=left_p;//辅助空间起始位置 int numElements=right_end-left_p+1;//辅助空间大小 //比较大小 while (left_p<=left_end&&right_p<=right_end){ if(array[left_p]<=array[right_p]) B[B_p++]=array[left_p++]; else B[B_p++]=array[right_p++]; } //如果有一个先没了,那剩下就是另一个的 while (left_p<=left_end){ B[B_p++]=array[left_p++]; } while (right_p<=right_end){ B[B_p++]=array[right_p++]; } //更新原数组 while(numElements!=0){ array[right_end]=B[right_end]; right_end--; numElements--; } }
-
快速排序:平均时间复杂度O(N logN)
-
选定枢纽元,将比它小的放在左边,比他大的放在右边
-
然后分成两部分,进行递归操作
-
选取枢纽元:
-
选择第一个数虽然简单,但不是好主意(错误)
-
随机在数组中抽取,大大减低接连不断产生劣质分割的风险(安全,但生成随机数的算法开销很大)
-
三数中值分割法:对比数组中,开头,中间,尾巴的三个值,选中中间的作为枢纽元
-
对于小数组,快速排序的效率可能没插入排序好
-
在快速排序的基础上可以改成快速选择,在类似寻找第k大元素的需求可以应用
-
(分治法选择问题):五数中值取中 分割法,基本的枢纽元选择算法如下
-
把N分为5个元素的组组,一共N/5个
-
找出每个组的中值项,得到N/5个项存在M表中
-
求M的中值项,将其作为枢纽元
//快速排序入口,带优化 void quick_sort(int array[]){ quick_sort(array,0, array.length-1); } // 交换 void swap(int array[],int x,int y){ int temp=array[x]; array[x]=array[y]; array[y]=temp; } // 快排主元选取的优化 int median3( int array[],int left,int right){ //三数中值分割法处理主元,使得left比主元小,right比主元大 //最后把主元放在后面,注意,是right-1位置,因为确定right比主元大了 int center=(left+right)>>1; if(array[center]<array[left]) swap(array,center,left); if(array[right]<array[left]) swap(array,right,left); if(array[right]<array[center]) swap(array,center,right); swap(array,center,right-1); return array[right-1]; } // 如果是小数组,那么就使用插入排序 void insert_sort( int array[],int left,int right){ int j; for(int i=left+1;i<=right;i++){ int temp=array[i]; for (j=i;j>0&&temp<array[j-1];j--) array[j]=array[j-1]; array[j]=temp; } } // 现实中快排核心主体,由于包含在实际中的优化;如果想纯净的快排,不能只是把CUTOFF修改0 // 因为如此操作会导致数组溢出,因为在优化主元时候少于上三个 void quick_sort(int array[],int left, int right){ //为了优化效率,我们设置CUTOFF大小比如20; //意思是在实际中数组少于20个就没必要用快排,插入排序更快 int CUTOFF=10; if(left+CUTOFF<=right){ int pivot=median3(array,left,right); int i=left,j=right-1; while(true){ while (array[++i]<pivot){ } while (array[--j]>pivot){ } if(i<j) swap(array,i,j); else break; } swap(array,i,right-1);//交换主元,数组分割 quick_sort(array,left,i-1); quick_sort(array,i+1,right); } else{ insert_sort(array,left,right); } }
-
不带优化的快速排序
static void quick_2(int array[], int q, int n)//q为起始位置,n为终点 { if(q>=n)return; int inter=array[q];//把起始位置作为主元 int L=q+1; int R=n; while(L<=R)///数组都遍历完了即R与L交错时停止循环 { while(L<=R&&inter>=array[L]) L++; while(L<=R&&inter<array[R]) R--; if(L<R)///交换位置,把比主元大的放后面 ,比主元小的放前面 { int temp=array[L]; array[L]=array[R]; array[R]=temp; } } array[q]=array[R]; array[R]=inter; ///交换位置把主元放进去作为间隔 quick_2(array,q,R-1); quick_2(array,R+1,n); }
-
线性时间的排序:桶排序和基数排
-
桶排序:将待排序元素划分到不同的桶。
-
先扫描一遍序列求出最大值 maxV 和最小值 minV ,
-
设桶的个数为 k ,则把区间 [minV, maxV] 均匀划分成 k 个区间,每个区间就是一个桶。将序列中的元素分配到各自的桶。
-
对每个桶内的元素进行排序。可以选择任意一种排序算法。
-
将各个桶中的元素合并成一个大的有序序列。
-
基数排序
-
得出最大数的位数
-
由于每一位的取值范围为0-9;所以定义长度为10的数组B ,B[i]又引出一个ArrayList,就像二维表
-
从个位开始,遍历原数组,我们将原数组值得对应位加入对应数组,比如456;我们把他加入B[6];231加入B[1];
-
然后根据B[i]顺序将值覆盖原数组;对同一个B[i]的ArrayList有多个值,一定要按头到尾输出,保留顺序,就是按插入的顺序
-
接下来就是十位了,我们重复上面那两个步奏就行
-
最大数有多少位就重复弄几次
-
应用:将字符串排序
static void radix_sortA(String [] str ,int stringlen){ //假设所有字符串有相同的长度 int BUCKETS=256; //Unicode编码的前256位 ArrayList<String>[] array=new ArrayList[BUCKETS]; //创建256空间的数组 for(int i=0;i<BUCKETS;i++) //根据下标创建链表 array[i]=new ArrayList<>(); for(int pos=stringlen-1;pos>=0;pos--)// 从低位开始到高位 { for(String s:str) //遍历原字符串数组的每一个字符串,将他插在合适的位置上 array[s.charAt(pos)].add(s); int idx=0; for(ArrayList<String> thisArr : array){ //复原原数组,就是把新的顺序覆盖原数组 for(String s:thisArr) str[idx++]=s; thisArr.clear();// 清空,以便下一位的进去 } } }
-
计数排序:
-
计数排序是一种稳定的线性时间排序算法。
-
假设数组中的所有数据为小于M的正整数
-
使用一个M大小的count数组,初始化为0;遍历原数组,读取array[i]时候;count[array[i]]++;最后扫描count数组进行输出
-
时间复杂度O(N),需要额外空间M
-
外部排序:
-
对于各种海量的数据,以上的排序方法就不适合了,因为我们的内存有限,上面那些算法称为内部排序
-
基本的外部排序算法使用的是归并排序中的合并算法
-
简单算法:以下代码不完整,但思路清晰,需要额外三块磁盘
//假设你电脑内存有限,你有数据在磁盘上,数据量远超你的内存,请问你如何排序 //内存大小N,磁盘A大小M,N<<M; void sort1(int A[]){ int N=10;//假设内存最多一次放10个元素 int M=A.length-1; //三块辅助磁盘,大小和A一样 int[] B= new int[M]; int[] C= new int[M]; int[] D= new int[M]; int k=1; //由于内存排序的速度高于外存 //所以第一步操作,将磁盘A每次取N的元素放在内存排序后 //分奇偶次分别存进,数组D,C int c1=0; int d1=0; while(true){ if(k*N-1<M) { insert_sort(A, (k - 1) * N, k * N-1); if(k%2==0){//存在C for(int i=(k-1)*N;i<=N*k-1;i++,c1++) C[c1]=A[i]; }else//存在D for(int i=(k-1)*N;i<=N*k-1;i++,d1++) D[d1]=A[i]; } else { insert_sort(A, (k - 1) * N, M); if(k%2==0){//存在C for(int i=(k-1)*N;i<=M;i++,c1++) C[c1]=A[i]; }else//存在D for(int i=(k-1)*N;i<=M;i++,d1++) D[d1]=A[i]; break;//退出 } k++; } //接下来就是归并排序中的合并算法 //将CD合并到AB A=sort2(C,D,A,B,N); } int [] sort2(int A[],int B[],int C[],int D[],int k){ //将AB合并到CD, //k表示AB中有序串的长度 //检查AB是否有一个为空,有就返回另一个 if (){ } int alen=A.length-1; int blen=B.length-1; int t=1; int c1=0; int d1=0; while((t-1)*k<=alen||(t-1)*k<=blen){ int a1=(t-1)*k; int b1=(t-1)*k; if(t*k-1<=alen&&t*k-1<=blen){ if(t%2==0){//AB中两个串合并到C andsort(A,B,C,a1,t*k-1,b1,t*k-1,(t-1)*k); } else andsort(A,B,D,a1,t*k-1,b1,t*k-1,(t-1)*k); } else if(t*k-1<=blen){ if(t%2==0){//AB中两个串合并到C andsort(A,B,C,a1,alen,b1,t*k-1,(t-1)*k); } else andsort(A,B,D,a1,alen,b1,t*k-1,(t-1)*k); } else if(t*k-1<=alen){ if(t%2==0){//AB中两个串合并到C andsort(A,B,C,a1,t*k-1,b1,blen,(t-1)*k); } else andsort(A,B,D,a1,t*k-1,b1,blen,(t-1)*k); } t++; } //必须清空数组AB A B.clear(); return A=sort2(C,D,A,B,2*k); } //用于合并两个数组AB到C void andsort(int a[],int b[],int c[],int abe,int aend,int bbe,int bend,int cbe){ while(true){ if(abe<=aend&&bbe<=bend){ if(a[abe]<b[bbe]) c[cbe++]=a[abe++]; else c[cbe++]=b[bbe++]; } else if(abe>aend&&bbe<=bend){ c[cbe++]=b[bbe++]; }else{ while(abe<=aend) c[cbe++]=a[abe++]; break; } } } //插入排序,用于内存内部排序 void insert_sort( int array[],int left,int right){ int j; for(int i=left+1;i<=right;i++){ int temp=array[i]; for (j=i;j>0&&temp<array[j-1];j--) array[j]=array[j-1]; array[j]=temp; } }
-
多路合并:如果可以使用更多的磁盘,那我们可以扩充为多路合并,减少数据排序所需要的趟数,但是对于合并排序中寻找小元素插入新磁盘就比较麻烦;k路合并需要2k个磁盘;
-
多相合并:k路合并需要k+1个磁盘;k个磁盘合并到一个后,这一个再把数据串分配下去各个磁盘,上面的是合并包分配
-
替换选择:对顺串的优化
浪波激泥