排序算法:归并排序(merge sort)

553 阅读3分钟

1/什么是归并排序?

归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法,是一种内排序(在内存中完成)。
该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。

和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,
因为归并排序始终都是 O(nlogn) 的时间复杂度,代价是需要额外的内存空间。
也就是说:用空间换时间。

2/什么是分治法

顾名思义:分而治之。
**就是把一个大问题分解成相似的小问题,通过解决这些小问题,最后用小问题的解构造大问题的解。**

听起来是不是和之前讲递归的时候很像?
没错,**分治法基本都是可以用递归来实现的**。

在之前,我们没有加以区分递归和分治法,当然现在我也认为不需要加以区分,
但你如果非要问它们之间是什么区别,我的理解是:
- **递归是一种编程技巧**,一个函数自己调用自己就是递归;
- **分治法是一种解决问题的思想**    - 把大的问题分解成小问题的这个过程就叫“分”,
    - 解决小问题的过程就叫“治”,
    - 解决小问题的方法往往是递归。

**所以分治法的三大步骤是:**
   「分」:大问题分解成小问题;
   「治」:用同样的方法解决这些小问题;
   「合」:用小问题的解构造大问题的解。

**那回到我们的归并排序上来:**
    分」:把一个数组拆成两个;
   「治」:用归并排序去排这两个小数组;
   「合」:把两个排好序的小数组合并成大数组。

这里还有个问题,就是什么时候能够解决小问题了?也就是什么时候拆分完毕了?
答:当只剩一个元素的时候,直接返回就好了,分解不了了。 
    这就是递归的 base case,是要直接给出答案的。

3/算法步骤

归并排序使用**分而治之**的概念对给定的元素列表进行排序。
它将问题分解为较小的子问题,直到它们变得足够简单以至可以直接解决为止。

以下是归并排序的步骤:
    1.将给定的列表分为两半(如果列表中的元素数为奇数,则使其大致相等)。
    2.以相同的方式继续划分子数组,直到只剩下单个元素数组。
    3.从单个元素数组开始,**合并**子数组,以便对每个合并的子数组进行排序。
    4.重复第 3 步单元,直到最后得到一个排好序的数组。

4/示例

待排序列表为[5,2,1,0]
Step1:
先拆成两半,
分成两个数组:{5, 2} 和 {1, 0}

image.png

Step2.
没到 base case,所以继续把大问题分解成小问题
当然了,虽然左右两边的拆分我都叫它 Step2,但是它们并不是同时发生的

image.png

Step3.
这一层都是一个元素了,是 base case,可以返回并合并了。

image.png

Step4.
合并的过程就是按大小顺序来排好,这里借助`两个指针`来比较,以及一个**额外的数组**来辅助完成。

image.png

比如在最后一步时,数组已经变成了:{2, 5, 0, 1},
那么通过两个指针 i 和 j,比较指针所指向元素的大小,把小的那个放到一个新的数组?里,然后指针相应的向右移动。

image.png

5/代码

   import random 
   
   temp_list = []
   for i in range(20):
       temp_list.append(random.randint(1,100))

   def merge_sort(temp_list): 
       n = len(temp_list)
       
       if n <= 1:
           return temp_list
          
       # 使用二分法进行拆分 
       mid = n // 2 
       left = temp_list[:mid] 
       right = temp_list[mid:] 
       # 函数自己调用自己,属于递归
       return f( merge_sort(left), merge_sort(right) ) 
          
          
   def f(left,right): 
       # 排序合并两个数列 
       final_list = [] # 定义一个额外的列表,来存放最后的结果
       
       # 两个数列都有值 
       while len(left) > 0 and len(right) > 0: 
           # 左右两个数列第一个最小放前面 
           if left[0] <= right[0]: 
               # 这里用的是pop()
               # 因为不仅要把元素加入到final_list中,还要从left中删除掉
               final_list.append( left.pop(0) ) 
           else: 
               final_list.append( right.pop(0) ) 
       
       # 只有一个数列中还有值,直接添加 
       # 把剩下的元素直接添加到result_list的末尾
       final_list += left 
       final_list += right 
       
       return final_list 
       
   print(merge_sort(arr))