算法学习-2

当我们在没有学习算法之前,在做排序问题的时候,首先想到的应该是选择排序或者是冒泡排序。其中选择排序可能是大脑最自然的思考方式。

很小的时候上体育课,在操场上,同学们零零散散站着,老师扫了一眼,叫了最高个同学站在到前面,然后再在剩余同学中选择最高的依次站成一排,重复此过程直到最后一个同学也站入队伍了后,队伍也就按高矮顺序排好了,自此我们潜意识的学习了选择排序法。一碰到排序问题,潜意识的联想到这种排序方式。

在我们进入高中或者大学,长大懂事了,上体育课时知道要站成一排一排,但一开始由于都喜欢跟自己玩的好的伙伴站在一起,站成的队列并没有按高低顺序站好。这时老师也变懒了,不会再一个个指定位置站了,只会说一句,你们按高矮顺序排列。我们就自个看看左右,如果右边的比自己矮就跟他换一个位置,这样过了一会队伍就按右到左从高到矮顺序排好了。这种排序方法差不多就是冒泡排序。

来源于实际生活中的体验能很好的被大脑所参考,所以我们在写排序算法,估计大多数人会写出这两种方法,而对于像插入排序,归并排序等等算法要是没有经过一定的训练和深入的了解,就算当时学会了,过了一段时间,想要写出来应该会很难。

以上都是题外话,进入主题,写出归并排序的伪代码:

归并排序是一种采用分治策略的排序方法,分治策略是学习算法的一个入门级的方法,讲起来很简单,当一个问题的求解过程规模比较大,步骤比较多的时候,把它拆分成2个或多个规模较小但是跟原问题一样的问题,然后分别求解,并把结果合并得到未拆分前问题的答案。把拆分后的子问题,重复这个过程,就是书面上说的递归操作,就可以把真正需要求解的问题分解成规模极小的问题,这些问题的答案往往是显然的。

如果只是会说出分治策略的思想,就觉得自己能很好的写出归并排序程序,就有点图样图森破了。

下面就按照上面表述的思路来试着写伪代码

//排序A[1,..,n]
//MERGE_SORT是一个procedure,是一个函数,是我们需要编写的函数,同时我们也要告诉自己他是一个已经实现的函数,他可以正确的排序传给它的数组参数。
MERGE_SORT(A) 
    n = A.length
    if n==1
        return //只有一个元素,必然是排好序的,也不需要分解成子问题求解,因此返回
    else
        //从中间等量分解为2个规模差不多的需要排序的数组,
        A1 = A[1,..,n/2]
        A2 = A[n/2+1,..,n]
        //排序A1,A2
        MERGE_SORT(A1)
        MERGE_SORT(A2)
        //A1,A2已排序,合并结果
        K=1,i=1;j=1
        for k = 1 to n
            if i<=A1.length and A1[i] <= A2[j]
                A[k] = A1[i]
                i = i+1
            else
                A[k] = A2[j]
                j = j+1
        //合并结束,A已经排好序

通过实践以后可知分治法最难的部分应该在合并结果上,这也是需要我们自己去思考编程最多的一部分。

下面贴出书本上的伪代码,从而可以比较一下自己写的代码有哪些不足及不好的地方

MERGE_SORT(A,p,r)
    if p < r
        q = (p + r)/2

    MERGE_SORT(A,p,q)
    MERGE_SORT(A,q+1,r)
    MERGE(A,p,q,r)

MERGE(A,p,q,r)
    n1 = q - p + 1
    n2 = r - q
    let L[1..n1+1] and R[1..n2+1] be new array
    for i=1 to n1
        L[i] = A[p+i-1]
    for j=1 to n2
        R[j] = A[q+j]

    L[n1+1] = ∞
    R[n2+1] = ∞

    i=1
    j=1
    for k=p to r
        if L[i] <= R[j]
            A[k] = L[i]
            i=i+1
        else
            A[k] = R[j]
            j=j+1

合并部分单独抽出来处理,可见编写归并排序算法主要实现的逻辑代码还是在归并操作,分治策略只是一个递归结构,了解就很简单。

该例程更加一般化,不但可以排序整个数组A(p=1,r=A.length),也可排序A的部分元素,在伪代码的编写上也更加清晰易懂。在合并操作中使用了一个小技巧,在两个子数组后面插入了一个哨兵元素防止对数组取值的时候越界。

再看出之前写的代码会发现出现数组越界问题

for k = 1 to n
    if i<=A1.length and A1[i] <= A2[j]
        A[k] = A1[i]
        i = i+1
    else
        A[k] = A2[j]
        j = j+1//当A1中最后一个元素最大时,A2把最后一个元素插入到A中后,会执行到这,然后在最后一次循环k=n时,A2[j]越界

修改:

for k = 1 to n
    if j > A2.length //防止越界
        A[k] = A1[i]
        i = i+1
    else if i > A1.length //防止越界
        A[k] = A2[j]
        j = j+1

    else if A1[i] <= A2[j]
        A[k] = A1[i]
        i = i+1
    else
        A[k] = A2[j]
        j = j+1

虽然正确,代码不够优雅简洁。

不要以为你会实现几个功能就说你会开发,会编程。

你可能感兴趣的:(算法学习)