JAVA排序算法总结
1、排序方法的分类:
插入类排序:直接插入排序、希尔排序
交换类排序:冒泡排序、快速排序
选择类排序:简单选择排序、堆排序
归并类排序:归并排序。
2、总结
各种排序算法的性能对比如下
排序类型 | 平均情况 | 最好情况 | 最坏情况 | 辅助空间 | 稳定性 |
---|---|---|---|---|---|
冒泡排序 | O(n²) | O(n) | O(n²) | O(1) | 稳定 |
选择排序 | O(n²) | O(n²) | O(n²) | O(1) | 不稳定 |
直接插入排序 | O(n²) | O(n) | O(n²) | O(1) | 稳定 |
希尔排序 | O(n^1.3) | O(nlogn) | O(n²) | O(1) | 不稳定 |
归并排序 | O(nlog₂n) | O(nlog₂n) | O(nlog₂n) | O(n) | 稳定 |
快速排序 | O(nlog₂n) | O(nlog₂n) | O(n²) | O(nlog₂n) | 不稳定 |
堆排序 | O(nlog₂n) | O(nlog₂n) | O(nlog₂n) | O(1) | 不稳定 |
基数排序 | O(n*k) | O(n*K) | O(n*k) | O(n+k) | 稳定 |
从时间复杂度来说:
- 平方阶O(n²)排序:各类简单排序:直接插入、直接选择和冒泡排序;
- 线性对数阶O(nlog₂n)排序:快速排序、堆排序和归并排序;
- O(n1+§))排序,§是介于0和1之间的常数:希尔排序
- 线性阶O(n)排序:基数排序,此外还有桶、箱排序。
3、冒泡排序
它是最简单也是最慢的排序方法,双层for循环对每两项数值进行比较交换
算法描述:依次比较相邻元素的值,若发现逆序则交换,使值较大的原色逐渐从前移向后部,就像水底下的气泡一样逐渐向上冒。
/** * 冒泡排序 * @param array */ public static void maoPao(int []array){ int temp; for(int i=1;i<array.length;i++) { for(int j=0;j<array.length-1-i;j++) { if(array[j]>array[j+1]) { temp=array[j]; array[j]=array[j+1]; array[j+1]=temp; } } } for(int i=0;i<array.length;i++) { System.out.print(array[i]+" "); } System.out.println(); }
4、选择排序
循环遍历整个数组,不断把最小的值放到前面
/** * 选择排序 * @param array */ public static void xuanZe(int []array) { int index,temp; for(int i=0;i<array.length-1;i++) { index=i;//用来记住数组元素的下标 for(int j=i+1;j<array.length;j++) { if(array[index]>array[j]) { index=j;//只对index值改变,不交换元素位置 } } if(i!=index) { temp=array[index]; array[index]=array[i]; array[i]=temp; }//一轮排序进行一次数组位置交换 System.out.print("第"+i+"次:"); } for(int i=0;i<array.length;i++) { System.out.print(array[i]+" "); } System.out.println(); }
5、插入排序
便利整个数组,不断插入较小的数字到已排号顺序的部分中
/** * 插入排序 * @param array */ public static void chaRu(int []array) { int temp; for(int i=1;i<array.length;i++) { int j=i; temp = array[i]; while( j>0 && temp < array[j-1]) { array[j] = array[j-1]; j--; } array[j] = temp; } for(int i=0;i<array.length;i++) { System.out.print(array[i]+" "); } System.out.println(); }
6、快速排序
采用分治的思想,先取中间值,分别从双端比较,把左端换成都比中间值小的,右端都比中间值大,然后重复此操作
/** * 快速排序 * @param array * @param left * @param right */ public static void kuaiSu(int []array,int left,int right){ if(left < right) { int i=left,j=right; int pivot = array[left];//选择最左边的元素作为中间值 /** * 分治 */ while(i < j) { while(i < j && array[j] >= pivot) { j--; } if(i < j) { array[i] = array[j]; i++; } while(i < j&& array[i] < pivot){ i++; } if(i < j) { array [j] =array [i]; j--; } } array [i]=pivot; //递归 kuaiSu(array,left,i-1); kuaiSu(array,i+1,right); } }
7、二分查找排序
与直接插入一样,只是找合适的插入位置的方式不同,这里是按照二分法找到合适的位置,可以减少比较的次数
/** * 二分查找插入排序 * @param a */ public class ERFENCHAZHAO { public static void main(String[] args) { Scanner scanner=new Scanner(System.in); String[] nums = null; System.out.println("请输入一组整数,以空格分隔:"); nums = scanner.nextLine().split(" "); int[] array = new int[nums.length]; int sum = 0; for (int i = 0; i < array.length; i++) { array[i] = Integer.valueOf(nums[i]); } erFenChaZhao(array); } public static void erFenChaZhao(int[] a){ for (int i=0;i<a.length;i++){ int temp=a[i]; int left=0; int right=i-1; int mid=0; while (left<=right){ mid=(left+right)/2; if (temp<a[mid]){ right=mid-1; }else { left=mid+1; } } for (int j=i-1;j>=left;j--){ a[j+1]=a[j]; } if (left!=i){ a[left]=temp; } } for(int i=0;i<a.length;i++) { System.out.print(a[i] + " "); } } }
8、希尔排序
希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。希尔排序是非稳定排序算法。
希尔排序是基于插入排序的以下两点性质而提出改进方法的:
1、插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率;
2、但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位。
先取一个正整数d1 < n, 把所有相隔d1的记录放一组,每个组内进行直接插入排序;然后d2 < d1,重复上述分组和排序操作;直至di = 1,即所有记录放进一个组中排序为止。
/** * 希尔排序 * @param array */ /** * 希尔排序的原理:根据需求,如果你想要结果从小到大排列,它会首先将数组进行分 组,然后将较小值移到前面,较大值移到后面,最后将整个数组进行插入排序, 这样比起一开始就用插入排序减少了数据交换和移动的次数, * 可以说希尔排序是加强 版的插入排序 拿数组5, 2,8, 9, 1, 3,4来说,数组长 度为7,当increment为3时,数组分为两个序列 * 5,2,8和9,1,3,4,第一次排序,9和5比较,1和2比较,3和8比较,4和比其 下标值小increment的数组值相比较 * 此例子是按照从小到大排列,所以小的会排在前面,第一次排序后数组为5, 1, 3, 4, 2, 8,9 * 第一次后increment的值变为3/2=1,此时对数组进行插入排序, 实现数组从大到 小排 */ public class shell { public static void shellSort(int[] arr) { // 空数组 或 只有一个元素的数组,则什么都不做。 if (arr == null || arr.length <= 1) return; // 定义希尔增量。 int gap = arr.length / 2; // gap缩小到0的时候就退出循环。 while (gap != 0) { // 每组进行直接插入排序。 for (int i = gap; i < arr.length; i++) { // i 代表待插入元素的索引。 int value = arr[i]; int j = i - gap; // j 代表i的上一个元素,相差一个增量gap。 // j < 0 时退出循环,说明 j 是最小的元素的索引值。 // 或者 arr[j] <= value 时退出循环,说明 j 是比value小的元素的索引值。 for (; j >= 0 && arr[j] > value; j -= gap) { arr[j + gap] = arr[j]; // 把元素往后挪。 } arr[j + gap] = value; } // 把每一趟排序的结果也输出一下。 print(arr); // 缩小增量。 gap /= 2; } } public static void main(String[] args) { int[] arr = {6, 9, 1, 4, 5, 8, 7, 0, 2, 3}; System.out.print("排序前: "); print(arr); shellSort(arr); System.out.print("排序后: "); print(arr); } // 打印数组 public static void print(int[] arr) { if (arr == null) return; for(int i : arr) { System.out.print(i + " "); } System.out.println(); } } /* 排序前: 6 9 1 4 5 8 7 0 2 3 6 7 0 2 3 8 9 1 4 5 0 1 3 2 4 5 6 7 9 8 0 1 2 3 4 5 6 7 8 9 排序后: 0 1 2 3 4 5 6 7 8 9 */
9、堆排序
堆排序是一种树形选择排序,是对直接选择排序的有效改进。
思想:初始时把要排序的数的序列看作是一棵顺序存储的二叉树,调整它们的存储序,使之成为一个堆,这时堆的根节点的数最大。然后将根节点与堆的最后一个节点交换。然后对前面(n-1)个数重新调整使之成为堆。依此类推,直到只有两个节点的堆,并对 它们作交换,最后得到有n个节点的有序序列。从算法描述来看,堆排序需要两个过程,一是建立堆,二是堆顶与堆的最后一个元素交换位置。所以堆排序有两个函数组成。一是建堆的渗透函数,二是反复调用渗透函数实现排序的函数。
/** * 堆排序 * @param array */ public static void HeapSort(int[] array){ for(int i=0;i<array.length-1;i++){ //建堆 buildMaxHeap(array,array.length-1-i); //交换堆顶和最后一个元素 swap(array,0,array.length-1-i); } System.out.println(Arrays.toString(array)); } public static void buildMaxHeap(int[] data,int lastIndex){ //从lastIndex处节点(最后一个节点)的父节点开始 for(int i = (lastIndex-1)/2;i>=0;i--){ //k保存正在判断的节点 int k = i; //如果k节点的子节点存在 while(k*2+1<=lastIndex){ //将k点左子节点的索引赋值给biggerIndex int biggerIndex = 2*k+1; //如果biggerIndex小于lastIndex,代表k节点的右子节点存在 if(biggerIndex < lastIndex){ //如果右子节点的值较大 if(data[biggerIndex]<data[biggerIndex+1]){ //biggerIndex总是记录较大子节点的索引 biggerIndex++; } } //如果k节点的值小于其较大子节点的值 if(data[k] < data[biggerIndex]){ //交换他们 swap(data,k,biggerIndex); //将biggerIndex赋予k,开始while循环的下一次循环,重新保证k节点的值大于其左右子节点的值 k = biggerIndex; } else { break; } } } } private static void swap(int[] data,int i,int j){ int tmp = data[i]; data[i] = data[j]; data[j] = tmp; }
10、归并排序
归并排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。
/** * 归并排序 * @param array */ public static void mergingSort(int[] array) { sort(array, 0, array.length - 1); System.out.println(Arrays.toString(array) + " mergingSort"); } private static void sort(int[] data, int left, int right) { if (left < right) { //找出中间索引 int center = (left + right) / 2; //对左边数组进行递归 sort(data, left, center); //对右边数组进行递归 sort(data, center + 1, right); //合并 merge(data, left, center, right); } } private static void merge(int[] data, int left, int center, int right) { int[] tmpArr = new int[data.length]; int mid = center + 1; //third记录中间数组的索引 int third = left; int tmp = left; while (left <= center && mid <= right) { //从两个数组中取出最小的放入中间数组 if (data[left] <= data[mid]) { tmpArr[third++] = data[left++]; } else { tmpArr[third++] = data[mid++]; } } //剩余部分依次放入中间数组 while (mid <= right) { tmpArr[third++] = data[mid++]; } while (left <= center) { tmpArr[third++] = data[left++]; } //将中间数组中的内容复制回原数组 while (tmp <= right) { data[tmp] = tmpArr[tmp++]; } }