1.冒泡排序
public class BubbleOrder { public void bubbleorder(int[] arr){ //如果数组为空或者数组元素少于2,那么就不需要排序 if (arr==null||arr.length<2){ return; } //end表示未排好数组的最后一个位置,i在0和end之间循环,两两比较,把最大值依次放到end位置 for (int end=arr.length-1;end>0;end--){ for (int i=0;i<end;i++){ if (arr[i]>arr[i+1]){ swap(arr,i,i+1);//交换元素 } } } } private void swap(int[] arr, int i, int i1) { int temp=arr[i]; arr[i]=arr[i+1]; arr[i+1]=temp; } }
2.插入排序
public class InsertOrder { public void insertOrder(int[] arr){ if (arr==null||arr.length<2){ return; } //i代表不断右移的指针,i的左边总是有序的,j在0~i-1内遍历,将i位置元素插入到左边数组,保证左边数组有序 for (int i=1;i<arr.length;i++){ for (int j=i-1;j>=0&&arr[j]>arr[j+1];j--){ swap(arr,j,j+1); } } } private void swap(int[] arr, int i, int j) { arr[i]=arr[i]^arr[j]; arr[j]=arr[i]^arr[j]; arr[i]=arr[i]^arr[j]; } }
3.选择排序
public class SelectOrder { public void selectOrder(int[] arr){ if (arr==null&&arr.length<2){ return; } //每次在数组寻找到最小的值放在数组的最前部分 for (int i=0;i<arr.length;i++){ int mIndex=i; for (int j=i+1;j<arr.length;j++){ mIndex=arr[j]<arr[mIndex]?j:mIndex; } Test.swap(arr,i,mIndex); } } }
以上三种排序方法:
时间复杂度:O(N^2)
空间复杂度:O(1)4.归并排序:
public class MergeOrder { public static void mergeSort(int[] a){ if (a==null||a.length<2){ return; } mergeSort(a,0,a.length-1); } /** * 递归排序 * @param a * @param i * @param length */ private static void mergeSort(int[] a, int i, int length) { //递归出口,当左边等于右边时,说明已经排好,递归结束 if (i==length){ return; } int mid=i+((length-i)>>1);//每次递归选取中点进行分割 mergeSort(a,i,mid);//将中点左边排好 mergeSort(a,mid+1,length);//将中点右边排好 merge(a,i,mid,length); } /** * 将排好序的左右两部分数组重排序到辅助数组中 * @param a * @param l * @param mid * @param r */ private static void merge(int[] a, int l, int mid, int r) { int[] help=new int[r-l+1];//辅助数组 int i=0; int p1=l;//p1代表在左边到中点之间移动的指针 int p2=mid+1;//p2代表在中点到右边之间移动的指针 while (p1<=mid&&p2<=r){//当p1和p2都未越界时,把原数组按顺序拷贝到辅助数组中 help[i++]=a[p1]<a[p2]?a[p1++]:a[p2++]; } //如果右边先越界,把左边直接拷贝 while (p1<=mid){ help[i++]=a[p1++]; } //如果左边先越界,把右边直接拷贝 while (p2<=r){ help[i++]=a[p2++]; } //将辅助数组中的元素重新拷贝到原数组中 for (i=0;i<help.length;i++){ a[l+i]=help[i]; } } }
归并排序时间复杂度:O(N*logN),空间复杂度为O(N)
归并排序的应用:
例:求小数和:求一个数组中元素左边比该元素小的数的和;
比如一个这样的数组:[1,2,6,8,3,7,0,2,3,5],它的所有小和数为:1+(1+2)+(1+2+6)+(1+2)+(1+2+6+3)+0+1+(1+2+2)+(1+2+2+3+3)=45
这里就可以使用归并排序进行处理:
/** * 获取数组中的小和数 * 例:[1,2,6,8,3,7,0,2,3,5] * 所有小和数为:1+(1+2)+(1+2+6)+(1+2)+(1+2+6+3)+0+1+(1+2+2)+(1+2+2+3+3) * =1+3+9+3+12+1+5+11=45 */ public class GetSmallSumMethod { public int getSmallSum(int[] a){ if (a==null||a.length<2){ return 0; } return mergeProgress(a,0,a.length-1); } //递归调用 private int mergeProgress(int[] a, int l, int r) { if (l==r){ return 0; } int mid=l+((r-l)>>1); return mergeProgress(a,l,mid)//计算出左半边的所有小和数 +mergeProgress(a,mid+1,r)//右边 +merge(a,l,mid,r);//总共 } private int merge(int[] a, int l, int mid, int r) { int[] help=new int[r-l+1]; int i=0; int p1=l; int p2=mid+1; int res=0; while (p1<=mid&&p2<=r){ res+=a[p1]<a[p2]?(r-p2+1)*a[p1]:0; help[i++]=a[p1]<a[p2]?a[p1++]:a[p2++]; } while (p1<=mid){ help[i++]=a[p1++]; } while (p2<=r){ help[i++]=a[p2++]; } for (i=0;i<help.length;i++){ a[l+i]=help[i]; } return res; } }
5.快速排序:
/** * 快速排序,选取一个划分点,经典的是选取数组的最后一位作为划分点,然后将数组分为小于划分点的放在前面,等于划分点的放在中间,大于划分点的放在后面 * 然后,不断递归调用,最终实现排序 * 经典快排(永远选取数组最后一个值作为划分点)的时间复杂度为: * 最好情况下可以达到O(N*logN) * 最坏情况下可以达到O(N^2) * 随机快排的时间复杂度的数学期望可以达到O(N*logN) * * 其空间复杂度为O(logN) * * 划分过程: * 1.当前值curr<划分值 小于区域的下一个值与curr交换,并将小于区域向右扩大一个位置,curr向右移动一个位置 * 2.当前值curr=划分值 curr直接向右移动一个位置 * 3.当前值curr>划分值 大于区域的前一个值与curr交换,并将大于区域向左扩大一个位置,curr的位置不变 */ public class QuicklyOrder { public void quickOrder(int[] a){ if (a==null||a.length<2){ return; } quickOrder(a,0,a.length-1); } /** * 快排的主方法,调用递归 * @param a 给定的数组 * @param l 数组左边界 * @param r 数组右边界 */ private void quickOrder(int[] a, int l, int r) { if(l<r){//递归调用的出口 //随机快速排序,随机选取一个划分值,并让这个划分值和数组最后一位交换 //随机快排可以做到时间复杂度的期望为O(N*logN) swap(a,l+(int)(Math.random()*(r-l+1)),r); int[] p=partition(a,l,r);//划分区域 quickOrder(a,l,p[0]-1);//递归划分小于区域 quickOrder(a,p[1]+1,r);//递归划分大于区域 } } /** * 快排的主要思想过程,划分区域的方法 * @param a 给定的数组 * @param l 数组左边界 * @param r 数组右边界 * @return 划分好区域的数组中相等区域的左右边界 */ private int[] partition(int[] a,int l,int r){ int less=l-1;//小于区域的初始右边界,不包含数组第一个数 int more=r;//大于区域的初始左边界,包含了数组的最后一个数 while (l<more){//循环的结束条件是当前值与大于区域的左边界碰撞 if (a[l]<a[r]){//当前值<划分值时 //当前值与小于区域的下一个值交换,小于区域+1,当前值向右+1 swap(a,++less,l++); }else if (a[l]>a[r]){//当前值>划分值时 //当前值与大于区域的前一个值交换,大于区域+1,当前值不变 swap(a,--more,l); }else {//当前值=划分值时 //当前值直接+1 l++; } } //最后将数组最后一个值与大于区域的第一个值交换 swap(a,more,r); return new int[] {less+1,more}; } private void swap(int[] arr, int i, int i1) { int temp=arr[i]; arr[i]=arr[i1]; arr[i1]=temp; }
6.堆排序
package order; /** * 堆排序 * 1.先将数组调整为大根堆,这个过程的时间复杂度为O(N) * 2.调整过后,将堆顶元素(最大值)与最后一个元素交换,将堆大小自减一个,然后再次调整为大根堆 * 排序过程的时间复杂度为O(N*logN) * 空间复杂度为O(1) */ public class HeapOrder { public void heapOrder(int[] a){ if (a==null||a.length<2){ return; } //第一步,先将数组调整为大根堆 for (int i=0;i<a.length;i++){ heapInsert(a,i); } //第二步,首尾交换,并把堆大小自减1 int heapSize=a.length; swap(a,0,--heapSize); //当堆大小大于0时,调整当前堆为大根堆 while (heapSize>0){ heapify(a,0,heapSize); swap(a,0,--heapSize); } } /** * 调整数组为大根堆 * @param a 给定的数组 * @param i 当前的数组元素下标 */ private void heapInsert(int[] a, int i) { while (a[i]>a[(i-1)/2]){ //循环条件是当前元素的大小大于其父节点的大小 //那么就交换两者位置 swap(a,i,(i-1)/2); //把当前位置变为父节点位置,再次比较 i=(i-1)/2; } } /** * 堆排序 * @param a * @param index * @param size */ private void heapify(int[] a,int index,int size){ int left=index*2+1;//获取此位置的左孩子 while (left<size){ //当左孩子不越界 //获取子节点中较大的一个的下标 int largest=left+1<size&&a[left+1]>a[left]?left+1:left; //比较父子节点,选取最大节点的下标作为largest largest=a[largest]>a[index]?largest:index; if (largest==index){ //如果最大值和当前值的下标相等 //那么代表不需要调整 break; } //如果需要调整,那么就将较大元素和当前元素交换 swap(a,largest,index); //将调整后的堆的下标重新赋值 index=largest; left=index*2+1; } } public static void swap(int[] arr, int i, int i1) { int temp=arr[i]; arr[i]=arr[i1]; arr[i1]=temp; } }
7.桶排序
package order; /** * 桶排序中的计数排序 * 比如有这样一个数组[1,5,4,3,7,8,2,5,4,3,2,2,9,0,5] * 这里有15个元素,数组最小值为0,最大值为9 * 这时创建一个help数组,长度为10,代表着0-9这几个数字 * help存储的是待排序数组中元素出现的次数,也就是词频 * 然后开始遍历数组,第一个元素是1,那么就把help[1]++; * 第二个元素5,那么就help[5]++ * 。。。 * 等到遍历完成之后,help存储的就是 * [1,1,3,2,2,3,0,1,1,1] * 意思就是,待排序数组中,0出现1次,1出现1次,2出现3次... * 然后再把help数组拷贝到数组中,就实现排序 */ public class BucketOrder { public void bucketOrder(int[] arr){ if (arr==null||arr.length<2){ return; } //找出数组中最大值,确定数组元素值的范围 int max=Integer.MIN_VALUE; for (int i=0;i<arr.length;i++){ max=Math.max(max,arr[i]); } //help数组,长度为max+1 int[] bucket=new int[max+1]; for (int i=0;i<arr.length;i++){ bucket[arr[i]]++;//将数组词频存到对应位置 } //进行拷贝 int i=0; for (int j=0;j<bucket.length;j++){ while (bucket[j]-->0){ arr[i++]=j; } } } }
桶排序的应用:
例:存在一个无序数组,在排序之后,返回相邻元素之间的最大差值,如[3,1,6,0],排序后为[0,1,3,6],那么返回值最大为6-3=3。
解题思路:准备N+1个桶,每个桶把数组元素的范围平分,第一个桶和最后一个桶必不为空,而中间必有桶是空的,那么各个空桶前一个桶的最大值和空桶后一个桶的最小值的差的最大值就是最大差值;
code:
package order; /** * 获取数组中相邻数差的最大值 * 解题思路:准备N+1个桶, * 每个桶把数组元素的范围平分, * 第一个桶和最后一个桶必不为空, * 而中间必有桶是空的, * 那么各个空桶前一个桶的最大值和空桶后一个桶的最小值的差的最大值就是最大差值; */ public class MaxGap { /** * 获取最大差值方法 * @param nums 给定数组 * @return */ public int maxGap(int[] nums){ if (nums==null||nums.length<2){ return 0; } int len=nums.length;//数组长度 int min=Integer.MAX_VALUE;//初始化一个最小值 int max=Integer.MIN_VALUE;//初始化一个最大值 //获取数组元素值的范围 for (int i=0;i<len;i++){ min=Math.min(min,nums[i]); max=Math.max(max,nums[i]); } //最大值=最小值,数组元素为一个定值,最大差值为0 if (min==max){ return 0; } //记录每个桶是否有元素 boolean[] hasNum=new boolean[len+1]; //记录各个桶中元素的最大值 int[] maxs=new int[len+1]; //记录各个桶中元素的最小值 int[] mins=new int[len+1]; int bid=0;//桶序号 for (int i=0;i<len;i++){ bid=bucket(nums[i],len,min,max); //更新桶中的最大值,最小值 mins[bid]=hasNum[bid]?Math.min(mins[bid],nums[i]):nums[i]; maxs[bid]=hasNum[bid]?Math.max(maxs[bid],nums[i]):nums[i]; hasNum[bid]=true;//桶中有元素 } int res=0; int lastMax=maxs[0];//上一个非空桶的最大值 //后一个非空桶的最小值-前一个桶的最大值为最大差值 for (int i=1;i<len;i++){ if (hasNum[i]){ res=Math.max(res,mins[i]-lastMax); lastMax=maxs[i]; } } return res; } /** * 通过传惨计算出数组中的该元素应该进入到哪一个桶中 * @param num 当前遍历到的元素 * @param len 数组长度 * @param min 数组的左范围(数组的最小值) * @param max 数组的右范围(数组的最大值) * @return 进入桶的序号 */ private int bucket(long num,long len,long min,long max){ return (int)((num-min)*len/(max-min)); } }