Basic of Tree and Graph Algorithm

About Graph

How to represent Graph in Python

Few programming languages provide direct support for graphs as a data type, and Python is no exception. However, graphs are easily built out of lists and dictionaries. For instance, here's a simple graph (I can't use drawings in these columns, so I write down the graph's arcs):

 graph = {'A': ['B', 'C'],
             'B': ['C', 'D'],
             'C': ['D'],
             'D': ['C'],
             'E': ['F'],
             'F': ['C']}

This is a dictionary whose keys are the nodes of the graph. For each key, the corresponding value is a list containing the nodes that are connected by a direct arc from this node. This is about as simple as it gets (even simpler, the nodes could be represented by numbers instead of names, but names are more convenient and can easily be made to carry more information, such as city names).

Let's write a simple function to determine a path between two nodes. It takes a graph and the start and end nodes as arguments. It will return a list of nodes (including the start and end nodes) comprising the path. When no path can be found, it returns None. The same node will not occur more than once on the path returned (i.e. it won't contain cycles). The algorithm uses an important technique called backtracking: it tries each possibility in turn until it finds a solution.

    def find_path(graph, start, end, path=[]):
        path = path + [start]
        if start == end:
            return path
        if not graph.has_key(start):
            return None
        for node in graph[start]:
            if node not in path:
                newpath = find_path(graph, node, end, path)
                if newpath: return newpath
        return None

A simple Python Graph Implementation:

class Graph:
    def __init__(self):
        self.nodes = set()
        self.edges = defaultdict(list)
        self.distances = {}

def add_node(self, value):
    self.nodes.add(value)

def add_edge(self, from_node, to_node, distance):
    self.edges[from_node].append(to_node)
    self.edges[to_node].append(from_node)
    self.distances[(from_node, to_node)] = distance

General Graph Traversal Algorithm

  • The most important difference between recursive and iterative style traversal is that, for recursive traversal, while test on node (or vertex); while for iterative traversal, while test on stack/queue.

  • For graph, there are:

    • iterative DFS/BFS, supported by stack/queue
    • recursive DFS
  • For binary tree, there are:

    • recursive DFS (including in-order, pre-order, post-order)
    • iterative BFS, no need support from queue
    • iterative DFS, need stack ???
      • this iterative DFS's order is not necessary any of the in-order, pre-order and post-order

Depth-First Search and Breadth-First Search in Python
Example graph representation in Python (which is used in the following example):

graph = {'A': set(['B', 'C']), 
  'B': set(['A', 'D', 'E']), 
  'C': set(['A', 'F']), 
  'D': set(['B']), 
  'E': set(['B', 'F']), 
  'F': set(['C', 'E'])}

Iterative traversal:

Difference between tree and graph iterative traversal:

  • Tree don't have cycle, so in BFS, we don't need to keep track which node is visited (but stack/queue is still needed) -- can we do tree iterative without using any additional data structure ???
  • Tree have node with empty child, we need to check None condition when we add child into stack/queue

General graph BFS

6 Steps:

def bfs(graph, start): 
    visited, queue = set(), [start]  # s1. If we want traversal order, can change this visited from set to list 
# (so visited has two functions: keep track of visited vertices, and keep track of visiting order) 
    while queue:  #s2
        vertex = queue.pop(0) # s3, pop first one, i.e.queue (only difference btw BFS and DFS) 
        # at each while iteration, we only process one vertex (node) (this vertex)
        if vertex not in visited: #s4
            print vertex # just do some operation
            visited.add(vertex) #s5, processed. 
            queue.extend(graph[vertex] - visited) # s6, set operation
            # add all connected vertices into queue for future process
            # if tree, we need to check if child is None
            # this step is different for different Graph representation
        return visited (maybe use a loop to add vertices) 

bfs(graph, 'A')   # {'B', 'C', 'A', 'F', 'D', 'E'}

General tree BFS

# not 100% sure if this is right
# the difference btw tree and graph is that tree don't have circle
# therefore we don't need to have visited set to keep track of visited nodes
def bfs(root):
    if not root:
        return
    queue = [root] # s1. we can also have visited=[] to keep track of visiting order, but this is not necessary. 
    while queue: #s2
        node = queue.pop(0) #s3
        print node
        if node.left: queue.append(node.left) #s4
        if node.right: queue.append(node.right)

General graph DFS

# with stack:
def dfs(graph, start): 
    visited, stack = set(), [start] 
    while stack: 
        vertex = stack.pop() # pop last one, i.e. stack
        if vertex not in visited: 
            visited.add(vertex) 
            stack.extend(graph[vertex] - visited) 
        return visited 

dfs(graph, 'A')   # {'E', 'D', 'F', 'A', 'C', 'B'}

General tree DFS:

def dfs(root):
    if not root:
        return
    stack = [root]
    while stack:
        node = stack.pop()
        print node.val
        if node.right: stack.append(node.right)
        # print right first, we get the same order as in-order. Otherwise we don't get any order. But anyways, still DFS
        if node.left: stack.append(node.left)


Recursive traversal:

  • the difference between tree and graph in terms of recursive traversal is,

General Graph DFS with recursion:

# using recursion: 
# remember this way of defining function !!!! 
def dfs(graph, start, visited=None): 
    # !!! trick: define visited=None, so we don't need to define an extra helper_dfs function ! 
    if visited is None: 
        visited = set() 
    visited.add(start) 
    for next in graph[start] - visited: # process one vertex
        # Only process these non-visited vertices.
        # with other data structure, need to check if this vertex's neighborhoods are visited or not.  
        dfs(graph, next, visited) 
    return visited

dfs(graph, 'C') # {'E', 'D', 'F', 'A', 'C', 'B'}

General Tree DFS with recursion:

This is just in-order, pre-order, post-order tree traversal

pre-order:
def preorder(tree): 
    if tree: 
        print(tree.getRootVal()) 
        preorder(tree.getLeftChild()) 
        preorder(tree.getRightChild())
post-order
def postorder(tree): 
    if tree != None: 
        postorder(tree.getLeftChild()) 
        postorder(tree.getRightChild()) 
        print(tree.getRootVal())
in-order:

If we use DFS(stack), and first push right, then push left, we also get in-order. But we cannot get pre-order and post-order.

def inorder(tree): 
    if tree != None: 
        inorder(tree.getLeftChild()) 
        print(tree.getRootVal()) 
        inorder(tree.getRightChild())

We are able to tweak both of the previous implementations to return all possible paths between a start and goal vertex.

def dfs_paths(graph, start, goal): 
    stack = [(start, [start])] 
    while stack: 
        (vertex, path) = stack.pop() 
        for next in graph[vertex] - set(path): 
            if next == goal: 
                yield path + [next] 
            else: 
                stack.append((next, path + [next]))

list(dfs_paths(graph, 'A', 'F')) # [['A', 'C', 'F'], ['A', 'B', 'E', 'F']]

Tree specific BFS

I don't think the following code is necessary, no idea why need to track level
Level Order Tree Traversal

Without using helper data structure

// sudo code:
printLevelorder(tree)
    for d = 1 to height(tree) 
        printGivenLevel(tree, d);

printGivenLevel(tree, level)
    if tree is NULL then return;
    if level is 1, 
        then print(tree->data);
    else if level greater than 1, 
        then printGivenLevel(tree->left, level-1); 
        printGivenLevel(tree->right, level-1);
# python code
# Function to  print level order traversal of tree
def printLevelOrder(root):
    h = height(root)
    for i in range(1, h+1):
        printGivenLevel(root, i)
 
# Print nodes at a given level
def printGivenLevel(root , level):
    if root is None:
        return
    if level == 1:
        print "%d" %(root.data),
    elif level > 1 :
        printGivenLevel(root.left , level-1)
        printGivenLevel(root.right , level-1)
 
def height(node):
    if node is None:
        return 0
    else :
        # Compute the height of each subtree 
        lheight = height(node.left)
        rheight = height(node.right)

        #Use the larger one
        if lheight > rheight :
            return lheight+1
        else:
            return rheight+1

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341

推荐阅读更多精彩内容