java常用排序算法
一、冒泡排序
1、基本介绍
冒泡排序(Bubble Sorting)的基本思想是:通过对待排序序列从前向后(从下标较小的元素开始),依次比较相邻元素的值,若发现逆序则交换,使值较大的元素逐渐从前移向后部,就象水底下的气泡一样逐渐向上冒。
因为排序的过程中,各元素不断接近自己的位置,如果一趟比较下来没有进行过交换,就说明序列有序,因此要在排序过程中设置
一个标志flag判断元素是否进行过交换。从而减少不必要的比较。(这里说的优化,可以在冒泡排序写好后,在进行)
例如:
2、代码实现
1 //基础版 2 public static void bubbleSort_v1(int[] arr) { 3 int tmp=0; 4 for (int i = 0; i < arr.length - 1; i++) { 5 for (int j = 0; j < arr.length - 1 - i; j++) { 6 if (arr[j] > arr[j + 1]) { 7 tmp = arr[j]; 8 arr[j] = arr[j + 1]; 9 arr[j + 1] = tmp; 10 } 11 } 12 } 13 }
1 //加强版 2 public static void bubbleSort_v2(int[] arr) { 3 boolean flag = false; 4 int tmp=0; 5 for (int i = 0; i < arr.length - 1; i++) { 6 for (int j = 0; j < arr.length - 1 - i; j++) { 7 if (arr[j] > arr[j + 1]) { 8 flag=true; 9 tmp = arr[j]; 10 arr[j] = arr[j + 1]; 11 arr[j + 1] = tmp; 12 } 13 } 14 //如果没进入比较,则表示已经是有序数组,中断排序即可 15 if(!flag){ 16 break; 17 }else { 18 flag=false; 19 } 20 } 21 }
二、快速排序
1、基本介绍
快速排序(Quicksort)是对冒泡排序的一种改进。基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列
2、代码实现
1 public class QuickSort1 { 2 // 我们的算法类不允许产生任何实例 3 private QuickSort1(){} 4 // 对arr[low...high]部分进行partition操作 5 // 返回p, 使得arr[low...p-1] < arr[p] ; arr[p+1...high] > arr[p] 6 private static int partition(int[] arr, int low, int high){ 7 // 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot 8 Common.swap(arr,low,(int)(Math.random() * (high - low + 1)) + 1); 9 10 int v = arr[low]; 11 int j = low;// arr[l+1...j] < v ; arr[j+1...i) > v 12 for (int i =low+1; i <=high ; i++) { 13 if(arr[i]<v){ 14 j++; 15 Common.swap(arr,i,j); 16 } 17 } 18 Common.swap(arr,low,j); 19 return j; 20 } 21 // 递归使用快速排序,对arr[l...r]的范围进行排序 22 private static void sort(int[] arr, int low, int high){ 23 if( low >= high ) 24 return; 25 int p=partition(arr,low,high); 26 sort(arr,low,p-1); 27 sort(arr,p+1,high); 28 } 29 public static void sort(int[] arr){ 30 int n = arr.length; 31 sort(arr,0,n-1); 32 } 33 }
1 package com.atguigu.Sort.QuickSort; 2 3 import com.atguigu.Sort.Common; 4 5 /** 6 * @author z 7 * @createdate 2019-08-06 9:02 8 */ 9 public class QuickSort2 { 10 // 我们的算法类不允许产生任何实例 11 private QuickSort2() { 12 } 13 14 // 对arr[l...r]部分进行partition操作 15 // 返回p, 使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p] 16 private static int partition(int[] arr, int l, int r) { 17 // 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot 18 Common.swap(arr, l, (int) (Math.random() * (r - l + 1)) + 1); 19 20 int v = arr[l]; 21 int i = l + 1, j = r; // arr[l+1...i) <= v; arr(j...r] >= v 22 while (true) { 23 // 注意这里的边界, arr[i].compareTo(v) < 0, 不能是arr[i].compareTo(v) <= 0 24 // 思考一下为什么? 25 while (i <= r && arr[i] < v) i++; 26 // 注意这里的边界, arr[j].compareTo(v) > 0, 不能是arr[j].compareTo(v) >= 0 27 // 思考一下为什么? 28 while (j >= l + 1 && arr[j] > v) j--; 29 // 对于上面的两个边界的设定, 有的同学在课程的问答区有很好的回答:) 30 // 大家可以参考: http://coding.imooc.com/learn/questiondetail/4920.html 31 if (i > j) break; 32 Common.swap(arr, i, j); 33 i++; 34 j--; 35 } 36 Common.swap(arr, l, j); 37 return j; 38 } 39 40 // 递归使用快速排序,对arr[l...r]的范围进行排序 41 private static void sort(int[] arr, int l, int r) { 42 if (l >= r) 43 return; 44 int p = partition(arr, l, r); 45 sort(arr, l, p - 1); 46 sort(arr, p + 1, r); 47 } 48 49 public static void sort(int[] arr) { 50 int n = arr.length; 51 sort(arr, 0, n - 1); 52 } 53 }
三、直接插入排序
1、基本介绍
插入排序(Insertion Sorting)的基本思想是:把n个待排序的元素看成为一个有序表和一个无序表,开始时有序表中只包含一个元素,无序表中包含有n-1个元素,排序过程中每次从无序表中取出第一个元素,把它的排序码依次与有序表元素的排序码进行比较,将它插入到有序表中的适当位置,使之成为新的有序表
2、代码实现
1 public static void insertSort_v1(int[] arr){ 2 //从第二个元素开始,因为如果从第一个元素向前插入,第一个元素之前是没有数据的 3 for (int i = 1; i < arr.length; i++) { 4 //从第i个元素开始向前查找正确的插入位置 5 //下标j>0并且当前元素值arr[j]小于arr[j-1]时,进入循环,即从小到大 6 for (int j = i; j > 0 && arr[j] < arr[j - 1]; j--) { 7 if (arr[j] < arr[j - 1]) { 8 int tmp = arr[j]; 9 arr[j] = arr[j - 1]; 10 arr[j - 1] = tmp; 11 } 12 } 13 14 } 15 }
1 public static void insertSort_v2(int[] arr){ 2 for (int i = 1; i < arr.length; i++) { 3 // 寻找元素arr[i]合适的插入位置 4 int e = arr[i]; 5 int j = 0; 6 // j保存元素e应该插入的位置,一次赋值,交换一次需要三次赋值 7 for (j = i; j > 0 && arr[j - 1] > e; j--) { 8 //此时arr[j],arr[j-1]是同一个数 9 arr[j] = arr[j - 1]; 10 } 11 //j是arr[i]应该插入的位置 12 arr[j] = e; 13 } 14 }
3.1、折半插入排序
1、基本介绍
2、代码实现
四、希尔排序
1、基本介绍
2、演化
1 //演化 2 private void evolution(){ 3 int[] arr = { 8, 9, 1, 7, 2, 3, 5, 4, 6, 0 }; 4 int temp = 0; 5 // 希尔排序的第1轮排序 6 // 因为第1轮排序,是将10个数据分成了 5组 7 for (int i = 5; i < arr.length; i++) { 8 // 遍历各组中所有的元素(共5组,每组有2个元素), 步长5 9 for (int j = i - 5; j >= 0; j -= 5) { 10 // 如果当前元素大于加上步长后的那个元素,说明交换 11 if (arr[j] > arr[j + 5]) { 12 temp = arr[j]; 13 arr[j] = arr[j + 5]; 14 arr[j + 5] = temp; 15 } 16 } 17 } 18 19 System.out.println("希尔排序1轮后=" + Arrays.toString(arr));// 20 21 22 // 希尔排序的第2轮排序 23 // 因为第2轮排序,是将10个数据分成了 5/2 = 2组 24 for (int i = 2; i < arr.length; i++) { 25 // 遍历各组中所有的元素(共5组,每组有2个元素), 步长5 26 for (int j = i - 2; j >= 0; j -= 2) { 27 // 如果当前元素大于加上步长后的那个元素,说明交换 28 if (arr[j] > arr[j + 2]) { 29 temp = arr[j]; 30 arr[j] = arr[j + 2]; 31 arr[j + 2] = temp; 32 } 33 } 34 } 35 36 System.out.println("希尔排序2轮后=" + Arrays.toString(arr));// 37 38 // 希尔排序的第3轮排序 39 // 因为第3轮排序,是将10个数据分成了 2/2 = 1组 40 for (int i = 1; i < arr.length; i++) { 41 // 遍历各组中所有的元素(共5组,每组有2个元素), 步长5 42 for (int j = i - 1; j >= 0; j -= 1) { 43 // 如果当前元素大于加上步长后的那个元素,说明交换 44 if (arr[j] > arr[j + 1]) { 45 temp = arr[j]; 46 arr[j] = arr[j + 1]; 47 arr[j + 1] = temp; 48 } 49 } 50 } 51 52 System.out.println("希尔排序3轮后=" + Arrays.toString(arr));// 53 }
3、代码实现
1 // 希尔排序时, 对有序序列在插入时采用->交换法 2 public static void sellSort_v1(int [] arr){ 3 int tmp = 0; 4 for (int gap =arr.length/2 ; gap >0 ; gap/=2) { 5 for (int i = gap; i <arr.length ; i++) { 6 // 遍历各组中所有的元素(共gap组,每组有个元素), 步长gap 7 for (int j = i-gap; j >=0; j-=gap) { 8 // 如果当前元素大于加上步长后的那个元素,说明交换 9 if(arr[j]<arr[j+gap]){ 10 tmp= arr[j]; 11 arr[j]=arr[j+gap]; 12 arr[j+gap]=tmp; 13 } 14 } 15 } 16 } 17 }
1 //对交换式的希尔排序进行优化->移位法 2 public static void sellSort_v2(int [] arr){ 3 for (int gap =arr.length/2 ; gap >0 ; gap/=2) { 4 // 从第gap个元素,逐个对其所在的组进行直接插入排序 5 for (int i = gap; i < arr.length; i++) { 6 int j = i; 7 int tmp = arr[j]; 8 if(arr[j]<arr[j-gap]){ 9 while (j - gap >= 0 && tmp < arr[j - gap]) { 10 //移动 11 arr[j] = arr[j-gap]; 12 j -= gap; 13 } 14 //当退出for后,就给temp找到插入的位置 15 arr[j]=tmp; 16 } 17 } 18 } 19 }
五、直接选择排序
1、基本介绍
选择排序(select sorting)也是一种简单的排序方法。它的基本思想是:第一次从arr[0]~arr[n-1]中选取最小值,与arr[0]交换,第二次从arr[1]~arr[n-1]中选取最小值,与arr[1]交换,第三次从arr[2]~arr[n-1]中选取最小值,与arr[2]交换,…,第i次从arr[i-1]~arr[n-1]中选取最小值,与arr[i-1]交换,…, 第n-1次从arr[n-2]~arr[n-1]中选取最小值,与arr[n-2]交换,总共通过n-1次,得到一个按排序码从小到大排列的有序序列。
2、代码实现
1 //O(n2) 2 public static void selectSort(int[] arr){ 3 for (int i = 0; i < arr.length-1; i++) { 4 //假定最小值为i 5 int minIndex = i; 6 //j从i+1开始 7 for (int j = i+1; j <arr.length ; j++) { 8 //如果假定的最小值大于后面一个元素即arr[j],则吧minIndex指向arr[j] 9 //否则不进入if判断 10 if(arr[minIndex]>arr[j]){ 11 minIndex = j; 12 } 13 } 14 //如果minIndex没有变化,即,minIndex就是arr[i],不需交换 15 if(minIndex!=i) { 16 int tmp = arr[i]; 17 arr[i] = arr[minIndex]; 18 arr[minIndex] = tmp; 19 } 20 } 21 }
六、堆排序
1、堆排序基本介绍
1)堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序。
2)堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆, 注意 : 没有要求结点的左孩子的值和右孩子的值的大小关系。
3)每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆
4)大顶堆举例
我们对堆中的结点按层进行编号,映射到数组中就是下面这个样子:
大顶堆的特点:
arr[i] >= arr[2*i+1] && arr[i] >= arr[2*i+2] // i 对应第几个节点,i从0开始编号
5)小顶堆举例
小顶堆的特点:
arr[i] <= arr[2*i+1] && arr[i] <= arr[2*i+2] // i 对应第几个节点,i从0开始编号
6)一般升序采用大顶堆,降序采用小顶堆
2、堆排序的基本思路
1)将待排序序列构造成一个大顶堆
2)此时,整个序列的最大值就是堆顶的根节点。
3)将其与末尾元素进行交换,此时末尾就为最大值。
4)然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了。
1、基本介绍
2、代码实现
1 public class HeapSort { 2 3 public static void heapSort(int[] arr) { 4 int temp= 0; 5 // int arr[] = {4, 6, 8, 5, 9}; 6 //将无序序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆 7 for (int i = arr.length/2-1; i >= 0; i--) { 8 adjustHeap(arr,i,arr.length); 9 } 10 //System.out.println(Arrays.toString(arr)); 11 //2).将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端; 12 //3).重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。 13 for (int j = arr.length-1; j >0 ; j--) { 14 temp = arr[j]; 15 arr[j] = arr[0]; 16 arr[0] = temp; 17 adjustHeap(arr,0,j); 18 } 19 //System.out.println(Arrays.toString(arr)); 20 } 21 22 //将一个数组(二叉树), 调整成一个大顶堆 23 /** 24 * 功能: 完成 将 以 i 对应的非叶子结点的树调整成大顶堆 25 * 举例 int arr[] = {4, 6, 8, 5, 9}; => i = 1 => adjustHeap => 得到 {4, 9, 8, 5, 6} 26 * 如果我们再次调用 adjustHeap 传入的是 i = 0 => 得到 {4, 9, 8, 5, 6} => {9,6,8,5, 4} 27 * @param arr 待调整的数组 28 * @param i 表示非叶子结点在数组中索引 29 * @param lenght 表示对多少个元素继续调整, length 是在逐渐的减少 30 */ 31 private static void adjustHeap(int arr[], int i,int length){ 32 //先取出当前元素的值,保存在临时变量 33 int temp = arr[i]; 34 //开始调整 35 //说明 36 //1. k = i * 2 + 1 k 是 i结点的左子结点 37 for (int k = i*2+1; k <length; k=i*2+1) { 38 //说明左子结点的值小于右子结点的值 39 if(k+1<length && arr[k]<arr[k+1]){ 40 // k 指向右子结点 41 k++; 42 } 43 //如果子结点大于父结点 44 if(arr[k]>temp){ 45 //把较大的值赋给当前结点 46 arr[i] = arr[k]; 47 //!!! i 指向 k,继续循环比较 48 i=k; 49 }else{ 50 break; 51 } 52 } 53 //当for 循环结束后,我们已经将以i 为父结点的树的最大值,放在了 最顶(局部) 54 arr[i] = temp;//将temp值放到调整后的位置 55 } 56 }
七、归并排序
1、基本介绍
归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)
策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。
2、代码实现
1 public class MergeSort { 2 public static void sort(int[] arr, int left, int right, int[] temp) { 3 if (left < right) { 4 int mid = (left + right) / 2; 5 //向左递归进行分解 6 sort(arr, left, mid, temp); 7 //向右递归进行分解 8 sort(arr, mid + 1, right, temp); 9 //合并 10 merge(arr, left, mid, right, temp); 11 } 12 } 13 14 /** 15 * @param arr 排序的原始数组 16 * @param left 左边有序序列的初始索引 17 * @param mid 中间索引 18 * @param right 右边索引 19 * @param temp 做中转的数组 20 */ 21 private static void merge(int[] arr, int left, int mid, int right, int[] temp) { 22 int i = left; // 初始化i, 左边有序序列的初始索引 23 int j = mid + 1; //初始化j, 右边有序序列的初始索引 24 int t = 0; // 指向temp数组的当前索引 25 //(一) 26 //先把左右两边(有序)的数据按照规则填充到temp数组 27 //直到左右两边的有序序列,有一边处理完毕为止 28 while (i <= mid && j <= right) { 29 //如果左边的有序序列的当前元素,小于等于右边有序序列的当前元素 30 //即将左边的当前元素,填充到 temp数组 31 //然后 t++, i++ 32 if (arr[i] <= arr[j]) { 33 temp[t++] = arr[i++]; 34 } else { 35 temp[t++] = arr[j++]; 36 } 37 } 38 39 //(二) 40 //把有剩余数据的一边的数据依次全部填充到temp 41 while (i <= mid) {//左边的有序序列还有剩余的元素,就全部填充到temp 42 temp[t++] = arr[i++]; 43 } 44 while (j <= right) {//右边的有序序列还有剩余的元素,就全部填充到temp 45 temp[t++] = arr[j++]; 46 } 47 48 //(三) 49 //将temp数组的元素拷贝到arr 50 //注意,并不是每次都拷贝所有 51 t=0; 52 int tempLeft=left; 53 //第一次合并 tempLeft = 0 , right = 1 // tempLeft = 2 right = 3 54 //最后一次 tempLeft = 0 right = 7 55 while (tempLeft<=right){ 56 arr[tempLeft++]=temp[t++]; 57 } 58 } 59 }
八、基数排序
1、基数排序(桶排序)介绍
1)基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,顾名思义,它是通过键值的各个位的值,将要排序的元素分配至某些“桶”中,达到排序的作用
2)基数排序法是属于稳定性的排序,基数排序法的是效率高的稳定性排序法
3)基数排序(Radix Sort)是桶排序的扩展
4)基数排序是1887年赫尔曼·何乐礼发明的。它是这样实现的:将整数按位数切割成不同的数字,然后按每个位数分别比较。
2、基数排序基本思想
1)将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。
然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。
2)这样说明,比较难理解,下面我们看一个图文解释,理解基数排序的步骤
3、基数排序图文说明
将数组 {53, 3, 542, 748, 14, 214} 使用基数排序, 进行升序排序
4、基数排序说明
1)基数排序是对传统桶排序的扩展,速度很快.
2)基数排序是经典的空间换时间的方式,占用内存很大, 当对海量数据排序时,容易造成 OutOfMemoryError 。(本人电脑1亿随机数排序会出现,1千万不会)
3)基数排序时稳定的。
[注:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的]
4)有负数的数组,我们不用基数排序来进行排序, 如果要支持负数,参考: https://code.i-harness.com/zh-CN/q/e98fa9
2、代码实现
1 public class RadixSort { 2 public static void sort(int[] arr){ 3 //1. 得到数组中最大的数的位数 4 int max = arr[0]; //假设第一数就是最大数 5 for (int i = 1; i <arr.length ; i++) { 6 if(arr[i]>max){ 7 max=arr[i]; 8 } 9 } 10 //得到最大数是几位数 11 int maxlength =(max+"").length(); 12 //定义一个二维数组,表示10个桶, 每个桶就是一个一维数组 13 //说明 14 //1. 二维数组包含10个一维数组 15 //2. 为了防止在放入数的时候,数据溢出,则每个一维数组(桶),大小定为arr.length 16 //3. 名明确,基数排序是使用空间换时间的经典算法 17 int[][] bucket = new int[10][arr.length]; 18 //为了记录每个桶中,实际存放了多少个数据,我们定义一个一维数组来记录各个桶的每次放入的数据个数 19 //可以这里理解 20 //比如:bucketElementCounts[0] , 记录的就是 bucket[0] 桶的放入数据个数 21 int[] bucketElementCounts = new int[10]; 22 for (int i = 0,n=1; i <maxlength ; i++,n*=10) { 23 //(针对每个元素的对应位进行排序处理), 第一次是个位,第二次是十位,第三次是百位.. 24 for (int j = 0; j <arr.length ; j++) { 25 int digitOfElement=arr[j]/n%10; 26 bucket[digitOfElement][bucketElementCounts[digitOfElement]]=arr[j]; 27 bucketElementCounts[digitOfElement]++; 28 } 29 //按照这个桶的顺序(一维数组的下标依次取出数据,放入原来数组) 30 int index =0; 31 //遍历每一桶,并将桶中是数据,放入到原数组 32 for (int k = 0; k < bucketElementCounts.length; k++) { 33 //如果桶中,有数据,我们才放入到原数组 34 if(bucketElementCounts[k]!=0){ 35 //循环该桶即第k个桶(即第k个一维数组), 放入 36 for (int l = 0; l < bucketElementCounts[k]; l++) { 37 arr[index++]=bucket[k][l]; 38 } 39 } 40 //第i+1轮处理后,需要将每个 bucketElementCounts[k] = 0 !!!! 41 bucketElementCounts[k]=0; 42 } 43 // System.out.println(Arrays.toString(arr)); 44 } 45 46 } 47 }
九、桶排序
1、基本介绍
2、代码实现
十、计数排序
1、基本介绍
2、代码实现