O(1)空间 的归并排序

一、 原始的归并排序

归并排序的主算法:

public void mergeSort(int[] T, int low, int high) {
    if (high - low < 2) {
        return;
    }
    int mid = (low + high) >> 1;
    mergeSort(T, low, mid);
    mergeSort(T, mid, high);
    merge(T, low, mid, high);
}

下面是merge过程的一种简单实现:

private void merge(int[] T, int low, int mid, int high) {
    int[] tempArray = new int[mid-low];
    // 先把左侧的部分拷贝到临时数组中
    for (int i=low; i<mid; i++) {
        tempArray[i-low] = T[i];
    }

    // merge T[mid, high) & tempArray[0, mid-low)
    int i = low, j = mid, k = 0;
    while (j < high && k < mid-low) {
        if (T[j] < tempArray[k]) {
            T[i++] = T[j++];
        } else {
            T[i++] = tempArray[k++];
        }
    }
    // 拷贝临时数组中剩余的元素
    while (k<mid-low) {
        T[i++] = tempArray[k++];
    }
}

虽然上面的merge过程还能继续优化,但总的来说,这类方法都必须有一个额外的数组来辅助merge过程,该数组的长度至少为n/2。
因此归并排序的空间复杂度为O(n),时间复杂度为O(n*logn)。

二、 一种新的merge方法

下面提出一种只需O(1)空间的merge算法。

2.1 不变性

2.1.1 区域划分

在merge过程中,始终将整个数组划分为4个区域,如下图所示:
区域划分
其中,

  • S区的范围是 [low, a),在图中是白色 。S区的元素均为已经就位的元素,即已经摆放到正确位置上的元素。
  • A区的范围是[a, b),在图中是红色。
  • B区的范围是[b, c),在图中是绿色。
  • C区的范围是[c, high),在图中是蓝色。

2.1.2 有序性

在每一个区域中,元素都是从小到大排列的。

2.2 初始化

从merge函数的定义来看:

merge(int[] T, int low, int mid, int high) 

是将 T[low, mid) 和 T[mid, high) 这两个有序的部分合并成一个有序的数组

因此,我们可以初始化成下图中的形式:
初始化
此时,只有A和C两个区域,而S区和B区都是空的。

2.3 迭代

不断的比较T[a], T[b], T[c] 三个元素的大小,选出其中的最小值。

这里先不讨论元素相等的情况(在代码实现部分讨论等号),因此只可能有如下3种情况:

2.3.1 T[c]最小

在这里插入图片描述

在这里插入图片描述
此时,先将a和c位置上的元素交换,然后再执行 a++, c++。操作完成后,S区中的元素增加一个,A区元素减少一个,
B区元素增加一个,C区元素减少一个。

这是唯一能让B区元素增加的情况

2.3.2 T[b]最小

在这里插入图片描述

在这里插入图片描述
此时,我们先递归调用merge(b, c, high) 来将B区和C区两个有序数组合并成一个有序数组。
再执行 c=b。操作完成后,B区元素全部清零。

2.3.3 T[a]最小

在这里插入图片描述
T[a]最小时,B区一定不存在($2.3.5),即 b==c

此时,执行操作 a++ 。操作完成后,S区中的元素就增加了一个,A区中的元素减少一个。

2.3.4 A区消失和C区消失

因为迭代过程中执行了a++和c++操作,会导致A区元素和C区元素减少,当区域中元素数量减少到0时,该区域就消失了。
此时可以做如下处理:
在这里插入图片描述
操作完成后,又回到了初始状态:未就位的部分只剩下A区和C区。

2.3.5 为什么T[a]最小时,B区一定不存在 ?

由2.3.1节可知,B区元素增加的唯一途径是——从A区的开头交换元素到B区的末尾。
由于A区原本就是有序的,因此B区中的所有元素都会小于或等于A区中的第一个元素,

在不考虑等号的情况下,"T[a]为T[a]、T[b]、T[c]中的最小值"这一现象说明T[a] < T[b],
这与上面的结论矛盾,因此B区一定不存在。

2.4 终止

当 A、B、C三个区域其中两个为空时,数组整体有序,算法终止。

三、代码实现

/**
 * Description: 归并排序
 *
 * @author zouxiang
 * @date 2020/10/26
 */
public class MergeSort {
    public void mergeSort(int[] T) {
        mergeSort(T, 0, T.length);
    }

    public void mergeSort(int[] T, int low, int high) {
        if (high - low < 2) {
            return;
        }
        int mid = (low + high) >> 1;
        mergeSort(T, low, mid);
        mergeSort(T, mid, high);
        merge(T, low, mid, high);
    }

    protected void merge(int[] T, int low, int mid, int high) {
        int a = low, b = mid, c = mid;
        while (a != c) { // A、B、C中至少存在两个区域
            if (a == b) { // A区不存在
                b = c;
            } else if (T[b] < T[c]) { // T[b]最小
                merge(T, b, c, high);
                c = b;
            } else if (T[a] <= T[c]) { // T[a] 最小
                a++;
            } else { // T[c]最小
                swap(T, a++, c++);
                if (c == high) { // C区不存在
                    c = b;
                }
            }
        }
    }

    private void swap(int[] T, int x, int y) {
        int temp = T[x];
        T[x] = T[y];
        T[y] = temp;
    }
}

四、性能

下面的复杂度是根据实验结果来估计的。

从实验结果来看,这个merge过程的时间复杂度应该是 O(N*logN),因此整个归并排序的时间复杂度不会超过 O(N*logN*logN)

空间上,新的merge过程已经不需要在堆上的额外数组空间的分配。但是在栈上的调用深度为O(logN),与归并排序的主算法递归深度一样;且当mergeSort深度约大时,merge的深度就越小。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值