1、数组排序算法
- 数组a中有N个元素,将所有元素按从小到大的顺序排列。
- 本文介绍几种常用的排序算法:冒泡排序、选择排序、直接插入排序、希尔排序、归并排序、快速排序、基数排序。
冒泡排序法:
-
冒泡排序算法原理:
依次比较两个相邻的元素,如果第一个比第二个大,就交换他们两个。
即,在a[0]到a[N-1]的范围内,依次比较a[i]和a[i+1],i的值取0,1,…,N-2,若a[i]>a[i+1],则交换。第一次排序完成后,数组中最大的数放到a[N-1]处。 -
原理图解:
-
冒泡排序法程序:
1、分步原理推导过程。
package org.practic.arraysort; import java.util.Arrays; public class BubbleSort { public static void main(String[] args) { //冒泡排序法:分步推导 int[] arr = {24, 69, 80, 57, 13}; StepSort(arr); } //分步排序 private static void StepSort(int[] arr) { //一共进行了arr.length-1 次排序 //第一次排序,从0出开始,比较了arr.length-1 次, for (int i = 0; i < arr.length-1; i++) { ExchangeValue(arr, i); } System.out.println(Arrays.toString(arr)); //[24, 69, 57, 13, 80] //第二次排序,从0开始,比较了arr.length-2 次 for (int i = 0; i < arr.length-2; i++) { ExchangeValue(arr, i); } System.out.println(Arrays.toString(arr)); //[24, 57, 13, 69, 80] //第三次排序,从0开始,比较了arr.length-3 次 for (int i = 0; i < arr.length-3; i++) { ExchangeValue(arr, i); } System.out.println(Arrays.toString(arr)); //[24, 13, 57, 69, 80] //第四次排序,从0开始,比较了arr.length-4 次 for (int i = 0; i < arr.length-4; i++) { ExchangeValue(arr, i); } System.out.println(Arrays.toString(arr)); //[13, 24, 57, 69, 80] } //数值交换 private static void ExchangeValue(int[] arr, int i) { int t = arr[i]; arr[i] = arr[i+1]; arr[i+1] = t; } }
2、冒泡排序算法程序。
一共进行了arr.length-1 次排序,j = 1 — arr.length-1;
第 j 次排序比较了arr.length-j 次。package org.practic.arraysort; import java.util.Arrays; public class BubbleSort { public static void main(String[] args) { //冒泡排序法: int[] arr = {24, 69, 80, 57, 13}; //外层循环控制排序次数,一共进行了arr.length-1 次排序 for (int j = 1; j <= arr.length-1; j++) { //内层循环控制每次排序的比较次数 for (int i = 0; i < arr.length - j; i++) { ExchangeValue(arr, i); } } System.out.println(Arrays.toString(arr)); } //数值交换 private static void ExchangeValue(int[] arr, int i) { int t = arr[i]; arr[i] = arr[i+1]; arr[i+1] = t; } }
选择排序法:
-
选择排序算法原理:
首先在未排序序列中找到最小元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
即,第一次用a[0]依次和后面元素比较,如果其他数比a[0]大就交换,不然就不换。第一次完毕,最小值出现在了最小索引a[0]处。第二次再用a[1]依次和后面元素比较,依次进行。
-
选择排序算法原理图解:
-
选择排序法程序:
1、分步原理推导过程。
一共进行了arr.length-1 次排序,j = 1 — arr.length-1;
第 j 次排序比较了arr.length-j 次。package org.practic.arraysort; import java.util.Arrays; public class SelectionSort { public static void main(String[] args) { int[] arr = {24, 69, 80, 57, 13}; //分步推理 StepSort(arr); } private static void StepSort(int[] arr) { //一共进行了arr.length-1 次排序 //第1次排序,用arr[0]依次与后面的元素比较,一共比较了arr.length-1 次 for (int i = 1; i <= arr.length-1; i++) { if(arr[0] > arr[i]){ ExchangeValue(arr,0,i); } } System.out.println(Arrays.toString(arr)); //[13, 69, 80, 57, 24] //第2次排序,用arr[1]依次与后面的元素比较,一共比较了arr.length-2 次 for (int i = 2; i <= arr.length-1; i++) { if(arr[1] > arr[i]){ ExchangeValue(arr,1,i); } } System.out.println(Arrays.toString(arr)); //[13, 24, 80, 69, 57] //第3次排序,用arr[2]依次与后面的元素比较,一共比较了arr.length-3 次 for (int i = 3; i <= arr.length-1; i++) { if(arr[2] > arr[i]){ ExchangeValue(arr,2,i); } } System.out.println(Arrays.toString(arr)); //[13, 24, 57, 80, 69] //第4次排序,用arr[3]依次与后面的元素比较,一共比较了arr.length-1 次 for (int i = 4; i <= arr.length-1; i++) { if(arr[3] > arr[i]){ ExchangeValue(arr,3,i); } } System.out.println(Arrays.toString(arr)); //[13, 24, 57, 69, 80] } //数值交换 private static void ExchangeValue(int[] arr,int j, int i) { int t = arr[j]; arr[j] = arr[i]; arr[i] = t; } }
2、选择排序算法程序。
一共进行了arr.length-1 次排序,j = 0 — arr.length-1-1;
第 j 次排序用arr[j]和后面的元素依次比较。package org.practic.arraysort; import java.util.Arrays; public class BubbleSort { public static void main(String[] args) { //选择排序法 int[] arr = {24, 69, 80, 57, 13}; //外层循环控制排序次数,一共进行了arr.length-1 次排序 for (int j = 0; j < arr.length-1; j++) { //里层循环控制每次排序比较的次数,用arr[j]和后面的元素依次比较。 for (int i = j+1; i <= arr.length - 1; i++) { if (arr[j] > arr[i]) { ExchangeValue(arr, j, i); } } } System.out.println(Arrays.toString(arr)); // } //数值交换 private static void ExchangeValue(int[] arr, int i) { int t = arr[i]; arr[i] = arr[i+1]; arr[i+1] = t; } }
直接插入排序:
-
直接插入排序算法原理:
将一个记录插入到一个长度为m 的有序表中,使之仍保持有序,从而得到一个新的长度为m+1的有序列表。
假设有一组元素{k1,k2…,kn},排序开始就认为k1是一个有序序列,让k2插入上述表长为1的有序序列,使之成为一个表长为2的有序序列,然后让k3插入上述表长为2的有序序列,使之成为一个表长为3的有序序列,以此类推,最后让kn插入表长为n-1的有序序列,得到一个表长为n的有序序列。
-
原理图解:
-
直接插入法程序:
public class DirectInsertionSort { public static void main(String[] args) { int[] arr ={24, 69, 80, 57, 13, 18, 51}; //外层循环控制比较次数,一共进行了arr.length-1 次 for (int j = 1; j < arr.length; j++) { //里层循环控制每次拿当前元素与之前的有序表中的元素比较 for (int i = j; i > 0; i--) { if(arr[i] < arr[i-1]){ ExchangeValue(arr,i,i-1); } } } System.out.println(Arrays.toString(arr)); //[13, 18, 24, 51, 57, 69, 80] } //数值交换 private static void ExchangeValue(int[] arr,int j, int i) { int t = arr[j]; arr[j] = arr[i]; arr[i] = t; } }
public class DirectInsertionSort { public static void main(String[] args) { int[] arr1 ={22, 69, 86, 79, 13, 4, 49}; for (int i = 1; i < arr1.length; i++) { int j = i; while(j > 0 && arr1[j] < arr1[j-1]) { ExchangeValue(arr1, j, j - 1); j--; } } System.out.println(Arrays.toString(arr1)); } //数值交换 private static void ExchangeValue(int[] arr,int j, int i) { int t = arr[j]; arr[j] = arr[i]; arr[i] = t; } }
希尔排序法:
-
希尔排序算法原理:
希尔排序算法一般指希尔排序(Shell Sort),也称缩小增量排序。希尔排序法(缩小增量法) 属于插入类排序,是将整个无序列分割成若干小的子序列分别进行插入排序的方法,是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。该方法因DL.Shell于1959年提出而得名。
即,先取一个小于n的整数d1作为第一个增量,把文件的全部记录分成d1个组。所有距离为dl的倍数的记录放在同一个组中。先在各组内进行直接插人排序;然后,取第二个增量d2<d1重复上述的分组和排序,直至所取的增量dt=1(dt<dt-l<…<d2<d1),即所有记录放在同一组中进行直接插入排序为止。
-
增量的合理选择:
最普通的选择就是使用数组长度的一半,即:d1=数组长度/2,d2=d1/2。这种增量从效率来说,不是非常好。
-
-
希尔排序法的原理图示:
-
希尔排序法的程序:
1、增量选择为数组长度的一半。
原始步长序列:N / 2,N / 4,…,1(重复除以2);public class ShellSort { public static void main(String[] args) { int[] arr = {46,55, 13,42,17,94,5,70}; //d 增量 d = arr.length/2 for (int d = arr.length/2; d > 0; d /= 2) { //以选择的增量为长度,进行比较 for (int i = d; i < arr.length; i++) { for (int j = i; j > d - 1; j -= d) { if (arr[j] < arr[j - d]) { ExchangeValue(arr, j, j - d); } } } } System.out.println(Arrays.toString(arr)); } private static void ExchangeValue(int[] arr, int i,int j) { int t = arr[i]; arr[i] = arr[j]; arr[j] = t; } }
2、增量选择为克努特序列。
克努特(Knuth)的步长序列:1,4,13,…,(3 k - 1)/ 2;public class ShellSort { public static void main(String[] args) { int[] arr1 = {46,55, 13,42,17,94,5,70}; //克努特序列 int t = 1; while (t <= arr1.length/3){ t = t*3 + 1; } for (int h = t; h > 0; h = (h-1)/3) { //以选择的增量为长度,进行比较 for (int i = h; i < arr1.length; i++) { for (int j = i; j > h - 1; j -= h) { if (arr1[j] < arr1[j - h]) { ExchangeValue(arr1, j, j - h); } } } } System.out.println(Arrays.toString(arr1)); } private static void ExchangeValue(int[] arr, int i,int j) { int t = arr[i]; arr[i] = arr[j]; arr[j] = t; } }
归并排序:
-
归并排序算法原理:
归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。 -
归并排序算法原理图示:
分阶段:可以理解为就是递归拆分子序列的过程;
治阶段:我们需要将两个已经有序的子序列合并成一个有序序列,比如上图中的最后一次合并,要将[4,5,7,8]和[1,2,3,6]两个已经有序的子序列,合并为最终序列[1,2,3,4,5,6,7,8],实现步骤如下:
(参考资料:图解归并排序算法) -
归并排序算法程序:
public class MergeSort { public static void main(String[] args) { int[] arr = {13,8,93,20,47,38,65}; splitSort(arr, 0, arr.length-1); System.out.println(Arrays.toString(arr)); } //拆分 public static void splitSort(int[] arr, int startIndex, int endIndex){ int centerIndex = (startIndex+endIndex)/2; if(startIndex < endIndex){ //拆分左边 splitSort(arr,startIndex,centerIndex); //拆分右边 splitSort(arr,centerIndex+1,endIndex); //合并 mergerSort(arr, startIndex, centerIndex, endIndex); } } //合并 public static void mergerSort(int[] arr, int startIndex, int centerIndex, int endIndex){ //定义一个临时数组 int[] tempArray = new int[endIndex-startIndex+1]; //定义临时数组的起始索引 int index = 0; //定义左边数组的起始索引 int i = startIndex; //定义右边数组的起始索引 int j = centerIndex+1; //比较合并 while ( i <= centerIndex && j <= endIndex){ if (arr[i] < arr[j]){ tempArray[index] = arr[i]; i++; }else{ tempArray[index] = arr[j]; j++; } index++; } //处理左边剩余 while(i<=centerIndex){ tempArray[index] = arr[i]; i++; index++; } //处理右边剩余 while( j <= endIndex ){ tempArray[index] = arr[j]; j++; index++; } //将临时数组的元素放回原数组 for (int k = 0; k < tempArray.length; k++) { arr[k+startIndex] = tempArray[k]; } } }
快速排序法:
-
快速排序算法原理:
快速排序(Quicksort)是对冒泡排序的一种改进。思想为:
-
实现思路:
-
快速排序算法程序:
public class QuickSort { public static void main(String[] args) { int[] arr = {2, 1, 0, 6, 9, 0, 10}; quickSort(arr,0,arr.length-1); System.out.println(Arrays.toString(arr)); } public static void quickSort(int[] arr,int startIndex,int endIndex){ if(startIndex<endIndex){ //找到基准数的索引 int index=getIndex(arr,startIndex,endIndex); //对左右两边进行递归调用 quickSort(arr,startIndex,index); quickSort(arr,index+1,endIndex); } } //挖坑填数,找出基准数 public static int getIndex(int[] arr, int startIndex, int endIndex){ int i = startIndex; int j = endIndex; //定义基准数,用第一个作为参数 int x = arr[i]; while(i<j) { //由后向前找比他小的数,找到后挖出此数填到前一个坑中 while(i < j && arr[j] >= x){ j--; } //填坑 if(i<j){ arr[i] = arr[j]; i++; } //由前向后找比他大的数,找到后挖出此数填到前一个坑中 while(i < j && arr[i] <= x){ i++; } //填坑 if(i<j){ arr[j] = arr[i]; j--; } } arr[i] = x; return i; } }
基数排序法 :
-
基数排序算法原理:
基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或 bin sort,顾名思义,它是透过元素的部分信息,将要排序的元素分配至某些“桶”中,以达到排序的作用。基数排序法是属于稳定性的排序,在某些时候,基数排序法的效率高于其它的稳定性排序法。
基数排序不同于之前所介绍的排序算法,它不需要对元素进行比较,只需要对元素进行“分配”与“收集”两种操作即可完成。
-
实现方法:
最高位优先(Most Significant Digit first)法,简称MSD法:从元素的最左边开始,先按k1排序分组,同一组中记录,关键码k1相等,再对各组按k2排序分成子组,之后,对后面的关键码继续这样的排序分组,直到按最次位关键码kd对各子组排序后。再将各组连接起来,便得到一个有序序列。
最低位优先(Least Significant Digit first)法,简称LSD法:从元素的最右边开始,先从kd开始排序,再对kd-1进行排序,依次重复,直到对k1排序后便得到一个有序序列。
-
解法分析:
以LSD为例,假设原来有一串数值为:73, 22, 93, 43, 55, 14, 28, 65, 39, 81。
首先根据个位数的数值,在走访数值时将它们分配至编号0到9的桶子中:
然后将这些桶子中的数值按桶编号顺序取出来,重新串接在一起,成为以下的数列:81, 22, 73, 93, 43, 14, 55, 65, 28, 39接着再进行一次分配,这次是根据十位数来分配:
然后将这些桶子中的数值重新串接起来,成为以下的数列:14, 22, 28, 39, 43, 55, 65, 73, 81, 93。这时候整个数列已经排序完毕。如果排序的对象有三位数以上,则持续进行以上的动 作直至最高位数为止。
-
LSD的基数排序适用于位数小的数列,如果位数多的话,使用MSD的效率会比较好。MSD的方式与LSD相反,是由高位数为基底开始进行分配,但在分配之后并不马上合并回一个数组中,而是在每个“桶子”中建立“子桶”,将每个桶子中的数值按照下一数位的值分配到“子桶”中。在进行完最低位数的分配后再合并回单一的数组中。(详细可见添加链接描述)
-
基数排序算法程序:
package org.practic.arraysort;
import java.util.Arrays;
public class RadixSort {
//基数排序
public static void main(String[] args) {
int[] arr = {73, 22, 93, 43, 55, 14, 28, 65, 39, 81};
radixSort(arr);
System.out.println(Arrays.toString(arr));
}
private static void radixSort(int[] arr) {
//定义一个二维数组,用于10个桶分配数据
int[][] tempArr = new int[10][arr.length];
//定义一个统计数组,统计每个桶中放入的元素个数
int [] count = new int[10];
//获取数组中的最大值,计算最大值的位数,确定需要分配收集的轮次
int max = getMax(arr);
int len = String.valueOf(max).length();
//获取各位上的数字,放入相应的桶中
for (int i = 0,n = 1; i < len; i++,n *= 10) {
for (int j = 0; j < arr.length; j++) {
int s = arr[j] / n % 10;
tempArr[s][count[s]++] = arr[j];
}
//取出桶中的数
//遍历统计数组
int index = 0;
for (int p = 0; p < count.length; p++) {
if( count[p] != 0){
for (int k = 0; k < count[p]; k++) {
arr[index] = tempArr[p][k];
index++;
}
}
//取完之后,清除上次统计的个数
count[p] = 0;
}
}
}
private static int getMax(int[] arr) {
int max = arr[0];
for (int i = 1; i < arr.length; i++) {
if( max < arr[i] ){
max = arr[i];
}
}
return max;
}
}