八大排序算法的性能比较图如下:
下面依次介绍几种排序算法以及用java语言的实现
1、直接插入排序
1.基本思想
从原数组中第二个数开始,将元素a[i](i>=1)依次取出,与数组里已有的值依次进行比较,如果比原有数据a[j](j=i-1)小,则将a[j]向后移动,再与它前面的元素a[j](j=i-2,j=i-3...)比较,直至a[i]比a[j]大,直接插在其后,最终确定位置。
2.代码实现
public class Insert {
public static void insertSort(int[] a) {
int i, j, insertNote;// 要插入的数据
for (i = 1; i < a.length; i++) {// 从数组的第二个元素开始,依次将原数组中的元素插入
insertNote = a[i];// 设置数组中的第2个元素为第一次循环要插入的数据
j = i - 1;
while (j >= 0 && insertNote < a[j]) {
a[j + 1] = a[j];// 如果要插入的元素小于第j个元素,就将第j个元素向后移动
j--; //依次向前比较
}
a[j + 1] = insertNote;// 直到要插入的元素不小于第j个元素,将insertNote插入到数组中
}
}
public static void main(String[] args) {
int a[] = {38,65,97,76,13,27,49 };
insertSort(a);
System.out.println(Arrays.toString(a));
}
3、遍历后的结果:
轮数 | 每一轮遍历后的结果 |
第1趟(i=2) | [38, 65, 97, 76, 13, 27, 49] |
第2趟(i=3) | [38, 65, 97, 76, 13, 27, 49] |
第3趟(i=4) | [38, 65, 76, 97, 13, 27, 49] |
第4趟(i=5) | [13, 38, 65, 76, 97, 27, 49] |
第5趟(i=6) | [13, 27, 38, 65, 76, 97, 49] |
第6趟(i=7) | [13, 27, 38, 49, 65, 76, 97] |
2、希尔排序
希尔排序(Shell's Sort)是插入排序的一种又称“缩小增量排序”(Diminishing Increment Sort),是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。该方法因D.L.Shell于1959年提出而得名。
1、基本思想
先取一个小于n的整数d1作为第一个增量,把文件的全部记录分组。所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量d2<d1重复上述的分组和排序,直至所取的增量 ,即所有记录放在同一组中进行直接插入排序为止。该方法实质上是一种分组插入方法.
2、代码实现
public class ShellSort {
private static void shellSort(int[] a) {
int i, j, insertNote;// 要插入的数据
//遍历所有步长
for(int d=a.length/2;d>0;d/=2)
{
//下面 对每一个根据步长分组后的子序列,进行步长为d的插入排序
for (i = d; i < a.length; i++) {//从数组的第d个元素开始循环将数组中的元素插入
insertNote = a[i];// 设置数组中的第d个元素为第一次循环要插入的数据
j = i - d;
while (j >= 0 && insertNote < a[j]) {
a[j + d] = a[j];// 如果要插入的元素小于第j个元素,就将第j个元素向后移动
j-=d;
}
a[j + d] = insertNote;// 直到要插入的元素不小于第j个元素,将insertNote插入到数组中
}
System.out.println("步长为:"+d+" 时排序后的数组为:"+Arrays.toString(a));
}
}
public static void main(String[] args) {
int[] arr=new int[] {49,38,65,97,76,13,27,49,55,4};
shellSort(arr);
System.out.println(Arrays.toString(arr));
}
}
3、遍历后的结果:
由上例可看出,数组一共有10条记录,其关键字分别为 49,38,65,97,76,13,27,49,55,4
增量序列的取值依次为 5、2、1
取d1=5,第一趟分组分组情况如下: 49,38,65,97,76,13,27,49,55,4
排序后的数组为:[13, 27, 49, 55, 4,49, 38, 65, 97, 76]
取d2=2,第2趟分组分组情况如下:13, 27, 49, 55, 4, 49, 38, 65, 97, 76
排序后的数组为:[4, 27, 13, 49, 38, 55, 49, 65, 97, 76]
取d2=1,第3趟分组分组情况如下:[4, 27, 13, 49, 38, 55, 49, 65, 97, 76]
排序后的数组为:[4, 13, 27, 38, 49, 49, 55, 65, 76, 97]
由上可观察出,根据不同增量划分的每一组子序列,在自己的序列中都是有序的。当刚开始元素很无序的时候,步长最大,所以插入排序的元素个数很少,速度很快;当元素基本有序了,步长很小,插入排序对于有序的序列效率很高。
3、简单选择排序
1、基本思想
选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理是每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到全部待排序的数据元素排完。 选择排序是不稳定的排序方法。
2、代码实现
public class Select {
public static void selectSort(int[] arr) {
for(int i=0;i<arr.length-1;i++) {
int min=i; //假定第一位的值最小
for(int j=i;j<arr.length;j++) {
if(arr[min]>arr[j]) {
int temp=arr[j];
arr[j]=arr[min];
arr[min]=temp;
}
}
System.out.println("第"+(i+1)+"轮遍历后的结果:"+Arrays.toString(arr));
}
}
public static void main(String[] args){
int[] arr= {38, 65, 97, 76, 13, 27, 49};
selectSort(arr);
System.out.println(Arrays.toString(arr));
}
}
3、遍历后的结果:
轮数 | 每一轮遍历后的结果 |
第1趟(i=0) | [13, 65, 97, 76, 38, 27, 49] |
第2趟(i=1) | [13, 27, 97, 76, 65, 38, 49] |
第3趟(i=2) | [13, 27, 38, 97, 76, 65, 49] |
第4趟(i=3) | [13, 27, 38, 49, 97, 76, 65] |
第5趟(i=4) | [13, 27, 38, 49, 65, 97, 76] |
第6趟(i=5) | [13, 27, 38, 49, 65, 76, 97] |
4、堆排序
堆排序(英语:Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
1、基本思想
- 最大堆调整: 将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。
- 进行堆排序: 调整堆结构+交换堆顶元素与末尾元素 。去掉此时的末尾元素,然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次大值。如此反复执行,便能得到一个有序序列了
2、代码实现 (大根堆)
public class HeapSort {
public static void heapSort(int []arr) {
//1.构建大顶堆
for(int i=arr.length/2-1;i>=0;i--){
//从第一个非叶子结点从下至上,从右至左调整结构
adjustHeap(arr,arr.length,i);
}
//2.利用大顶堆,进行堆排序---调整堆结构+交换堆顶元素与末尾元素
for(int j=arr.length-1;j>0;j--){
swap(arr,0,j);//将堆顶元素与末尾元素进行交换
adjustHeap(arr,j,0);//去掉末尾元素(即筛选出每一个新构建堆的最大值),重新对堆进行调整
System.out.println("每一趟的结果依次是:"+Arrays.toString(arr));
}
}
// 调整大顶堆
public static void adjustHeap(int []arr,int length,int i){
if(i>=length) {
return;
}
int c1=2*i+1; //左边的子节点
int c2=2*i+2; //右边的子节点
int max=i; //假设父节点的值最大
//左边子节点的值大于父节点的值
if(c1<length&&arr[c1]>arr[max]) {
max=c1;
}
//右边子节点的值大于父节点的值
if(c2<length&&arr[c2]>arr[max]) {
max=c2;
}
//父节点的值不是最大值,与子节点交换位置
if(max!=i) {
swap(arr,max,i); //保证父节点的值大于子节点
adjustHeap(arr,length,max); //父节点与子节点交换位置后,难以保证父节点对于它两个子节点而言也是最大的,
//因此这里要进行递归
}
}
public static void swap(int []arr,int a ,int b){
int temp=arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
public static void main(String []args){
int []arr = {38, 65, 76, 13, 27, 49, 97};
heapSort(arr);
System.out.println(Arrays.toString(arr));
}
}
3、遍历后的结果:
轮数 | 每一轮遍历后的结果 |
第1趟 | [76, 65, 49, 13, 27, 38, 97] |
第2趟 | [65, 38, 49, 13, 27, 76, 97] |
第3趟 | [49, 38, 27, 13, 65, 76, 97] |
第4趟 | [38, 13, 27, 49, 65, 76, 97] |
第5趟(i=4) | [27, 13, 38, 49, 65, 76, 97] |
第6趟(i=5) | [13, 27, 38, 49, 65, 76, 97] |
5、冒泡排序
这个算法的名字由来是因为越大的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列),就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序”。
1、基本思想
冒泡排序算法的原理如下:
-
比较相邻的元素。如果第一个比第二个大,就交换他们两个。
-
对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
-
针对所有的元素重复以上的步骤,除了最后一个。
-
持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较
2、代码实现
public class BubbleSort {
public static void bubbleSort(int[] arr) {
for(int i = 0;i<arr.length-1;i++) {
for(int j=0;j<arr.length-1-i;j++) {
if(arr[j]>arr[j+1]) {
int temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
System.out.println("第"+(i+1)+"轮遍历后的结果:"+Arrays.toString(arr));
}
}
public static void main(String[] args) {
int[] arr= {38, 65, 97, 76, 13, 27, 49};
bubbleSort(arr);
System.out.println(Arrays.toString(arr));
}
}
3、遍历后的结果:
轮数 | 每一轮遍历后的结果 |
第1趟(i=0) | [38, 65, 76, 13, 27, 49, 97] |
第2趟(i=1) | [38, 65, 13, 27, 49, 76, 97] |
第3趟(i=2) | [38, 13, 27, 49, 65, 76, 97] |
第4趟(i=3) | [13, 27, 38, 49, 65, 76, 97] |
第5趟(i=4) | [13, 27, 38, 49, 65, 76, 97] |
第6趟(i=5) | [13, 27, 38, 49, 65, 76, 97] |
6、快速排序
快速排序(Quicksort)是对冒泡排序的一种改进。
1、基本思想
通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
2、代码实现
public class QuickSort {
public static void quickSort(int[] array,int left,int right){
int base=array[left];
int start=left;
int end=right;
while(left<right){
//从右向遍历,找小于基准元素的值
while(left<right&&array[right]>=base){
right--;
}
array[left]=array[right];
//从左向右遍历,找大于基准元素的值
while(left<right&&array[left]<=base){
left++;
}
array[right]=array[left];
}
array[right]=base;
//保证子序列至少有两个元素
if(right-1>start) quickSort(array,start,right-1);
if(right+1<end) quickSort(array,right+1,end);
}
public static void main(String[] args) {
int[] array={26,12,23,35,6,45,77,62,102,4,16,130};
quickSort(array,0,array.length-1);
System.out.println(Arrays.toString(array));
}
}
3、每一趟后的结果:
初始数组: [26,12,23,35,6,45,77,62,102,4,16,130]
轮数 | 每一轮遍历后的结果 |
第1趟 | [16 12 23 4 6] 26 [77 62 102 45 35 130] |
第2趟 | [6 12 4] 16 [23] 26 [35 62 45] 77 [102 130] |
第3趟 | [4] 6 [12] 16 [23] 26 35 [62 45] 77 102 [130] |
第4趟 | 4 6 12 16 23 26 35 45 [62] 77 102 130 |
第5趟 | 4 6 12 16 23 26 35 45 62 77 102 130 |
现对第一趟的结果进行说明,依次类推,这里为了节省空间,只写了有变化的情况
26 | 12 | 23 | 35 | 6 | 45 | 77 | 62 | 102 | 4 | 16 | 130 |
16 | 12 | 23 | 35 | 6 | 45 | 77 | 62 | 102 | 4 | 26 | 130 |
16 | 12 | 23 | 26 | 6 | 45 | 77 | 62 | 102 | 4 | 35 | 130 |
16 | 12 | 23 | 4 | 6 | 45 | 77 | 62 | 102 | 26 | 35 | 130 |
16 | 12 | 23 | 4 | 6 | 26 | 77 | 62 | 102 | 45 | 35 | 130 |
7、归并排序
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
1、基本思想
二路归并排序主旨是“分解”与“归并”
分解:
1.将一个数组分成两个数组,分别对两个数组进行排序。
2.循环第一步,直到划分出来的“小数组”只包含一个元素,只有一个元素的数组默认为已经排好序。
归并:
1.将两个有序的数组合并到一个大的数组中。
2.从最小的只包含一个元素的数组开始两两合并。此时,合并好的数组也是有序的。
第一步:申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
第二步:设定两个指针,最初位置分别为两个已经排序序列的起始位置
第三步:比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
重复步骤3直到某一指针超出序列尾
将另一序列剩下的所有元素直接复制到合并序列尾
2、代码实现
public class MergeSort {
/**
* 算法驱动方法
*/
private static void mergeSort(int[] arr) {
int[] temp=new int[arr.length];
mergeSort(arr,0,arr.length-1,temp);
}
/**
*利用递归实现子序列的拆分与合并
* @param left 左子序列的开始索引
* @param right 右子序列开始索引
* @param temp 临时数组存放每次子序列归并后结果
*/
private static void mergeSort(int[] arr, int left, int right, int[] temp) {
if(left<right){
int mid=(left+right)/2;
//使得左子序列有序
mergeSort(arr,left,mid,temp);
//使得右子序列有序
mergeSort(arr,mid+1,right,temp);
merge(arr,left,mid,right,temp);
}
}
/**
* 对有序的子序列进行排序
* @param left 左子序列的开始索引
* @param mid 左子序列终止索引,mid+1,是右子序列的开始索引
* @param right 右子序列终止索引
* @param temp 临时数组存放每次子序列归并后结果
*/
private static void merge(int[] arr, int left, int mid, int right, int[] temp) {
int i=left;
int j=mid+1;
int t=0;
while(i<=mid&&j<=right){
if (arr[i]<arr[j]){
temp[t++]=arr[i++];
}else {
temp[t++]=arr[j++];
}
}
//将左边剩余元素填充进数组
while(i<=mid){
temp[t++]=arr[i++];
}
//将右边剩余元素填充进数组
while(j<=right){
temp[t++]=arr[j++];
}
//每一轮临时数组下标清零,为下一次排序做准备
t=0;
//将临时数组里的元素遍历至原数组
while(left<=right){
arr[left++]=temp[t++];
}
}
//public static void mergeSort(int[] arr){}
public static void main(String []args)
{
int []arr = {8,4,5,7,1,3,6,2};
mergeSort(arr);
System.out.println(Arrays.toString(arr));
}
}
3、每一趟后的结果:
轮数 | 每一轮遍历后的结果 |
第1趟 | 4,8 ,5,7, 1,3, 2,6 |
第2趟 | 4,5,7,8 1,2,3,6 |
第3趟 | 1,2,3,4,5,6,7,8,9 |
博客参考链接:
https://www.cnblogs.com/chengxiao/p/6194356.html
https://jingyan.baidu.com/article/9113f81b1f3cfd2b3214c7ab.html
8、基数排序
基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,它是将将整数按位数切割成不同的数字,然后按每个位数分别比较。
1、基本思想
第一步:将所有待比较数值统一为同样的数位长度,数位较短的数前面补零
第二步:从最低位开始,依次进行一次排序
第三步:从最低位排序一直到最高位排序,依次排序。
2、代码实现
(方法一:基于桶排序)
public class RadixSort {
//分别对个位、十位、百位...进行桶排序
private static void sort(int[] arr,int exp) {
int buckets[][]=new int[10][arr.length];//排序桶用于保存每次排序后的结果,这一位上排序结果相同的数字放在同一个桶里
int datas[]=new int[10]; //用于保存每个桶里有多少个数字,因为是10个桶,故它的范围是datas[0]-datas[9]计数作用
int k=0; //从下标0开始覆盖原数组
//step1:将数组array里的每个数字放在相应的桶里
for (int i = 0; i < arr.length; i++) {
int index=datas[data];//计数,统计datas[桶号]里的元素个数
int data=(arr[i]/exp)%10; //个位、十位...的数值,也就是对应的桶号
buckets[data][index]=arr[i];//将数组元素值,依次放进对应的桶datas[data]初始值为0,故元素从buckets[桶号][0]开始存放
index++; //对应的桶里的元素从0开始计数,每添加一个元素,datas[桶号]里的元素个数+1
}
///step2:将前一个循环生成的桶里的数据覆盖到原数组中用于保存这一位的排序结果
for(int i = 0; i <10;i++) {
if(datas[i]!=0) { //桶中有数据
for(int j=0;j<datas[i];j++) {
arr[k]=buckets[i][j];
k++;
}
}
datas[i]=0;//将桶里计数器置0,用于下一次位排序
}
k=0;//将k置0,用于下一轮保存位排序结果
System.out.println("当指数exp的值是"+exp+" 数组的结果是:"+Arrays.toString(arr));
}
//得到数组中最大值
public static int getMax(int[] arr) {
int max=arr[0];
for(int i=0;i<arr.length;i++) {
if(arr[i]>max) {
max=arr[i];
}
}
return max;
}
private static void radixSort(int[] arr) {
int max=getMax(arr);
int exp; //指数,当对数组按各位进行排序时,exp=1;按十位进行排序时,exp=10;...
for(exp=1;max/exp>0;exp*=10) {
sort(arr,exp);
}
}
public static void main(String[] args) {
int[] arr=new int[]{73,22,93,543,7776,11,48};
radixSort(arr);
System.out.println(Arrays.toString(arr));
}
}
3、每一趟后的结果:
第一趟:当指数exp的值是1,数组的结果是
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
11 | 22 | 73, 93, 543 | 7776 | 48 |
第二趟:当指数exp的值是10,数组的结果是
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
11 | 22 | 543、48 | 73, 7776 | 93 |
第三趟:当指数exp的值是100,数组的结果是
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
11, 22, 48, 73, 93 | 543 | 7776 |
第四趟:当指数exp的值是1000,数组的结果是
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
11, 22, 48, 73, 93, 543 | 7776 |
(方法二:基于计数排序)
public class RadixSort2 {
private static void countsort(int[] arr,int exp) {
//step1:得到最大值,确定循环的次数即exp的取值
int max=getMax(arr);
//step2:构建一个数组/桶,用来存放0-9对应出现的次数
int [] countArray=new int [10];
//统计次数
for (int i = 0; i < arr.length; i++) {
countArray[(arr[i]/exp)%10]++;
}
System.out.println("统计不同元素出现的次数:"+Arrays.toString( countArray));
//step3:对此时的数组做变形,统计数组从第二个元素开始,每一个元素等于它本身都加上前面所有元素之和。
for(int i=1;i<10;i++) {
countArray[i]+=countArray[i-1];
}
System.out.println("变形后的数组:"+Arrays.toString( countArray));
//step4:倒序遍历原始数列,从统计数组找到正确位置,输出到结果数组,确保稳定性
int[] sortedArray = new int[arr.length];
for(int i=arr.length-1;i>=0;i--) {
sortedArray[countArray[(arr[i]/exp)%10]-1]=arr[i];
countArray[(arr[i]/exp)%10]--;
}
//将sortedArray的值覆盖到原数组
for(int i=0;i<arr.length;i++) {
arr[i]=sortedArray[i];
}
System.out.println("当exp值为"+exp+"时,结果输出:"+Arrays.toString(arr));
System.out.println("-----------------------------------------------------------------");
countArray=null;
sortedArray=null;
}
public static int getMax(int[] arr) {
int max=arr[0];
for (int i = 1; i < arr.length; i++) {
if(arr[i]>max) {
max=arr[i];
}
}
return max;
}
public static void radixSort(int[] arr) {
int max=getMax(arr);
for(int exp=1;max/exp>0;exp*=10) {
countsort(arr,exp);
}
}
public static void main(String[] args) {
int arr[]={93,695,498,198,294,2,16,41};
radixSort(arr);
System.out.println("最终结果输出:"+Arrays.toString(arr));
}
}
输出结果:
统计不同元素出现的次数:[0, 1, 1, 1, 1, 1, 1, 0, 2, 0]
变形后的数组:[0, 1, 2, 3, 4, 5, 6, 6, 8, 8]
当exp值为1时,结果输出:[41, 2, 93, 294, 695, 16, 498, 198]
-----------------------------------------------------------------
统计不同元素出现的次数:[1, 1, 0, 0, 1, 0, 0, 0, 0, 5]
变形后的数组:[1, 2, 2, 2, 3, 3, 3, 3, 3, 8]
当exp值为10时,结果输出:[2, 16, 41, 93, 294, 695, 498, 198]
-----------------------------------------------------------------
统计不同元素出现的次数:[4, 1, 1, 0, 1, 0, 1, 0, 0, 0]
变形后的数组:[4, 5, 6, 6, 7, 7, 8, 8, 8, 8]
当exp值为100时,结果输出:[2, 16, 41, 93, 198, 294, 498, 695]
-----------------------------------------------------------------
最终结果输出:[2, 16, 41, 93, 198, 294, 498, 695]