動機

就簡單地(口頭)描述演算法的重點在哪 話說這讓我想起許多做過的leetcode

手法

  • greedy
    • 選大(結果變大)
    • 去掉小(可能性變大)
      • 沒辦法就當前資訊知道結果
  • BFS
    • 走的node數最少
  • 列舉就是往組成的東西下手
    • 距離就會是 中點、起點

shortest-path

BFS

類別: BFS(走的node數最少)

從起點往外擴散,直到起點

每個點之間的距離都是一樣的,所以決定到終點之間距離多遠,是中間有多少點

def bfs(graph, start, end):
  vis = set()
  q = deque([(start,0)])
  while q:
    node, dis = q.popleft()
    if node is end:
      return dis
    else:
      vis.add(node)
      q.extend([(x, dis+1) for x in graph[node] if x not in vis])
  return -1 # cycle exists

Dijkstra

類別: BFS+greedy(能走過去的邊)

現在點之間的距離會變,所以需要一個表紀錄從起點到這個點的最短距離

會需要挑最短的下一個點,所以還要Heap去sort下一個最短的點

def dijkstra(graph, start, end):
  hq = [(0,start)] # heapq
  dists = defaultdict(lambda : float('inf')) # node -> distance
  dists[start] = 0
  while hq:
    dis, node = heappop(hq)

    for x,w in graph[node]:
      if dists[node]+w < dists[x]:
        dists[x] = dists[node]+w
        heappush(hq, (dists[x], x))
    
  return dists[end]

Floyd-Warshall

類別: 列舉(中間點)

試過每個中間點,在比原本距離小時更新,同時這可以知道所有點到另一個點的最短距離

def floyd-warshall(graph, start, end):
  dists = defaultdict(lambda : float('inf')) # node -> distance
  dists[(start,start)] = 0
  
  for mid in graph.keys():
    for a in graph.keys():
      for b in graph.keys(): 
        if dists[(a,b)] > dists[(a,mid)]+graph[mid][b]:
          dists[(a,b)] = dists[(a,mid)]+graph[mid][b]
  return dists[(start,end)]

Bellman–Ford

類別: 列舉(起點)

最長的長度就是所以點都走過一次(所以可以避免負環一直走的問題) 一直延長每個點的長度,最後就是最短路徑

def bellmanford(graph, start, end):
  dists = defaultdict(lambda : float('inf')) # node -> distance
  dists[(start,start)] = 0
  
  for _ in len(graph.keys()):
    for a in graph.keys():
      for b,w in graph[a]: 
        if dists[b] > dists[a]+w:
          dists[b] = dists[a]+w
  if any(dists[b] > dists[a]+w for a in graph.keys() for b,w in graph[a]): # negtive cycle
    return None
  else:
    return dists[end]

Minimum Spanning Tree

Prim

類別: BFS+greedy(能走過去的邊)

Dijkstra是距離,dist[a]+graph[a][b] Prim,一樣是求最小,一樣點之間的距離會變,但SMT只要edge最小就好,所以用heap每次挑最小的edge

def prim(graph):
  start = next(graph.keys())
  hq = [(0,start)] # heapq
  dists = defaultdict(lambda : float('inf')) # node -> distance
  parent = {}
  dists[start] = 0
  parent[start] = None
  while hq:
    dis, node = heappop(hq)

    for x,w in graph[node]:
      if w < dists[x]: # HERE!!
        parent[x] = node
        dists[x] = w
        heappush(hq, (dists[x], x))
    
  return parent

Kruskal

類別: greedy(邊) 每次挑最小的edge去湊,直到變成一棵樹

def kruskal(graph):
  hq = []
  for a in graph.keys():
    for b,w in graph[a]:
      heappush(hq, (w, (a,b)))
  
  uf = {k:k for k in graph.keys()}
  ranks = {k:1 for k in graph.keys()}

  def find(a):
    while a != uf[a]:
      a = uf[a]
    return a

  def union(a,b):
    if find(a) != find(b):
      if ranks[a] >= ranks[b]:
        uf[b] = a
        rank[a] += rank[b]
      else:
        uf[a] = uf[b]
        rank[b] += rank[a]

  vis = set()
  while len(vis) < len(graph.keys()):
    _, (a,b) = heappop(hq)

    if a in vis and b in vis:
      continue
    else:
      [vis.add(x) for x in (a,b)]
      union(a,b)
  return uf

Flow Networks

這裡要求的是讓流過的水量最大

這個不像距離,每走一步就知道當前距離多少。 這要走到最後才會知道bottleneck在哪。

Ford-Fulkerson

類別: greedy(用到的edge最少, 所以算是BFS)

我們沒辦法就當前資訊知道結果如何,但可以在每次選擇中保留可能性

讓path用到的edge越少越好 (讓總edge的quota剩的多) 並且每次只丟掉最小的edge!!

所以用BFS讓走的node數最少,這樣就ok了

def BFS(graph, s, t):
    queue = deque()
    parent = {}

    queue.append(s)
    parent[s] = None

    while queue:
      a = queue.popleft()

      for b, val in enumerate(graph[a]):
          if b not in parent and val > 0: # 不走0,每次只丟掉最小的edge
              queue.append(b)
              parent[b] = a
          if b == t:
              return parent

    return False

def FordFulkerson(source, sink):
    max_flow = 0

    while (parent = BFS(graph, source, sink)) :
        path_flow = float("Inf")
        s = sink
        while s != source:
            path_flow = min(path_flow, graph[parent[s]][s])
            s = parent[s]

        max_flow +=  path_flow

        v = sink
        while(v != source):
            u = parent[v]
            graph[u][v] -= path_flow # 不走0,每次只丟掉最小的edge
            graph[v][u] += path_flow # 反向流
            v = parent[v]

    return max_flow