目录
[☞ 基本思想:](#☞ 基本思想:)
[☞ 代码:](#☞ 代码:)
[☞ 归并排序的非递归方法:](#☞ 归并排序的非递归方法:)
❄️一、归并排序:
☞ 基本思想:
归并排序是一个建立在归并操作上的一种排序算法,这个算法是采用分治法的典型案例。
就是:将有序的子序列合并,得到一个完全有序的列表。
即是先使每个子序列有序,再使子序列段间有序。
若将两个有序的列表合并成一个列表,称为二路归并。
我们来看看这个排序是如何进行排序的:
就是分成了 分解操作 和 合并操作 ,那么我们的这个 分解操作是如何做的,合并操作又是如何做的?我们呢来一一的看看如何操作的:
分解操作:
1、我们呢对于这个数据 开始位置设置为left ,结束位置设置为 right ,之后求出中间值 mid ,
2、之后把left 到 mid 下标的值分为一组,mid +1 到 right 的值分为一组。
3、对于 left 到 mid这一组,再分的话把 mid 的下标给到 right 之后再求 mid 位置。
4、对于 mid+1 到 right 这一组,再分的话就把 mid+1 的下标给到 left 之后再求 mid 位置。
5、这样直至我们的 left >= right 的时候结束。
OK,我们的分解思路就是这样的,我们呢来看看代码是如何编写的 :
java
public static void mergeSort(int[] array) {
mergeSortTmp(array,0,array.length - 1);
}
private static void mergeSortTmp(int[] array, int left, int right) {
if (left >= right) {
return;
}
//分解操作
int mid = (left + right) / 2;
mergeSortTmp(array,left,mid);
mergeSortTmp(array,mid+1,right);
}
合并操作:
1、我们在每次分解完一个区间的数据之后呢,就对其进行合并操作,合并之后就是有序的数据了
2、比如我们合并长度为2 的两个有序的数据,就是相当于合并两个有序的数组
3、我们要知道每个数组的开始和结尾
第一个数组:开头是left 用s1 保存,结尾是mid 用e1 保存
第二个数组:开头是mid+1 用s2 保存,结尾是right 用e2 保存
4、我们把 s1 和 s2 下标的值进行比较之后小的值呢放到新的数组中,只要有一个超过了e1或者e2就需要退出循环,进行下一步的判断操作。
5、还有判断哪个数组放到新的数组中,之后再把没放入的放进数组中
6、把排好序的数组放到原先的数组中,但是要注意放的时候我们的原数组的下标要加上一个left
因为我们第二次排序的时候呢下标不是从 0 开始的是从 left 下标开始的。
我们来看看代码是如何编写的:
java
private static void merge(int[] array, int left, int mid, int right) {
int[] tmp = new int[right - left + 1];
int k = 0;
int s1 = left;
int e1 = mid;
int s2 = mid + 1;
int e2 = right;
while(s1 <= e1 && s2 <= e2) {
if (array[s1] <= array[s2]) {
tmp[k++] = array[s1++];
}else {
tmp[k++] = array[s2++];
}
}
while (s1 <= e1) {
tmp[k++] = array[s1++];
}
while (s2 <= e2) {
tmp[k++] = array[s2++];
}
for (int i = 0; i < k; i++) {
array[i+left] = tmp[i];
}
}
这就是我们的合并排序的代码了,我们来看看整体的代码:
☞ 代码:
java
/**
* 归并排序
* 时间复杂度:O(N*logN)
* 空间复杂度:O(N)
* 稳定性:稳定
* @param array
*/
public static void mergeSort(int[] array) {
mergeSortTmp(array,0,array.length - 1);
}
private static void mergeSortTmp(int[] array, int left, int right) {
if (left >= right) {
return;
}
//分解操作
int mid = (left + right) / 2;
mergeSortTmp(array,left,mid);
mergeSortTmp(array,mid+1,right);
//每一个分解完之后呢,都要进行排序合并操作
merge(array,left,mid,right);
}
private static void merge(int[] array, int left, int mid, int right) {
int[] tmp = new int[right - left + 1];
int k = 0;
int s1 = left;
int e1 = mid;
int s2 = mid + 1;
int e2 = right;
while(s1 <= e1 && s2 <= e2) {
if (array[s1] <= array[s2]) {
tmp[k++] = array[s1++];
}else {
tmp[k++] = array[s2++];
}
}
while (s1 <= e1) {
tmp[k++] = array[s1++];
}
while (s2 <= e2) {
tmp[k++] = array[s2++];
}
for (int i = 0; i < k; i++) {
array[i+left] = tmp[i];
}
}
☞ 归并排序的非递归方法:
我们的非递归呢,就是把我们的分解操作变成非递归的操作,那么如何做呢?
对于分解操作,我们一开始是不是可以看成它们是 一个一个有序,之后是两个两个有序,之后是四个四个有序,这样下去就可以把我们的分解操实现了。
1、我们定义一个 gap 用来 判断是几个一组的
我们的 gap 每次变化都是 乘2 的,并且 gap < 数组长度 才可以执行下面的操作
2、我们的定义个 i 从 0 下标开始,我们的 left 就是 i
这里我们的 i 不能是++操作进行往下走,我们的 i 这里是 i = i + gap*2
3、mid = left + gap - 1
在赋值之后呢,我们的mid 要进行判断操作,是否是 >= 数组的长度 ,如果是,就把其进行 - 1
因为这里我们之后要传给 合并有序数组的操作,防止数组越界
4、right = mid + gap
这里同样要进行判断操作, 是否是 >= 数组的长度 ,如果是,就把其进行 - 1
代码:
java
public static void mergeSort(int[] array) {
mergeSortNor(array);
}
private static void mergeSortTmp(int[] array, int left, int right) {
if (left >= right) {
return;
}
//分解操作
int mid = (left + right) / 2;
mergeSortTmp(array,left,mid);
mergeSortTmp(array,mid+1,right);
//每一个分解完之后呢,都要进行排序合并操作
merge(array,left,mid,right);
}
//非递归实现归并
public static void mergeSortNor(int[] array) {
int gap = 1;
while (gap < array.length) {
for (int i = 0; i < array.length; i = i + gap*2) {
int left = i;
int mid = left + gap - 1;
if (mid >= array.length) {
mid = array.length - 1;
}
int right = mid + gap;
if (right >= array.length) {
right = array.length - 1;
}
merge(array,left,mid,right);
}
gap *= 2;
}
}
❄️二、排序算法的分析:
|----------|----------------|----------------|----------------|---------------------|---------|
| 排序方法 | 最好 | 平均 | 最坏(时间复杂度) | 空间复杂度 | 稳定性 |
| 冒泡排序 | 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) | O(N^1.5) | O(1) | 不稳定 |
| 堆排序 | O(N*logN) | O(N*logN) | O(N*logN) | O(1) | 不稳定 |
| 快速排序 | O(N*logN) | O(N*logN) | O(N^2) | O(log(N))~O(N) | 不稳定 |
| 归并排序 | O(N*logN) | O(N*logN) | O(N*logN) | O(N) | 稳定 |
❄️三、非基于比较的排序:
我们之前介绍的排序都是需要比较的排序方法,我们还有不需要比较的排序方法:计数排序、基数排序、桶排序。
我们的非基于比较的排序可以通过 "传送门" 来了解:
基数排序:
桶排序:
当然如果需要 计数排序的 传送门 的话,这里也有:
❄️总结:
OK,到这里呢我们的排序呢就结束了,这次关于排序的代码呢还是比较多的,要认真的理解它们,同时要画图的去理解它们的执行流程。 下一篇博客呢我们就要介绍关于Map与Set的知识啦,让我们尽情期待吧!!!拜拜~~~