数据结构复习总结之归并排序(五)

前两天就看了归并排序,一直还没实现,昨天晚上只是想了一下实现的过程,以为会了呢,,,,动手写了一遍代码才发现,仍然有问题,,,,记录一下,过几天再来做一遍!

原理概述

归并排序和快速排序一样都是需要用到递归的,

先来回顾一下快速排序
快速排序是首先把一个完整序列的第一个元素找到它的合适位置:位于该元素左边的元素都比它小,位于该元素右边的元素都比它大(这就需要low 和high 游标在移动的过程中可遇到不符合上述条件的元素需要交换其位置)

然后找到该元素的合适位置之后,就需要以该元素为分界点把原来的序列分为左右两个部分,对于这两个子序列仍然采用quick_sort快速排序的原理找到各自第一个元素的合适位置,在分别以该位置为分界点再继续分子序列,直到最后划分子序列之后只有一个元素,就停止return就行;
接下来再来看归并排序:
归并排序也是需要把原来的序列不断的划分成子序列,可以每次都是二分,然后对于每个子序列在进行二分,直到最后每个子序列划分之后的子序列都只含有一个元素,上述过程仅仅是完成了分割子序列,接下来需要对分割的子序列进行排序,然后归并为原来的组(怎么分的就怎么合,只不过跟原来相比元素的顺序变了,属于哪个组并没有改变)

接下来举个例子吧~~
1 5 7 3 9 2 4 0(需要排序的列表总共有8个元素)
首先把这个列表分为两个子列表:
1 5 7 3 ### #### 9 2 4 0

1 5 ### ### 7 3 ### ### 9 2 ### ### 4 0
1 #### 5 #### 7 #### 3 #### 9 #### 2 #### 4 #### 0

拆成单个元素之后进行合并(带有顺序的)
1 5 #### 3 7 #### 2 9 #### 0 4
1,3,5,7 #### 0,2,4,9
0,1,2,3,4,5,7,9
其实合并的步骤很简单,就是对于原来序列分成的两个子列表left_list 和right_list 进行排序
两个子列表都分别有一个游标:left_pointer 和right_pointer
当两个游标都没有指向各自列表的尾部时(while left_pointer > len(left_list) and right_pointer < len(right_list)
执行下面的判断,如果左边游标对应位置的值小就把左边的值追加到列表result中(result是一个merge_sort函数需要返回的列表,代表的是每次合并时排好序的列表)
另外需要注意的时有可能一边的列表已经走完了,循环就会推出,这时候需要把另一边的列表全都放到result的后面

另外递归调用结束的条件是每一个划分的子序列只有一个元素就直接return即可

代码实现
#**************************************归并排序**********************************************
def merge_sort(L):
    n=len(L)
    mid=n//2

    if n<=1:
        return L

    left_list=merge_sort(L[:mid])
    right_list=merge_sort(L[mid:])

    left_pointer,right_pointer=0,0
    result=[]  #存放每次合并的结果

    while left_pointer<len(left_list) and right_pointer<len(right_list):
        if left_list[left_pointer]<=right_list[right_pointer]: 
            result.append(left_list[left_pointer])
            left_pointer+=1
        else:
            result.append(right_list[right_pointer])
            right_pointer+=1

    result+=left_list[left_pointer:]
    result+=right_list[right_pointer:]

    return result



if __name__=="__main__":
    L=[4,2,1,6,3,9,5,7]
    print("归并排序之前:",L)
    L_sorted=merge_sort(L)
    print("归并排序之后:",L_sorted)

时间复杂度

回顾一下快速排序,它是找子序列的第一个元素的合适位置,依据这个位置来把原来的序列划分为左右两个序列,好的情况当然是该元素可以划分为左右两部分,总共需要遍历logn次 但是每一次遍历的复杂度都是n 因为左右夹击刚好把n长度的序列走一遍—–>O(n logn);

最坏的时间复杂度就是每次找第一个元素的合适位置它都是子序列的第一个元素,这样就不可以把原来的序列划分为左右两个部分,所以执行的仍然是n次遍历(从左走到右)每一次遍历仍然是n的复杂度所以最坏是n^2——>O(n^2)

再来看归并排序,由于它是从中间开始把原来的序列划分,所以一定是分为左右两个部分,总共的遍历次数就是logn (从n个子序列每次两部分合并直到为包含n个元素的完整序列 n–n/2–n/4——–1 (1,2,4,8,—n 共需要x次 2^x=n可知x=logn))
由于复杂度都是在合并的过程中才会进行比较,与n有关系,前面拆分的过程是跟n无关的 可以视为O(1),然后合并的时候由于需要比较n个元素的大小,两两之间比较,所以每一次遍历时间复杂度都是n 总的时间复杂度为nlogn (最优和最坏时间复杂度都是这个)

归并排序

归并排序是稳定的,因为在代码中当比较left_pointer和right_pointer位置的元素大小时,当两个元素相等时仍然是左边的先添加到result列表中

while left_pointer<len(left_list) and right_pointer<len(right_list):
        if left_list[left_pointer]<=right_list[right_pointer]: 
            result.append(left_list[left_pointer])
            left_pointer+=1

if条件中取了等号哦~~
这样就可以保证该算法的稳定性了~~

猜你喜欢

转载自blog.csdn.net/jiaowosiye/article/details/80493599