手把手教你用Java代码画二叉树

r囧r小猫 2022-12-27 12:59 330阅读 0赞

最近在研究二叉树,所以需要经常画二叉树来弥补我的匮乏的想象力,但是画一颗二叉树也不是那么容易的事,不仅画的慢,还画的丑,昨天晚上睡觉的时候还在思考,能不能用代码来画二叉树呢?我确信这种方案是可行的,本文就来分享我的成果,其实用代码画二叉树挺有趣的。

另外,由于代码不是一成不变的,我会时不时改一些bug、或者做一些优化,因此我将代码上传到gitee仓库上面了,读者可以自行下载。附地址:https://gitee.com/bobo_tea/datastructure/tree/master/src/main/datastructure/com/bobo/group/tree/draw

先来看一下效果,如图1所示。

图片

图1

结点类DrawNode如下所示。

  1. public static class DrawNode{
  2. protected int val;
  3. protected DrawNode left;
  4. protected DrawNode right;
  5. public DrawNode() {}
  6. public DrawNode(int val) {
  7. this.val = val;
  8. }
  9. public DrawNode(int val, DrawNode left, DrawNode right) {
  10. this.val = val;
  11. this.left = left;
  12. this.right = right;
  13. }
  14. }

如果你的结点类可能叫Node或者TreeNode,你可以继承DrawNode类,从而不用修改代码就能画出你想要的树来。

我用硬编码构建了一棵树,就是图1中的树,如下所示。

  1. public DrawNode build(){
  2. DrawNode e = new DrawNode(73);
  3. DrawNode h = new DrawNode(51);
  4. DrawNode i = new DrawNode(93);
  5. DrawNode j = new DrawNode(37);
  6. DrawNode k = new DrawNode(120);
  7. DrawNode l = new DrawNode(9);
  8. DrawNode f = new DrawNode(99,i,k);
  9. DrawNode c = new DrawNode(88,e,f);
  10. DrawNode g = new DrawNode(35,l,j);
  11. DrawNode d = new DrawNode(47,g,h);
  12. DrawNode b = new DrawNode(58,d,null);
  13. DrawNode a = new DrawNode(62,b,c);
  14. return a;
  15. }

画二叉树的三个核心方法:

  • firstTraverseAndDraw:一边中序遍历,一边画图。而画图是调用了drawOval方法和drawLine方法。
  • drawOval:画圆圈、画圆圈里面的值。
  • drawLine:画两个圆圈之间的连线。

画二叉树的入口方法:

  • drawEntrance(DrawNode drawNode)

main方法调用入口方法,进行画图。

  1. public static void main(String[] args) throws IOException {
  2. Drawing d = new Drawing();
  3. //先构建一颗二叉树
  4. DrawNode root = d.build();
  5. //调用入口方法画图
  6. d.drawEntrance(root);
  7. }

图片保存位置用常量PATH表示。

  1. private static final String PATH = "F:/tree.png";

完整代码如下所示。

  1. import javax.imageio.ImageIO;
  2. import java.awt.*;
  3. import java.awt.image.BufferedImage;
  4. import java.io.FileOutputStream;
  5. import java.io.IOException;
  6. public class Drawing {
  7. // 以下是常量
  8. private static final String PATH = "F:/tree.png";
  9. // 画板长度
  10. private static final int WIDTH = 800;
  11. // 画板宽度
  12. private static final int HEIGHT = 500;
  13. // 圆形半径
  14. private static final int OVAL_RADIUS = 15;
  15. // 字体大小
  16. private static final int FONT_SIZE = 15;
  17. // 根据字体位数,选择字体偏移量
  18. private static final int offset_1 = 12;
  19. private static final int offset_2 = 9;
  20. private static final int offset_3 = 4;
  21. //画连线时的偏移量
  22. private static final int LINE_OFFSET = 3;
  23. //字体格式
  24. private static final String FONT_STYLE = "宋体";
  25. //图片格式
  26. private static final String IMAGE_STYLE = "PNG";
  27. //写数字时用到的偏移量
  28. private static final int FONT_OFFSET = 1;
  29. //父圆形与子圆形之间的偏移量
  30. private static final int OVAL_OFFSET = 10;
  31. //root结点的y坐标
  32. private static final int ROOT_Y = 50;
  33. //图形缓存区
  34. private BufferedImage bi;
  35. //画图笔
  36. private Graphics2D g2;
  37. //以下是变量
  38. // 当前圆位置和值
  39. private int my_x;
  40. private int my_y;
  41. private int val;
  42. // 当前字体位置
  43. private int my_font_x;
  44. private int my_font_y;
  45. private void initSettings(){
  46. //得到图片缓冲区
  47. bi = new BufferedImage(WIDTH,HEIGHT,BufferedImage.TYPE_INT_RGB);
  48. //得到绘制环境(这张图片的笔)
  49. g2 = (Graphics2D) bi.getGraphics();
  50. g2.fillRect(0,0,WIDTH,HEIGHT);
  51. g2.setBackground(Color.WHITE);
  52. g2.setColor(Color.BLACK);
  53. g2.setFont(new Font(FONT_STYLE,Font.PLAIN,FONT_SIZE));
  54. }
  55. private void closeSettings() throws IOException {
  56. ImageIO.write(bi,IMAGE_STYLE,new FileOutputStream(PATH));
  57. }
  58. public static class DrawNode{
  59. protected int val;
  60. protected DrawNode left;
  61. protected DrawNode right;
  62. public DrawNode() {}
  63. public DrawNode(int val) {
  64. this.val = val;
  65. }
  66. public DrawNode(int val, DrawNode left, DrawNode right) {
  67. this.val = val;
  68. this.left = left;
  69. this.right = right;
  70. }
  71. }
  72. /**
  73. * 画一个圆,包括数字
  74. */
  75. private void drawOval(){
  76. g2.drawOval(my_x,my_y,OVAL_RADIUS*2,OVAL_RADIUS*2);
  77. my_font_y = my_y+OVAL_RADIUS+FONT_SIZE/2-FONT_OFFSET;
  78. switch (String.valueOf(val).length()){
  79. case 1: my_font_x = my_x+offset_1;break;
  80. case 2: my_font_x = my_x+offset_2;break;
  81. case 3: my_font_x = my_x+offset_3;break;
  82. default: System.out.println("值为"+String.valueOf(val).length()+"位数,不支持!");
  83. }
  84. g2.drawString(String.valueOf(val),my_font_x,my_font_y);
  85. }
  86. /**
  87. * 画连线
  88. * @param isLeft 父结点与子结点的关系
  89. * @param parent_x 父结点的x坐标
  90. * @param parent_y 父结点的y坐标
  91. */
  92. private void drawLine(boolean isLeft,int parent_x,int parent_y){
  93. int parent_point_x;
  94. int parent_point_y;
  95. int my_point_x;
  96. int my_point_y;
  97. if(isLeft){
  98. parent_point_x = parent_x+OVAL_RADIUS/2-LINE_OFFSET;
  99. parent_point_y = parent_y+(3*OVAL_RADIUS)/2+LINE_OFFSET;
  100. my_point_x = my_x+(3*OVAL_RADIUS)/2+LINE_OFFSET;
  101. my_point_y = my_y+OVAL_RADIUS/2-LINE_OFFSET;
  102. }else{
  103. parent_point_x = parent_x+(3*OVAL_RADIUS)/2+LINE_OFFSET;
  104. parent_point_y = parent_y+(3*OVAL_RADIUS)/2+LINE_OFFSET;
  105. my_point_x = my_x+OVAL_RADIUS/2-LINE_OFFSET;
  106. my_point_y = my_y+OVAL_RADIUS/2-LINE_OFFSET;
  107. }
  108. g2.drawLine(parent_point_x,parent_point_y,my_point_x,my_point_y);
  109. }
  110. /**
  111. * 一边中序遍历一边画图
  112. * @param node
  113. */
  114. private void firstTraverseAndDraw(DrawNode parentNode,DrawNode node,boolean isLeft,int parent_x,int parent_y){
  115. if(node == null){
  116. return;
  117. }
  118. //只画当前结点,而不画父结点。但是要根据父结点与当前结点的关系,确定当前结点的位置,并将连线画出来
  119. val = node.val;
  120. if(parentNode == null){
  121. my_x = WIDTH/2 - OVAL_RADIUS;
  122. my_y = ROOT_Y;
  123. }else{
  124. my_y = parent_y+2*OVAL_RADIUS+OVAL_OFFSET;
  125. if(isLeft){
  126. my_x = parent_x-2*OVAL_RADIUS-OVAL_OFFSET;
  127. }else{
  128. my_x = parent_x+2*OVAL_RADIUS+OVAL_OFFSET;
  129. }
  130. drawLine(isLeft,parent_x,parent_y);
  131. }
  132. drawOval();
  133. // 递归画出左子树和右子树
  134. parent_x = my_x;
  135. parent_y = my_y;
  136. firstTraverseAndDraw(node,node.left,true,parent_x,parent_y);
  137. firstTraverseAndDraw(node,node.right,false,parent_x,parent_y);
  138. }
  139. public DrawNode build(){
  140. DrawNode e = new DrawNode(73);
  141. DrawNode h = new DrawNode(51);
  142. DrawNode i = new DrawNode(93);
  143. DrawNode j = new DrawNode(37);
  144. DrawNode k = new DrawNode(120);
  145. DrawNode l = new DrawNode(9);
  146. DrawNode f = new DrawNode(99,i,k);
  147. DrawNode c = new DrawNode(88,e,f);
  148. DrawNode g = new DrawNode(35,l,j);
  149. DrawNode d = new DrawNode(47,g,h);
  150. DrawNode b = new DrawNode(58,d,null);
  151. DrawNode a = new DrawNode(62,b,c);
  152. return a;
  153. }
  154. public void drawEntrance(DrawNode drawNode) throws IOException {
  155. initSettings();
  156. firstTraverseAndDraw(null,drawNode,true,0,0);
  157. closeSettings();
  158. }
  159. public static void main(String[] args) throws IOException {
  160. Drawing d = new Drawing();
  161. //先构建一颗二叉树
  162. DrawNode root = d.build();
  163. //调用入口方法画图
  164. d.drawEntrance(root);
  165. }
  166. }

最后扫码关注一下笔者的微信公众号再走吧,感谢!

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3hsXzE4MDM_size_16_color_FFFFFF_t_70

发表评论

表情:
评论列表 (有 0 条评论,330人围观)

还没有评论,来说两句吧...

相关阅读

    相关 手把手Java代码

    最近在研究二叉树,所以需要经常画二叉树来弥补我的匮乏的想象力,但是画一颗二叉树也不是那么容易的事,不仅画的慢,还画的丑,昨天晚上睡觉的时候还在思考,能不能用代码来画二叉树呢?我