2744557306 发表于 2023-11-29 10:20

深入解析排序算法之——归并排序

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

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

冒泡排序(Bubble Sort)

插入排序(Insertion Sort)

选择排序(Selection Sort)

归并排序(Merge Sort)

快速排序(Quick Sort)

堆排序(Heap Sort)

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

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

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

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

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

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

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

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

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

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

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

假设要对数组 进行排序。

首先将数组分成两部分: 和 。

对左右两部分分别递归调用归并排序。对于左半部分,继续进行分解,将其分成两部分: 和 。对于右半部分,也进行相同的操作,将其分成两部分: 和 。

对于 和 ,由于它们的长度都小于等于 1,因此直接返回它们本身。对于 和 ,同样返回它们本身。

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

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

因此,对于输入的数组 ,使用归并排序后得到的排好序的数组为 。

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

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

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

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

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

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

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

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

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

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

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

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

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

二、代码实现
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

    # 对左右两部分分别递归调用归并排序
    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 < right_half:
            merged.append(left_half)
            i += 1
        else:
            merged.append(right_half)
            j += 1

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

    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

    # 对左右两部分分别递归调用归并排序
    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 < right_half:
            merged.append(left_half)
            i += 1
        else:
            merged.append(right_half)
            j += 1

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

    return merged
```

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

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

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

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

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

测试
在使用上述代码实现归并排序时,可以通过以下代码测试:arr =
print(merge_sort(arr))  # 输出 这个例子中,将一个无序的数组作为输入,调用 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)); // 输出
    }

    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;
        int i = left, j = mid + 1, k = 0;

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

        while (i <= mid) {
            temp = arr;
        }

        while (j <= right) {
            temp = arr;
        }

        for (int m = 0; m < temp.length; m++) {
            arr = temp;
        }
    }
}这个实现也使用了两个函数,一个是 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;
    int i = left, j = mid + 1, k = 0;

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

    while (i <= mid) {
        temp = arr;
    }

    while (j <= right) {
        temp = arr;
    }

    for (int m = 0; m < temp.length; m++) {
        arr = temp;
    }
}
这个函数用于合并两个有序数组。在函数内部,使用两个指针 i 和 j 分别指向左右两部分的起始位置,以及一个新的数组 temp 来存储合并后的结果。

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

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

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

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







页: [1]
查看完整版本: 深入解析排序算法之——归并排序