使用递归绘制树 [英] Drawing a tree using recursion

查看:72
本文介绍了使用递归绘制树的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用递归绘制一棵树.树需要看起来像这样:

I am trying to draw a tree using recursion. The tree needs to look like this:

我应该如何做的简短摘要:

A short summary of how I'm supposed to do it:

  • 树干的长度为length,宽度为width
  • 树干分成两个分支
  • 左边是躯干长度的 3/4,右边是躯干长度的 2/3
  • 左分支宽度为树干宽度的 3/4,右分支宽度为树干宽度的 1/2
  • 我们收到的参数是 length、min_length、width、alpha(都是双精度)
  • 分支会不断增长,直到分支的长度超过 min_length

这是我解决问题的方法.我只想画树干,左分支和右分支.我设法做到了这一点,具有以下功能:

Here's how I tackled the problem. I wanted to just draw the trunk, left branch and right branch. I managed to do this, with the following function:

public void drawTree(double length, double min_length, double width, double alpha) {
        //Draws the trunk of the tree
        StdDraw.setPenRadius(width);
        StdDraw.line(250, 150, 250, 150+length);        

        //Left branch
        double hypotenuse = (3.0/4.0)*length;
        double opposite = Math.sin(alpha) * hypotenuse;
        double adjacent = Math.sqrt(Math.pow(hypotenuse, 2)-Math.pow(opposite, 2));
        StdDraw.setPenRadius(width*3.0/4.0);
        StdDraw.line(250,150+length,250-adjacent,150+length+opposite);

        //Right branch
        double hypotenuse2 = (2.0/3.0)*length;
        double opposite2 = Math.sin(alpha) * hypotenuse2;
        double adjacent2 = Math.sqrt(Math.pow(hypotenuse2, 2)-Math.pow(opposite2, 2));
        StdDraw.setPenRadius(width*1.0/2.0);
        StdDraw.line(250,150+length,250+adjacent2,150+length+opposite2);       

    }

这是我想要的输出:

我认为剩下的会很容易,但在过去的 3 小时内我没有取得任何进展:/.我包含了停止条件的 if 语句.但我不知道递归部分.我试过这个:

I thought the rest would be easy, but I haven't made any progress in the last 3 hours :/. I included the if statement for the stopping condition. But I don't have an idea about the recursive part. I've tried this:

if (length > min_length) {
    //Left branch
    double hypotenuse = (3.0/4.0)*length;
    double opposite = Math.sin(alpha) * hypotenuse;
    double adjacent = Math.sqrt(Math.pow(hypotenuse, 2)-Math.pow(opposite, 2));
    StdDraw.setPenRadius(width*3.0/4.0);
    StdDraw.line(250,150+length,250-adjacent,150+length+opposite);
    //Right branch
    double hypotenuse2 = (2.0/3.0)*length;
    double opposite2 = Math.sin(alpha) * hypotenuse2;
    double adjacent2 = Math.sqrt(Math.pow(hypotenuse2, 2)-Math.pow(opposite2, 2));
    StdDraw.setPenRadius(width*1.0/2.0);
    StdDraw.line(250,150+length,250+adjacent2,150+length+opposite2);
    //My first attempt
    drawTree(hypotenuse*hypotenuse, min_length, width, alpha);
    drawTree(hypotenuse2*hypotenuse2, min_length, width, alpha);
    //My second attempt
    drawTree(hypotenuse, min_length, width, alpha);
    drawTree(hypotenuse2, min_length, width, alpha);       
}

我理解简单的递归,如阶乘、回文等,但我被困在这个问题上,我很感激任何帮助.

I understand simple recursion like factorials, palindrome, etc, but I'm stuck on this one and I'd appreciate any help.

推荐答案

正如评论和当前答案中已经指出的那样,让 drawTree 方法不知道树的哪一部分是至关重要的目前正在绘制.

As already pointed out in the comments and the current answer, it's crucial to make the drawTree method agnostic of which part of the tree is currently drawn.

您不能在此方法中使用绝对坐标.而且您必须跟踪位置您当前的位置.例如,这可以通过描述当前树部分起点的递归方法传递 Point2D 来完成.

You may not use absolute coordinates in this method. And you have to keep track of where you currently are. This can be done, for example, by passing a Point2D through the recursive method that describes the starting point of the current tree part.

您甚至不需要显式代码来绘制分支:请注意,单行已经是一棵树.树枝只是较小的树":它们又是单行,但具有不同的长度和宽度.

You don't even need explicit code to draw the branches: Note that a single line already is a tree. The branches are then just "smaller trees": They are, again, single lines, but with different length and widths.

(并且与之前的树相比有一定的角度.你没有提到这一点,但根据截图,角度似乎是Math.PI/5)

(And with a certain angle compared to the previous tree. You did not mention this, but the angle seems to be Math.PI / 5 according to the screenshot)

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.RenderingHints;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.util.function.DoubleConsumer;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

public class RecursiveTreeDrawing
{
    public static void main(String[] args) 
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            @Override
            public void run()
            {
                createAndShowGUI();
            }
        });
    }    

    private static void createAndShowGUI()
    {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.getContentPane().setLayout(new BorderLayout());

        RecursiveTreeDrawingPanel p = new RecursiveTreeDrawingPanel();
        p.setPreferredSize(new Dimension(500,500));
        f.getContentPane().add(p, BorderLayout.CENTER);

        JPanel c = new JPanel(new GridLayout(0,1));

        c.add(createControl("left length", 0, 0.9, 
            d -> p.setLeftLengthFactor(d)));
        c.add(createControl("left width", 0, 0.9, 
            d -> p.setLeftWidthFactor(d)));
        c.add(createControl("left angle", 0, Math.PI, 
            d -> p.setLeftAngleDelta(d)));

        c.add(createControl("right length", 0, 0.9, 
            d -> p.setRightLengthFactor(d)));
        c.add(createControl("right width", 0, 0.9, 
            d -> p.setRightWidthFactor(d)));
        c.add(createControl("right angle", -Math.PI, 0, 
            d -> p.setRightAngleDelta(d)));
        f.getContentPane().add(c, BorderLayout.SOUTH);

        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    private static JPanel createControl(
        String name, double min, double max, DoubleConsumer doubleConsumer)
    {
        JPanel p = new JPanel(new GridLayout(1,0));
        p.add(new JLabel(name));
        JSlider slider = new JSlider(0, 100, 0);
        slider.addChangeListener(new ChangeListener()
        {

            @Override
            public void stateChanged(ChangeEvent e)
            {
                int value = slider.getValue();
                double v = value / 100.0;
                double d = min + v * (max - min);
                doubleConsumer.accept(d);
            }
        });
        p.add(slider);

        return p;
    }

}


class RecursiveTreeDrawingPanel extends JPanel
{
    private double leftLengthFactor = 3.0 / 4.0;
    private double leftWidthFactor = 3.0 / 4.0;
    private double leftAngleDelta = Math.PI / 5.0;
    private double rightLengthFactor = 2.0 / 3.0;
    private double rightWidthFactor = 1.0 / 2.0;
    private double rightAngleDelta = - Math.PI / 5.0; 

    @Override
    protected void paintComponent(Graphics gr)
    {
        super.paintComponent(gr);
        Graphics2D g = (Graphics2D)gr;
        g.setColor(Color.BLACK);
        g.fillRect(0,0,getWidth(),getHeight());
        g.setRenderingHint(
            RenderingHints.KEY_ANTIALIASING, 
            RenderingHints.VALUE_ANTIALIAS_ON);
        Point2D start = new Point2D.Double(
            getWidth() * 0.5, 
            getHeight() * 0.7);
        g.setColor(Color.GRAY);
        drawTree(g, start, 100.0, 2.0, 10.0, 0);
    }

    private void drawTree(Graphics2D g, 
        Point2D start, double length, double minLength, 
        double width, double alpha)
    {
        if (length < minLength)
        {
            return;
        }
        g.setStroke(new BasicStroke((float)width, 
            BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
        Point2D end = new Point2D.Double(
            start.getX() + Math.sin(alpha + Math.PI) * length,
            start.getY() + Math.cos(alpha + Math.PI) * length);
        g.draw(new Line2D.Double(start, end));
        drawTree(g, end, length * leftLengthFactor, minLength, 
            width * leftWidthFactor, alpha + leftAngleDelta);
        drawTree(g, end, length * rightLengthFactor, minLength, 
            width * rightWidthFactor, alpha + rightAngleDelta);
    }

    public void setLeftLengthFactor(double leftLengthFactor)
    {
        this.leftLengthFactor = leftLengthFactor;
        repaint();
    }

    public void setLeftWidthFactor(double leftWidthFactor)
    {
        this.leftWidthFactor = leftWidthFactor;
        repaint();
    }

    public void setLeftAngleDelta(double leftAngleDelta)
    {
        this.leftAngleDelta = leftAngleDelta;
        repaint();
    }

    public void setRightLengthFactor(double rightLengthFactor)
    {
        this.rightLengthFactor = rightLengthFactor;
        repaint();
    }

    public void setRightWidthFactor(double rightWidthFactor)
    {
        this.rightWidthFactor = rightWidthFactor;
        repaint();
    }

    public void setRightAngleDelta(double rightAngleDelta)
    {
        this.rightAngleDelta = rightAngleDelta;
        repaint();
    }

}

这篇关于使用递归绘制树的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆