【算法图解】学习笔记

一 、算法简介

1.1 二分查找

  • 对于包含n个元素的列表,用二分查找最多需要log2n步,而简单查找最多需要n

  • 仅当列表是有序的时候,二分查找才管用

  • 代码实现:

    def binary_search(list, item):
        low = 0
        high = len(list) - 1
        
        while low <= high:
            mid = (low + high)
            guess = list[mid]
            if guess == item:
                return mid
            if guess > item:
                high = mid - 1
            else:
                low = mid + 1
        return None
    
    my_list = [1, 3, 5, 7, 9]
    
    print(binary_search(my_list, 3))
    
  • 运行时间:对数时间

1.2 大O表示法

  • 大O表示法是一种特殊的表示法,指出了算法的速度

  • 大O表示法让你能够比较操作数,它指出了算法运行时间的增速。

  • 大O 表示法指出了最糟情况下的运行时间

  • 常见的大O运行时间:

    • O ( l o g n ) O(log n) O(logn),对数时间,包括二分查找。

    • O ( n ) O(n) O(n),线性时间,包括简单查找。

    • O ( n l o g n ) O(nlog n) O(nlogn),包括快速排序——一种速度较快的排序算法。

    • O ( n 2 ) O(n^2) O(n2),包括选择排序——一种速度较慢的排序算法。

    • O ( n ! ) O(n!) O(n!),包括旅行商问题的解决方案——一种非常慢的算法。

      大O运行时间 算法
      O ( 1 ) O(1) O(1)(常量时间) 散列表
      O ( l o g n ) O(log n) O(logn)(对数时间) 二分查找
      O ( n ) O(n) O(n)(线性时间) 简单查找
      O ( n l o g n ) O(nlog n) O(nlogn) 快速排序
      O ( n 2 ) O(n^2) O(n2) 选择排序
      O ( n ! ) O(n!) O(n!) 旅行商问题
  • 启示:

    • 算法的速度指的并非时间,而是操作数的增速。
    • 谈论算法的速度时,我们说的是随着输入的增加,其运行时间将以什么样的速度增加。
    • 算法的运行时间用大O表示法表示。
    • O ( l o g n ) O(log n) O(logn)比 O ( n ) O(n) O(n)快,当需要搜索的元素越多时,前者比后者快得越多。

二、选择排序

2.1 数组与链表

  • 数组

  • 链表:链表的每个元素都存储了下一个元素的地址,从而使一系列随机的内存地址串在一起

  • 数组与链表运行时间:

    数组 链表
    读取 O ( 1 ) O(1) O(1) O ( n ) O(n) O(n)
    输入 O ( n ) O(n) O(n) O ( 1 ) O(1) O(1)
    删除 O ( n ) O(n) O(n) O ( 1 ) O(1) O(1)
  • 索引:元素的位置称为索引

  • 插入:使用链表时,插入元素很简单,只需修改它前面的那个元素指向的地址。而使用数组时,则必须将后面的元素都向后移。当需要在中间插入元素时,链表是更好的选择。

  • 删除:当删除元素时,链表是更好的选择。

2.2 选择排序

  • 遍历整个列表,找出最大的元素,并添加到一个新列表中。继续这样做,将得到一个有序列表

  • 运行时间: O ( n 2 ) O(n^2) O(n2)

  • 代码实现:

    #找到最小值
    def findSmallest(arr):
        smallest = arr[0]
        smallest_index = 0
        for i in range(1, len(arr)):
            if arr[i] < smallest:
                smallest = arr[i]
                smallest_index = i
        return smallest_index
    
    #把最小值加入新数组
    def selectionSort(arr):
        newArr = []
        for i in range(len(arr)):
            smallest = findSmallest(arr)
            newArr.append(arr.pop(smallest))
        return newArr
    
    print(selectionSort([5, 3, 6, 2, 10]))
    

三、递归

  • 递归:调用自己的函数
  • 递归条件:递归条件指的是函数调用自己
  • 基线条件:基线条件指的是函数不再调用自己,从而避免形成无限循环。
  • 递归调用栈
  • 栈的操作:压入和弹出

四、快速排序

4.1 分而治之(D&C)

  • 一种著名的递归式问题解决方法。

  • 步骤

    • 找出基线条件
    • 确定如何缩小问题的规模,使其符合基线条件

4.2 快速排序

  • 步骤:

    • 从数组中选择一个元素,这个元素被称为基准值,一般将数组的第一个元素用作基准值
    • 分区:找出比基准值小的元素以及比基准值大的元素:
      • 一个由所有小于基准值的数字组成的子数组
      • 基准值
      • 一个由所有大于基准值的数组组成的子数组
    • 对两个子数组进行快速排序
  • 代码实现:

    def quicksort(array):
        if len(array) < 2:
            return array
        else:
            pivot = array[0]
            less = [i for i in array[1:] if i <= pivot]
            greater = [i for i in array[1:] if i > pivot]
            return quicksort(less) + [pivot] + quicksort(greater)
    
    print(quicksort([10, 5, 2, 3]))
    

4.3 再谈大O表示法

  • 快速排序的性能高度依赖于你选择的基准值

五、散列表

5.1 散列函数

  • 将输入映射到数字

  • 要求:

    • 一致性
    • 一一对应
  • 散列函数总是将同样的输入映射到相同的索引

  • 散列函数将不同的输入映射到不同的索引

  • 散列函数知道数组有多大,只返回有效的索引

5.2 散列表

  • 散列表:散列映射,映射,字典,关联数组

  • python提供的散列表为字典

  • 散列表的应用:

    • 模拟映射关系
    • 防止重复
    • 用作缓存
      • 优点:用户更快地看到网页,服务器工作减少
  • 冲突:给两个键分配的位置相同

    • 反省:
      • 散列函数很重要。尽量让散列函数将键均匀地映射到散列表的不同位置。
      • 如果散列表存储的链表过长,散列表的速度将急剧下降
  • 性能:散列表的查找、插入和删除速度都非常快

    散列表(平均情况) 散列表(最糟情况) 数组
    读取 O ( 1 ) O(1) O(1) O ( n ) O(n) O(n) O ( 1 ) O(1) O(1) O ( n ) O(n) O(n)
    输入 O ( 1 ) O(1) O(1) O ( n ) O(n) O(n) O ( n ) O(n) O(n) O ( 1 ) O(1) O(1)
    删除 O ( 1 ) O(1) O(1) O ( n ) O(n) O(n) O ( n ) O(n) O(n) O ( 1 ) O(1) O(1)

5.3 装填因子

  • 装填因子=散列表包含的元素数 / 位置总数
  • 装填因子=1:最佳情况,每个元素都有自己的位置
  • 装填因子>1:元素数量超出了数组的位置数
  • 当填装因子开始增大,你就需要在散列表中添加位置,这被称为调整长度
  • 填装因子越低,发生冲突的可能性越小,散列表的性能越高。
  • 经验规律:一旦填装因子大于0.7,就调整散列表的长度。

六、广度优先搜索

6.1 最短路径问题

  • 步骤:
    • 使用图来建立问题模型
    • 使用广度优先搜索解决问题

6.2 广度优先搜索

  • 用于图的查找算法

  • 应用:

    • 第一类问题:寻找路径:从节点A出发,有前往节点B的路径吗?
    • 第二类问题:查找最短路径(段数最少的路径,而不是最快路径):从节点A出发,前往节点B的哪条路径最短?
  • 查找最短路径:

    • 在广度优先搜索的执行过程中,搜索范围从起点开始逐渐向外延伸,即先检查一度关系,再检查二度关系
    • 只有按添加顺序查找时,才能实现这样的目的
  • 队列:

    • 操作:入队和出队

    • 操作顺序:先进先出

      数据结构 操作顺序
      先进后出
      队列 先进先出
  • 实现图

    • 有向图:其中的关系是单向的
    • 无向图:没有箭头,直接相连的节点互为关系
  • 算法工作原理:

    • 创建一个队列,用于存储要检查的人
    • 从队列中弹出一个人
    • 检查这个人是否是目标
    • 将这个人的所有邻居都列入队列
  • 算法实现:

    from collections import deque
    
    graph = {}
    graph["you"] = ["alice", "bob", "claire"]
    graph["bob"] = ["anuj", "peggy"]
    graph["alice"] = ["peggy"]
    graph["claire"] = ["thom", "jonny"]
    graph["anuj"] = []
    graph["peggy"] = []
    graph["thom"] = []
    graph["jonny"] = []
    
    def person_is_seller(name):
        return name[-1] == 'm'
    
    def search(name):
        search_queue = deque()
        search_queue += graph[name]
        searched = []
        while search_queue:
            person = search_queue.popleft()
            if not person in searched:
                if person_is_seller(person):
                    print(person + " is a mango seller!")
                    return True
                else:
                    search_queue += graph[person]
                    searched.append(person)
        return False
    
    search("you")
    
  • 运行时间: O ( V + E ) O(V+E) O(V+E),V为顶点数,E为边数


七、狄克斯特拉算法

7.1 使用狄克斯特拉算法

  • 目的:找出最快路径
  • 步骤:
    • 找出“最便宜”的节点,即可在最短时间内到达的节点
    • 更新该节点的邻居的开销,其含义将稍后介绍
    • 重复这个过程,直到对图中的每个节点都这样做了
    • 计算最终路径

7.2 术语

  • 权重:狄克斯特拉算法用于每条边都有关联数字的图,这些数字称为权重
  • 加权图:带权重的图称为加权图。要计算加权图中的最短路径,可使用狄克斯特拉算法
  • 非加权图:不带权重的图称为非加权图。要计算非加权图中的最短路径,可使用广度优先搜索

7.3 实现

graph = {}
graph["you"] = ["alice", "bob", "claire"]

graph["start"] = {}
graph["start"]["a"] = 6
graph["start"]["b"] = 2

graph["a"] = {}
graph["a"]["fin"] = 1
graph["b"] = {}
graph["b"]["a"] = 3
graph["b"]["fin"] = 5

graph["fin"] = {}

infinity = float("inf")

infinity = float("inf")
costs = {}
costs["a"] = 6
costs["b"] = 2
costs["fin"] = infinity

parents = {}
parents["a"] = "start"
parents["b"] = "start"
parents["fin"] = None

processed = []

def find_lowest_cost_node(costs):
    lowest_cost = float("inf")
    lowest_cost_node = None
    for node in costs:
        cost = costs[node]
        if cost < lowest_cost and node not in processed:
            lowest_cost = cost
            lowest_cost_node = node
    return lowest_cost_node

node = find_lowest_cost_node(costs)
while node is not None:
    cost = costs[node]
    neighbors = graph[node]
    for n in neighbors.keys():
        new_cost = cost + neighbors[n]
        if costs[n] > new_cost:
            costs[n] = new_cost
            parents[n] = node
    processed.append(node)
    node = find_lowest_cost_node(costs)

八、贪婪算法

8.1 教室调度问题

  • 思想:
    • 选出结束最早的课,它就是要在这间教室上的第一堂课。
    • 接下来,必须选择第一堂课结束后才开始的课。同样,你选择结束最早的课,这将是要在这间教室上的第二堂课。

8.2 背包问题

  • 思想:
    • 盗窃可装入背包的最贵商品。
    • 再盗窃还可装入背包的最贵商品,以此类推。

8.3 集合覆盖问题

  • 近似算法:在获得精确解需要的时间太长时,可使用近似算法

    • 标准:速度有多快、得到的近似解与最优解的接近程度、
  • 运行时间: O ( n 2 ) O(n^2) O(n2)

  • 代码实现:

    states_needed = set(["mt", "wa", "or", "id", "nv", "ut","ca", "az"])
    
    stations = {}
    stations["kone"] = set(["id", "nv", "ut"])
    stations["ktwo"] = set(["wa", "id", "mt"])
    stations["kthree"] = set(["or", "nv", "ca"])
    stations["kfour"] = set(["nv", "ut"])
    stations["kfive"] = set(["ca", "az"])
    final_stations = set()
    
    while states_needed:
        best_station = None
        states_covered = set()
        for station, states_for_station in stations.items():
            covered = states_needed & states_for_station
            if len(covered) > len(states_covered):
                best_station = station
                states_covered = covered
        
        final_stations.add(best_station)
        states_needed -= states_covered
        
    print(final_stations)
    
  • 运行时间:

    精确算法 贪婪算法
    O ( n ! ) O(n!) O(n!) O ( n 2 ) O(n^2) O(n2)

8.4 旅行商问题

  • 问题:要找出经由指定几个点的的最短路径——NP完全问题

九、K最近邻算法

9.1 创建推荐系统

  • 特征抽取:毕达哥拉斯公式: ( a 1 − a 2 ) 2 + ( b 1 − b 2 ) 2 + ( c 1 − c 2 ) 2 + ( d 1 − d 2 ) 2 + ( e 1 − e 2 ) 2 \sqrt{(a_1-a_2)^2+(b_1-b_2)^2+(c_1-c_2)^2+(d_1-d_2)^2+(e_1-e_2)^2} (a1​−a2​)2+(b1​−b2​)2+(c1​−c2​)2+(d1​−d2​)2+(e1​−e2​)2

  • 分类:编组

  • 回归:预测结果

9.2 机器学习

  • OCR(光学字符识别)

    • 训练:浏览大量的数字图像,将这些数字的特征提取出来。
    • 遇到新图像时,你提取该图像的特征,再找出它最近的邻居都是谁!
  • 朴素贝叶斯分类器

  • 预测股票市场

上一篇:Python学习系列之 xrange和range的区别!


下一篇:Python2.x与3.x版本有哪些主要的区别?