算法图解整理

最近学习了《算法图解》这本书,写下一些笔记。

由于第一次发博客,写的很烂望理解嘻嘻嘻。

算法图解

1、算法简介
  1.2二分查找
  实现代码

  `def binary_search(list,item):
          low = 0
          high = len(list)-1

          while low <=high:
          mid = (low+high)/2
          guess = list[mid]
          if(guess == item):
            return mid
          if(guess >= item):
            high = mid-1
          else
            low = mid +1
          return None
          `

1.3大O运行时间(时间复杂度)

      O(logn):也叫对数时间,例如二分查找

      O(n):也叫线性时间,例如简单算法

      O(nlogn):例如快速排序——速度较快

      O(n^2):例如选择排序——速度较慢

      O(n!):例如旅行商算法——非常慢

1.4小结

     1、二分查找的速度比简单查找快得多

     2、O(logn)比O(n)快,,需要搜索的元素越多,前者比后者就快得多

     3、算法运行时间比不以秒为单位

     4、算法运行时间是从其增速的角度度量的

     5、算法运行时间用大O表示法表示

2、选择排序

     2.2数组和链表

         存储多项数据时,有两种基本方式——数组和链表

常见的数组和链表操作的运行时间
 数组链表
读取O(1)O(n)
插入O(n)O(1)
删除O(n)O(1)

          访问方式分两种

                1、随机访问

                2、顺序访问:要从第一个元素开始逐个地读取元素

          数组擅长随机访问,链表擅长插入与删除

     2.3选择排序

      

//找出数组中最小的元素
def findSmallest(arr):
   smallest = arr[0]
   smallest_index = 0
   for i in range(1,len(arr)):
       if arr[i] > samllest:
          smallest = arr[i]
          smallest_index = i
   return smallest_index

//对数组进行排序
def selectionSort(arr):
   newArr = []
   for i in range(i,len(arr)):
       smallest = findSmallest(arr)
       newArr.append(arr.pop(smallest))
   return newArr

3、递归

           3.1递归

                Leigh Caldwell:如果使用循环,程序的性能可能更高,如果使用递归,程序可能更容易理解。

          3.2基线条件与递归条件

               每个递归函数都有两部分:基线条件与递归条件。

               基线条件是指函数不再调用自己,避免形成无限循环。

               递归条件是指函数自己调用自己。

         3.4小结

               1、递归指的是调用自己的函数

               2、每个递归函数都有两个条件:基线条件与递归条件

               3、栈有两种操作:压入与弹出

               4、所有的函数调用都进入调用栈,调用另一个函数时,当前函数暂停并处于未完成的状态

               5、调用栈可能很长,这将占用大量的内存

4、快速排序

         4.1分而治之

               使用分治法解决问题的过程包括两个步骤

               (1)找出基线条件,这种条件必须尽可能简单

                 (2)不断将问题分解,直到符合基线条件

       求数组中各元素之和

def sun(list):
  if list == []
     return 0;
  return list[0]+sum(list[1:])

      求数组元素个数

def count(list):
   if list == []
      return 0
   return 1 + count(list[1:])

     找出列表中最大的数

def max(list):
  if len(list) == 2:
     return list[0] if list[0] > list[1] else list[1]
  sub_max = max(list[1:])
  return list[0] if list[0] > sub_max else sub_max

     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)

    快速排序在平均情况下,运行时间为O(nlogn),最糟糕的情况下运行时间为O(n^2)

   4.4小结

      1、分治法将问题逐步分解。使用分治法处理列表时,基线条件很可能时空数组或者只包含一个元素的数组。

      2、实现快速排序时,请随机的选择用作基准值的元素。快速排序的平均运行时间为O(logn)

      3、大O表示法中的常量有时候事关重大,这就是快速排序比合并排序快的原因

      4、比较简单查找和二分查找时,常量几乎无关紧要,因为列表很长时,O(logn)的速度比O(n)快得多

5、散列表

         散列表的查找的时间复杂度为O(1)

         散列函数:将输入映射到数字

         散列函数必须满足一些要求:(1)它必须是一致的,例如输入apple得到4,那么每次输入apple,都要得到4.(2)它应将不同的输入映射到不同的数字。

         散列表应用:1、将散列表用于查找

                               2、防止重复

                               3、将散列表用作缓存

                               4、模拟映射关系

                               5、缓存/记住数据,以免服务器再通过处理来生成他们

       好的散列函数很少会导致冲突。

      最理想的散列函数将键均匀的映射到散列表的不同位置,并且如果散列表存储的链表很长,散列表的速度将会急剧下降,若有好的散列函数,这些链表就不会很长。

时间复杂度对比
 散列表(平均情况)散列表(最糟情况)数组链表
查找O(1)O(n)O(1)O(n)
插入O(1)O(n)O(n)O(1)
删除O(1)O(n)O(n)O(1)

        在平均情况下,散列表的查找速度与数组一样快,而插入速度与删除与链表一样快。但在最糟情况下,散列表的各项操作都很慢,因此,在使用散列表时,避开最糟糕的情况至关重要,为避免冲突,需要(1)较低的填装因子(2)良好的散列函数

         填装因子=散列表包含的元素数/位置总数

         一个不错的经验就是:一旦填装因子大于0.7,就调整散列表的长度。

        良好的散列函数让数组中的值呈均匀分布。

        小结 1、一般的编程语言提供了散列表的实现 例如Python

phone_book = dict()

phont_book = {}   //快捷方式

                2、可以结合散列函数与数组来创建散列表

                3、冲突很糟糕,你应该使用可以最大限度减少冲突的散列函数

                4、散列表的查找、插入和删除速度都非常快

                5、散列表适合用于模拟映射关系

                6、一旦填装因子大于0.7,就该调整散列表的长度

                7、散列表可用于缓存数据(例如,在web服务器上)

                8、散列表非常适合用于防止重复

6、广度优先搜

                本章介绍图,再介绍第一种图算法——广度优先搜索

                广度优先搜索可以找出两样东西之间的最短距离

                检查你的朋友中有没有芒果经销商:

               用散列表实现图

              

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 search(name):
    search_queue = deque()              //创建一个队列
    search_queue += graph[name]         //将name的邻居都加入到这个搜索队列中
    searched =[]                        //这个数组用于记录检查过的人
    while search_queue:                 //只要队列不为空
            person = search_queue.popleft()  取出队列里的第一个人
            if person not in searched: //仅当这个人没检查过时才检查
               if person_is_selle(person):
                  print person + "is a mango seller!"
                  return True
               else
                  search_queue += graph[person]  //将其邻居都加入到搜索队列
                  searched.append(person)  //将这个人标记为检查过
    return False                         //没有人是芒果经销商



def person_is_seller(name):     //判断一个人是不是芒果经销商
    return name[-1] == 'm'      //例如检查这个人的姓名是不是以m结尾

          小结  1、广度优先搜索指出是否有从A到B的路径

                   2、如果有广度优先搜索将找出最短路径

                   3、面临类似于寻找最短路径的问题,可尝试使用图来建立模型,在使用广度优先搜索来解决问题

                   4、有向图中的边不带箭头,箭头的方向制定了关系的方向,例如:rama——>adit表示rama欠了adit的钱

                   5、无向图中的边不带箭头,其中的关系是双向的,例如:ross - rachel 表示ross与rachel约会,而rachel也与ross约会

                   6、队列是先进先出的(FIFO)

                   7、栈是先进后出的(LIFO)

                   8、你需要按加入顺序检查搜索列表中的人,否则找到的就不是最短路径,因此搜索列表必须是队列

                   9、对于检查过的人,务必不要再次检查,否则可能导致无限循环

7、迪克斯特拉算法

                   上一章使用了广度优先搜索,找出的是段数最少的路径。本章介绍加权图与迪克斯特拉算法,他可以找出加权图中 

            前往X的最短路径。

                   迪克斯特拉算法步骤:(1)找出"最便宜"的节点,即可在最短时间内到达的节点。

                                                       (2)更新该节点的邻居的开销。

                                                       (3)重复这个过程,知道对图中每个节点都这样做了。

                                                       (4)计算最终路径。

                  迪克斯特拉算法只适用于有向无环图

                

                    rama想用乐谱换一架钢琴,如何才能花费最少呢?

                    最终的表格为

乐谱黑胶唱片5
乐谱海报0
黑胶唱片低音吉他20
黑胶唱片架子鼓25
架子鼓钢琴35

               代码实现

              

graph = {}   //用散列表实现图

//记录起点的邻居与其权重
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")
costs = {}
costs["b"] = 6
costs["b"] = 2
costs["fin"]  = infinity


//创建一个存储父节点的散列表
parents = {}
parents["a"] = "start"
parents["b"] = "start"
parents["fin"] = None

//创建一个记录已经处理过的节点
processed = []
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      //将该邻居的父节点设置为当前节点
    proecssed.append(node)         //同时将该节点标记为处理过
    node = find_lowest_cost_node(costs)  //找出接下来要处理的节点并循环

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

        小结 1、广度优先搜索用于在非加权图中中查找最短路径

                2、狄克斯特拉算法用于在加权图中查找最短路径

                3、仅当权重为正时狄克斯特拉算法才管用

               4、如果途中包含负边权,请使用贝尔曼-福德算法

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值