• go语言|数据结构:二叉树可视化(svg树形图改进版)


    题目

     以图形展示任意二叉树,如下图,一个中缀表达式表示的二叉树:3.14*r²*h/3

    源代码

    1. package main
    2. import (
    3. "fmt"
    4. "io"
    5. "os"
    6. "os/exec"
    7. "strconv"
    8. "strings"
    9. )
    10. type any = interface{}
    11. type btNode struct {
    12. Data any
    13. Lchild *btNode
    14. Rchild *btNode
    15. }
    16. type biTree struct {
    17. Root *btNode
    18. Info *biTreeInfo
    19. }
    20. type biTreeInfo struct {
    21. Data []any
    22. DataLevel [][]any
    23. L, R []bool
    24. X, Y, W []int
    25. Index, Nodes int
    26. Width, Height int
    27. MarginX, MarginY int
    28. SpaceX, SpaceY int
    29. SvgWidth, SvgHeight int
    30. SvgXml string
    31. }
    32. func Build(Data ...any) *biTree {
    33. if len(Data) == 0 || Data[0] == nil {
    34. return &biTree{}
    35. }
    36. node := &btNode{Data: Data[0]}
    37. Queue := []*btNode{node}
    38. for lst := Data[1:]; len(lst) > 0 && len(Queue) > 0; {
    39. cur, val := Queue[0], lst[0]
    40. Queue, lst = Queue[1:], lst[1:]
    41. if val != nil {
    42. cur.Lchild = &btNode{Data: val}
    43. Queue = append(Queue, cur.Lchild)
    44. }
    45. if len(lst) > 0 {
    46. val, lst = lst[0], lst[1:]
    47. if val != nil {
    48. cur.Rchild = &btNode{Data: val}
    49. Queue = append(Queue, cur.Rchild)
    50. }
    51. }
    52. }
    53. return &biTree{Root: node}
    54. }
    55. func BuildFromList(List []any) *biTree {
    56. return Build(List...)
    57. }
    58. func AinArray(sub int, array []int) int {
    59. for idx, arr := range array {
    60. if sub == arr {
    61. return idx
    62. }
    63. }
    64. return -1
    65. }
    66. func Pow2(x int) int { //x>=0
    67. res := 1
    68. for i := 0; i < x; i++ {
    69. res *= 2
    70. }
    71. return res
    72. }
    73. func Max(L, R int) int {
    74. if L > R {
    75. return L
    76. } else {
    77. return R
    78. }
    79. }
    80. func (bt *btNode) MaxDepth() int {
    81. if bt == nil {
    82. return 0
    83. }
    84. Lmax := bt.Lchild.MaxDepth()
    85. Rmax := bt.Rchild.MaxDepth()
    86. return 1 + Max(Lmax, Rmax)
    87. }
    88. func (bt *btNode) Coordinate(x, y, w int) []any {
    89. var res []any
    90. if bt != nil {
    91. L, R := bt.Lchild != nil, bt.Rchild != nil
    92. res = append(res, []any{bt.Data, L, R, x, y, w})
    93. res = append(res, bt.Lchild.Coordinate(x-w, y+1, w/2)...)
    94. res = append(res, bt.Rchild.Coordinate(x+w, y+1, w/2)...)
    95. }
    96. return res
    97. }
    98. func (bt *biTree) NodeInfo() []any {
    99. return bt.Root.Coordinate(0, 0, Pow2(bt.Root.MaxDepth()-2))
    100. }
    101. func (bt *biTree) TreeInfo() {
    102. height := bt.Root.MaxDepth()
    103. width := Pow2(height - 1)
    104. lsInfo := bt.NodeInfo()
    105. btInfo := &biTreeInfo{
    106. Height: height,
    107. Width: width,
    108. Nodes: len(lsInfo),
    109. }
    110. for _, data := range lsInfo {
    111. for i, info := range data.([]any) {
    112. switch i {
    113. case 0:
    114. btInfo.Data = append(btInfo.Data, info.(any))
    115. case 1:
    116. btInfo.L = append(btInfo.L, info.(bool))
    117. case 2:
    118. btInfo.R = append(btInfo.R, info.(bool))
    119. case 3:
    120. btInfo.X = append(btInfo.X, info.(int))
    121. case 4:
    122. btInfo.Y = append(btInfo.Y, info.(int))
    123. case 5:
    124. btInfo.W = append(btInfo.W, info.(int))
    125. }
    126. }
    127. }
    128. for j, k := 0, width*2; j < height; j++ {
    129. DLevel := []any{}
    130. for i := k / 2; i < width*2; i += k {
    131. index := AinArray(i-width, btInfo.X)
    132. if index > -1 {
    133. DLevel = append(DLevel, btInfo.Data[index])
    134. } else {
    135. DLevel = append(DLevel, nil)
    136. }
    137. DLevel = append(DLevel, []int{i, j})
    138. if k/4 == 0 {
    139. DLevel = append(DLevel, []int{0, 0})
    140. DLevel = append(DLevel, []int{0, 0})
    141. } else {
    142. DLevel = append(DLevel, []int{i - k/4, j + 1})
    143. DLevel = append(DLevel, []int{i + k/4, j + 1})
    144. }
    145. }
    146. k /= 2
    147. btInfo.DataLevel = append(btInfo.DataLevel, DLevel)
    148. }
    149. bt.Info = btInfo
    150. }
    151. func (bt *biTree) Info2SVG(Margin ...int) string {
    152. var res, Line, Color string
    153. info := bt.Info
    154. MarginX, MarginY := 0, 10
    155. SpaceX, SpaceY := 40, 100
    156. switch len(Margin) {
    157. case 0:
    158. break
    159. case 1:
    160. MarginX = Margin[0]
    161. case 2:
    162. MarginX, MarginY = Margin[0], Margin[1]
    163. case 3:
    164. MarginX, MarginY, SpaceX = Margin[0], Margin[1], Margin[2]
    165. default:
    166. MarginX, MarginY = Margin[0], Margin[1]
    167. SpaceX, SpaceY = Margin[2], Margin[3]
    168. }
    169. info.MarginX, info.MarginY = MarginX, MarginY
    170. info.SpaceX, info.SpaceY = SpaceX, SpaceY
    171. info.SvgWidth = Pow2(info.Height)*info.SpaceX + info.SpaceX
    172. info.SvgHeight = info.Height * info.SpaceY
    173. for i, Data := range info.Data {
    174. Node := "\n\t\n\t\n\t\n\t\n\t"
    175. DataStr := ""
    176. switch Data.(type) {
    177. case int:
    178. DataStr = strconv.Itoa(Data.(int))
    179. case float64:
    180. DataStr = strconv.FormatFloat(Data.(float64), 'g', -1, 64)
    181. case string:
    182. DataStr = Data.(string)
    183. default:
    184. DataStr = "Error Type"
    185. }
    186. Node = strings.Replace(Node, "INDEX", strconv.Itoa(info.Index), 1)
    187. Node = strings.Replace(Node, "M", strconv.Itoa(info.X[i]), 1)
    188. Node = strings.Replace(Node, "N", strconv.Itoa(info.Y[i]), 1)
    189. x0, y0 := (info.X[i]+info.Width)*SpaceX+MarginX, 50+info.Y[i]*SpaceY+MarginY
    190. x1, y1 := x0-info.W[i]*SpaceX, y0+SpaceY-30
    191. x2, y2 := x0+info.W[i]*SpaceX, y0+SpaceY-30
    192. Color = "orange"
    193. if info.L[i] && info.R[i] {
    194. Line = XmlLine(x0-21, y0+21, x1, y1) + "\n\t" + XmlLine(x0+21, y0+21, x2, y2)
    195. } else if info.L[i] && !info.R[i] {
    196. Line = XmlLine(x0-21, y0+21, x1, y1)
    197. } else if !info.L[i] && info.R[i] {
    198. Line = XmlLine(x0+21, y0+21, x2, y2)
    199. } else {
    200. Color = "lightgreen"
    201. }
    202. Node = strings.Replace(Node, "", XmlCircle(x0, y0, Color), 1)
    203. Node = strings.Replace(Node, "", XmlText(x0, y0, DataStr), 1)
    204. if info.L[i] || info.R[i] {
    205. Node = strings.Replace(Node, "", Line, 1)
    206. }
    207. res += Node
    208. }
    209. info.SvgXml = res
    210. return res
    211. }
    212. func XmlCircle(X, Y int, Color string) string {
    213. Radius := 30
    214. Circle := " + strconv.Itoa(X) + "\" cy=\"" + strconv.Itoa(Y) +
    215. "\" r=\"" + strconv.Itoa(Radius) + "\" stroke=\"black\" stroke-width=" +
    216. "\"2\" fill=\"" + Color + "\" />"
    217. return Circle
    218. }
    219. func XmlText(X, Y int, DATA string) string {
    220. iFontSize, tColor := 20, "red"
    221. Text := " + strconv.Itoa(X) + "\" y=\"" + strconv.Itoa(Y) +
    222. "\" fill=\"" + tColor + "\" font-size=\"" + strconv.Itoa(iFontSize) +
    223. "\" text-anchor=\"middle\" dominant-baseline=\"middle\">" + DATA + ""
    224. return Text
    225. }
    226. func XmlLine(X1, Y1, X2, Y2 int) string {
    227. Line := " + strconv.Itoa(X1) + "\" y1=\"" + strconv.Itoa(Y1) +
    228. "\" x2=\"" + strconv.Itoa(X2) + "\" y2=\"" + strconv.Itoa(Y2) +
    229. "\" style=\"stroke:black;stroke-width:2\" />"
    230. return Line
    231. }
    232. func (bt *biTree) ShowSVG(FileName ...string) {
    233. var file *os.File
    234. var err1 error
    235. Head := " +
    236. "=\"http://www.w3.org/1999/xlink\" version=\"1.1\" width=" +
    237. "\"Width\" height=\"Height\">\nLINKCONTENT\n"
    238. Link := `
    239. Xml := strings.Replace(Head, "LINK", Link, 1)
    240. Xml = strings.Replace(Xml, "Width", strconv.Itoa(bt.Info.SvgWidth), 1)
    241. Xml = strings.Replace(Xml, "Height", strconv.Itoa(bt.Info.SvgHeight), 1)
    242. Xml = strings.Replace(Xml, "CONTENT", bt.Info.SvgXml, 1)
    243. svgFile := "biTree.svg"
    244. if len(FileName) > 0 {
    245. svgFile = FileName[0] + ".svg"
    246. }
    247. file, err1 = os.Create(svgFile)
    248. if err1 != nil {
    249. panic(err1)
    250. }
    251. _, err1 = io.WriteString(file, Xml)
    252. if err1 != nil {
    253. panic(err1)
    254. }
    255. file.Close()
    256. exec.Command("cmd", "/c", "start", svgFile).Start()
    257. //Linux 代码:
    258. //exec.Command("xdg-open", svgFile).Start()
    259. //Mac 代码:
    260. //exec.Command("open", svgFile).Start()
    261. }
    262. func main() {
    263. list := []any{"*", "*", "*", "/", 5, "*", 3.14, 1, 3, nil, nil, 6, 6}
    264. tree := Build(list...)
    265. tree.TreeInfo()
    266. tree.Info2SVG()
    267. tree.ShowSVG()
    268. fmt.Println(tree.Info.Data)
    269. fmt.Println(tree.Info.DataLevel)
    270. }

    做题思路

    增加一个结构biTreeInfo,在遍历二叉树时把作图要用的信息存入此结构中,方便读取信息。

    type any = interface{}
    
    type btNode struct {
        Data   any
        Lchild *btNode
        Rchild *btNode
    }
    
    type biTree struct {
        Root *btNode
        Info *biTreeInfo
    }
    
    type biTreeInfo struct {
        Data                []any
        DataLevel           [][]any
        L, R                []bool
        X, Y, W             []int
        Index, Nodes        int
        Width, Height       int
        MarginX, MarginY    int
        SpaceX, SpaceY      int
        SvgWidth, SvgHeight int
        SvgXml              string
    }

    //数据域类型用 type any = interface{} 自定义类型,模拟成any数据类型。

    遍历二叉树获取每个结点在svg图形中的坐标,使用先序递归遍历:

    func (bt *btNode) Coordinate(x, y, w int) []any {
        var res []any
        if bt != nil {
            L, R := bt.Lchild != nil, bt.Rchild != nil
            res = append(res, []any{bt.Data, L, R, x, y, w})
            res = append(res, bt.Lchild.Coordinate(x-w, y+1, w/2)...)
            res = append(res, bt.Rchild.Coordinate(x+w, y+1, w/2)...)
        }
        return res
    }

    二叉树的每个结点,转svg时有圆、文字、左或右直线(叶结点没有真线)。

    func XmlCircle(X, Y int, Color string) string {
        Radius := 30
        Circle := "         "\" r=\"" + strconv.Itoa(Radius) + "\" stroke=\"black\" stroke-width=" +
            "\"2\" fill=\"" + Color + "\" />"
        return Circle
    }

    func XmlText(X, Y int, DATA string) string {
        iFontSize, tColor := 20, "red"
        Text := "         "\" fill=\"" + tColor + "\" font-size=\"" + strconv.Itoa(iFontSize) +
            "\" text-anchor=\"middle\" dominant-baseline=\"middle\">" + DATA + "
    "
        return Text
    }

    func XmlLine(X1, Y1, X2, Y2 int) string {
        Line := "         "\" x2=\"" + strconv.Itoa(X2) + "\" y2=\"" + strconv.Itoa(Y2) +
            "\" style=\"stroke:black;stroke-width:2\" />"
        return Line
    }

    TreeInfo()写入二叉树结点信息,其中DataLevel是层序遍历的结果,也可以用它来作图。

    Info2XML()就是把上述方法所得信息,转化成SVG的xml代码;

    ShowSVG()生成并显示图形,svg的xml如下:



        Hann's CSDN Homepage

        
        
        *
        
        
        

        
        
        *
        
        
        

        
        
        /
        
        
        

        
        
        1
        
        

        
        
        3
        
        

        
        
        5
        
        

        
        
        *
        
        
        

        
        
        *
        
        
        

        
        
        6
        
        

        
        
        6
        
        

        
        
        3.14
        
        

    扩展

    多棵二叉树同时展示,Info2SVG()可以设置起始位置

    左右并列展示

    1. tree2 := Build("*", "*", 3.14, 6, 6)
    2. tree2.TreeInfo()
    3. tree2.Info2SVG()
    4. tree2.ShowSVG("tree2")
    5. //左右并列展示
    6. tree2.Info2SVG(tree.Info.SvgWidth, tree.Info.SpaceY)
    7. tree.Info.SvgXml += tree2.Info.SvgXml
    8. tree.Info.SvgWidth += tree2.Info.SvgWidth
    9. tree.ShowSVG("tree12")
    10. tree.Info2SVG() //恢复tree原状

    上下并列展示

    1. //上下并列展示
    2. tree2.Info2SVG(tree.Info.SvgWidth-tree2.Info.SvgWidth, tree.Info.SvgHeight)
    3. tree.Info.SvgXml += tree2.Info.SvgXml
    4. tree.Info.SvgHeight += tree2.Info.SvgHeight
    5. tree.ShowSVG("tree123")
    6. tree.Info2SVG() //恢复tree原状

    以上2段代码放在前文源代码的main()函数中测试。

     

    总结回顾

    结点显示的代码固定了文字和圆形的大小颜色,如果读者愿意自己动手的话,可以尝试把这些要素设成参数或者增加biTreeInfo结构的属性。

  • 相关阅读:
    【c++】stringstream基础:实现数据类型转换和字符串分割
    RabbitMQ
    vue使用.filter方法检索数组中指定时间段内的数据
    4.7 wait notify - 4.11 多把锁
    携创教育:2022年10月广东自考成绩查询时间?成绩查询流程?
    Python_Numpy库的ndarray对象的属性有哪些?如何获取它们的值?
    昔日红极一时,如今重出江湖,这种按键位要怎么设计
    App 软件开发《判断5》试卷及答案
    关于nginx一个域名,配置多个端口https的方法
    5分钟学会canvas的使用
  • 原文地址:https://blog.csdn.net/boysoft2002/article/details/126908846