归并排序的思想很简单,如果有一组待排序列,先切分再重组。任何一篇教程也都会说采用的是分治法,意思就是这么个意思,再细致一点的理解就是:
将一组待排序,先分割成一个一个的元素,然后将这些元素先两两排序,再四四排序,八八排序,一直到排序完所有的元素。
也就是说归并排序其实就是将已有序的子序列合并,得到完全有序的序列。我们看一张动图来认识一下:
是不是有点理解了,动态图的作用其实就是为了方便你了解整个归并过程,不理解也没关系,我们再来看一张静态图,帮助你从细节上来理解。
上面的这张图基本上能把整个归并排序的流程了解清楚了。也就是文章开头提到的先切分再重组。下面我们使用代码来实现一下归并排序,并对其做一个改进:
基本实现的思想很简单,我们就是先切分成一个个元素,然后再合并就好了。
//合并方法
private static void merge(int[] arr, int low, int mid, int high, int[] temp) {
int i = low; // 初始化i, 左边有序序列的初始索引
int j = mid + 1; //初始化j, 右边有序序列的初始索引
int t = 0; // 指向temp数组的当前索引
//先把左右两边(有序)的数据按照规则填充到temp数组
//直到左右两边的有序序列,有一边处理完毕为止
while(i <= mid&&j <= high){
//如果左边的有序序列的当前元素,小于等于右边有序序列的当前元素
//即将左边的当前元素,填充到temp数组
//然后 t++, i++
if(arr[i] <= arr[j]){
temp[t] = arr[i];
t++;
i++;
}else{
//反之,将右边有序序列的当前元素,填充到temp数组
temp[t] = arr[j];
t++;
j++;
}
}
//把有剩余数据的一边的数据依次全部填充到temp
while( i <= mid) { //左边的有序序列还有剩余的元素,就全部填充到temp
temp[t] = arr[i];
t++;
i++;
}
while( j <= high) { //右边的有序序列还有剩余的元素,就全部填充到temp
temp[t] = arr[j];
t++;
j++;
}
//将temp数组的元素拷贝到arr
//注意,并不是每次都拷贝所有,这句话和递归有关,要理解有一定的难度
//因为这里并不是全部分完之后再合,而是分一点合一点
t = 0;
int tempLow = low; //
//第一次合并 tempLow = 0 , high = 1 // tempLow = 2 high = 3 // tL=0 ri=3
//最后一次 tempLow = 0 high = 7
while(tempLow <= high) {
arr[tempLow] = temp[t];
t += 1;
tempLow += 1;
}
}
//归并排序代码
//主要思路,先递归拆分数组,然后再合并数组
public static void mergeSort(int arr[],int low,int high,int temp[]){
if(low < high){
int mid = (low + high) / 2;
//左递归进行拆分
mergeSort(arr,low,mid,temp);
//右递归进行拆分
mergeSort(arr,mid + 1,high,temp);
//合并
merge(arr,low,mid,high,temp);
}
}
package com.homyit;
import java.util.Arrays;
public class MergeSortDemo {
//归并排序代码
//主要思路,先递归拆分数组,然后再合并数组
public static void mergeSort(int arr[],int low,int high,int temp[]){
if(low < high){
int mid = (low + high) / 2;
//左递归进行拆分
mergeSort(arr,low,mid,temp);
//右递归进行拆分
mergeSort(arr,mid + 1,high,temp);
//合并
merge(arr,low,mid,high,temp);
}
}
//合并方法
private static void merge(int[] arr, int low, int mid, int high, int[] temp) {
int i = low; // 初始化i, 左边有序序列的初始索引
int j = mid + 1; //初始化j, 右边有序序列的初始索引
int t = 0; // 指向temp数组的当前索引
//先把左右两边(有序)的数据按照规则填充到temp数组
//直到左右两边的有序序列,有一边处理完毕为止
while(i <= mid&&j <= high){
//如果左边的有序序列的当前元素,小于等于右边有序序列的当前元素
//即将左边的当前元素,填充到temp数组
//然后 t++, i++
if(arr[i] <= arr[j]){
temp[t] = arr[i];
t++;
i++;
}else{
//反之,将右边有序序列的当前元素,填充到temp数组
temp[t] = arr[j];
t++;
j++;
}
}
//把有剩余数据的一边的数据依次全部填充到temp
while( i <= mid) { //左边的有序序列还有剩余的元素,就全部填充到temp
temp[t] = arr[i];
t++;
i++;
}
while( j <= high) { //右边的有序序列还有剩余的元素,就全部填充到temp
temp[t] = arr[j];
t++;
j++;
}
//将temp数组的元素拷贝到arr
//注意,并不是每次都拷贝所有,这句话和递归有关,要理解有一定的难度
//因为这里并不是全部分完之后再合,而是分一点合一点
t = 0;
int tempLow = low; //
//第一次合并 tempLow = 0 , high = 1 // tempLow = 2 high = 3 // tL=0 ri=3
//最后一次 tempLow = 0 high = 7
while(tempLow <= high) {
arr[tempLow] = temp[t];
t += 1;
tempLow += 1;
}
}
public static void main(String[] args) {
int arr[] = {8, 4, 5, 7, 1, 3, 6, 2};
int temp[] = new int[arr.length]; //归并排序需要一个额外空间
System.out.println("排序前的结果为:" + Arrays.toString(arr));
mergeSort(arr, 0, arr.length - 1, temp);
System.out.println("排序后的结果为:" + Arrays.toString(arr));
}
}
输出结果如下:
排序前的结果为:[8, 4, 5, 7, 1, 3, 6, 2]
排序后的结果为:[1, 2, 3, 4, 5, 6, 7, 8]
希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率;但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位;
希尔排序的基本思想是:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。
public static void shellSort(int arr[]){
//遍历步长,每遍历了一轮步长除以2,直到小于等于0结束循环
for(int step = (arr.length) / 2;step > 0;step /= 2){
//遍历每个元素
for(int i = step;i < arr.length;i++){
//遍历本步长数组中的元素
for(int j = i - step;j >= 0;j -= step){
//将当前元素与加上步长的元素对比
//如果当前元素大舅交换位置
if(arr[j] > arr[j+step]){
//交换位置
int temp = arr[j];
arr[j] = arr[j + step];
arr[j + step] = temp;
}
}
}
}
}
package com.homyit;
import java.util.Arrays;
public class ShellSortDemo {
public static void shellSort(int arr[]){
//遍历步长,每遍历了一轮步长除以2,直到小于等于0结束循环
for(int step = (arr.length) / 2;step > 0;step /= 2){
//遍历每个元素
for(int i = step;i < arr.length;i++){
//遍历本步长数组中的元素
for(int j = i - step;j >= 0;j -= step){
//将当前元素与加上步长的元素对比
//如果当前元素大舅交换位置
if(arr[j] > arr[j+step]){
//交换位置
int temp = arr[j];
arr[j] = arr[j + step];
arr[j + step] = temp;
}
}
}
}
}
public static void main(String[] args) {
int arr[] = {8, 4, 5, 7, 1, 3, 6, 2};
System.out.println("排序前的结果为:" + Arrays.toString(arr));
shellSort(arr);
System.out.println("排序后的结果为:" + Arrays.toString(arr));
}
}
方法 | 时间复杂度(平均) | 时间复杂度(最好) | 时间复杂度(最坏) | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|
归并排序 | O(nlog2n) | O(nlog2n) | O(nlog2n) | O(n) | 稳定 |
希尔排序 | O(nlog2n)-O(n^2) | O(n) | O(n^2) | O(1) | 不稳定 |