【算法系列 | 4】深入解析排序算法之——归并排序

序言

你只管努力,其他交给时间,时间会证明一切。

文章标记颜色说明:

  • 黄色:重要标题
  • 红色:用来标记结论
  • 绿色:用来标记一级论点
  • 蓝色:用来标记二级论点

决定开一个算法专栏,希望能帮助大家很好的了解算法。主要深入解析每个算法,从概念到示例。

我们一起努力,成为更好的自己!

今天第3讲,讲一下排序算法的归并排序(Merge Sort)

1 基础介绍

排序算法是很常见的一类问题,主要是将一组数据按照某种规则进行排序。

以下是一些常见的排序算法:

  1. 冒泡排序(Bubble Sort)

  2. 插入排序(Insertion Sort)

  3. 选择排序(Selection Sort)

  4. 归并排序(Merge Sort)

  5. 快速排序(Quick Sort)

  6. 堆排序(Heap Sort)

一、基本介绍介绍

1.1 原理介绍

归并排序(Merge Sort)是一种基于分治思想的排序算法,它将待排序的数组分成两部分,分别对这两部分递归地进行排序,最后将两个有序子数组合并成一个有序数组。它的时间复杂度为 O(nlogn)。

归并排序的基本思路是将待排序的数组分成两个部分,分别对这两部分进行排序,然后将排好序的两部分合并成一个有序数组。这个过程可以用递归来实现。具体的实现步骤如下:

  1. 分解将待排序的数组不断分成两个子数组,直到每个子数组只有一个元素为止

  2. 合并:将相邻的两个子数组合并成一个有序数组,直到最后只剩下一个有序数组为止。

合并的过程中,需要用到一个辅助数组来暂存合并后的有序数组。具体来说,假设待合并的两个有序数组分别为 A 和 B,它们的长度分别为 n 和 m,合并后的有序数组为 C,那么合并的过程可以按如下步骤进行:

  1. 定义三个指针 i、j 和 k,分别指向数组 A、B 和 C 的起始位置。

  2. 比较 A[i] 和 B[j] 的大小,将小的元素放入 C[k] 中,并将对应指针向后移动一位。

  3. 重复步骤 2,直到其中一个数组的元素全部放入 C 中。

  4. 将另一个数组中剩余的元素放入 C 中。

归并排序的优点是稳定性好,即对于相等的元素,在排序前后它们的相对位置不会改变。缺点是需要额外的空间来存储辅助数组。

原理简单示例 

以下是一个示例,演示了如何使用归并排序对一个数组进行排序:

假设要对数组 [5, 2, 4, 6, 1, 3] 进行排序。

  1. 首先将数组分成两部分:[5, 2, 4] 和 [6, 1, 3]

  2. 对左右两部分分别递归调用归并排序。对于左半部分,继续进行分解,将其分成两部分:[5] 和 [2, 4]。对于右半部分,也进行相同的操作,将其分成两部分:[6] 和 [1, 3]

    对于 [5] 和 [2, 4],由于它们的长度都小于等于 1,因此直接返回它们本身。对于 [6] 和 [1, 3],同样返回它们本身。

  3. 接下来将排好序的左右两部分合并成一个有序数组。对于左半部分,由于它只有一个元素,因此可以直接将其作为有序数组。对于右半部分,需要将 [1, 3] 进行排序,排序后得到 [1, 3, 6]

  4. 将排好序的左右两部分合并成一个有序数组。对于左半部分,指针 i 指向其起始位置,即 0;对于右半部分,指针 j 指向其起始位置,即 0。比较左右两部分的元素大小,发现左半部分的第一个元素 5 大于右半部分的第一个元素 1,因此将 1 添加到新的数组 sorted_arr 中,并将右半部分的指针 j 向后移动一位。此时,sorted_arr 的内容为 [1]。接着比较左半部分的第二个元素 2 和右半部分的第一个元素 3,发现左半部分的元素较小,因此将 2 添加到 sorted_arr 中,并将左半部分的指针 i 向后移动一位。此时,sorted_arr 的内容为 [1, 2]。接着继续比较左右两部分的元素大小,将它们依次添加到 sorted_arr 中。最终得到排好序的数组 [1, 2, 3, 5, 6]

因此,对于输入的数组 [5, 2, 4, 6, 1, 3],使用归并排序后得到的排好序的数组为 [1, 2, 3, 4, 5, 6]

1.2 复杂度 

归并排序的时间复杂度为 O(nlogn),其中 n 是待排序数组的长度。

这个复杂度可以通过分治的思想来解释。

首先将待排序的数组分成两部分,对每一部分递归调用归并排序,然后将两部分合并成一个有序数组。

每次递归调用都将数组的长度减半,因此需要进行 logn 次递归调用。在每个递归层次中,需要将两个有序数组合并成一个有序数组,这一过程需要线性时间 O(n)。因此,归并排序的总时间复杂度为 O(nlogn)。

归并排序的空间复杂度为 O(n),其中 n 是待排序数组的长度。在排序过程中,需要使用一个辅助数组来存储合并后的有序数组。

这个辅助数组的长度等于待排序数组的长度,因此归并排序的空间复杂度为 O(n)。如果实现中使用链表来存储数据,空间复杂度可以降低为 O(1)。

1.3使用场景

归并排序的应用场景比较广泛,主要适用于以下几种情况:

  1. 对于大规模的数据排序:归并排序的时间复杂度为 O(nlogn),相比于其他排序算法如冒泡排序、插入排序等,它在处理大规模数据时更加高效。

  2. 对于稳定排序的需求:归并排序是一种稳定排序算法,即对于相等的元素,在排序前后它们的相对位置不会改变。

  3. 对于需要保证排序稳定性的需求:归并排序是一种基于比较的排序算法,不依赖于数据的初始状态,具有较好的稳定性。

  4. 对于需要多路排序的需求:归并排序可以轻松地扩展到多路排序,即将待排序的数组分成多个子数组,对每个子数组分别进行归并排序,然后将它们合并成一个有序数组。

  5. 对于需要外部排序的需求:归并排序可以应用于外部排序,即在排序过程中将数据存储在外部存储器中,而不是在内存中。在外部排序中,需要使用多路归并排序来合并不同的子文件。

总的来说,归并排序是一种高效、稳定的排序算法适用于大规模数据的排序、需要保证排序稳定性的需求以及外部排序等场景。

二、代码实现

2.1 Python 实现

以下是使用 Python 实现归并排序的完整代码:

def merge_sort(arr):
    if len(arr) <= 1:
        return arr

    # 将数组分成两个部分
    mid = len(arr) // 2
    left_half = arr[:mid]
    right_half = arr[mid:]

    # 对左右两部分分别递归调用归并排序
    left_half = merge_sort(left_half)
    right_half = merge_sort(right_half)

    # 合并左右两部分
    return merge(left_half, right_half)

def merge(left_half, right_half):
    i = j = 0
    merged = []

    # 比较左右两部分的元素,将较小的元素添加到 merged 中
    while i < len(left_half) and j < len(right_half):
        if left_half[i] < right_half[j]:
            merged.append(left_half[i])
            i += 1
        else:
            merged.append(right_half[j])
            j += 1

    # 将左右两部分中剩余的元素添加到 merged 中
    merged += left_half[i:]
    merged += right_half[j:]

    return merged

代码讲解 

这个实现使用了两个函数,一个是 merge_sort() 函数,用于进行递归调用,另一个是 merge() 函数,用于合并两个有序数组。下面对这两个函数进行详细讲解:

merge_sort() 函数

def merge_sort(arr):
    if len(arr) <= 1:
        return arr

    # 将数组分成两个部分
    mid = len(arr) // 2
    left_half = arr[:mid]
    right_half = arr[mid:]

    # 对左右两部分分别递归调用归并排序
    left_half = merge_sort(left_half)
    right_half = merge_sort(right_half)

    # 合并左右两部分
    return merge(left_half, right_half)
```

这个函数使用递归的方式对数组进行排序。对于输入的数组,首先判断其长度是否小于等于 1,如果是,则直接返回该数组。否则,将数组分成两个部分,分别对左半部分和右半部分递归调用 `merge_sort()` 函数。最后,将排好序的左右两部分合并成一个有序数组,并将其作为结果返回。需要注意的是,此处的 `merge()` 函数是在 `merge_sort()` 函数中调用的,因为只有在递归到最底层时才会对单个元素进行排序,而在其他情况下需要将数组分成两部分进行递归调用。

merge() 函数

def merge(left_half, right_half):
    i = j = 0
    merged = []

    # 比较左右两部分的元素,将较小的元素添加到 merged 中
    while i < len(left_half) and j < len(right_half):
        if left_half[i] < right_half[j]:
            merged.append(left_half[i])
            i += 1
        else:
            merged.append(right_half[j])
            j += 1

    # 将左右两部分中剩余的元素添加到 merged 中
    merged += left_half[i:]
    merged += right_half[j:]

    return merged
```

这个函数用于合并两个有序数组。在函数内部,使用两个指针 i 和 j 分别指向左右两部分的起始位置,以及一个新的数组 merged 来存储合并后的结果。合并的过程中,不断比较左右两部分的元素大小,并将较小的元素加入 merged 中。最后,将左右两部分中剩余的元素添加到 merged 中,最终返回 merged。

在实现归并排序时,需要注意以下几点:

  1. 判断数组长度是否小于等于 1:这一步是递归调用的终止条件,防止出现无限递归的情况。

  2. 将数组分成两部分:需要使用 Python 的切片操作来实现,将数组分成左右两部分。

  3. 对左右两部分进行递归调用:将左右两部分作为参数传入 merge_sort() 函数并进行递归调用,直到数组长度小于等于 1。

  4. 合并两个有序数组:使用 merge() 函数将排好序的左右两部分合并成一个有序数组。

测试 

在使用上述代码实现归并排序时,可以通过以下代码测试:

arr = [3, 5, 1, 9, 7, 2, 8, 4, 6]
print(merge_sort(arr))  # 输出 [1, 2, 3, 4, 5, 6, 7, 8, 9]

这个例子中,将一个无序的数组作为输入,调用 merge_sort() 函数进行排序,并输出排好序的结果。

总的来说,这个实现是一种简单而清晰的归并排序实现方式,适合初学者学习和理解。虽然这个实现的时间复杂度为 O(nlogn),但其空间复杂度为 O(n),因为在合并过程中需要额外的空间来存储排好序的元素,因此在处理大规模数据时可能会占用较多的内存。

2.2Java实现

以下是使用 Java 实现归并排序的代码:

public class MergeSort {
    public static void main(String[] args) {
        int[] arr = {3, 5, 1, 9, 7, 2, 8, 4, 6};
        mergeSort(arr, 0, arr.length - 1);
        System.out.println(Arrays.toString(arr)); // 输出 [1, 2, 3, 4, 5, 6, 7, 8, 9]
    }

    public static void mergeSort(int[] arr, int left, int right) {
        if (left >= right) {
            return;
        }

        int mid = (left + right) / 2;
        mergeSort(arr, left, mid);
        mergeSort(arr, mid + 1, right);
        merge(arr, left, mid, right);
    }

    public static void merge(int[] arr, int left, int mid, int right) {
        int[] temp = new int[right - left + 1];
        int i = left, j = mid + 1, k = 0;

        while (i <= mid && j <= right) {
            if (arr[i] < arr[j]) {
                temp[k++] = arr[i++];
            } else {
                temp[k++] = arr[j++];
            }
        }

        while (i <= mid) {
            temp[k++] = arr[i++];
        }

        while (j <= right) {
            temp[k++] = arr[j++];
        }

        for (int m = 0; m < temp.length; m++) {
            arr[left + m] = temp[m];
        }
    }
}

这个实现也使用了两个函数,一个是 mergeSort() 函数,用于进行递归调用,另一个是 merge() 函数,用于合并两个有序数组。

下面对这两个函数进行详细讲解:

mergeSort() 函数

public static void mergeSort(int[] arr, int left, int right) {
    if (left >= right) {
        return;
    }

    int mid = (left + right) / 2;
    mergeSort(arr, left, mid);
    mergeSort(arr, mid + 1, right);
    merge(arr, left, mid, right);
}


这个函数使用递归的方式对数组进行排序。

对于输入的数组和左右下标,首先判断左下标是否大于等于右下标,如果是,则直接返回。否则,将数组分成两个部分,分别对左半部分和右半部分递归调用 `mergeSort()` 函数。

最后,将排好序的左右两部分合并成一个有序数组,并将其作为结果返回。需要注意的是,此处的 `merge()` 函数是在 `mergeSort()` 函数中调用的,因为只有在递归到最底层时才会对单个元素进行排序,而在其他情况下需要将数组分成两部分进行递归调用。

merge() 函数

public static void merge(int[] arr, int left, int mid, int right) {
    int[] temp = new int[right - left + 1];
    int i = left, j = mid + 1, k = 0;

    while (i <= mid && j <= right) {
        if (arr[i] < arr[j]) {
            temp[k++] = arr[i++];
        } else {
            temp[k++] = arr[j++];
        }
    }

    while (i <= mid) {
        temp[k++] = arr[i++];
    }

    while (j <= right) {
        temp[k++] = arr[j++];
    }

    for (int m = 0; m < temp.length; m++) {
        arr[left + m] = temp[m];
    }
}

这个函数用于合并两个有序数组。在函数内部,使用两个指针 i 和 j 分别指向左右两部分的起始位置,以及一个新的数组 temp 来存储合并后的结果。

合并的过程中,不断比较左右两部分的元素大小,并将较小的元素加入 temp 中。

最后,将左右两部分中剩余的元素添加到 temp 中,最终将 temp 中的元素复制回原数组中。

需要注意的是,在复制回原数组时,需要计算出每个元素在原数组中的位置。

这个归并排序的实现是比较基础的,但是足以演示归并排序的算法思想和实现过程。当然,实际应用中可能需要对代码进行一些优化,比如可以对小数组使用插入排序来提高效率,或者使用迭代的方式来避免递归调用带来的额外开销。

图书推荐

图书名称:

  • 精通Hadoop3
  • pandas1.X实例讲解
  • 人人都离不开的算法——图解算法应用
  • Python数据清洗

精通Hadoop3

pandas1.X实例讲解

人人都离不开的算法——图解算法应用

Python数据清洗

活动说明

   618,清华社 IT BOOK 多得图书活动开始啦!活动时间为 2023 年 6 月 7 日至 6 月 18 日,清华社为您精选多款高分好书,涵盖了 C++、Java、Python、前端、后端、数据库、算法与机器学习等多个 IT 开发领域,适合不同层次的读者。全场 5 折,扫码领券更有优惠哦!快来京东点击链接 IT BOOK 多得,查看详情吧!

活动链接:IT BOOK

​ 

参与方式 

图书数量:本次送出 3 本   !!!⭐️⭐️⭐️
活动时间:截止到 2023-06-15 12:00:00

抽奖方式:

  • 评论区随机挑选小伙伴!

留言内容,以下方式都可以:

  • 文章高质量评论+【你想要的书名】
  • 要相信,所有的美好都是为了迎接美好,所有的困难都会为努力让道+【你想要的书名】

参与方式:关注博主、点赞、收藏,评论区留言 

中奖名单 

🍓🍓 获奖名单🍓🍓

 中奖名单:请关注博主动态

名单公布时间:2023-06-15 下午

名单详情:https://bbs.csdn.net/topics/615881211

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
社会演员多的头像社会演员多普通用户
上一篇 2023年12月14日
下一篇 2023年12月14日

相关推荐