冒泡、选择、插入、二分插入、希尔、堆、归并和基数排序算法小结

    xiaoxiao2021-03-25  85

    各种算法直接上代码,主要想提下排序算法的稳定性的概念:

    通俗地讲就是能保证排序前2个相等的数其在序列的前后位置顺序和排序后它们两个的前后位置顺序相同。在简单形式化一下,如果Ai = Aj, Ai原来在位置前,排序后Ai还是要在Aj位置前。

    一般情况下:

    选择排序、快速排序、希尔排序、堆排序不是稳定的排序算法,而冒泡排序、插入排序、归并排序和基数排序是稳定的排序算法。

    需要注意的是,排序算法是否为稳定的是由具体算法决定的,不稳定的算法在某种条件下可以变为稳定的算法,而稳定的算法在某种条件下也可以变为不稳定的算法。

    冒泡排序:

    /** * * 冒泡排序 * 冒泡排序算法的运作如下: 比较相邻的元素。如果第一个比第二个大,就交换他们两个。 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。 针对所有的元素重复以上的步骤,除了最后一个。 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。 * 平均时间O(n^2) 最差情形O(n^2) , 稳定排序,额外空间O(1) n小时较好 * * 注:排序的稳定性是指如果在排序的序列中,存在前后相同的两个元素的话,排序前 和排序后他们的相对位置不发生变化 * Created by Administrator on 2017/3/12 0012. */ public class BubbleSort extends DataSort{ @Override public void dataSort() { int length = sourceArray.length; for(int i=0;i<length;i++){ for(int j=0;j<length-i-1;j++){ if(sourceArray[j]>sourceArray[j+1]){ swap(sourceArray,j,j+1); } } } } }

    选择排序:

    /** * 选择排序 * 从所有序列中先找到最小的,然后放到第一个位置。之后再看剩余元素中最小的,放到第二个位置……以此类推,就可以完成整个的排序工作 * 选择排序是固定位置,找元素 * 平均时间O(n^2) 最差情形O(n^2) , 不稳定排序,额外空间O(1) n小时较好 * Created by Administrator on 2017/3/12 0012. */ public class SelectSort extends DataSort { @Override public void dataSort() { int length = sourceArray.length; for(int i=0;i<length;i++){ int min = sourceArray[i]; int index = i; for(int j=i+1;j<length;j++){ if(sourceArray[j]<min){ min = sourceArray[j]; index = j; } } swap(sourceArray,i,index); } } }

    直接插入排序:

    /** * 直接插入排序 * 插入排序是在一个已经有序的小序列的基础上,一次插入一个元素。 * 当然,刚开始这个有序的小序列只有1个元素,就是第一个元素。 * 比较是从有序序列的末尾开 始,也就是想要插入的元素和已经有序的最大者开始比起,如果比它大则直接插入在其后面,否则一直往前找直到找到它该插入的位置。 * 如果碰见一个和插入元素相 等的,那么插入元素把想插入的元素放在相等元素的后面 * 每一步都将一个待排数据按其大小插入到已经排序的数据中的适当位置,直到全部插入完毕。 * 直接插入排序是固定元素,找位置 * 平均时间O(n^2) 最差情形O(n^2) , 稳定排序,额外空间O(1)大部分已排好序时较好 * Created by Administrator on 2017/3/12 0012. */ public class InsertSort extends DataSort { @Override public void dataSort() { int length = sourceArray.length; for(int i=1;i<length;i++){ //待插入元素 int temp = sourceArray[i]; int j ; for(j=i-1;j>=0;j--){ if(sourceArray[j]>temp){ sourceArray[j+1] = sourceArray[j]; }else{ break; } } sourceArray[j+1] = temp; } } }

    二分插入排序:

    /** * 二分插入排序 * 二分法插入排序是在插入第i个元素时,对前面的0~i-1元素进行折半,先跟他们中间的那个元素比,如果小,则对前半再进行折半,否则对后半进行折半, * 直到left>right,然后再把第i个元素前1位与目标位置之间的所有元素后移,再把第i个元素放在目标位置上。 * 从下标0开始循环处理,这样第i个元素之前的数据都是已经排好序的,在一个排好序的数组中通过二分查找法查找到比i元素的位置,然后把该位置开始的全部数据后移一位,再插入i的元素值 * 平均时间O(n^2) 最差情形O(n^2) , 稳定排序,额外空间O(1) 大部分已排好序时较好,优于插入排序 * Created by Administrator on 2017/3/12 0012. */ public class BinaryInsertSort extends DataSort { @Override public void dataSort() { int length = sourceArray.length; for (int i=0;i<length;i++){ int temp = sourceArray[i]; int left = 0; int right = i-1; int mid; //通过二分查找到位置,为什么可以用二分查找,是因为i位置前面所有的数据都已经排好序(从下标0开始排序的) while(left <= right){ mid = (left+right)/2; if(temp < sourceArray[mid]){ right = mid - 1; }else{ //这里同时处理等于的情况,因为是按照left下标来后移的 left = mid + 1; } } //把left之后包括left的元素后移一位 for(int j = i-1;j>=left;j--){ sourceArray[j+1] = sourceArray[j]; } //left == i 表示该元素不用移动 if(left != i){ sourceArray[left] = temp; } } } }

    希尔排序: /**希尔排序 * 属于插入类排序,是将整个有序序列分割成若干小的子序列分别进行插入排序。 * 平均时间O(nlogn) 最差情形O(n^s 1<s<2) , 不稳定排序,额外空间O(1) , s为所选分组 * Created by Administrator on 2017/3/12 0012. */ public class ShellSort extends DataSort { @Override public void dataSort() { int length = sourceArray.length; //增量 int addUp = length; while(true){ addUp = addUp/2; for(int i=0;i<addUp;i++){ //下面的排序也可以选择其他的排序方式,这里采用插入排序,因为当增量越来越小时,数组的有序越来越好 //进行插入排序,可以参考插入排序算法,需要注意的是这里的增量是addUp,插入排序的增量一般是1 for(int j=i+addUp;j<length;j+=addUp){ int temp = sourceArray[j]; int k; for(k=j-addUp;k>=0;k-=addUp){ if(temp<sourceArray[k]){ sourceArray[k+addUp] = sourceArray[k]; }else{ break; } } sourceArray[k+addUp] = temp; } } if(addUp == 1){ break; } } } } 堆排序:

    /** * 堆排序 * 堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。 * 可以利用数组的特点快速定位指定索引的元素。堆分为大根堆和小根堆,是完全二叉树。 * 大根堆的要求是每个节点的值都不大于其父节点的值,即A[PARENT[i]] >= A[i]。 * 在数组的非降序排序中,需要使用的就是大根堆,因为根据大根堆的要求可知,最大的值一定在堆顶 * 平均时间O(nlogn) 最差情形O(nlogn) , 不稳定排序,额外空间O(1) ,n大时较好 * Created by Administrator on 2017/3/12 0012. */ public class HeapSort extends DataSort { @Override public void dataSort() { buildLatestHeap(sourceArray); headSort(sourceArray); } /** * 排序,把堆顶元素一个个拿出来 */ private void headSort(int[] array){ //因为创建最大堆后最大数值处于堆顶,需要交换到最后一个位置 for (int i = array.length-1; i > 0 ; i--) { swap(array,i,0); //因为已经是最大堆,同时堆顶已经被替换,所以只要重新再处理一次堆顶,即index 0 位置即可 maxHeap(array,i,0); } } /** * 创建最大堆 * @param array */ private void buildLatestHeap(int[] array){ if(array == null || array.length == 0){ return; } //按照满二叉树的机构,我们只需要从length/2处开始遍历 int maxLength = array.length/2; for(int i=maxLength;i>=0;i--){ maxHeap(array,array.length,i); } } /** * 创建堆 * @param array 数组 * @param length 堆的长度 * @param i 父节点 */ private void maxHeap(int[] array,int length,int i){ int leftNode = 2*i+1; int rightNode = 2*i +2; int latestIndex = i; if(leftNode<length && array[leftNode]>array[latestIndex]){ latestIndex = leftNode; } if(rightNode<length && array[rightNode]>array[latestIndex]){ latestIndex = rightNode; } //如果i与latestIndex不等,表示有子节点比根结点大,需要交换 if(i != latestIndex){ swap(array,i,latestIndex); //继续对交换后的子节点进行建堆 maxHeap(array,length,latestIndex); } } } 归并排序:

    /** * 归并排序 * 归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。 * 将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 * 归并过程为:比较a[i]和a[j]的大小,若a[i]≤a[j],则将第一个有序表中的元素a[i]复制到r[k]中,并令i和k分别加上1; * 否则将第二个有序表中的元素a[j]复制到r[k]中,并令j和k分别加上1,如此循环下去,直到其中一个有序表取完, * 然后再将另一个有序表中剩余的元素复制到r中从下标k到下标t的单元。 * 归并排序的算法我们通常用递归实现,先把待排序区间[s,t]以中点二分,接着把左边子区间排序,再把右边子区间排序, * 最后把左区间和右区间用一次归并操作合并成有序的区间[s,t]。 * 平均时间O(nlogn) 最差情形O(nlogn) , 稳定排序,额外空间O(1) ,n 较大时比较好 * Created by Administrator on 2017/3/12 0012. */ public class MergeSort extends DataSort { @Override public void dataSort() { mergeSort(sourceArray,0,sourceArray.length-1); } private void mergeSort(int[] array,int left,int right){ if(left < right){ int middle = (left+right)/2; mergeSort(array,left,middle); mergeSort(array,middle+1,right); mergeData(array,left,middle,right); } } /** * 核心算法 * 因为采用递归,所以先是从左右两个元素比较起,然后再层层返回进行合并,可以知道left到middle和middle到right的数据是有序的 * @param array * @param left * @param middle * @param right */ private void mergeData(int[] array,int left, int middle, int right){ //定义一个中间数组,用来存放合并后的元素 int[] tempArray = new int[right - left + 1]; int rightStart = middle + 1; int tempLeft = left; int tempIndex = 0; //首先比较左右元素,先放小的进中间数组 while(left<=middle && rightStart<=right){ if(array[left]<=array[rightStart]){ tempArray[tempIndex++] = array[left++]; }else{ tempArray[tempIndex++] = array[rightStart++]; } } //如果左边还有数据 while(left<=middle){ tempArray[tempIndex++] = array[left++]; } //如果右边还有数据 while(rightStart<=right){ tempArray[tempIndex++] = array[rightStart++]; } //将tempArray数据复制到array for(int i=0;i<tempArray.length;i++){ array[tempLeft+i] = tempArray[i]; } } }

    基数排序:

    /**基数排序(只针对正整数,对负整数只需要把负号去掉后排序再逆序) * 基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort, * 顾名思义,它是透过键值的部份资讯,将要排序的元素分配至某些“桶”中,藉以达到排序的作用,基数排序法是属于稳定性的排序, * 其时间复杂度为O (nlog(r)m),其中r为所采取的基数,而m为堆数,在某些时候,基数排序法的效率高于其它的稳定性排序法。 * Created by Administrator on 2017/3/12 0012. */ public class RadixSort extends DataSort { @Override public void dataSort() { int length = sourceArray.length; //数字的最大长度,即位数 int maxLength = 1; //获取最大长度,通过拿到最大数得到 int temp = 0; for(int i=0;i<length;i++){ if(temp < sourceArray[i]){ temp = sourceArray[i]; } } while(temp != 0){ temp = temp/10; maxLength ++; } ArrayList<List<Integer>> baseIndexList = new ArrayList<>(); for(int i=0;i<10;i++){ baseIndexList.add(new ArrayList<Integer>()); } for(int i=0;i<maxLength;i++){ //遍历数组,把i(0表示个位0,1表示十位,2表示百位,以此类推) for(int j=0;j<length;j++){ //在此位的数组值,i=0表示个位的值,值为1 则放入到baseIndexList下标为1的List中 //先求余再除,对结果取整 int x = sourceArray[j]%(int)Math.pow(10,i+1)/(int)Math.pow(10,i); baseIndexList.get(x).add(sourceArray[j]); } //对sourceArray按照i位排序 int index = 0; for(int k=0;k<baseIndexList.size();k++){ int listSize = baseIndexList.get(k).size(); if(listSize == 0){ continue; } while(baseIndexList.get(k).size()>0){ sourceArray[index++] = baseIndexList.get(k).get(0); //获取到数据后就移除掉,方便下个位数用 baseIndexList.get(k).remove(0); } } } } }

    抽象类:

    /** * Created by Administrator on 2017/3/12 0012. */ public abstract class DataSort { /** * 源数组,用来排序 */ protected int[] sourceArray = new int[]{20,12,3,5974,1,2,43,12,0,8,89,1235,235,66,897,586}; /** * sort the data */ public abstract void dataSort(); /** * 交换数组下标i 与 下标 j的值 * @param desArray * @param i * @param j */ public void swap(int[] desArray , int i , int j){ if(desArray == null || i < 0 || j < 0 || i >= desArray.length || j >= desArray.length || i==j){ //do nothing return; } desArray[i] ^= desArray[j]; desArray[j] ^= desArray[i]; desArray[i] ^= desArray[j]; } /** * 打印array 数组 * @param array * @return */ public String arrayToString(int[] array){ if(array == null ){ return null; } if(array.length == 0){ return ""; } StringBuilder sb = new StringBuilder(); sb.append("["); int length = array.length; for (int i=0;i<length;i++){ if(i == length-1){ sb.append(array[i]); }else{ sb.append(array[i]).append(","); } } sb.append("]"); return sb.toString(); } public int[] getSourceArray() { return sourceArray; } public void setSourceArray(int[] sourceArray) { this.sourceArray = sourceArray; } } git源码 源码

    转载请注明原文地址: https://ju.6miu.com/read-37927.html

    最新回复(0)