排序算法之归并排序及利用归并排序求逆序数

排序算法之归并排序

1. 自顶向下的归并排序

中心思想

将待排序的数组平均切分为两半,将前半部分和后半部分分别进行排序,再讲两个有序数组归并到一个数组中

特点

递归,需要额外的空间(辅助数组)来保存切分完成的子数组,主要难点在于合并

操作步骤
  • 将待排序数组均分为两半
  • 对前半部分进行排序
  • 对后半部分进行排序
  • 合并两个子数组
  • 递归调用以上过程
代码实现
public void sort(int[] a){
    int N = a.length;;
    int b = new int[N];//创建辅助数组
    sort(a, 0, N-1);
}
public void sort(int[] a, int low, int high){
    if (low >= high) {
        return;
    }
    int mid = (low + high) / 2;//均分数组
    sort(a, low, mid);//排前半部分
    sort(a, mid, high);//排后半部分
    merge(a, low, high, mid);//归并
}

private void merge(int[] a, int low, int high, int mid){
    for(int i = low; i <= high; i++) {
        b[i] = a[i];
    }//将a[low...high]复制到b[low...high]
    int j = low;//扫描左半部分
    int k = mid + 1;//扫描右半部分
    for(int i = low; i <= high; i++){
        // 从b[low...high]中依次取出较小的元素放入a[low...high]
        if (j > mid) {
            a[i] = b[k++];//若左半部分元素用尽,取右半部分元素
        } else if (k > high) {
            a[i] = b[j++]//若右半部分元素用尽,取左半部分元素
        } else if (b[j] < b[k]) {
            a[i] = b[j++];//两半部分元素都未用尽时,取较小的
        } else {
            a[i] = b[k++];
        }
    }

}

2. 自底向上的归并排序

特点

不切分数组,直接两两归并(将每个元素当做大小为1的子数组)、四四归并(将两个大小为2的子数组归并为一个大小为4的数组)…最后对整个数组归并

优点

相对于自顶向下的归并,代码量少

代码实现
public void sort(int[] a){
    int N = a.length;
    int b[] = new int[N];
    sort(a, 0, N-1);
}

private void sort(int[] a, int low, int high){
    //sz为子数组的大小,依次为1,2,4,8...N
    for(int sz = 1; sz < N; sz = sz + sz){
    //i为子数组的索引,而子数组大小为N,因而当i移动到靠近边界时边界元素的索引要满足i+sz<N
        for(int i = low; i < N - sz; i = i + sz + sz){
            //当数组大小不是2的偶数倍时,最后一个子数组会比前一个要小,导致该子数组的索引不满足i+2sz-1,而是N-1
            merge(a, i, min(i+2sz-1, N-1), i+sz-1);
            //当N为2的偶数倍时,mid=(i+(i+2sz-1)-1)/2;非偶数倍时,mid=(i+N-1-1)/2,而i<N-sz,可推出此种情况下,(i+(i+2sz-1)-1)<(i+N-1-1),即N为偶数倍,mid可取mid=(i+(i+2sz-1)-1)/2
        }
    }
}

归并排序的应用-求逆序数

逆序数指的是逆序数对的个数,在一个序列a[0, 1, 2, …N]中,对任意的i和j,如果i < j且a[i] > a[j],则a[i]和a[j]为逆序数对,一个队列中所有的逆序数对数量之和即为该序列的逆序数。
蛮力法求逆序数
for(int i = 1; i < N; i++){
    for(int j = i - 1; j >= 0; j-- ){
        if(a[i] < a[j]){
            count++;
        }
    }
}

显然两个循环的存在导致此法复杂度为n的平方。

利用归并排序

merge过程中,由于两个子数组(假设为A和B)已各自有序,用i扫描A,用j扫描B,某次循环中,若a[i] > a[j],则该次循环中得到的逆序数count = A.length - i。对count进行累加后即可得到总逆序数。
由于归并排序时间复杂度为NlogN(数学问题,本文不做证明),因而优于蛮力法。

实现方式

只需在merge方法中计算逆序对数量即可

private void merge(int[] a, int low, int high, int mid){
    for(int i = low; i <= high; i++) {
        b[i] = a[i];
    }//将a[low...high]复制到b[low...high]
    int j = low;//扫描左半部分
    int k = mid + 1;//扫描右半部分
    for(int i = low; i <= high; i++){
        // 从b[low...high]中依次取出较小的元素放入a[low...high]
        if (j > mid) {
            a[i] = b[k++];//若左半部分元素用尽,取右半部分元素
        } else if (k > high) {
            a[i] = b[j++]//若右半部分元素用尽,取左半部分元素
        } else if (b[j] < b[k]) {
            a[i] = b[j++];//两半部分元素都未用尽时,取较小的
        } else {
            a[i] = b[k++];
            count = count + (mid - low + 1) - j;//累加该次循环的逆序对数量
        }
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值