• go语言|二叉树递归遍历,可能有你没见过的花样玩法


    在上一篇博客《go语言|数据结构:二叉树可视化(制作svg格式树形图)》中,已实现了用svg图片展示二叉树的树形结构。但对非满二叉树的比较复杂,需要先转成满二叉树,再获取各结点在满二叉树中的对应坐标位置,这种做法明显有个缺点就是遍历的次数比较多。

    树形图的关键在于获取结点的坐标。对满二叉树的来说,它的坐标数组可以标记为:[[(0,0)], [(1,0), (1,1)], [(2,0), (2,1), (2,2), (2,3)], [(3,0),(3,1), ...], ...];如能对任意二叉树只做一次遍布,就获得这样的坐标,效率就提高了。首先,从递归遍历说起——

    递归遍历

    递归遍历时,看数据域和左右子树结点出现的位置顺序看,分先序、中序、后序三种方式。不管哪种方式都可以用2种方法,一种是直接打印遍历结果、一种返回遍历结果(结点的数据域数组)。如下代码,以先序遍历为例:

    1. package main
    2. import "fmt"
    3. type btNode struct {
    4. Data interface{}
    5. Lchild *btNode
    6. Rchild *btNode
    7. }
    8. type biTree struct {
    9. Root *btNode
    10. }
    11. func Build(List ...interface{}) *biTree {
    12. if len(List) == 0 || List[0] == nil {
    13. return &biTree{}
    14. }
    15. node := &btNode{Data: List[0]}
    16. Queue := []*btNode{node}
    17. for lst := List[1:]; len(lst) > 0 && len(Queue) > 0; {
    18. cur, val := Queue[0], lst[0]
    19. Queue, lst = Queue[1:], lst[1:]
    20. if val != nil {
    21. cur.Lchild = &btNode{Data: val}
    22. Queue = append(Queue, cur.Lchild)
    23. }
    24. if len(lst) > 0 {
    25. val, lst = lst[0], lst[1:]
    26. if val != nil {
    27. cur.Rchild = &btNode{Data: val}
    28. Queue = append(Queue, cur.Rchild)
    29. }
    30. }
    31. }
    32. return &biTree{Root: node}
    33. }
    34. func (bt *btNode) PreorderPrint() {
    35. if bt != nil {
    36. fmt.Print(bt.Data, " ")
    37. bt.Lchild.PreorderPrint()
    38. bt.Rchild.PreorderPrint()
    39. }
    40. }
    41. func (bt *btNode) PreorderList() []interface{} {
    42. var res []interface{}
    43. if bt != nil {
    44. res = append(res, bt.Data)
    45. res = append(res, bt.Lchild.PreorderList()...)
    46. res = append(res, bt.Rchild.PreorderList()...)
    47. }
    48. return res
    49. }
    50. func main() {
    51. tree := Build(1, 2, 3, 4, 5, 6, 7, 8)
    52. fmt.Println(tree.Root.PreorderList())
    53. tree.Root.PreorderPrint()
    54. }

    创建函数Build()也作了改进,直接用变长参数,而非之前用数组作参数。另外,参数中如有不能连接的结点,直接放弃而非抛出错误panic()。如参数 1,nil,nil,2,3 就只返回一个结点。

    深度的遍历

    递归遍历核心就以下三行代码,先读出数据域,后左右递归的即先序遍历;读出放中间或最后一行就变成中序和后序遍历了。

    fmt.Print(bt.Data, " ")                //读出数据域
    bt.Lchild.PreorderPrint()          //左子树递归
    bt.Rchild.PreorderPrint()          //右子树递归

    如果把bt.Data换成其它呢,结点结构就定义了三个值,换成bt.Lchild,bt.Rchild就能遍历出左右子树结点。这样用处不大, 如果写成 bt.Lchild!=nil 或者 bt.Rchild!=nil,遍历结果就是该结点是否有左子树或右子树;写成 bt.Lchild==nil && bt.Rchild==nil 就能判断是否为叶结点。如果换作一个自定义函数呢,那就能得到自己想要的结果。也可以玩其它花样,比如累加所有结点的和,为了方便默认它们的数据域都是整数:

    1. //相同代码部分略,见上面的代码
    2. func (bt *btNode) SumTree() int {
    3. var res int
    4. if bt != nil {
    5. res += bt.Data.(int)
    6. res += bt.Lchild.SumTree()
    7. res += bt.Rchild.SumTree()
    8. }
    9. return res
    10. }
    11. func main() {
    12. tree := Build(1, 5, 26, 9, 8, 51)
    13. fmt.Println(tree.Root.SumTree())
    14. }
    15. // 输出结果 100

    深度和高度的概念

    深度是从根节点数到它的叶节点,高度是从叶节点数到它的根节点。即深度是从上到下计数的,而高度是从下往上计数。

    二叉树的深度是指所有结点中最深的结点所在的层数。对于整棵树来说,最深的叶结点的深度就是树的深度,即树的高度和深度是相等的。但同一层的节点的深度是相同,它们的高度不一定相同。如下图,节点2和3的深度相等,但高度不相等;节点4、5、6深度也相等,但高度不全相等。

    深度的遍历

    从二叉树深度的定义可知,二叉树的深度应为其左、右子树深度的最大值加1。由此,需先分别求得左、右子树的深度,算法中“访问结点”的操作为:求得左、右子树深度的最大值,然后加 1 。递归代码如下:

    1. func (bt *btNode) MaxDepth() int {
    2. if bt == nil {
    3. return 0
    4. }
    5. Lmax := bt.Lchild.MaxDepth()
    6. Rmax := bt.Rchild.MaxDepth()
    7. if Lmax > Rmax {
    8. return 1 + Lmax
    9. } else {
    10. return 1 + Rmax
    11. }
    12. }

    遍历时用上这个函数就可得到所有结点的深度了,完整测试代码以8个结点的完全二叉树为例,返回结果是:“4 3 2 1 1 2 1 1”。最大值4为根结点的尝试,也就是整个二叉树的深度或高度;四个最小值1,表示有四个叶结点。

    1. package main
    2. import "fmt"
    3. type btNode struct {
    4. Data interface{}
    5. Lchild *btNode
    6. Rchild *btNode
    7. }
    8. type biTree struct {
    9. Root *btNode
    10. }
    11. func Build(List ...interface{}) *biTree {
    12. if len(List) == 0 || List[0] == nil {
    13. return &biTree{}
    14. }
    15. node := &btNode{Data: List[0]}
    16. Queue := []*btNode{node}
    17. for lst := List[1:]; len(lst) > 0 && len(Queue) > 0; {
    18. cur, val := Queue[0], lst[0]
    19. Queue, lst = Queue[1:], lst[1:]
    20. if val != nil {
    21. cur.Lchild = &btNode{Data: val}
    22. Queue = append(Queue, cur.Lchild)
    23. }
    24. if len(lst) > 0 {
    25. val, lst = lst[0], lst[1:]
    26. if val != nil {
    27. cur.Rchild = &btNode{Data: val}
    28. Queue = append(Queue, cur.Rchild)
    29. }
    30. }
    31. }
    32. return &biTree{Root: node}
    33. }
    34. func (bt *btNode) PreorderDepth() {
    35. if bt != nil {
    36. fmt.Print(bt.MaxDepth(), " ")
    37. bt.Lchild.PreorderDepth()
    38. bt.Rchild.PreorderDepth()
    39. }
    40. }
    41. func (bt *btNode) MaxDepth() int {
    42. if bt == nil {
    43. return 0
    44. }
    45. Lmax := bt.Lchild.MaxDepth()
    46. Rmax := bt.Rchild.MaxDepth()
    47. if Lmax > Rmax {
    48. return 1 + Lmax
    49. } else {
    50. return 1 + Rmax
    51. }
    52. }
    53. func main() {
    54. tree := Build(1, 2, 3, 4, 5, 6, 7, 8)
    55. tree.Root.PreorderDepth()
    56. }

    叶结点的遍历

    1. func (bt *btNode) LeafPrint() {
    2. if bt != nil {
    3. if bt.Lchild == nil && bt.Rchild == nil {
    4. fmt.Print(bt.Data, " ")
    5. }
    6. bt.Lchild.LeafPrint()
    7. bt.Rchild.LeafPrint()
    8. }
    9. }
    10. func (bt *btNode) LeafList() []interface{} {
    11. var res []interface{}
    12. if bt != nil {
    13. if bt.Lchild == nil && bt.Rchild == nil {
    14. res = append(res, bt.Data)
    15. }
    16. res = append(res, bt.Lchild.LeafList()...)
    17. res = append(res, bt.Rchild.LeafList()...)
    18. }
    19. return res
    20. }

    结点结构的三变量都用过了,对得到结点的坐标帮助不大,坐标需要的是结点在树中的层数和所在层数的索引号。于是尝试引入其它变量——

    坐标的遍历

    纵坐标遍历

    1. //相同代码部分略,见上面的代码
    2. func (bt *btNode) LevelPrint(y int) {
    3. if bt != nil {
    4. fmt.Print(bt.Data, ":", y, " ")
    5. y++
    6. bt.Lchild.LevelPrint(y)
    7. bt.Rchild.LevelPrint(y)
    8. y--
    9. }
    10. }
    11. func main() {
    12. tree := Build(1, 2, 3, 4, 5, 6, 7, 8)
    13. tree.Root.LevelPrint(0)
    14. }
    15. //输出结果 1:0 2:1 4:2 8:3 5:2 3:1 6:2 7:2

    改成返回数组形式如下: 另外也可以把y++,y--改成参数y+1,输出[0 1 2 3 2 1 2 2],结果相同。

    1. //相同代码部分略,见上面的代码
    2. func (bt *btNode) LevelList(y int) []int {
    3. var res []int
    4. if bt != nil {
    5. res = append(res, y)
    6. res = append(res, bt.Lchild.LevelList(y+1)...)
    7. res = append(res, bt.Rchild.LevelList(y+1)...)
    8. }
    9. return res
    10. }
    11. func main() {
    12. tree := Build(1, 2, 3, 4, 5, 6, 7, 8)
    13. fmt.Print(tree.Root.LevelList(0))
    14. }

    横坐标遍历

    依葫芦画瓢,照纵坐标的方式遍历:

    1. func (bt *btNode) IndexPrint(x int) {
    2. if bt != nil {
    3. fmt.Print(bt.Data, ":", x, " ")
    4. bt.Lchild.IndexPrint(x - 1)
    5. bt.Rchild.IndexPrint(x + 1)
    6. }
    7. }

    但结果不符合要求:1:0 2:-1 4:-2 8:-3 5:0 3:1 6:0 7:2

    最底下一层最靠近中间的左右两结点横坐标是-1,1就OK了,以满二叉树为例来说比较好理解:

    从上图的规律看,每层平移的距离不一样的,不能平移正负1,移动距离应该是2的高度减一次方才对,调用时参数为(0,2的高度-2次方),完整的测试代码如下:

    1. package main
    2. import "fmt"
    3. type btNode struct {
    4. Data interface{}
    5. Lchild *btNode
    6. Rchild *btNode
    7. }
    8. type biTree struct {
    9. Root *btNode
    10. }
    11. func Build(List ...interface{}) *biTree {
    12. if len(List) == 0 || List[0] == nil {
    13. return &biTree{}
    14. }
    15. node := &btNode{Data: List[0]}
    16. Queue := []*btNode{node}
    17. for lst := List[1:]; len(lst) > 0 && len(Queue) > 0; {
    18. cur, val := Queue[0], lst[0]
    19. Queue, lst = Queue[1:], lst[1:]
    20. if val != nil {
    21. cur.Lchild = &btNode{Data: val}
    22. Queue = append(Queue, cur.Lchild)
    23. }
    24. if len(lst) > 0 {
    25. val, lst = lst[0], lst[1:]
    26. if val != nil {
    27. cur.Rchild = &btNode{Data: val}
    28. Queue = append(Queue, cur.Rchild)
    29. }
    30. }
    31. }
    32. return &biTree{Root: node}
    33. }
    34. func Pow2(x int) int { //x>=0
    35. res := 1
    36. for i := 0; i < x; i++ {
    37. res *= 2
    38. }
    39. return res
    40. }
    41. func Max(L, R int) int {
    42. if L > R {
    43. return L
    44. } else {
    45. return R
    46. }
    47. }
    48. func (bt *btNode) MaxDepth() int {
    49. if bt == nil {
    50. return 0
    51. }
    52. Lmax := bt.Lchild.MaxDepth()
    53. Rmax := bt.Rchild.MaxDepth()
    54. return 1 + Max(Lmax, Rmax)
    55. }
    56. func (bt *btNode) IndexPrint(x, w int) {
    57. if bt != nil {
    58. fmt.Print(bt.Data, ":", x, " ")
    59. bt.Lchild.IndexPrint(x-w, w/2)
    60. bt.Rchild.IndexPrint(x+w, w/2)
    61. }
    62. }
    63. func main() {
    64. tree := Build(1, 2, 3)
    65. tree.Root.IndexPrint(0, Pow2(tree.Root.MaxDepth()-2))
    66. fmt.Println()
    67. tree = Build(1, 2, 3, 4, 5, 6, 7)
    68. tree.Root.IndexPrint(0, Pow2(tree.Root.MaxDepth()-2))
    69. fmt.Println()
    70. tree = Build(1, 2, 3, 4, 5, 6, 7, 8)
    71. tree.Root.IndexPrint(0, Pow2(tree.Root.MaxDepth()-2))
    72. fmt.Println()
    73. tree = Build(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)
    74. tree.Root.IndexPrint(0, Pow2(tree.Root.MaxDepth()-2))
    75. fmt.Println()
    76. }
    77. /*输出结果
    78. 1:0 2:-1 3:1
    79. 1:0 2:-2 4:-3 5:-1 3:2 6:1 7:3
    80. 1:0 2:-4 4:-6 8:-7 5:-2 3:4 6:2 7:6
    81. 1:0 2:-4 4:-6 8:-7 9:-5 5:-2 10:-3 11:-1 3:4 6:2 12:1 13:3 7:6 14:5 15:7
    82. */

    横纵坐标联合输出

    综上,把横纵坐标放一起以数组形式输出,参数多一个是变动的平移距离:

    1. //相同代码部分略,见上面的代码
    2. func (bt *btNode) Coordinate(x, y, w int) []interface{} {
    3. var res []interface{}
    4. if bt != nil {
    5. res = append(res, []interface{}{bt.Data, x, y})
    6. res = append(res, bt.Lchild.Coordinate(x-w, y+1, w/2)...)
    7. res = append(res, bt.Rchild.Coordinate(x+w, y+1, w/2)...)
    8. }
    9. return res
    10. }
    11. func main() {
    12. tree := Build(1, 2, 3)
    13. fmt.Println(tree.Root.Coordinate(0, 0, Pow2(tree.Root.MaxDepth()-2)))
    14. tree = Build(1, 2, 3, 4, 5, 6, 7)
    15. fmt.Println(tree.Root.Coordinate(0, 0, Pow2(tree.Root.MaxDepth()-2)))
    16. tree = Build(1, 2, 3, nil, nil, 5, 6, 7, 8)
    17. fmt.Println(tree.Root.Coordinate(0, 0, Pow2(tree.Root.MaxDepth()-2)))
    18. tree = Build(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)
    19. fmt.Println(tree.Root.Coordinate(0, 0, Pow2(tree.Root.MaxDepth()-2)))
    20. tree = Build(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)
    21. fmt.Println(tree.Root.Coordinate(0, 0, Pow2(tree.Root.MaxDepth()-2)))
    22. }
    23. /*输出结果
    24. [[1 0 0] [2 -1 1] [3 1 1]]
    25. [[1 0 0] [2 -2 1] [4 -3 2] [5 -1 2] [3 2 1] [6 1 2] [7 3 2]]
    26. [[1 0 0] [2 -4 1] [3 4 1] [5 2 2] [7 1 3] [8 3 3] [6 6 2]]
    27. [[1 0 0] [2 -4 1] [4 -6 2] [8 -7 3] [9 -5 3] [5 -2 2] [10 -3 3] [11 -1 3] [3 4 1] [6 2 2] [12 1 3] [13 3 3] [7 6 2] [14 5 3] [15 7 3]]
    28. [[1 0 0] [2 -8 1] [4 -12 2] [8 -14 3] [16 -15 4] [9 -10 3] [5 -4 2] [10 -6 3] [11 -2 3] [3 8 1] [6 4 2] [12 2 3] [13 6 3] [7 12 2] [14 10 3] [15 14 3]]
    29. */

    至此,坐标遍历全部完成,再加上判断有否左右子树结点就能作图了:

    1. package main
    2. import "fmt"
    3. type btNode struct {
    4. Data interface{}
    5. Lchild *btNode
    6. Rchild *btNode
    7. }
    8. type biTree struct {
    9. Root *btNode
    10. }
    11. func Build(List ...interface{}) *biTree {
    12. if len(List) == 0 || List[0] == nil {
    13. return &biTree{}
    14. }
    15. node := &btNode{Data: List[0]}
    16. Queue := []*btNode{node}
    17. for lst := List[1:]; len(lst) > 0 && len(Queue) > 0; {
    18. cur, val := Queue[0], lst[0]
    19. Queue, lst = Queue[1:], lst[1:]
    20. if val != nil {
    21. cur.Lchild = &btNode{Data: val}
    22. Queue = append(Queue, cur.Lchild)
    23. }
    24. if len(lst) > 0 {
    25. val, lst = lst[0], lst[1:]
    26. if val != nil {
    27. cur.Rchild = &btNode{Data: val}
    28. Queue = append(Queue, cur.Rchild)
    29. }
    30. }
    31. }
    32. return &biTree{Root: node}
    33. }
    34. func Pow2(x int) int { //x>=0
    35. res := 1
    36. for i := 0; i < x; i++ {
    37. res *= 2
    38. }
    39. return res
    40. }
    41. func Max(L, R int) int {
    42. if L > R {
    43. return L
    44. } else {
    45. return R
    46. }
    47. }
    48. func (bt *btNode) MaxDepth() int {
    49. if bt == nil {
    50. return 0
    51. }
    52. Lmax := bt.Lchild.MaxDepth()
    53. Rmax := bt.Rchild.MaxDepth()
    54. return 1 + Max(Lmax, Rmax)
    55. }
    56. func (bt *btNode) Coordinate(x, y, w int) []interface{} {
    57. var res []interface{}
    58. if bt != nil {
    59. res = append(res, []interface{}{bt.Data, x, y, w, bt.Lchild != nil, bt.Rchild != nil})
    60. res = append(res, bt.Lchild.Coordinate(x-w, y+1, w/2)...)
    61. res = append(res, bt.Rchild.Coordinate(x+w, y+1, w/2)...)
    62. }
    63. return res
    64. }
    65. func main() {
    66. tree := Build(1, 2, 3, 4, 5, 6, 7, 8)
    67. list := tree.Root.Coordinate(0, 0, Pow2(tree.Root.MaxDepth()-2))
    68. fmt.Println(list)
    69. //数组的遍历
    70. for _, data := range list {
    71. for _, d := range data.([]interface{}) {
    72. fmt.Print(d, " ")
    73. }
    74. /*内循环或者用:
    75. for i, _ := range data.([]interface{}) {
    76. fmt.Print(data.([]interface{})[i], " ")
    77. }
    78. */
    79. fmt.Println()
    80. }
    81. }
    82. /*输出结果
    83. [[1 0 0 true true] [2 -4 1 true true] [4 -6 2 true false] [8 -7 3 false false] [5 -2 2 false false] [3 4 1 true true] [6 2 2 false false] [7 6 2 false false]]
    84. 1 0 0 true true
    85. 2 -4 1 true true
    86. 4 -6 2 true false
    87. 8 -7 3 false false
    88. 5 -2 2 false false
    89. 3 4 1 true true
    90. 6 2 2 false false
    91. 7 6 2 false false
    92. */

    作图时的用法,以上面完整代码的输出为例:

    遍历到 2 -4 1 2 true true ,表示(-4,1)处是结点2,true true就有2条子树的连线,指向坐标 (-4-2, 1+1) (-4+2, 1+1),即 (-6, 2) 和 (-2, 2),即结点4和结点;

    遍历到 4 -6 2 1 true false,表示(-6,2)处是结点4,true false说明它只有左子树,指向坐标 (-7,3),即结点8;

    遍历到 8 -7 3 0 false false,表示(-7,3)处是结点8,false,false说明它是叶结点。

    具体如何把结点和坐标转换到二叉树svg图形格式,请参见上篇文章:

     《go语言|数据结构:二叉树可视化(制作svg格式树形图)》。

    BTW,以上所有代码不论是哪一个递归遍历都可以有三种顺序,比如坐标遍历的代码:

    1. //先序
    2. func (bt *btNode) Coordinate(x, y, w int) []interface{} {
    3. var res []interface{}
    4. if bt != nil {
    5. res = append(res, []interface{}{bt.Data, x, y})
    6. res = append(res, bt.Lchild.Coordinate(x-w, y+1, w/2)...)
    7. res = append(res, bt.Rchild.Coordinate(x+w, y+1, w/2)...)
    8. }
    9. return res
    10. }

    换个顺序就成为中序和后序遍历:

    1. //中序
    2. func (bt *btNode) Coordinate(x, y, w int) []interface{} {
    3. var res []interface{}
    4. if bt != nil {
    5. res = append(res, bt.Lchild.Coordinate(x-w, y+1, w/2)...)
    6. res = append(res, []interface{}{bt.Data, x, y})
    7. res = append(res, bt.Rchild.Coordinate(x+w, y+1, w/2)...)
    8. }
    9. return res
    10. }
    11. //后序
    12. func (bt *btNode) Coordinate(x, y, w int) []interface{} {
    13. var res []interface{}
    14. if bt != nil {
    15. res = append(res, bt.Lchild.Coordinate(x-w, y+1, w/2)...)
    16. res = append(res, bt.Rchild.Coordinate(x+w, y+1, w/2)...)
    17. res = append(res, []interface{}{bt.Data, x, y})
    18. }
    19. return res
    20. }

    (本文完)

  • 相关阅读:
    2067: [蓝桥杯2023初赛] 幸运数
    svn(乌龟svn)和SVN-VS2022插件(visualsvn) 下载
    C语言动态实现顺序栈
    你还不懂java的日志系统吗
    749个看图识字含MP3音频ACCESS\EXCEL数据库
    Jvm类加载机制
    Java中支持分库分表的框架/组件/中间件简介
    Java正则表达式之捕获组奥秘探索
    企业为什么要选择通配符SSL证书使用?
    如何在 Windows 10/11 上编辑 PDF [4 种简单方法]
  • 原文地址:https://blog.csdn.net/boysoft2002/article/details/126018226