数据结构与算法 --- 排序算法(二)

引言

上一篇数据结构与算法 --- 排序算法(一)中,学习了冒泡排序,插入排序,选择排序这三种时间复杂度为 \(O(n^2)\) 的算法。实时上 \(O(n^2)\) 时间复杂度是非常高的,所以一般只适合小规模数据排序,那接下来,就在看一看时间复杂度为 \(O(nlog)\) 的算法:归并排序和快速排序。

分治算法思想

归并排序和快速排序的核心思想就是分治算法思想,所以先介绍一下分治算法思想:

分治算法思想简单来说就是将一个复杂的问题分解成几个较简单的子问题,再递归地解决这些子问题。通常遵循以下三个步骤:

  • 分解:将问题分解成几个较小的子问题,这些子问题必须是相同类型的问题,且解决这些子问题必须可以解决原问题。

  • 解决:递归地解决各个子问题,如果子问题足够小可以直接解决则解决,否则继续分解。

  • 合并:将子问题的解合并为原问题的解。

归并排序

归并排序(Merge Sort)是一种基于分治思想的排序算法。它的基本思路是将待排序的数组分成两个子序列,然后对每个子序列进行递归排序,最后将排好序的两个子序列合并成一个有序序列。

算法图解

来看一下归并排序的执行过程如下图:

image.png

接下来考虑如何使用C#代码实现一个归并排序算法?

一般归并排序就是通过递归实现的,那么在数据结构与算法 --- 递归(一)中总结了递归代码的编写技巧:写递推公式,寻找终止条件,最后将递推公式翻译为代码。

那么看一下归并排序的递归代码的递推公式为:

\[merge\_sort(p,r)=merge(merge(p,q),merge(q+1,r)), \quad (p<q<r) \]

其终止条件为:\(p\ge r\)

公式中 $ merge_sort(p,r) $ 表示对下标从 \(p\)\(r\) 的数组数据进行归并排序,然后将这个问题拆分成了两个子问题: \(merge(p,q)\)\(merge(q+1,r)\) ,其中下标 \(q\) 表示 \(p\)\(r\) 的中间位置,也就是\(\frac{p+r}{2}\),当这两个子数组排好序之后,再将这两个有序子数组合并(\(merge()\)),就完成了该数组的排序。

这里还需要着重讲解一下两个有序子数组的合并,实际上一般在这里合并方法使用的是双指针法,双指针法是合并两个有序数组最高效的算法,其时间复杂度为 \(O(m+n)\),其中 m 和 n 分别是两个数组的长度。
具体实现步骤如下:

  1. 定义两个指针 i 和 j,分别指向两个有序数组的起始位置。
  2. 定义一个空数组 temp,用于存储合并后的有序数组。
  3. 比较两个指针所指的元素大小,将较小的元素加入 temp 数组中,并将对应的指针向后移动一位。
  4. 重复步骤 3,直到其中一个指针超出了数组的范围。
  5. 将另一个数组中剩余的元素加入 temp 数组中。
  6. 最后将temp中的元素拷贝回arr中,完成排序。

综上,用C#代码实现一个归并算法如下:

public static void MergeSort(int[] arr, int left, int right)
{
    if (left < right)
    {
        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)
{
    //双指针法合并数据到temp数组
    int i = left, j = mid + 1, k = 0;
    
    int[] temp = new int[right - left + 1];
    
    while (i <= mid && j <= right)
    {
        if (arr[i] <= arr[j])
        {
            temp[k] = arr[i];
            i++;
        }
        else
        {
            temp[k] = arr[j];
            j++;
        }
        k++;
    }
    //当一边遍历完成后,将另一边剩余元素直接放入temp。
    while (i <= mid)
    {
        temp[k] = arr[i];
        i++;
        k++;
    }
    while (j <= right)
    {
        temp[k] = arr[j];
        j++;
        k++;
    }
    for (int p = 0; p < k; p++)
    {
        arr[left + p] = temp[p];
    }
}

posted @ 2023-08-13 18:29  NiueryDiary  阅读(23)  评论(0编辑  收藏  举报