算法图解笔记

算法简介

二分法查找,输入一个有序列表,返回元素位置或null。
一般而言,对于包含n个元素的列表,用二分法查找最多需要 l o g 2 n log_2 n log2n

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

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

binary_search.py

  • 大O表示法是指最坏情况下的时间复杂度
  • 简单查找: O ( l o g n ) O(logn) O(logn)
  • 二分法查找: O ( l o g 2 n ) O(log_2 n) O(log2n)
  • 快速排序: O ( n l o g n ) O(nlogn) O(nlogn)
  • 选择排序: O ( n 2 ) O(n^2) O(n2)

选择排序

由于数组的结构在内存中是连续的,添加新元素十分麻烦,链表的优势在于插入新元素;
数组的优势在于元素的随机访问。也就是说数组和链表在插入和读取元素的时间复杂度刚好互补
selectSort

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 selectSort(arr):
    newArr = []
    for i in xrange(len(arr)):
        smallest = findSmallest(arr)
        newArr.append(arr.pop(smallest))
    return newArr

我的写法

def selectSort2(arr):
    n = len(arr)
    for i in range(n):
        for j in range(i, n):
            if arr[j] < arr[i]:
                arr[i], arr[j] = arr[j], arr[i]
    return arr

时间复杂度为 O ( n 2 ) O(n^2) O(n2)

递归

递归函数要有基线条件和递归条件,否则会无限循环.

  • 调用一个函数,计算机会首先为该函数调用分配一块内存,并且把该函数的变量存储在该内存中,所有函数调用都用到调用栈。
  • 调用栈可能时间很长,这需要大量内存,递归函数都要用到调用栈。

看一个阶乘函数的实现:

def fact(x):
    if x == 1:return 1
    else: return x*fact(x-1)

快速排序

快速排序是分而治之的策略quickSort

分而治之

  • 找出基线条件,这种条件尽可能简单;
  • 不断将问题分解(或者说缩小规模),直到符合基线条件。
def quickSort(arr):
    if len(arr)<2:
        return arr
    else:
        pivot = arr[0]
        less = [i for i in arr[1:] if i <= pivot]
        greater = [i for i in arr[1:] if i > pivot]
        return quickSort(less)+[pivot]+quickSort(greater)   

这么看来比严版快排好理解多了,严版是用两个指针和一个基准值将数组划分成两部分,可以说时间复杂度确实小了不少。

def quickSort2(arr, left, right):
    if left>=right:
        return arr
    
    low = left
    high = right
    key = arr[left]

    while left < right:
        while left < right and arr[right] >= key:
            right -= 1
        arr[left] = arr[right]
        while left < right and arr[left] <= key:
            left += 1
        arr[right] = arr[left]

    arr[left] = key

    quickSort2(arr, low, left-1)
    quickSort2(arr, left+1, high)
    return arr
  • 快速排序的时间复杂度在最坏的情况下为 O ( n 2 ) O(n^2) O(n2),平均情况下为 O ( n l o g n ) O(nlogn) O(nlogn),最好情况也是 O ( n l o g n ) O(nlogn) O(nlogn)
    总体而言时间复杂度为 O ( n 2 ) O(n^2) O(n2).

散列表

散列函数将输入映射到数字。它必须是一致的;它将不同的输入映射到不同的数字。Python实现了散列表的实现——字典。
散列表是提供DNS解析这种功能的方式之一。

一个简单的投票:

voted = {}
def check_voter(name):
    if voted.get(name):
        print "kick them out!"
    else:
        voted[name] = True
        print "let them vote!"

将散列表用作缓存,缓存是一种常用的加速方式,缓存的数据则存储在散列表中。

cache = {}
def get_page(url):
    if cache.get(url):
        return cache[url]
    else:
        data = get_data_from_server(url)
        cache[url] = data
        return data

冲突的解决方法:将两个键映射到同一个位置,就在这个位置存储一个链表。为避免冲突,需要好的散列函数和较低的填充因子。

填装因子=散列表包含的元素数/位置总数,一旦填充因子大于0.7,就开始调整撒列表长度。

在平均条件下,散列表的时间复杂度为 O ( 1 ) O(1) O(1),在最糟糕的情况下,时间复杂度为 O ( n ) O(n) O(n)

广度优先搜索

图由节点——边组成。可以通过散列表将节点映射到其所有邻居,散列表是无序的。

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

创建一个队列,用于存储要检查的人,从队列中弹出一个人,检查他是否是芒果销售商,否的坏将这个人所有的另据加入队列。

from collections import deque
search_deque = deque()
search_deque += graph["you"]

def person_is_seller(name):
    return name[-1] == "m"

while search_deque:
    person = search_deque.popleft()
    if person_is_seller(persson):
        return True
    else:
        search_deque += graph[person]
return False

但是这种方法有缺陷,检查会有重复。应该检查完一个人,就将其标记。

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(persson):
                return True
            else:
                search_queue += graph[person]
                searched.append(person)
     return False

时间复杂度为 O ( V + E ) O(V+E) O(V+E)边数和人数

狄克斯特拉算法

  • 加权图,边的数字称为权重。计算非加权图的最短路径,使用广度优先搜索;计算加权图的最短路径,使用狄克斯特拉算法。
    狄克斯特拉算法只适用有向无环图。
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

贪婪算法

就是每步都选择最优解,最终得到全局的最优解。

NP完全问题

  • 涉及n个城市的路线,一共有 n ! n! n!条可能的路线。
  • 计算所有的解,并从中选取最小/最短的那个,这类问题就是NP完全问题。
  • 涉及“所有组合”、序列问题难以解决、集合问题难以解决,它可能就是NP完全问题;
  • 问题可以转化为集合覆盖问题或者旅行商问题,它就是NP完全问题。

动态规划

与上一章对应,贪婪算法得到的可能不是最优解,而动态规划可以得到最优解。动态规划先解决子问题,再逐步解决大问题。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值