常用排序算法之归并排序

前面给大家分享了冒泡排序、插入排序和选择排序,他们的时间复杂度都是O(n2),比较高,适合小规模的数据排序。今天我们就分享一种时间复杂度为O(nlogn)的算法,它就是–归并排序。

归并排序原理

它的原理比较简单,就是将一个数组从中间分为两部分然后分别进行排序,最后将排好序的两部分合并到一起,这样数组就有序了。

原理如下图: 在这里插入图片描述

归并排序用到了分治思想,字面就是分而治之,将一个大问题分成若干个小问题单独进行处理,后面我们再单独讲下。

从上图也可以看出,归并用到了递归,归并一般都是通过递归来实现的。归并是一种解决问题的思想,递归是一种编程技巧。

代码示例

Go示例:


package main

import "fmt"

func MergeSort(arr []int) {
  len := len(arr)
  if len <= 1 {
    return
  }

  mergeSort(arr, 0, len-1)
}

func mergeSort(arr []int, start, end int) {
  if start >= end {
    return
  }
  middle := (start + end) / 2
  mergeSort(arr, start, middle)
  mergeSort(arr, middle+1, end)
  merge(arr, start, middle, end)
}

func merge(arr []int, start, middle, end int) {
  tempArr := make([]int, end-start+1)
  i := start
  j := middle + 1
  k := 0
  for ; i <= middle && j <= end; k++ {
    if arr[i] <= arr[j] {
      tempArr[k] = arr[i]
      i++
    } else {
      tempArr[k] = arr[j]
      j++
    }
  }

  for ; i <= middle; i++ {
    tempArr[k] = arr[i]
    k++
  }
  for ; j <= end; j++ {
    tempArr[k] = arr[j]
    k++
  }
  copy(arr[start:end+1], tempArr)
}

func main() {
  arr := []int{8, 3, 4, 5, 9, 2, 1}
  MergeSort(arr)
  fmt.Println(arr)
}

PHP示例:

function merge_sort($nums)
{
    if (count($nums) <= 1) {
        return  $nums;
    }

    merge_sort_c($nums, 0, count($nums) - 1);
    return $nums;
}

function merge_sort_c(&$nums, $p, $r)
{
    if ($p >= $r) {
        return;
    }

    $q = floor(($p + $r) / 2);
    merge_sort_c($nums, $p, $q);
    merge_sort_c($nums, $q + 1, $r);

    merge($nums, ['start' => $p, 'end' => $q], ['start' => $q + 1, 'end' => $r]);
}

function merge(&$nums, $nums_p, $nums_q)
{
    $temp = [];
    $i = $nums_p['start'];
    $j = $nums_q['start'];
    $k = 0;
    while ($i <= $nums_p['end'] && $j <= $nums_q['end']) {
        if ($nums[$i] <= $nums[$j]) {
            $temp[$k++] = $nums[$i++];
        } else {
            $temp[$k++] = $nums[$j++];
        }
    }

    if ($i <= $nums_p['end']) {
        for (; $i <= $nums_p['end']; $i++) {
            $temp[$k++] = $nums[$i];
        }
    }

    if ($j <= $nums_q['end']) {
        for (; $j <= $nums_q['end']; $j++) {
            $temp[$k++] = $nums[$j];
        }
    }

    for ($x = 0; $x < $k; $x++) {
        $nums[$nums_p['start'] + $x] = $temp[$x];
    }
}

$nums = [4, 5, 6, 3, 2, 1];
$nums = merge_sort($nums);
print_r($nums);

JS示例:


const mergeArr = (left, right) => {
    let temp = []
    let leftIndex = 0
    let rightIndex = 0

    while (left.length > leftIndex && right.length > rightIndex) {
        if (left[leftIndex] <= right[rightIndex]) {
            temp.push(left[leftIndex])
            leftIndex++
        } else {
            temp.push(right[rightIndex])
            rightIndex++
        }
    }

    return temp.concat(left.slice(leftIndex)).concat(right.slice(rightIndex))
}

const mergeSort = (arr) => {
    if (arr.length <= 1) return arr
    const middle = Math.floor(arr.length / 2)
    const left = arr.slice(0, middle)
    const right = arr.slice(middle)
    return mergeArr(mergeSort(left), mergeSort(right))
}

const testArr = []
let i = 0
while (i < 100) {
    testArr.push(Math.floor(Math.random() * 1000))
    i++
}

const res = mergeSort(testArr)
console.log(res)

性能分析
最后我们看下归并排序的性能和稳定性:

  • 时间复杂度:是O(nlogn),要优于冒泡和插入排序
  • 空间复杂度:需要额外的空间存放排序的数据,不是原地排序
  • 算法稳定性:不涉及相等元素位置交换,是稳定的排序算法

归并排序时间复杂度计算方式:
归并的思想将一个复杂的问题a拆解为b和c,再将子问题合并计算结果,最终得到问题的答案,这里我们将归并排序总的时间复杂度设为 T(n),则 T(n) = 2*T(n/2) + n,其中 T(n/2) 是递归拆解的第一步对应子问题的时间复杂度,n 则是合并函数的时间复杂度(一个循环遍历),依次类推,我们可以推导 T(n) 的计算逻辑如下:

T(n) = 2*T(n/2) + n
        = 2*(2*T(n/4) + n/2) + n = 4*T(n/4) + 2*n
        = 4(2*T(n/8) + n/4) + 2*n = 8*T(n/8) + 3*n
        = ...
        = 2^k*T(n/2^k) + k*n

递归到最后,T(n/2k)≈T(1),也就是 n/2k = 1,计算归并排序的时间复杂度,就演变成了计算 k 的值,2k = n,所以 k=log2n,我们把 k 的值带入上述 T(n) 的推导公式,得到:

T(n) = n*T(1) + n*log2n = n(C + log2n) //其中2为log的下标
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值