目录
写在前面:
今天,参考了下方文章,自己手动写了几种常见排序算法的代码。此文非常清晰明了,推荐:
Java的几种常见排序算法 - 小不点丶 - 博客园 (cnblogs.com)
1. 几种常见的排序算法的复杂度
图片来自上述文章
2. 冒泡排序
2.1 什么是冒泡排序?
基本思想是:
从头开始让相邻的两个元素进行比较,符合条件就交换位置,这样就可以把最大值或者最小值放到数组的最后面了;
接着再从头开始两两比较交换,直到把最大值或者最小值放到数组的倒数第二位(即不需要与最后一位数进行对比).....
以此类推,直到排序完成。
2.2 代码实现
/*冒泡排序
1. 通过每一次遍历获取最大/最小值
2. 将最大值/最小值放在尾部/头部
3. 然后除开最大值/最小值,剩下的数据在进行遍历获取最大/最小值
**/
package com.my.demo;
public class BubbleSort {
public static void main(String[] args) {
int arr[] = {6, 4, 7, 8, 1};
//打印排序前数组
System.out.println("排序前:");
printArr(arr);
//打印排序过程
System.out.println("\n冒泡排序过程:");
for(int i=0; i<arr.length; i++){ //外层循环
for(int j=0; j<arr.length-1-i; j++){ //内层循环,注意内层循环的次数 arr.length-1-i
if(arr[j] > arr[j+1]){ //注意:此代码是实现将最大值放到最后的效果排序
int tmp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tmp;
}
}
printArr(arr);
}
//打印排序后数组
System.out.println("\n排序后:"); //打印排序后数组
printArr(arr);
}
public static void printArr(int[] arr){
for(int k=0; k<arr.length; k++){
System.out.print(arr[k]);
System.out.print(" ");
}
System.out.println();
}
}
2.3 运行结果
排序前:
6 4 7 8 1
冒泡排序过程:
4 6 7 1 8
4 6 1 7 8
4 1 6 7 8
1 4 6 7 8
1 4 6 7 8
排序后:
1 4 6 7 8
Process finished with exit code 0
3. 选择排序
3.1 什么是选择排序?
选择排序(Selection-sort)是一种简单直观的排序算法,基本思想是:
(1)首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置(第一趟先假设第一个元素就是最小值)。
(2)然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾......
(3)以此类推,直到所有元素均排序完毕。
3.2 代码实现
/* 选择排序
1. 将第一个值看成最小值,然后和后续的比较,找出最小值和下标
2. 交换本次遍历的起始值和最小值
3. 每次遍历的时候,将前面找出的最小值,看成一个有序的列表,后面的看成无序的列表,然后每次遍历无序列表找出最小值。
**/
package com.my.demo;
public class SelectionSort {
public static void main(String[] args) {
int arr[] = {9, 3, 8, 1, 5, 2};
//打印排序前数组
System.out.println("排序前:");
printArr(arr);
//打印排序过程
int min; //存放每轮(外层循环的)循环的最小值
int index; //存放每轮(外层循环的)循环的最小值的索引
System.out.println("\n选择排序过程:");
for(int i=0; i<arr.length; i++){ //外层循环
min = arr[i]; //假设数组未排序部分的第一个是最小值
index = i; //最小值的下标
for(int j=i+1; j<arr.length; j++){ //内层循环,一次循环结束,即找出数组未排序部分的最小值及其下标
if(min > arr[j]){
min = arr[j];
index = j;
}
}
//将最小值与本次循环开始设置的假设最小值,进行交换
//将i前面的数据看成一个排好的队列,i后面的看成一个无序队列。每次只需要未排序部分的最小值,做替换。
int tmp = arr[i];
arr[i] = min;
arr[index] = tmp;
printArr(arr);
}
//打印排序后数组
System.out.println("\n选择排序后:"); //打印排序后数组
printArr(arr);
}
public static void printArr(int[] arr){
for(int k=0; k<arr.length; k++){
System.out.print(arr[k]);
System.out.print(" ");
}
System.out.println();
}
}
3.3 运行结果
排序前:
9 3 8 1 5 2
选择排序过程:
1 3 8 9 5 2
1 2 8 9 5 3
1 2 3 9 5 8
1 2 3 5 9 8
1 2 3 5 8 9
1 2 3 5 8 9
选择排序后:
1 2 3 5 8 9
Process finished with exit code 0
4. 插入排序
4.1 什么是插入排序
插入排序是一种简单直观的排序算法,基本思想是:
(1)通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
(2)插入排序在实现上,在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间...
(3)直到所有元素均排序完毕。
4.2 代码实现
/* 插入排序
1. 默认从第二个数据开始比较。
2. 如果第二个数据比第一个小,则交换。然后再用第三个数据比较,如果比前面小,则插入。否则,退出循环。
3. 说明:默认将第一数据看成有序列表,后面无序的列表循环每一个数据,如果比前面的数据小则插入(交换)。否则退出。
**/
package com.my.demo;
public class InsertionSort {
public static void main(String[] args) {
int arr[] = {5, 3, 6, 1, 9, 0};
//打印排序前数组
System.out.println("排序前:");
printArr(arr);
//打印排序过程
System.out.println("\n插入排序过程:");
for(int i=1; i<arr.length; i++){ //外层循环,i=1从第二个开始比较,第一个则默认为已经拍好的有序列表
for(int j=i; j>0; j--){ //内层循环,与前面排好序的有序列表比较,如果后面的数据小于前面的则交换
if(arr[j] < arr[j-1]){ //该第j项比它前面的第j-1个小,则有可能比第j-2个也小,所以先j和j-1互换一次,然后再继续往前比
int tmp = arr[j-1];
arr[j-1] = arr[j];
arr[j] = tmp;
} else { //如果第j个已经比第j-1个大了,那肯定比第j-2个往前的数据都大,因为前面j-1个是已经从小到大排好序了的
break; //如果不小于,说明插入完毕,退出内层for循环
}
}
printArr(arr);
}
//打印排序后数组
System.out.println("\n插入排序后:"); //打印排序后数组
printArr(arr);
}
public static void printArr(int[] arr){
for(int k=0; k<arr.length; k++){
System.out.print(arr[k]);
System.out.print(" ");
}
System.out.println();
}
}
4.3 运行结果
排序前:
5 3 6 1 9 0
插入排序过程:
3 5 6 1 9 0
3 5 6 1 9 0
1 3 5 6 9 0
1 3 5 6 9 0
0 1 3 5 6 9
插入排序后:
0 1 3 5 6 9
Process finished with exit code 0
5. 希尔排序
5.1 什么是希尔排序?
希尔排序(Shell's sort)是插入排序的一种,是直接插入排序算法的一种更高版本的改进版本。
基本思想是:
通过间隔多个数据来进行插入排序。
(1)将数组列在一个表中,并对列分别进行插入排序。
(2)重复这过程,不过每次用更长的列(步长更长了,列数更少了)来进行。
(3)最后整个表就只有一列了。
5.2 代码实现
/**
* 希尔排序(Shell’s Sort)是插入排序的一种,是直接插入排序算法的一种更高版本的改进版本。
*
* 把记录按步长gap分组,对每组记录采用直接插入排序方法进行排序;
* 随着步长逐渐减小,所分成的组包含的记录越来越多;
* 当步长值减小到1时,整个数据合成一组,构成一组有序记录,完成排序;
*/
package com.my.demo;
public class ShellSort {
public static void main(String[] args) {
// int[] arr = {27, 12, 53, 28, 19, 44, 36, 79, 10};
int[] arr = {9, 1, 2, 5, 7, 4, 8, 6, 3, 5};
//打印排序前数组
System.out.println("排序前:");
printArr(arr);
//打印排序过程
System.out.println("\n希尔排序过程:");
shellSort(arr);
//打印排序后数组
System.out.println("\n希尔排序后:"); //打印排序后数组
printArr(arr);
}
public static void shellSort(int[] arr){
//第一层循环,设置每一趟排序的步长。把所有数据按步长gap分组。通过减半的方式来实现。
for(int step= arr.length/2; step>0; step=step/2){
//第二层循环,具体实现一趟排序,把所有数据过一遍。对每组记录采用直接插入排序方法进行排序
//在这层循环中,每一次循环,即每一个i的取值,完成一组记录(这里的记录,不是完整的所有数据,只是一部分)的排序
//对一个步长区间进行比较 [step,arr.length]
for(int i=step; i<arr.length; i++){
int value = arr[i];
int j; //这里声明的原因是,不只是在第三层循环使用
//第三层循环,具体实现每一组记录的排序
//对步长区间中具体的元素进行比较
for(j=i-step; j>=0 && arr[j]>value; j=j-step){
//j为左区间的取值,j+step为右区间与左区间的对应值。
arr[j+step] = arr[j]; //注意:这里没有直接用arr[i]=arr[j]
}
//此时j为一个负数,[j + step]为左区间上的初始交换值
arr[j+step] = value; //因为退出第三层循环之前,减去了一次step,这里加回去,再赋值
}
printArr(arr);
}
}
public static void printArr(int[] arr){
for(int k=0; k<arr.length; k++){
System.out.print(arr[k]);
System.out.print(" ");
}
System.out.println();
}
}
5.3 运行结果
排序前:
9 1 2 5 7 4 8 6 3 5
希尔排序过程:
4 1 2 3 5 9 8 6 5 7
2 1 4 3 5 6 5 7 8 9
1 2 3 4 5 5 6 7 8 9
希尔排序后:
1 2 3 4 5 5 6 7 8 9
Process finished with exit code 0
6. 快速排序
6.1 什么是快速排序?
(推荐此文,说得非常清晰:基于Java实现的快速排序)
是一种排序执行效率很高的排序算法,利用分治法来对待排序序列进行分治排序,基本思想是:
(1)通过一趟排序将待排记录分隔成独立的两部分,其中的一部分比关键字小,后面一部分比关键字大.
(2)然后再对这前后的两部分分别采用这种方式进行排序,通过递归的运算.
(3)最终达到整个序列有序。
6.2 快速排序的思路
这里用一个数组来逐步逐步说明快速排序的思路:
(1)假设对数组{7, 1, 3, 5, 13, 9, 3, 6, 11}进行快速排序。
(2)首先在这个序列中找一个数作为基准数,为了方便可以取第一个数。
(3)遍历数组,将小于基准数的放置于基准数左边,大于基准数的放置于基准数右边。
(4)完成第一轮/层遍历之后,得到类似于这种排序的数组{3, 1, 3, 5, 6, 7, 9, 13, 11}。即:在初始状态下7是第一个位置,现在需要把7挪到中间的某个位置k,也即k位置是两边数的分界点。
(6)那如何做到把小于和大于基准数7的值分别放置于两边呢?
我们采用双指针法,从数组的两端分别进行比对。
先从最右位置往左开始找直到找到一个小于基准数的值,记录下该值的位置(记作 i)。
再从最左位置往右找直到找到一个大于基准数的值,记录下该值的位置(记作 j)。
如果位置i<j,则交换i和j两个位置上的值,然后继续从(j-1)的位置往前和(i+1)的位置往后重复上面比对基准数然后交换的步骤。
(7)如果执行到i==j,表示本次比对已经结束,将最后 i 的位置的值与基准数做交换。
此时基准数就找到了临界点的位置k,位置k两边的数组都比当前位置k上的基准值或都更小或都更大。
即:基准值7已经把数组分为了两半,基准值7算是已归位(找到排序后的位置)。
(8)通过相同的排序思想,利用递归算法,分别对7两边的数组进行快速排序,左边对[left, k-1]子数组排序,右边则是[k+1, right]子数组排序。
(9)直到所有元素均排序完毕。
6.3 代码实现
/**
* 快速排序
* 核心算法:递归
*/
package com.my.demo;
public class QuickSort {
public static void main(String[] args) {
int[] arr = {7, 2, 5, 8, 1, 4, 6, 9, 0};
System.out.println("排序前:"); //打印排序前数组
printArr(arr);
System.out.println("\n快速排序过程:");
quickSort(arr, 0, arr.length-1); //调用快排方法,传3个参数
System.out.println("\n快速排序后:"); //打印排序后数组
printArr(arr);
}
public static void quickSort(int[] arr, int left, int right){
if(left > right){ //递归结束的出口,这个不能漏
return ;
}
int base = arr[left]; //将待排序的部分的最左边一个,作为基准,存在base中
int i = left, j = right;
while(i != j){ //如果没有再在上面的if语句中退出,则i<=j的。这里设置循环退出的条件是i==j
while(arr[j] >= base && i < j){ //注意:顺序很重要,要先从右边开始往左找,直到找到比base值小的数,j记录下标
j--;
}
while( arr[i] <= base && i < j){ //再从左往后找,直到找到比base值大的数,i记录下标
i++;
}
if(i<j){ // 上面两个循环结束,表示找到了位置或者(i>=j)了,交换两个数在数组中的位置,即j和i下标所对应的数
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
//跳出循环,此时已经完成了一轮排序,即已经将原数组分为两部分,一部分比基准数小,一部分比基准数大。现在就差将基准数放到这两部分中间的位置了
//下方将基准数放到中间的位置,实现基准数归为
arr[left] = arr[i];
arr[i] = base;
printArr(arr);
//递归调用
//在当前基准数的左边和右边,分别执行上面的操作
//i的索引处为上面已确定好的当前的基准值的位置,无需再处理
quickSort(arr, left, i-1);
quickSort(arr, i+1, right);
}
public static void printArr(int[] arr){
for(int k=0; k<arr.length; k++){
System.out.print(arr[k]);
System.out.print(" ");
}
System.out.println();
}
}
6.4 运行结果
排序前:
7 2 5 8 1 4 6 9 0
快速排序过程:
6 2 5 0 1 4 7 9 8
4 2 5 0 1 6 7 9 8
0 2 1 4 5 6 7 9 8
0 2 1 4 5 6 7 9 8
0 1 2 4 5 6 7 9 8
0 1 2 4 5 6 7 9 8
0 1 2 4 5 6 7 9 8
0 1 2 4 5 6 7 8 9
0 1 2 4 5 6 7 8 9
快速排序后:
0 1 2 4 5 6 7 8 9
Process finished with exit code 0
7. 归并排序
7.1 什么是归并排序?
归并排序是一种概念上最简单的排序算法,与快速排序一样,归并排序也是基于分治法的。基本思想是:
(1)归并排序将待排序的元素序列分成两个长度相等的子序列,为每一个子序列排序。
(2)然后再将他们合并成一个子序列。合并两个子序列的过程也就是两路归并。
(3)直到所有元素均排序完毕。
即:将数组先对半拆成最小单位,然后将两半数据合并成一个有序的列表。
7.2 代码实现
/**
* 合并排序
* 将列表按照对等的方式进行拆分
* 拆分小最小快的时候,在将最小块按照原来的拆分,进行合并
* 合并的时候,通过左右两块的左边开始比较大小。小的数据放入新的块中
* 说明:简单一点就是先对半拆成最小单位,然后将两半数据合并成一个有序的列表。
*/
package com.my.demo;//package com.my.demo;
public class MergeSort {
public static void main(String[] args) {
int[] arr = {27, 12, 53, 28, 19, 44, 36, 79, 10};
// int[] arr = {7, 2, 5, 8, 1, 4, 6, 9, 0};
System.out.println("排序前:"); //打印排序前数组
printArr(arr);
System.out.println("\n合并排序过程:");
mergeSort(arr, 0, arr.length-1);
System.out.println("\n合并排序后:"); //打印排序后数组
printArr(arr);
}
//两路归并算法,两个排好序的子序列合并为一个子序列
//通过递归分别实现两个子序列的排序
public static void mergeSort(int[] arr, int start, int end){
if(start<end){
int mid = (start + end)/2;
mergeSort(arr, start, mid);
mergeSort(arr, mid+1, end);
merge(arr, start, mid, end);
printArr(arr);
}
}
//实现两个子序列的合并过程
public static void merge(int[] arr, int left, int mid, int right){
int[] tmp = new int[arr.length]; //暂存数组,辅助作用
int pl = left; //左子序列的起点
int pr = mid+1; //右子序列的起点
int p = left; //类似指针,用于协助往暂存数组中逐个放排序好的数据
while(pl<=mid && pr<=right){
if(arr[pl]<=arr[pr]){
tmp[p] = arr[pl];
p++;
pl++;
} else {
tmp[p] = arr[pr];
p++;
pr++;
}
}
while(pl<=mid){ //如果左子序列未检测完,直接将后面所有元素加到合并的序列中
tmp[p] = arr[pl];
p++;
pl++;
}
while(pr<=right){ //如果右子序列未检测完,直接将后面所有元素加到合并的序列中
tmp[p] = arr[pr];
p++;
pr++;
}
//将暂存序列中的数据复制回原数组
//注意:原数组的起点和重点,并非0和length-1. 而是正在处理的该子序列在数组中的实际位置的索引。
//跟递归有关
for(int i=left; i<=right; i++){
arr[i] = tmp[i];
}
}
public static void printArr(int[] arr){
for(int k=0; k<arr.length; k++){
System.out.print(arr[k]);
System.out.print(" ");
}
System.out.println();
}
}
7.3 运行结果
排序前:
27 12 53 28 19 44 36 79 10
合并排序过程:
12 27 53 28 19 44 36 79 10
12 27 53 28 19 44 36 79 10
12 27 53 19 28 44 36 79 10
12 19 27 28 53 44 36 79 10
12 19 27 28 53 36 44 79 10
12 19 27 28 53 36 44 10 79
12 19 27 28 53 10 36 44 79
10 12 19 27 28 36 44 53 79
合并排序后:
10 12 19 27 28 36 44 53 79
Process finished with exit code 0