zoukankan      html  css  js  c++  java
  • 算法抽象及用Python实现具体算法

    一、算法抽象

        它们一般是在具体算法的基础上总结、提炼、分析出来的,再反过来用于指导解决其它问题。它们适用于某一类问题的解决,用辩

    证法的观点看,抽象的算法和具体的算法就是抽象与具体、普遍性与特殊性、共性和个性的关系。马是白马的抽象,无论是白马还是红

    马,都是马,我们用马的唯一本质属性——染色体来决定一种动物是否是马,这个本质属性就是马的抽象。

    (1)分治法

        分治法的基本思想是先分割原问题,将原问题分割成一个或多个简单的子问题,这些子问题的解经过一定组合得到原问题的解,而求

    解这些子问题时,常常会再次分解,组合,即迭代上面的过程。再进一步总结可以得到分治法的步骤:分解、求解子问题、组合。而至

    于怎么分解、组合,分治法没有抽象出规律。自己想怎么做就怎么做,比如归并排序和快速排序,都是用的分治法,但分解和组合过程

    不一样。分治法适用于解决易分解为子问题的问题。

        我们可以给出分治法的伪代码框架,针对某个具体的问题,需进行适当的修改,比如参数个数等,由于数学功底不是很牛B,我没有办

    法证明该框架适用于所有的递归函数和所有基于分治法的算法,但我还没有发现不适用的情况。还有,如果子结节是直接在原问题对象上进

    行的操作,那有时子结节就不需要返回值(返回None就行)给父结点了,父结点也不会用这些结点的返回值,因为所有结点操作的是同

    一个共享资源,这点见快速排序;另外,对于像二分查找,对于任意一次查找的过程,它其实只是走的某个结点到根结点的路径而已,它

    每次分解后只用到其中一个结点,这样它也就没有merge()可言了。

        看了下面代码,是不是感觉跟MapReduce的思想有些相似,是的,MapReduce其实是就是基于分治法思想的。MapReduce中的Map

    就对应下面的map()方法,而Reduce其实是merge()方法的一种特例而已,因为merge()是根据具体问题而定的,不一定就是迭代函数。

    因为MapReduce是基于分治法的,所以适用场景与只能是分治法适用场景的一个子集。

    def handler(obj):
        end_ret = is_end(obj) 
        if end_ret[0]== True: #递归结束点
            return end_ret[1]   
        else:                 #非结束点,递归调用
           subnode_tuple = separate(obj) #该节点分割,分割为一个或多个子节点
           subnode_rettuple = map(handler, subnode_tuple) #求解每个子节点的解,递归调用
           node_ret = merge(subnode_rettuple,obj) #将子节点的解组合,组合时有时也会用到节点的某些数据
           return node_ret  

       

    二、 具体算法

    (1)冒泡排序

        这是最简单的一种排序方法,每次遍历后找出最大的数,冒泡排序是稳定排序,时间复杂度为O(n^2)

    OK,我们先看一下一次遍历怎么做,由简单到复杂,这样把问题分解一下,其实感觉并没有那么难了,这也是分治法的思想。

    #一次排序
    def bubble_sort(s):
        i = 0
        for index in range(i,len(s)):
            if s[index] > s[i]:
                s[i],s[index] = s[index],s[i]
        return s     

    然后把代码修改一下,真正的冒泡排序出来如下

    #冒泡排序
    def func(s):
        length = len(s)
        for i in range(length):
            for index in range(i,length):
                if s[index] > s[i]:
                    s[i],s[index] = s[index],s[i]
        return s

      

    (2)快速排序

        快速排序其实是用的分治法的思想,归并排序也是用的分治法,同样都是分治法,但策略不同。我们在“分治法”中提到分治法分为三

    个步骤:分解、求解子问题、组合。快速排序和归并排序的不同体现在分解上,分解不同,后面的组合也常常不同。快速排序在分解时

    会有一定的排序,也就是边分解边求解,它们是在原对象上直接进行的操作,操作完也不需要组合。归并的主要工作在组合上,分解过

    程非常简单,归并的过程也完全可以在原对象(原序列)上进行。

        快速排序一次分割会把序列中的某一个元素(任意选择一个即可)确定好位置,并把序列以该元素为界分为两部分,一部分大于等于

    该元素,另一部分小于等于该元素,然后再递归分解这两部分(确定好位置的那个元素无需再排序,不用放在这两部分中),直到分割

    的部分只有一个元素或为空。递归就是树的分解过程,子节点可以直接操作原问题对象(原序列),操作完之后,相当于原序列的某个

    部分就是有序的了。这样子结节无需再返回值让父节点用了。下面是快速排序代码,第一个没用分治法框架,第二个用了。子结点和叶

    节点其实不需用返回值,但为了更好的用分治法框架理解,还是让他们返回值好了。

    #-*- coding=utf-8 -*-
    def qsort(s,start_index,end_index):
        if start_index>=end_index:
            if start_index == end_index:
    return [s[start_index]]
    else:
    return []
    else: i = separate(s,start_index,end_index) #不用考虑索引为i的元素,它已经排好序了 left_ret = qsort(s,start_index,i-1) right_ret = qsort(s,i+1,end_index) node_ret = left_ret + [s[i]] + right_ret
    return node_ret
    #分治,排好start_index的位置,并将序列分为两部分 def separate(s,start_index,end_index): key = s[start_index] empty_index = start_index #空位指针 track_index = end_index #跟踪指针 while empty_index != track_index: if track_index > empty_index: if s[track_index] < key: s[empty_index] = s[track_index] track_index,empty_index = empty_index,track_index track_index +=1 else: track_index -=1 else: if s[track_index] > key: s[empty_index] = s[track_index] track_index,empty_index = empty_index,track_index track_index -=1 else: track_index +=1 s[empty_index] = key return empty_index a = [12,3,9,10,87,25,8,9,47,11,1,100,85,47,59,81,60,33] b = qsort(a,0,len(a)-1) print a print b


    使用分治法框架,与不使用的代码相比,只是微小的重构而已。

    # -*- coding=utf-8 -*-
    def qsort(obj):
        s = obj[0]
        start_index = obj[1]
        end_index = obj[2]
        end_ret = is_end(obj)
        if end_ret[0]== True:
            return end_ret[1]
        else:
            i = separate(obj)
            #不用考虑索引为i的元素,它的位置已经确定了
    subnode1 = (s,start_index,i-1)
    subnode2 = (s,i+1,end_index)
    subnode_tuple = (subnode1,subnode2) subnode_rettuple = map(qsort,subnode_tuple) node_ret = merge(subnode_rettuple,obj[0][i]) return node_ret def is_end(obj):
    s = obj[0]
    start_index
    = obj[1]
    end_index
    = obj[2]
    if start_index>=end_index: if start_index == end_index: return (True,[s[start_index]]) else: return (True,[]) else: return (False,None) #分治,排好start_index的位置,并将序列分割成两部分 def separate(obj): s = obj[0] start_index = obj[1] end_index = obj[2] key = s[start_index] empty_index = start_index #空位指针 track_index = end_index #跟踪指针 while empty_index != track_index: if track_index > empty_index: if s[track_index] < key: s[empty_index] = s[track_index] track_index,empty_index = empty_index,track_index track_index +=1 else: track_index -=1 else: if s[track_index] > key: s[empty_index] = s[track_index] track_index,empty_index = empty_index,track_index track_index -=1 else: track_index +=1 s[empty_index] = key return empty_index def merge(subnode_rettuple,obj): return subnode_rettuple[0] + [obj] + subnode_rettuple[1] a = [12,3,9,10,87,25,8,9,47,11,1,100,85,47,59,81,60,33]
    obj = (a,0,len(a)-1) b
    = qsort(obj) print a print b

     

    (3)二分查找

    二分查找的条件是序列必须是有序的,下面假设是从小到大排序的序列,如果查找成功返回下标,如果失败,返回None。两种方法的代码均测试通过

    方法一:使用递归

    #使用递归,递归中只创建一个序列对象s,而没使用s的切片,这样做可以节省内存
    def binary_search(s,start_index,end_index,key):
        if start_index <= end_index:
    mid_index = (start_index + end_index)/2
    if key == s[mid_index]: return mid_index elif key > s[mid_index]: return func(s,mid_index+1,end_index,key) else: return func(s,start_index,mid_index-1,key) else: return None

    方法二:使用循环

    #使用循环,其实就是把递归简单改一下就可以了
    def binary_search(s,start_index,end_index,key):
        while start_index <= end_index:
            mid_index = (start_index + end_index)/2
            if key == s[mid_index]:
                return mid_index
            elif key > s[mid_index]:
                start_index = mid_index + 1
            else:
                end_index = mid_index - 1
                
        return None

    下面使用循环方法测试

    >>> a = [2,8,20,77,190,490]
    >>> binary_search(a,0,len(a)-1,1)
    >>> binary_search(a,0,len(a)-1,300)
    >>> binary_search(a,0,len(a)-1,490)
    5
    >>> binary_search(a,0,len(a)-1,77)
    3
    >>>

     

     

     

  • 相关阅读:
    arrow
    简单库函数
    计算机视觉从入门到放肆
    合并排序算法
    React应用数据传递的方式
    发布一个npm package
    绝对路径/相对路径/根路径
    常见的数据处理方法
    从设计稿到实现React应用(分类数据处理)
    提高React组件的复用性
  • 原文地址:https://www.cnblogs.com/ajianbeyourself/p/3761108.html
Copyright © 2011-2022 走看看