归并排序(自底向上)

归并排序(自底向上)

算法概述

自底向上归并排序是一种非递归排序算法。它首先把待排序的数组拆分为多个长度为 2 的子序列(不足2的部分也作为一个子序列看待),接着对每个子序列进行排序,保证每个子序列内部是有序的,然后在这个基础上把数组拆分为多个长度为 4 的子序列,对每个子序列进行排序,以此类推,每次迭代子序列的长度翻倍,直到子序列长度能覆盖数组长度,最终完成排序。

算法实现

package main

func SortBU(arr []int) {
  n := len(arr)
  temp := make([]int, n)

  // 对数组中的每 16 个元素进行插入排序处理,16 是一个经验值
  // 数据量少和基本有序的情况下插入排序效率更高
  for i := 0; i < n; i += 16 {
    InsertionSort(arr, i, min(n-1, i+15))
  }

  // 划分子序列,子序列的半长以 16 为起始值(不再重复处理插入排序中已排序的部分),每次翻倍
  for sz := 16; sz < n; sz += sz {
    // 对每个子序列进行归并排序
    for i := 0; i+sz < n; i += 2 * sz {
      // 子序列前一半的最后一个元素大于后一半的第一个元素时,证明子序列内部存在乱序需要进行归并
      if arr[i+sz-1] > arr[i+sz] {
        // min(i+sz+sz-1, n-1) 用于处理 i+sz+sz-1 大于 n-1 的情况
        merge(arr, temp, i, i+sz-1, min(i+sz+sz-1, n-1))
      }
    }
  }
}

func InsertionSort(arr []int, left, right int) {
  for i := left + 1; i <= right; i++ {
    temp := arr[i]
    j := i - 1

    for ; j >= left && arr[j] > temp; j-- {
      arr[j+1] = arr[j]
    }
    arr[j+1] = temp
  }
}

func merge(arr, temp []int, l, mid, r int) {
  // 把 arr 指定位置的元素复制到 temp 保存副本
  // 后续排序时就是取 temp 中指定位置的前半部分和后半部分元素进行比较,出结果后再放到 arr 中合适的位置
  copy(temp[l:r+1], arr[l:r+1])
  // i 为左子序列的起始位置,j 为右子序列的起始位置
  i, j := l, mid+1

  // 对子序列进行排序
  for k := l; k <= r; k++ {
    // 子序列的左边已经处理完毕
    if i > mid {
     arr[k] = temp[j]
     j++
    // 子序列的右边已经处理完毕
    } else if j > r {
     arr[k] = temp[i]
     i++
    } else if temp[i] <= temp[j] {
     arr[k] = temp[i]
     i++
    } else {
     arr[k] = temp[j]
     j++
    }
  }
}
### 归并排序自底向上的实现原理 归并排序通常以递归的方式实现,即通过不断分割数组直到每个子数组只有一个元素(此时自然有序),然后再逐步合并这些子数组形成更大的有序数组。然而,归并排序也可以通过一种迭代的方式来实现——称为 **自底向上** 的方法。 #### 自底向上的基本思路 在自底向上归并排序中,初始时将整个数组视为由许多长度为 1 的子数组组成。随后,按照固定的步长依次两两合并相邻的子数组,每次合并后的子数组长度会加倍。这一过程持续进行,直至所有子数组被合并成一个完整的有序数组为止[^2]。 以下是具体的步骤描述: 1. 将输入数组看作是由若干个长度为 `1` 的已排序子数组构成。 2. 使用循环控制变量定义当前处理的子数组大小 `step`,初始化为 `1`。 3. 对于每一轮合并操作,遍历整个数组并将相邻的两个子数组按顺序合并到临时存储空间中。 4. 合并完成后更新原始数组的内容,并使 `step *= 2` 进入下一轮更大规模的合并。 5. 当 `step >= 数组长度` 时结束算法执行。 这种非递归形式避免了大量的函数调用开销,在某些场景下可以带来更好的性能表现[^2]。 ```java public class MergeSortBottomUp { public static void sort(int[] array) { int n = array.length; int[] temp = new int[n]; // 辅助数组用于暂存中间结果 for (int step = 1; step < n; step *= 2) { // 控制子数组大小的增长 for (int leftStart = 0; leftStart < n - step; leftStart += 2 * step) { int mid = leftStart + step - 1; int rightEnd = Math.min(leftStart + 2 * step - 1, n - 1); merge(array, temp, leftStart, mid, rightEnd); // 执行局部区域内的归并 } } } private static void merge(int[] array, int[] temp, int left, int mid, int right){ System.arraycopy(array, left, temp, left, right-left+1); int i = left; int j = mid + 1; int k = left; while(i<=mid && j<=right){ if(temp[i]<=temp[j]){ array[k++] = temp[i++]; }else{ array[k++] = temp[j++]; } } while(i<=mid){ array[k++] = temp[i++]; } while(j<=right){ array[k++] = temp[j++]; } } } ``` 以上代码片段展示了一个典型的基于 Java 实现的自底向上归并排序逻辑[^1]。 ### 自底向上实现的优势 相比传统的递归版本,自底向上归并排序具备以下几个显著优点: - **减少栈溢出风险**:由于完全摒弃了递归结构,因此不会因为过深的递归层次而导致栈溢出问题发生,尤其适用于超大规模的数据集处理环境[^2]。 - **提高缓存命中率**:连续访问内存单元有助于提升现代计算机体系架构下的 CPU 缓存利用率,从而间接加快程序运行速度。 - **简化边界条件管理**:无需显式地追踪每一层递归的状态变化情况,使得整体设计更加清晰简洁。 尽管如此,需要注意的是,对于大多数日常应用场景而言,默认采用标准库提供的高效排序接口往往更为合理;只有当特定需求明确指向某类定制化解决方案时才考虑手动调整底层算法细节[^2]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值