Java实现七大排序算法篇
1.插入排序
1.1 直接插入排序
直接插入排序是一种最简单直观的排序算法,它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
- 原理
整个区间被分为
- 有序区间
- 无序区间
每次选择无序区间的第一个元素,在有序区间内选择合适的位置插入
- 动图演示
- 代码演示
import java.util.Arrays;
import java.util.Random;
/**
* ClassName: InsertSort
* Description: 直接插入排序
* date: 2021/5/13 13:24
* 时间复杂度:
* 最好情况下:当数据有序的时候,O(n) -》 当数据越有序越快
* 最坏情况下:当数据逆序的时候,可以达到O(n^2)
* 空间复杂度:O(1)
* 稳定性:稳定
* 任何一个稳定的排序 一定可以变成不稳定的排序
* 但是如果本身就是不稳定的排序 不可能变成稳定的排序的
*/
public class InsertSort {
public static void insertSort(int[] array) {
for (int i = 1; i < array.length; i++) {
// 有序区间: [0, i)
// 无序区间: [i, array.length)
int tmp = array[i]; // 无序区间的第一个数
int j = i-1;
for (; j >= 0; j--) {
if (array[j] > tmp) {
array[j+1] = array[j];
} else {
//array[j+1] = tmp;
break;//遇到break说名j下标的元素已经小于了tmp,于是将tmp放入j+1的下标,后面的循环就没必要进行了
}
}
array[j+1] = tmp;
}
}
//测试
public static void main(String[] args) {
Random random = new Random();
int[] array = new int[10];
for (int i = 0; i < array.length; i++) {
array[i] = random.nextInt(10);
}
System.out.println(Arrays.toString(array));
insertSort(array);
System.out.println(Arrays.toString(array));
}
}
1.2 希尔排序
希尔排序,也称递减增量排序算法,是直接插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。
希尔排序是基于插入排序的以下两点性质而提出改进方法的:
1.插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率;
2.但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位;
希尔排序的基本思想是:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录"基本有序"时,再对全体记录进行依次直接插入排序。
- 动图演示
- 代码演示
import java.util.Arrays;
import java.util.Random;
/**
* ClassName: ShellSort
* Description: 希尔排序
* 时间复杂度:建议:-> O(n^1.3 - 1.5)
* 空间复杂度:O(1)
* 稳定性:不稳定
*/
public class ShellSort {
private static void shellSort(int[] array) {
int[] arr = {5,3,1};
for (int i = 0 ;i <arr.length; i++) {
shell(array, arr[i]);
}
//这样做一般不太好,不一定能得出len都是素数,更不能保证len最后会为1
// int len = array.length;
// while (len>1) {
// shell(array, len);
// len = (len/3) +1;
// }
//运行这个相当于直接插入排序了
shell(array, 1);
}
private static void shell(int[] array, int k) {
for (int i = k; i <array.length ; i++) {
int tmp = array[i];
int j = i-k;
for (; j >= 0 ; j=j-k) {
if (array[j] > tmp) {
array[j+k] = array[j];
} else {
break;
}
}
array[j+k] = tmp;
}
}
//测试
public static void main(String[] args) {
Random random = new Random();
int[] array = new int[100];
for (int i = 0; i < array.length; i++) {
array[i] = random.nextInt(100)+1;
}
System.out.println(Arrays.toString(array));
shellSort(array);
System.out.println(Arrays.toString(array));
}
}
2.选择排序
2.1 选择排序
选择排序是一种简单直观的排序算法,无论什么数据进去都是 O(n²) 的时间复杂度。所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间了吧。
- 原理
1.首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。
2.再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
3.重复第二步,直到所有元素均排序完毕。
- 动图演示
- 代码演示
import java.util.Arrays;
import java.util.Random;
/**
* ClassName: SelectSort
* Description: 选择排序
* date: 2021/5/13 21:13
* 时间复杂度:O(n^2)
* 空间复杂度:O(1)
* 稳定性:不稳定
*/
public class SelectSort {
public static void selectSort(int[] array) {
for (int i = 0; i < array.length; i++) {
for (int j = i + 1; j <array.length ; j++) {
if (array[i] > array[j]) {
int tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
}
}
}
//测试
public static void main(String[] args) {
Random random = new Random();
int[] array = new int[100];
for (int i = 0; i <array.length ; i++) {
array[i] = random.nextInt(50)+1;
}
System.out.println(Arrays.toString(array));
selectSort(array);
System.out.println(Arrays.toString(array));
}
}
2.2 堆排序
堆排序是指利用堆这种数据结构所设计的一种排序算法。堆排序其实本身不难,但难的是创建堆的过程。这里我们需要注意的是:排升序要建大堆;排降序要建小堆。
- 原理(结合代码易懂)
1.创建一个堆,创建的过程中,调用 adjustDown(),不断的调整每一颗树为大堆。
2.排序,把堆首(最大值)和堆尾互换,把堆的尺寸缩小 1,并再次调用 adjustDown(),(parent从0开始向下再次调整为大堆)。
3.重复步骤 2,直到堆的尺寸为 1。
- 动图演示
- 代码演示
import java.util.Arrays;
import java.util.Random;
public class HeapSort {
public static void heapSort(int[] array) {
//1.创建大堆
createHeap(array);
//2.排序
int end = array.length - 1;
while (end > 0) {
int tmp = array[0];
array[0] = array[end];
array[end] = tmp;
adjustDown(array,0,end);
end--;
}
}
public static void createHeap(int[] array) {
//定义p表示每颗子树的根节点
for(int p = (array.length-1-1)/2 ;p >= 0 ;p--) {
//向下调整每一棵树
adjustDown(array,p,array.length);
}
}
public static void adjustDown(int[] array, int parent, int len) {
int child = parent*2 + 1;
while (child < len) {
if (child+1 < len && array[child+1] > array[child]) {
child++;
}
if (array[child] > array[parent]) {
int tmp = array[parent];
array[parent] = array[child];
array[child] = tmp;
parent = child;
child = parent*2 + 1;
} else {
break;
}
}
}
//测试
public static void main(String[] args) {
Random random = new Random();
int[] array = new int[10];
for (int i = 0; i < array.length; i++) {
array[i] = random.nextInt(10)+1;
}
System.out.println(Arrays.toString(array));
heapSort(array);
System.out.println(Arrays.toString(array));
}
}
3.交换排序
3.1 冒泡排序
冒泡排序也是一种简单直观的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来
- 原理
1.比较相邻的元素。如果第一个比第二个大,就交换他们两个。
2.对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这步做完后,最后的元素会是最大的数。
3.针对所有的元素重复以上的步骤,除了最后一个,持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
- 动图演示
- 代码演示
public static void bubbleSort(int[] array) {
for (int i = 0; i <array.length-1 ; i++) {
// 设定一个标记,若为false,则表示此次循环没有进行交换,也就是待排序列已经有序,排序已经完成(优化排序)
boolean fla = false;
//从前往后每两个数字比较,把数字大的向后交换,每一趟结束最大值一定在最后
for (int j = 0; j < array.length -1-i ; j++) {
if (array[j] > array[j+1]) {
int tmp = array[j];
array[j] = array[j+1];
array[j+1] = tmp;
fla = true;
}
}
if (fla == false) {
break;
}
}
}
3.2 快速排序(递归法)
快速排序的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据比另一部分的所有数据要小,再按这种方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,使整个数据变成有序序列。
- 原理
在待排序的数列中,我们首先要找一个数字作为基准数(这只是个专用名词)。为了方便,我们一般选择第 1 个数字作为基准数(其实选择第几个并没有关系)。接下来我们需要把这个待排序的数列中小于基准数的元素移动到待排序的数列的左边,把大于基准数的元素移动到待排序的数列的右边(升序)。这时,左右两个分区的元素就相对有序了;接着把两个分区的元素分别按照上面两种方法继续对每个分区找出基准数,然后移动,直到各个分区只有一个数时为止。
- 动图演示
- 代码演示
import java.util.Arrays;
import java.util.Random;
public class QuickSort {
//测试
public static void main(String[] args) {
Random random = new Random();
int[] array = new int[100];
for (int i = 0; i < array.length; i++) {
array[i] = random.nextInt(100)+1;
}
System.out.println(Arrays.toString(array));
quickSort(array);
System.out.println(Arrays.toString(array));
}
public static void quickSort(int[] array) {
quick(array,0,array.length-1);
}
public static void quick(int[] array, int low, int high) {
if (low >= high) {
return;
}
//找基准值
int par = partion(array,low,high);
quick(array,low,par-1);
quick(array,par+1,high);
}
public static int partion(int[] array, int start, int end) {
int tmp = array[start];
while (start < end) {
while (array[end] >= tmp && start < end) {
end--;
}
if (start >= end) {
//tmp放回去
array[start] = tmp;
break;
} else {
array[start] = array[end];
}
while (array[start] <= tmp && start < end) {
start++;
}
if (start >= end) {
//tmp放回去
array[start] = tmp;
break;
} else {
array[end] = array[start];
}
}
return start;
}
//存在优化方案和非递归方法,持续更新中...
//优化1.三位取中法优化
//优化2.直接插入排序
4.归并排序(递归法)
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide andConquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
- 动图演示
- 代码演示(MergeSort.java)
import java.util.Arrays;
public class MergeSort {
public static void mergeSort(int[] array) {
mergeSortRecursion(array,0,array.length-1);
}
//先递归,分多个组
public static void mergeSortRecursion(int[] array,int low,int high) {
//递归的结束条件
if (low >= high) {
return;
}
int mid = (low + high)>>>1;
mergeSortRecursion(array,low,mid);
mergeSortRecursion(array,mid+1,high);
//进行合并
mergeSortRet(array,low,mid,high);
}
//是每两个数组合并为一个数组的问题
private static void mergeSortRet(int[] array,int low,int mid,int high) {
int[] tmpArray = new int[high-low+1]; //申请一个数组用来存放合并后的排序数据
int k = 0; //tmpArray的下标
int s1 = low;
int e1= mid;
int s2 = mid+1;
int e2 = high;
while(s1 <= e1 && s2 <= e2) {
if (array[s1] <= array[s2]) {
tmpArray[k++] = array[s1++];
} else {
tmpArray[k++] = array[s2++];
}
}
while (s1 <= e1) {
tmpArray[k++] = array[s1++];
}
while (s2 <= e2) {
tmpArray[k++] = array[s2++];
}
for (int i = 0; i <tmpArray.length; i++) {
array[i + low ] = tmpArray[i];
}
}
}
5.基数排序(了解)
基数排序:根据键值的每位数字来分配桶;是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。
性能分析
排序方法 | 最好 | 平均 | 最坏 | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|
冒泡排序 | O(n^2) | O(n^2) | O(n^2) | O(1) | 稳定 |
插入排序 | O(n) | O(n^2) | O(n^2) | O(1) | 稳定 |
选择排序 | O(n^2) | O(n^2) | O(n^2) | O(1) | 不稳定 |
希尔排序 | O(n) | O(n^1.3~1.5) | O(n^2) | O(1) | 不稳定 |
堆排序 | O(n * log(n)) | O(n * log(n)) | O(n * log(n)) | O(1) | 不稳定 |
快速排序 | O(n * log(n)) | O(n * log(n)) | O(n^2) | O(log(n)) ~ O(n) | 不稳定 |
归并排序 | O(n * log(n)) | O(n * log(n)) | O(n * log(n)) | O(n) | 稳定 |