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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末昧穿,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子胃碾,更是在濱河造成了極大的恐慌雁佳,老刑警劉巖脐帝,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異糖权,居然都是意外死亡堵腹,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進(jìn)店門星澳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來疚顷,“玉大人,你說我怎么就攤上這事募判〉春” “怎么了?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵届垫,是天一觀的道長释液。 經(jīng)常有香客問我,道長装处,這世上最難降的妖魔是什么误债? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮妄迁,結(jié)果婚禮上寝蹈,老公的妹妹穿的比我還像新娘。我一直安慰自己登淘,他們只是感情好箫老,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著黔州,像睡著了一般耍鬓。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上流妻,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天牲蜀,我揣著相機(jī)與錄音,去河邊找鬼绅这。 笑死涣达,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播度苔,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼匆篓,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了林螃?” 一聲冷哼從身側(cè)響起奕删,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎疗认,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體伏钠,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡横漏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了熟掂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片缎浇。...
    茶點(diǎn)故事閱讀 39,834評論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖赴肚,靈堂內(nèi)的尸體忽然破棺而出素跺,到底是詐尸還是另有隱情,我是刑警寧澤誉券,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布指厌,位于F島的核電站,受9級特大地震影響踊跟,放射性物質(zhì)發(fā)生泄漏踩验。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一商玫、第九天 我趴在偏房一處隱蔽的房頂上張望箕憾。 院中可真熱鬧,春花似錦拳昌、人聲如沸袭异。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽御铃。三九已至,卻和暖如春刻像,著一層夾襖步出監(jiān)牢的瞬間畅买,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工细睡, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留谷羞,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像湃缎,于是被迫代替她去往敵國和親犀填。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 背景 一年多以前我在知乎上答了有關(guān)LeetCode的問題, 分享了一些自己做題目的經(jīng)驗(yàn)嗓违。 張土汪:刷leetcod...
    土汪閱讀 12,744評論 0 33
  • 說白了蹂季,直性子就是真冕广! 直性子的女人,就是真女人偿洁,從不玩虛的撒汉。 直性子女人,心交女人 不會(huì)轉(zhuǎn)彎抹角涕滋,口心一致睬辐,不虛...
    一簾舊夢閱讀 610評論 0 1
  • 嘀嗒嘀嗒,窗外的雨滴從天空灑下宾肺,凌晨的風(fēng)吹散長發(fā)溯饵。是誰在深夜里哭泣? 是你嗎锨用?枯萎的玫瑰丰刊?飄落的羽毛?還是天使的睫...
    429e9865665b閱讀 157評論 0 1
  • 這是一個(gè)最好的時(shí)代,也是一個(gè)最壞的時(shí)代跪者】妹保——狄更斯 用英國作家狄更斯的這句話來形容今天的出版產(chǎn)業(yè)是非常恰當(dāng)?shù)模还?..
    0f5fbe338d53閱讀 282評論 0 0
  • 顧客在進(jìn)店購買的過程中,有一些關(guān)鍵性的環(huán)節(jié)忘衍,在不同的環(huán)節(jié)逾苫,顧客的心理和期待是不同的,這些心理活動(dòng)會(huì)通過一些行業(yè)變化...
    小記小悟閱讀 161評論 0 0