一、八大排序算法总结
八大排序算法的稳定性及复杂度总结如下:
二、选择排序算法准则
每种排序算法都各有优缺点。因此,在实用时需根据不同情况适当选用,甚至可以将多种方法结合起来使用。
影响排序的因素有很多,平均时间复杂度低的算法并不一定就是最优的。相反,有时平均时间复杂度高的算法可能更适合某些特殊情况。同时,选择算法时还得考虑它的可读性,以利于软件的维护。一般而言,需要考虑的因素有以下四点:
1.待排序的记录数目n的大小;
2.记录本身数据量的大小,也就是记录中除关键字外的其他信息量的大小;
3.关键字的结构及其分布情况;
4.对排序稳定性的要求。
设待排序元素的个数为n。
1)当n较大,则应采用时间复杂度为O(nlog2n)的排序方法:快速排序、堆排序或归并排序。
快速排序:是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;
堆排序:如果内存空间允许且要求稳定性的,
归并排序:它有一定数量的数据移动,所以我们可能过与插入排序组合,先获得一定长度的序列,然后再合并,在效率上将有所提高。
2)当n较大,内存空间允许,且要求稳定性,推荐归并排序
3)当n较小,可采用直接插入或直接选择排序。
直接插入排序:当元素分布有序,直接插入排序将大大减少比较次数和移动记录的次数。
直接选择排序 :元素分布有序,如果不要求稳定性,选择直接选择排序
4)一般不使用或不直接使用传统的冒泡排序。
5)基数排序
它是一种稳定的排序算法,但有一定的局限性:
1、关键字可分解。
2、记录的关键字位数较少,如果密集更好
3、如果是数字时,最好是无符号的,否则将增加相应的映射复杂度,可先将其正负分开排序。
三、java实现
1、简单排序:冒泡排序 、选择排序 、插入排序
package com.xin.sort; public class Sort { private int [] array; public Sort(int [] array){ this.array = array; } //按顺序打印数组中的元素 public void display(){ for(int i=0;i<array.length;i++){ System.out.print(array[i]+"\t"); } System.out.println(); } //冒泡排序 public void bubbleSort(){ int temp; int len = array.length; for(int i=0;i<len-1;i++) { //外层循环:每循环一次就确定了一个相对最大元素 for(int j=1;j<len-i;j++) { //内层循环:有i个元素已经排好,根据i确定本次的比较次数 if(array[j-1] >array[j]) { //如果前一位大于后一位,交换位置 temp = array[j-1]; array[j-1] = array[j]; array[j] = temp; } } System.out.print("第"+(i+1)+"轮排序结果:"); display(); } } //冒泡排序改进1 public void bubbleSort_improvement_1(){ int temp; int len = array.length; for(int i=0;i<len-1;i++){ boolean exchange = false; //设置交换变量 for(int j=1;j<len-i;j++){ if(array[j-1]>array[j]){ //如果前一位大于后一位,交换位置 temp = array[j-1]; array[j-1] = array[j]; array[j] = temp; if(!exchange) exchange =true; //发生了交换操作 } } System.out.print("第"+(i+1)+"轮排序结果:"); display(); if(!exchange) break; //如果上一轮没有发生交换数据,证明已经是有序的了,结束排序 } } //冒泡排序改进2 public void bubbleSort_improvement_2(){ int temp; int counter = 1; int endPoint = array.length-1; //endPoint代表最后一个需要比较的元素下标 while(endPoint>0){ int pos = 1; for(int j=1;j<=endPoint;j++){ if(array[j-1]>array[j]){ //如果前一位大于后一位,交换位置 temp= array[j-1]; array[j-1]= array[j]; array[j]= temp; pos= j; //下标为j的元素与下标为j-1的元素发生了数据交换 } } endPoint= pos-1; //下一轮排序时只对下标小于pos的元素排序,下标大于等于pos的元素已经排好 System.out.print("第"+counter+"轮排序结果:"); counter++; display(); } } //冒泡排序改进3 public void bubbleSort_improvement_3(){ int temp; int low = 0; int high = array.length-1; int counter = 1; while(low<high){ for(int i=low;i<high;++i){ //正向冒泡,确定最大值 if(array[i]>array[i+1]){ //如果前一位大于后一位,交换位置 temp= array[i]; array[i]= array[i+1]; array[i+1]= temp; } } --high; for(int j=high;j>low;--j){ //反向冒泡,确定最小值 if(array[j]<array[j-1]){ //如果前一位大于后一位,交换位置 temp= array[j]; array[j]= array[j-1]; array[j-1]= temp; } } ++low; System.out.print("第"+counter+"轮排序结果:"); display(); counter++; } } //选择排序 public void selectionSort(){ int minPoint; //存储最小元素的小标 int len = array.length; int temp; int counter = 1; for(int i=0;i<len-1;i++){ minPoint= i; for(int j=i+1;j<=len-1;j++){ //每完成一轮排序,就确定了一个相对最小元素,下一轮排序只对后面的元素排序 if(array[j]<array[minPoint]){ //如果待排数组中的某个元素比当前元素小,minPoint指向该元素的下标 minPoint= j; } } if(minPoint!=i){ //如果发现了更小的元素,交换位置 temp= array[i]; array[i]= array[minPoint]; array[minPoint]= temp; } System.out.print("第"+counter+"轮排序结果:"); display(); counter++; } } //选择排序改进版 public void selectionSort_improvement(){ int minPoint; //存储最小元素的小标 int maxPoint; //存储最大元素的小标 int len = array.length; int temp; int counter = 1; for(int i=0;i<len/2;i++){ minPoint= i; maxPoint= i; for(int j=i+1;j<=len-1-i;j++){ //每完成一轮排序,就确定了两个最值,下一轮排序时比较范围减少两个元素 if(array[j]<array[minPoint]){ //如果待排数组中的某个元素比当前元素小,minPoint指向该元素的下标 minPoint= j; continue; }else if(array[j]>array[maxPoint]){ //如果待排数组中的某个元素比当前元素大,maxPoint指向该元素的下标 maxPoint= j; } } if(minPoint!=i){ //如果发现了更小的元素,与第一个元素交换位置 temp= array[i]; array[i]= array[minPoint]; array[minPoint]= temp; //原来的第一个元素已经与下标为minPoint的元素交换了位置 //如果之前maxPoint指向的是第一个元素,那么需要将maxPoint重新指向array[minPoint] //因为现在array[minPoint]存放的才是之前第一个元素中的数据 if(maxPoint== i){ maxPoint= minPoint; } } if(maxPoint!=len-1-i){ //如果发现了更大的元素,与最后一个元素交换位置 temp= array[len-1-i]; array[len-1-i]= array[maxPoint]; array[maxPoint]= temp; } System.out.print("第"+counter+"轮排序结果:"); display(); counter++; } } //插入排序 public void insertionSort(){ int len = array.length; int counter = 1; for(int i=1;i<len;i++){ int temp = array[i]; //存储待排序的元素值 int insertPoint = i-1; //与待排序元素值作比较的元素的下标 while(insertPoint>=0 && array[insertPoint]>temp){ //当前元素比待排序元素大 array[insertPoint+1]= array[insertPoint]; //当前元素后移一位 insertPoint--; } array[insertPoint+1]= temp; //找到了插入位置,插入待排序元素 System.out.print("第"+counter+"轮排序结果:"); display(); counter++; } } //二分插入排序 public void BinaryInsertionSort(){ int len = array.length; int counter = 1; for(int i=1;i<len;i++){ int temp = array[i]; //存储待排序的元素值 if(array[i-1]>temp){ //比有序数组的最后一个元素要小 int insertIndex = binarySearch(0, i-1, temp); //获取应插入位置的下标 for(int j=i;j>insertIndex;j--){ //将有序数组中,插入点之后的元素后移一位 array[j]= array[j-1]; } array[insertIndex]= temp; //插入待排序元素到正确的位置 } System.out.print("第"+counter+"轮排序结果:"); display(); counter++; } } /** * 二分查找法 * @param lowerBound 查找段的最小下标 * @param upperBound 查找段的最大下标 * @param target 目标元素 * @return 目标元素应该插入位置的下标 */ public int binarySearch(int lowerBound,int upperBound,int target){ int curIndex; while(lowerBound<upperBound){ curIndex= (lowerBound+upperBound)/2; if(array[curIndex]>target){ upperBound= curIndex - 1; }else{ lowerBound= curIndex + 1; } } return lowerBound; } //2-路插入排序 public void two_wayInsertionSort(){ int len = array.length; int [] newArray = new int [len]; newArray[0]= array[0]; //将原数组的第一个元素作为枢纽元素 int first = 0; //指向最小元素的指针 int last = 0; //指向最大元素的指针 for(int j=0;j<newArray.length;j++){ //打印初始化数组 System.out.print(newArray[j]+"\t"); } System.out.println(); for(int i=1;i<len;i++){ if(array[i]>= newArray[last]){ //大于等于最大元素,直接插入到last后面,不用移动元素 last++; newArray[last]= array[i]; }else if(array[i] < newArray[first]){ //小于最小元素,直接插到first前面,不用移动元素 first= (first-1+len) % len; newArray[first]= array[i]; }else if(array[i] >= newArray[0]){ //在最大值与最小值之间,且大于等于枢纽元素,插入到last之前,需要移动元素 int curIndex = last; last++; do{ //比array[i]大的元素后移一位 newArray[curIndex+1]= newArray[curIndex]; curIndex--; }while(newArray[curIndex]>array[i]); newArray[curIndex+1]= array[i]; //插入到正确的位置 }else{ //在最大值与最小值之间,且小于枢纽元素,插入到first之后,需要移动元素 int curIndex = first; first= (first-1+len) % len; do{ //比array[i]小的元素前移一位 newArray[curIndex-1]= newArray[curIndex]; curIndex= (curIndex+1+len)%len; }while(newArray[curIndex]<=array[i]); newArray[(curIndex-1+len)%len]= array[i]; //插入到正确的位置 } for(int j=0;j<newArray.length;j++){ //打印新数组中的元素 System.out.print(newArray[j]+"\t"); } System.out.println(); } } }
2、 高级排序:归并排序 、快速排序 、希尔排序 、堆排序 、基数排序
package com.xin.sort; import java.util.ArrayList; import java.util.List; /** * 高级排序 * @author Administrator * */ public class HighSort { private int [] array; //待排序的数组 public HighSort(int [] array){ this.array= array; } //按顺序打印数组中的元素 public void display(){ for(int i=0;i<array.length;i++){ System.out.print(array[i]+"\t"); } System.out.println(); } //归并排序 public void mergeSort(){ int[] workSpace = new int [array.length]; //用于辅助排序的数组 recursiveMergeSort(workSpace,0,workSpace.length-1); } /** * 递归的归并排序 * @param workSpace 辅助排序的数组 * @param lowerBound 欲归并数组段的最小下标 * @param upperBound 欲归并数组段的最大下标 */ private void recursiveMergeSort(int [] workSpace,int lowerBound,int upperBound){ if(lowerBound== upperBound){ //该段只有一个元素,不用排序 return; }else{ int mid = (lowerBound+upperBound)/2; recursiveMergeSort(workSpace,lowerBound,mid); //对低位段归并排序 recursiveMergeSort(workSpace,mid+1,upperBound); //对高位段归并排序 merge(workSpace,lowerBound,mid,upperBound); display(); } } /** * 对数组array中的两段进行合并,lowerBound~mid为低位段,mid+1~upperBound为高位段 * @param workSpace 辅助归并的数组,容纳归并后的元素 * @param lowerBound 合并段的起始下标 * @param mid 合并段的中点下标 * @param upperBound 合并段的结束下标 */ private void merge(int [] workSpace,int lowerBound,int mid,int upperBound){ int lowBegin = lowerBound; //低位段的起始下标 int lowEnd = mid; //低位段的结束下标 int highBegin = mid+1; //高位段的起始下标 int highEnd = upperBound; //高位段的结束下标 int j = 0; //workSpace的下标指针 int n = upperBound-lowerBound+1; //归并的元素总数 while(lowBegin<=lowEnd && highBegin<=highEnd){ if(array[lowBegin]<array[highBegin]){//将两者较小的那个放到workSpace中 workSpace[j++]= array[lowBegin++]; }else{ workSpace[j++]= array[highBegin++]; } } while(lowBegin<=lowEnd){ workSpace[j++]= array[lowBegin++]; } while(highBegin<=highEnd){ workSpace[j++]= array[highBegin++]; } for(j=0;j<n;j++){ //将归并好的元素复制到array中 array[lowerBound++]= workSpace[j]; } } //快速排序 public void quikSort(){ recursiveQuikSort(0,array.length-1); } /** * 递归的快速排序 *@param low 数组的最小下标 *@param high 数组的最大下标 */ private void recursiveQuikSort(int low,int high){ if(low>=high){ return; }else{ int pivot = array[low]; //以第一个元素为基准 int partition =partition(low,high,pivot); //对数组进行划分,比pivot小的元素在低位段,比pivot大的元素在高位段 display(); recursiveQuikSort(low,partition-1); //对划分后的低位段进行快速排序 recursiveQuikSort(partition+1,high); //对划分后的高位段进行快速排序 } } /** * 以pivot为基准对下标low到high的数组进行划分 *@param low 数组段的最小下标 *@param high 数组段的最大下标 *@param pivot 划分的基准元素 *@return 划分完成后基准元素所在位置的下标 */ private int partition(int low,int high,int pivot){ while(low<high){ while(low<high &&array[high]>=pivot){ //从右端开始扫描,定位到第一个比pivot小的元素 high--; } swap(low,high); while(low<high &&array[low]<=pivot){ //从左端开始扫描,定位到第一个比pivot大的元素 low++; } swap(low,high); } return low; } /** * 交换数组中两个元素的数据 *@param low 欲交换元素的低位下标 *@param high 欲交换元素的高位下标 */ private void swap(int low,int high){ int temp = array[high]; array[high] = array[low]; array[low] = temp; } //希尔排序 public void shellSort(){ int len = array.length; int counter = 1; int h = 1; while(3*h+1<len){ //确定第一轮排序时的间隔 h = 3*h+1; } while(h>0){ for(int i=0;i<h;i++){ shellInsertSort(i,h); //对间隔为h的元素进行插入排序 } h = (h-1)/3; //下一轮排序的间隔 System.out.print("第"+counter+"轮排序结果:"); display(); counter++; } } /** * 希尔排序内部使用的插入排序: * 需要进行插入排序的元素为array[beginIndex]、array[beginIndex+increment]、array[beginIndex+2*increment]... *@param beginIndex 起始下标 *@param increment 增量 */ private void shellInsertSort(int beginIndex,int increment){ int targetIndex =beginIndex+increment; //欲插入元素的下标 while(targetIndex<array.length){ int temp =array[targetIndex]; int previousIndex =targetIndex - increment; //前一个元素下标,间隔为increment while(previousIndex>=0 && array[previousIndex]>temp){ array[previousIndex+increment]= array[previousIndex]; //比欲插入数据项大的元素后移一位 previousIndex =previousIndex - increment; } array[previousIndex+increment]= temp; //插入到合适的位置 targetIndex =targetIndex+increment; //插入下一个元素 } } //堆排序 public void heapSort(){ buildHeap(); System.out.println("建堆:"); printTree(array.length); int lastIndex = array.length-1; while(lastIndex>0){ swap(0,lastIndex); //取出堆顶元素,将堆底放入堆顶。其实就是交换下标为0与lastIndex的数据 if(--lastIndex == 0) break; //只有一个元素时就不用调整堆了,排序结束 adjustHeap(0,lastIndex); //调整堆 System.out.println("调整堆:"); printTree(lastIndex+1); } } /** * 用数组中的元素建堆 */ private void buildHeap(){ int lastIndex = array.length-1; for(int i= (lastIndex-1)/2;i>=0;i--){ //(lastIndex-1)/2就是最后一个元素的根节点的下标,依次调整每棵子树 adjustHeap(i,lastIndex); //调整以下标i的元素为根的子树 } } /** * 调整以下标是rootIndex的元素为根的子树 *@param rootIndex 根的下标 *@param lastIndex 堆中最后一个元素的下标 */ private void adjustHeap(int rootIndex,int lastIndex){ int biggerIndex = rootIndex; int leftChildIndex = 2*rootIndex+1; int rightChildIndex = 2*rootIndex+2; if(rightChildIndex<=lastIndex){ //存在右子节点,则必存在左子节点 if(array[rootIndex]<array[leftChildIndex] || array[rootIndex]<array[rightChildIndex]){ //子节点中存在比根更大的元素 biggerIndex = array[leftChildIndex]<array[rightChildIndex] ? rightChildIndex :leftChildIndex; } }else if(leftChildIndex<=lastIndex){ //只存在左子节点 if(array[leftChildIndex]>array[rootIndex]){ //左子节点更大 biggerIndex = leftChildIndex; } } if(biggerIndex != rootIndex){ //找到了比根更大的子节点 swap(rootIndex,biggerIndex); //交换位置后可能会破坏子树,将焦点转向交换了位置的子节点,调整以它为根的子树 adjustHeap(biggerIndex,lastIndex); } } /** * 将数组按照完全二叉树的形式打印出来 */ private void printTree(int len){ int layers = (int)Math.floor(Math.log((double)len)/Math.log((double)2))+1; //树的层数 int maxWidth = (int)Math.pow(2,layers)-1; //树的最大宽度 int endSpacing = maxWidth; int spacing; int numberOfThisLayer; for(int i=1;i<=layers;i++){ //从第一层开始,逐层打印 endSpacing = endSpacing/2; //每层打印之前需要打印的空格数 spacing = 2*endSpacing+1; //元素之间应该打印的空格数 numberOfThisLayer = (int)Math.pow(2, i-1); //该层要打印的元素总数 int j; for(j=0;j<endSpacing;j++){ System.out.print(" "); } int beginIndex = (int)Math.pow(2,i-1)-1; //该层第一个元素对应的数组下标 for(j=1;j<=numberOfThisLayer;j++){ System.out.print(array[beginIndex++]+""); for(int k=0;k<spacing;k++){ //打印元素之间的空格 System.out.print(" "); } if(beginIndex == len){ //已打印到最后一个元素 break; } } System.out.println(); } System.out.println(); } //基数排序 public void radixSort(){ int max = array[0]; for(int i=0;i<array.length;i++){ //找到数组中的最大值 if(array[i]>max){ max = array[i]; } } int keysNum = 0; //关键字的个数,我们使用个位、十位、百位...当做关键字,所以关键字的个数就是最大值的位数 while(max>0){ max /= 10; keysNum++; } List<ArrayList<Integer>> buckets = new ArrayList<ArrayList<Integer>>(); for(int i=0;i<10;i++){ //每位可能的数字为0~9,所以设置10个桶 buckets.add(new ArrayList<Integer>()); //桶由ArrayList<Integer>构成 } for(int i=0;i<keysNum;i++){ //由最次关键字开始,依次按照关键字进行分配 for(int j=0;j<array.length;j++){ //扫描所有数组元素,将元素分配到对应的桶中 //取出该元素对应第i+1位上的数字,比如258,现在要取出十位上的数字,258%100=58,58/10=5 int key =array[j]%(int)Math.pow(10, i+1)/(int)Math.pow(10, i); buckets.get(key).add(array[j]); //将该元素放入关键字为key的桶中 } //分配完之后,将桶中的元素依次复制回数组 int counter = 0; //元素计数器 for(int j=0;j<10;j++){ ArrayList<Integer> bucket =buckets.get(j); //关键字为j的桶 while(bucket.size()>0){ array[counter++] = bucket.remove(0); //将桶中的第一个元素复制到数组,并移除 } } System.out.print("第"+(i+1)+"轮排序:"); display(); } } }