前端數據結構--二叉樹先序、中序、後序 遞歸、非遞歸遍歷

二叉樹遍歷

二叉樹的遍歷是指從根節點出發,按照某種順序依次訪問所有節點,而且只訪問一次,二叉樹的遍歷方式很多,如果限制了從左到右的方式,那麼主要有4種:

  1. 前序遍歷:根左右
  2. 中序遍歷:左根右
  3. 後續遍歷:左右根
  4. 層序遍歷:按層級、從上到下,在同一層從左到右遍歷

以上一篇的二叉樹爲例子,先序遍歷 先訪問根節點,在訪問左節點,在訪問右節點,如圖:

  • 先(根)序遍歷(根左右):A、B、D、E、C、F、G
  • 中(根)序遍歷(左根右) : D、B、E、A、F、C、G
  • 後(根)序遍歷(左右根) : D、E、B、F、G、C、A
  • 層序序遍歷(上到下,左到右):A、B、C、D、E、F、G

遞歸實現先序、中序、後序、層序遍歷

  二叉樹的創建用上一篇鏈表方法存儲的二叉樹,只不過增加了4個遍歷的方法而已。一顆大的樹都是若干顆小樹(根節點、左節點、右節點)構成,根節點也有可能是其他節點的左子樹(樹的根節點除外),所以遞歸遍歷就是不斷的遞歸子樹的過程。

 1 /*
 2  * @Description: 
 3  * @Version: 1.0
 4  * @Autor: longbs
 5  */
 6 class Node {
 7   constructor (data = '#') {
 8     this.data = data
 9     this.lNode = null
10     this.rNode = null
11   }
12 }
13 
14 class BiTree {
15   root = null
16   nodeList = []
17   constructor (nodeList) {
18     this.root = new Node()
19     this.nodeList = nodeList
20   }
21   createNode (node) {
22     const data = this.nodeList.shift()
23     if (data === '#') return
24     node.data = data
25     // 下一個元素是不是空節點, 如果不是創建左節點
26     if (this.nodeList[0] !== '#') {
27       node.lNode = new Node(data)
28     }
29     this.createNode(node.lNode)
30 
31     // 下一個元素是不是空節點, 如果不是創建右節點
32     if (this.nodeList[0] !== '#') {
33       node.rNode = new Node(data)
34     }
35     this.createNode(node.rNode)
36     
37   }
38   preorderTraverse (node) {
39     if (node === null) return
40     console.log(node.data)
41     this.preorderTraverse(node.lNode)
42     this.preorderTraverse(node.rNode)
43   }
44   inorderTraverse (node) {
45     if (node === null) return
46     this.inorderTraverse(node.lNode)
47     console.log(node.data)
48     this.inorderTraverse(node.rNode)
49   }
50   postorderTraverse (node) {
51     if (node === null) return
52       this.postorderTraverse(node.lNode)
53       this.postorderTraverse(node.rNode)
54       console.log(node.data)
55   }
56   sequenceTraverse (root) {
57     if (!root) return
58     let queue = []
59     queue.push(root)
60     while (queue.length) {
61       const node = queue.shift()
62       console.log(node.data)
63       if(node.lNode) {
64         queue.push(node.lNode)
65       }
66       if (node.rNode) {
67         queue.push(node.rNode)
68       }
69     }
70   }
71 }
72 
73 let arr = ['A','B','D','#','#','E','#','#','C','F','#', '#', 'G', '#', '#']
74 let bi = new BiTree(arr)
75 bi.createNode(bi.root)
76 console.log(bi.root)
77 
78 console.log('----先序----')
79 console.log(bi.preorderTraverse(bi.root))
80 
81 console.log('----中序----')
82 console.log(bi.inorderTraverse(bi.root))
83 
84 console.log('----後序----')
85 console.log(bi.postorderTraverse(bi.root))
86 
87 console.log('----層序----')
88 console.log(bi.sequenceTraverse(bi.root))

層級遍歷使用了隊列來實現,思路也比較簡單,一開始入隊根節點,出隊最後一個節點,出隊時把相關左節點、右節點入隊,如此循環,隊列爲空即遍歷完成。

非遞歸實現先序、中序、後序

二叉樹的遞歸遍歷非常容易理解,但因爲是遞歸調用需要在內存棧中分配內存用來保存參數,層數多了內存容易溢出。

非遞歸先序基本思路:使用數組來模擬棧的數據結構,首先根節點入棧,然後出棧,在出棧的時候把相關需要的節點按要求把左右節點入棧,如此循環一直到這個棧爲空。

步驟:

  1. 根節點放入棧
  2. 取出棧頂的節點,把該節點結果放入數組
  3. 如果該右節點存在,將該節點右節點放入棧
  4. 如果該左節點存在,將該節點左節點放入棧
  5. 重複 2-4
  6. 棧爲空退出循環
 1 /*
 2     非遞歸:用棧來模擬遞歸
 3   */
 4   preorderNonRecursion (root) {
 5     if (!root) return ''
 6     let stack = []
 7     let result = []
 8     stack.push(root)
 9     while (stack.length) {
10       const node = stack.pop()
11       result.push(node.data)
12 
13       // 如果存在右節點,先壓入右節點
14       if (node.rNode) {
15         stack.push(node.rNode)
16       }
17       if (node.lNode) {
18         stack.push(node.lNode)
19       }
20     }
21     return result
22   }

中序、後序代碼基本思路類似代碼如下:

 1 // 非遞歸-中序
 2   inorderNonRecursion (root) {
 3     if (!root) return ''
 4     let stack = []
 5     let result = []
 6     // stack.push(root)
 7     while (root !== null || stack.length) {
 8       // 找到左節點
 9       while (root !== null) {
10         stack.push(root)
11         root = root.lNode
12       }
13       root = stack.pop()
14       result.push(root.data)
15       // 右節點
16       root = root.rNode
17     }
18     return result
19   }
20   // 非遞歸-後序
21   postorderNonRecursion (root) {
22     if (!root) return ''
23     let stack = []
24     let result = []
25     stack.push(root)
26     while (stack.length) {
27       const node = stack.pop()
28       result.unshift(node.data)
29 
30       if (node.lNode) {
31         stack.push(node.lNode)
32       }
33       if (node.rNode) {
34         stack.push(node.rNode)
35       }
36     }
37     return result
38   }

遞歸遍歷、非遞歸遍歷完整代碼

/*
 * @Description: 
 * @Version: 1.0
 * @Autor: longbs
 */
class Node {
  constructor (data = '#') {
    this.data = data
    this.lNode = null
    this.rNode = null
  }
}

class BiTree {
  root = null
  nodeList = []
  constructor (nodeList) {
    this.root = new Node()
    this.nodeList = nodeList
  }
  // 創建二叉樹
  createNode (node) {
    const data = this.nodeList.shift()
    if (data === '#') return
    node.data = data
    // 下一個元素是不是空節點, 如果不是創建左節點
    if (this.nodeList[0] !== '#') {
      node.lNode = new Node(data)
    }
    this.createNode(node.lNode)

    // 下一個元素是不是空節點, 如果不是創建右節點
    if (this.nodeList[0] !== '#') {
      node.rNode = new Node(data)
    }
    this.createNode(node.rNode)
    
  }
  // 先序
  preorderTraverse (node) {
    if (node === null) return
    console.log(node.data)
    this.preorderTraverse(node.lNode)
    this.preorderTraverse(node.rNode)
  }
  // 中序
  inorderTraverse (node) {
    if (node === null) return
    this.inorderTraverse(node.lNode)
    console.log(node.data)
    this.inorderTraverse(node.rNode)
  }
  // 後序
  postorderTraverse (node) {
    if (node === null) return
      this.postorderTraverse(node.lNode)
      this.postorderTraverse(node.rNode)
      console.log(node.data)
  }
  // 層序
  sequenceTraverse (root) {
    if (!root) return
    let queue = []
    queue.push(root)
    while (queue.length) {
      const node = queue.shift()
      console.log(node.data)
      if(node.lNode) {
        queue.push(node.lNode)
      }
      if (node.rNode) {
        queue.push(node.rNode)
      }
    }
  }
  /*
    非遞歸-先序
    用棧來模擬遞歸
  */
  preorderNonRecursion (root) {
    if (!root) return ''
    let stack = []
    let result = []
    stack.push(root)
    while (stack.length) {
      const node = stack.pop()
      result.push(node.data)

      // 如果存在右節點,先壓入右節點
      if (node.rNode) {
        stack.push(node.rNode)
      }
      if (node.lNode) {
        stack.push(node.lNode)
      }
    }
    return result
  }
  // 非遞歸-中序
  inorderNonRecursion (root) {
    if (!root) return ''
    let stack = []
    let result = []
    // stack.push(root)
    while (root !== null || stack.length) {
      // 找到左節點
      while (root !== null) {
        stack.push(root)
        root = root.lNode
      }
      root = stack.pop()
      result.push(root.data)
      // 右節點
      root = root.rNode
    }
    return result
  }
  // 非遞歸-後序
  postorderNonRecursion (root) {
    if (!root) return ''
    let stack = []
    let result = []
    stack.push(root)
    while (stack.length) {
      const node = stack.pop()
      result.unshift(node.data)

      if (node.lNode) {
        stack.push(node.lNode)
      }
      if (node.rNode) {
        stack.push(node.rNode)
      }
    }
    return result
  }
}

let arr = ['A','B','D','#','#','E','#','#','C','F','#', '#', 'G', '#', '#']
let bi = new BiTree(arr)
bi.createNode(bi.root)
console.log(bi.root)

console.log('----遞歸先序----')
console.log(bi.preorderTraverse(bi.root))
console.log('----非遞歸先序----')
console.log(bi.preorderNonRecursion(bi.root))

console.log('----遞歸中序----')
console.log(bi.inorderTraverse(bi.root))
console.log('----非遞歸中序----')
console.log(bi.inorderNonRecursion(bi.root))


console.log('----遞歸後序----')
console.log(bi.postorderTraverse(bi.root))
console.log('----非遞歸後序----')
console.log(bi.postorderNonRecursion(bi.root))

console.log('----層序----')
console.log(bi.sequenceTraverse(bi.root))

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章