n種解法破DFS與BFS

  • 2019 年 10 月 5 日
  • 筆記

n種解法破DFS與BFS

0.說在前面1.二叉樹的層次遍歷I1.1 BFS非遞歸思路11.2 BFS非遞歸思路21.3 BFS雙端隊列1.4 BFS遞歸思路1.5 DFS遞歸思路2.二叉樹的層次遍歷II2.1 反轉思路2.2 直接插入

0.說在前面

最近實在太忙了,但是算法我時刻不會忘記,每周都刷至少兩道,比如我昨天上課無聊,拿手機刷了兩道,感覺很easy,或許我刷的比較簡答吧,然後今天回到實驗室,看了之前給Datawhale編程實踐定的任務,是一道層次遍歷,於是這道題引發了我的思考,剛好晚上有時間,就花了時間來研究一波,結果真的很有趣。

一件事,簡單而又直白;一件事,複雜而又晦澀;我寧願選擇後者,因為他可以激發你的潛能!

今天呢主要來介紹兩道題,二叉樹的層次遍歷I與II,運用的思想為DFS與BFS,實現算法包含遞歸與非遞歸!

1.二叉樹的層次遍歷I

關於DFS與BFS這裡不多做介紹,會在後面寫出幾篇簡單文章讓大家來看,如果有什麼需求,可以留言!

問題

給定一個二叉樹,返回其按層次遍歷的節點值。 (即逐層地,從左到右訪問所有節點)。

例如: 給定二叉樹: [3,9,20,null,null,15,7],

    3     /     9  20      /       15   7  

返回其層次遍歷結果:

[    [3],    [9,20],    [15,7]  ]  

1.1 BFS非遞歸思路1

思路

採用傳統的隊列思路,先入先出思想,層次遍歷大家都知道吧,就是一行訪問完畢,再訪問下一行,很有順序,而對於二叉樹BFS與層次遍歷一致,那麼這裡就是採用BFS隊列的思路來操作!

實現

代碼實現思路為:首先對特殊情況處理,然後定義一個queue(用來存放每層節點)及結果list。然後進入循環,再次建立一個空的list用來存放每層的節點值,然後對queue循環出隊,對出隊的節點操作(讓左孩子與右孩子入隊),所以在代碼中引入了同層節點個數的變量length,主要是queue要做修改,所以會發生改變,自然不能直接遍歷原先的queue!每層訪問完畢,讓這層的節點值list添加到結果list中,返回即可!

def levelOrder(self, root):      res = []      if root is None:          return res      queue = [root]      # 列表為空時,循環終止      # 可以省去len(queue)!=0      while queue and len(queue) != 0:          # 使用列表存儲同層節點          level_val = []          # 同層節點的個數          length = len(queue)          for i in range(length):              # 將同層節點依次出隊              h = queue.pop(0)              if h.left:                  # 左孩子入隊                  queue.append(h.left)              if h.right:                  # 右孩子入隊                  queue.append(h.right)              level_val.append(h.val)          res.append(level_val)      return res  

1.2 BFS非遞歸思路2

思路

這個思路與上面類似,不同之處在於,這裡在循環裏面建立了兩個list,而這兩個list的作用分別為,一個用來存儲每層節點值,一個用來存儲每層的左右孩子節點,其實思路1與2基本一致,只是代碼風格不一樣,建議採用第一種思路寫!

實現

def levelOrder(self, root):      res = []      if not root:          return []      res_list = []      root_list = [root]      while root_list and len(root_list)!=0:          # 每層節點值          val_list = []          # 每層左右孩子節點          level_list = []          for node in root_list:              val_list.append(node.val)              if node.left:                  level_list.append(node.left)              if node.right:                  level_list.append(node.right)          # 修改循環條件          root_list = level_list          # 結果集中添加每層list          res_list.append(val_list)      return res_list  

1.3 BFS雙端隊列

思路

思路同上1

實現

實現思路:採用了collections裏面的雙端隊列deque!具體的用法注釋標出了!

def levelOrder(self, root):      res = []      if not root:          return res      # 導包      import collections      # 雙端隊列定義      queue = collections.deque()      queue.append(root)      # visited = set(root)      while queue:          length = len(queue)          level_val = []          for _ in range(length):              # 彈出最左邊的節點              node = queue.popleft()              level_val.append(node.val)              if node.left:                  queue.append(node.left)              if node.right:                  queue.append(node.right)          res.append(level_val)      return res  

1.4 BFS遞歸思路

思路

思路就是遞歸+BFS,具體思路看實現!

實現

首先在結果list中每層插入一個空list,然後循環每一層所有節點,將當前節點直接加入對應層當中,然後更新下一層節點list(更新方法為:將當前及節點的左右孩子入list即可),然後不斷遞歸,直到下一層沒有節點了,結束遞歸!

所以這裡是一層層遞歸,一層層實現,所以名字為BFS遞歸!

def levelOrder(self, root):      if root is None:          return []      self.res = []      self.bfs([root], 0)      return self.res    def bfs(self, level_nodes, level):      if not level_nodes:          return      self.res.insert(level, [])      temp = []      for node in level_nodes:          self.res[level].append(node.val)          if node.left:              temp.append(node.left)          if node.right:              temp.append(node.right)      self.bfs(temp, level+1)  

1.5 DFS遞歸思路

思路

思路就是採用深度優先搜索,先選擇一個分支不斷往下遍歷,標記訪問過的節點,在去繼續往下,如果已經到達終點,回退各個分支節點,進入下一分支,重複前面步驟,直到所有節點訪問完畢!

實現

在代碼中體現深度優先搜索的為:

self.result[level].append(root.val)  

這一行表示為當前層添加節點值!

具體的解釋放在注釋中!

def levelOrder(self, root):      # 特殊情況處理      if not root:          return []      # 定義結果list      self.result = []      # 調用函數      self.dfs(root, 0)      # 返回結果      return self.result  # dfs函數  def dfs(self, root, level):      # 這一行很關鍵,主要是用來為訪問下一層的節點添加一個空的list      if len(self.result) < level + 1:          self.result.append([])      # 訪問對應level的list,並添加數據      self.result[level].append(root.val)      # 左孩子遞歸      if root.left:          self.dfs(root.left, level + 1)      # 右孩子遞歸      if root.right:          self.dfs(root.right, level + 1)  

對於上述的dfs遞歸的終止條件就是,沒有左右孩子了,就結束了,為了更加好看,可以寫成如下代碼:

改寫變動為終止條件變動及左右孩子訪問判斷條件變動!

def dfs(self, root, level):      # 遞歸終止條件      if not root:              return      # 這一行很關鍵,主要是用來為訪問下一層的節點添加一個空的list      if len(self.result) < level + 1:          self.result.append([])      # 訪問對應level的list,並添加數據      self.result[level].append(root.val)      # 左孩子遞歸      self.dfs(root.left, level + 1)      # 右孩子遞歸      self.dfs(root.right, level + 1)  

2.二叉樹的層次遍歷II

上面介紹了5種方法解決二叉樹的層次遍歷!那麼下面又有一道題是對上面的簡單處理,下面接着來看!

問題

給定一個二叉樹,返回其節點值自底向上的層次遍歷。 (即按從葉子節點所在層到根節點所在的層,逐層從左向右遍歷)

例如: 給定二叉樹 [3,9,20,null,null,15,7],

    3     /     9  20      /       15   7  

返回其自底向上的層次遍歷為:

[    [15,7],    [9,20],    [3]  ]  

2.1 反轉思路

反轉思路為將一個list直接做反轉即可,實現為:

list[::-1]

所以將上面的所有方法最後返回結果直接跟個[::-1],那麼這道題就解決了!

2.2 直接插入

上面最後需要做反轉,也就是切片,這樣的話時間複雜度會變高,有沒有更高效率了,當然有了,那就是直接插入了!

怎麼改呢?

直接將上面的結果list的append改為insert(0,xxx),就是在每次數據的前面插入,這樣就保證的先入的在棧底!也就滿足了題意!