Coursera-Algorithms,Part I-Robert Sedgewick-Programming Assignment 5

Programming Assignment 5: Kd-Trees

问题描述

0.完结祝贺

这里写图片描述

1.一个语言相关的问题

有一个关于语言的问题,记录一下理解,表述不一定准确,有空再深究:

Java的参数传递问题。

一个前提:自定义类型都是引用传递

作业里有一处递归,不断往一个栈里添加满足条件的点。各层递归之间用参数传递这个栈。递归完成以后,最外层的栈里就装好所有点了。

合理性:每一层递归都生成一个 指针,通过引用传递,它们指向的是同一个栈——最外层初始化好的那一个。所以各层操作的是同一个栈。内层递归完成后,内层指针消失,但栈是在最外层定义的,其内容不受影响。

思维惯性使然下,打算套用这一形式写“最近邻点”功能,就出现问题了。
以为会发生这种事:变量作为参数在层间引用传递,每层找到更近的点就赋一个新值,递归完以后这个变量肯定是最近的点了,完美。
而实际发生的事:每层创建一个新指针,这个指针确实指向了外层找到的当前最近点。而在这这一层找到更近的点以后,赋值使得新指针指向新点(而不像前面的情况:一直指向最外层定义的栈),与外层断开联系。这层递归完成以后,指针消失,找到的更近的结果也丢失了。

“赋值”被加粗了,赋值是导致指针指向新内容、层间联系断开的原因,可是找到更近的点以后除了赋值没有别的办法——这与Point2D类型不可变(immutable)、Stack类型可变有关。如果Point2D也有一个方法改变自身坐标,就可以类似“在矩形内”的方法实现“最近点”了。可见类是否mutable是造成两者实现方法不同的深层原因。

2.StdDraw绘图

本课程提供标准绘图类StdDraw,使用前应进行一定初始化,设置画布大小画笔大小等。

        StdDraw.setXscale(0, 1);
        StdDraw.setYscale(0, 1);
        StdDraw.setPenRadius(.01);

否则画出来是空白。
(共线问题不画图硬着头皮还是写出来了,这次作业还是需要一些调试的,故上网搜索解决了画图空白问题。。)

3.最后的优化

最后几分有一个关于搜索顺序的优化,调试了有一阵子。
该优化针对类似下图的情况:左边半边没有被排除,搜索完右边以后左边的两个点谁先谁后?

本着“近的先搜索”的原则,能直观地判断应该先搜索2点。但是查询点既没有包含于2对应的矩形(rect)中、也没有包含于1对应的矩形中。人眼之所以能判断先搜索2点,是因为查询点与2都处于相对下方的位置。更准确地描述应为:将1、2对应矩形的分界线延长,将整个画布分为两部分,查询点与2处在同一部分。

到这里解法应该已经比较明朗了。我的方法:每个节点额外设置一个RectHV·对象rectDivideIntoTwo,表示该节点P与它的兄弟节点Q将整个画布划分成两份时,节点P对应的部分。下图的情况中,由于查询点包含于点2对应的rectDivideIntoTwo,故点2应先于1搜索。具体实现可能还有别的方式。
这里写图片描述

4.一个巧合:还需要优化吗?

提交时又碰到一个问题,只差1分不能通过,情况如图:查询点落在了分界线上
这里写图片描述
问题出在:第一步应该往左还是往右?我的解法与参考解相反。
测试信息:

Test 6a: insert points from file; check nearest() with random query points
         and check traversal of kd-tree
  * input5.txt
    - failed on trial 14 of 1000
    - performs incorrect traversal of kd-tree during call to nearest()
    - sequence of points inserted: 
      A  0.7 0.2
      B  0.5 0.4
      C  0.2 0.3
      D  0.4 0.7
      E  0.9 0.6
    - query point                   = (0.7, 0.01)
    - student   nearest()           = (0.7, 0.2)
    - reference nearest()           = (0.7, 0.2)
    - student   distanceSquaredTo() = 0.0361
    - reference distanceSquaredTo() = 0.0361
    - student sequence of kd-tree nodes involved in calls to distanceSquaredTo():
      A B C E 
    - reference sequence of kd-tree nodes involved in calls to distanceSquaredTo():
      A E B C 

  * input10.txt
==> FAILED

可以见到,给出的参考顺序是先搜索的右边的点(0.9 , 0.6)——一个比左边更远的点。从这里基本可以判定,这属于测试bug,而不是缺少某种优化了。

针对这种情况没有较简单的优化办法,先后顺序存粹取决于代码里哪种情况被放在了if中、哪种情况被放在了else中(contain方法是包括边界情况的,所以边界情况被归作哪一类与if-elseif的条件有关)。由于该单元测试的查询点是随机给出的,我猜测重复提交可能就能通过了,但是我没有做尝试——我调换了ifelse的语句块,随后通过了测试。

5.代码

PointSET.java

import edu.princeton.cs.algs4.Point2D;
import edu.princeton.cs.algs4.RectHV;
import edu.princeton.cs.algs4.SET;
import edu.princeton.cs.algs4.StdDraw;

public class PointSET {
    private SET<Point2D> points;

    public         PointSET()                               // construct an empty set of points 
    {   points = new SET<Point2D>();    }

    public           boolean isEmpty()                      // is the set empty? 
    {   return points.isEmpty();    }

    public               int size()                         // number of points in the set
    {   return points.size();   }

    public              void insert(Point2D p)              // add the point to the set (if it is not already in the set)
    {   
        if (p == null) throw new java.lang.IllegalArgumentException();
        points.add(p);  
    }

    public           boolean contains(Point2D p)            // does the set contain point p? 
    {   
        if (p == null) throw new java.lang.IllegalArgumentException();
        return points.contains(p);  
    }

    public              void draw()                         // draw all points to standard draw 
    {   
        for (Point2D p : points)
            p.draw();       
    }

    public Iterable<Point2D> range(RectHV rect)             // all points that are inside the rectangle (or on the boundary) 
    {   
        if (rect == null) throw new java.lang.IllegalArgumentException();
        SET<Point2D> pointsInside = new SET<Point2D>();
        for (Point2D p:points)
        {
            if (rect.contains(p))   
                pointsInside.add(p);
        }
        return pointsInside;
    }

    public           Point2D nearest(Point2D p)             // a nearest neighbor in the set to point p; null if the set is empty 
    {   
        if (p == null) throw new java.lang.IllegalArgumentException();
        Double minDistance = null;
        Double thisDistance = null;
        Point2D nearest = null;
        for (Point2D point:points)
        {
            thisDistance = point.distanceTo(p);
            if (minDistance == null || thisDistance < minDistance)  
            {
                minDistance = point.distanceTo(p);
                nearest = point;
            }
        }
        return nearest; 
    }

    public static void main(String[] args)                  // unit testing of the methods (optional) 
    {
        System.out.println("Client Starts:");
        PointSET points = new PointSET();

        Point2D p1 = new Point2D(1.1, 1.1); points.insert(p1);
        Point2D p2 = new Point2D(2.2, 2.2); points.insert(p2);
        Point2D p3 = new Point2D(3.3, 3.4); points.insert(p3);
        Point2D p4 = new Point2D(4.5, 4.3); points.insert(p4);
        Point2D p5 = new Point2D(5.9, 5.2); points.insert(p5);
        Point2D p6 = new Point2D(6.7, 6.3); points.insert(p6);
        Point2D p7 = new Point2D(7.2, 7.8); points.insert(p7);
        Point2D p8 = new Point2D(8.5, 8.5); points.insert(p8);
        Point2D p9 = new Point2D(9.7, 9.9); points.insert(p9);

        StdDraw.setXscale(0, 10);
        StdDraw.setYscale(0, 10);
        StdDraw.setPenRadius(.01);


        // points.draw();

        RectHV rhv = new RectHV(2.5,2.5,6.5,9.7);
        Iterable<Point2D> pointsInside = points.range(rhv);
        for (Point2D p : pointsInside)
            p.draw();
        rhv.draw();

        Point2D p = new Point2D(1.1,2.1);
        p.drawTo(points.nearest(p));

    }
}

KdTree.java

import edu.princeton.cs.algs4.Point2D;
import edu.princeton.cs.algs4.RectHV;
import edu.princeton.cs.algs4.StdDraw;
import java.util.Stack;

public class KdTree {
    private Node root;

    private class Node
    {
        private Point2D point;
        private Node left,right;
        private boolean ifUsingXCoord;    // True: compare by x coordinates. False: y coordinates.
        private RectHV rect;              // Corresponding rectangular:
                                          // One that contains the point and all its descendants

        private RectHV rectDivideIntoTwo; // This point and its sibling divide the whole canvas into two
                                          // parts. And this rectangular represents the part corresponding
                                          // to this point. This is for some detailed optimization.
        private int count;

        public Node(Point2D point, Node parent)
        {
            if (parent == null) 
            // Constructing the root: 1. corresponding rectangle is the whole canvas
            //                        2. comparing by x coordinates
            {
                rect = new RectHV(0, 0, 1, 1);
                rectDivideIntoTwo = new RectHV(0, 0, 1, 1);
                // rect.draw(); // Debugging drawing
                ifUsingXCoord = true;
            }
            else 
            // Construction other nodes: 1. corresponding rectangle is obtained by change one parameter
            //                              of its parent's
            //                           2. alternately compare by x or y. Contrast to its parent 
            {
                double xmin, xmax, ymin, ymax;
                double xminDIT, xmaxDIT, yminDIT, ymaxDIT; // DIT: Divide Into Two
                xmin = parent.rect.xmin();
                xmax = parent.rect.xmax();
                ymin = parent.rect.ymin();
                ymax = parent.rect.ymax();
                xminDIT = 0;
                xmaxDIT = 1;
                yminDIT = 0;
                ymaxDIT = 1;
                int comp = compare(point, parent.point, parent.ifUsingXCoord);
                if (parent.ifUsingXCoord)
                {
                    if (comp > 0) 
                        xminDIT = xmin = parent.point.x();
                    else 
                        xmaxDIT = xmax = parent.point.x();
                }
                else 
                {
                    if (comp > 0)
                        yminDIT = ymin = parent.point.y();
                    else
                        ymaxDIT = ymax = parent.point.y();
                }
                rect = new RectHV(xmin, ymin, xmax, ymax);
                // rect.draw();
                rectDivideIntoTwo = new RectHV(xminDIT, yminDIT, xmaxDIT, ymaxDIT);
                ifUsingXCoord = !parent.ifUsingXCoord;
            }
            this.point = point;
            count = 1;
        }
    }

    public         KdTree()                               // construct an empty set of points 
    {}

    public           boolean isEmpty()                      // is the set empty? 
    {   return root == null;    }

    public               int size()                         // number of points in the set 
    {   return size(root);  }

    private              int size(Node x)
    {   
        if (x == null) return 0;
        return x.count; 
    }

    public              void insert(Point2D p)              // add the point to the set (if it is not already in the set)
    {   
        if (p == null) throw new java.lang.IllegalArgumentException();
        root = insert(root, null, p);
    }

    private Node insert(Node x, Node parentOfx, Point2D p)
    {   
        if (x == null) return new Node(p, parentOfx);

        int comp = compare(p, x.point, x.ifUsingXCoord);
        if      (comp > 0) x.right = insert(x.right, x, p);
        else if (comp < 0) x.left  = insert(x.left, x, p);
        else               x.point = p;

        x.count = size(x.left) + 1 + size(x.right);
        return x;
    }

    public           boolean contains(Point2D p)            // does the set contain point p? 
    {
        if (p == null) throw new java.lang.IllegalArgumentException();
        Node x = root;
        int comp;
        while (x != null)
        {
            comp = compare(p, x.point, x.ifUsingXCoord);
            if      (comp > 0) x = x.right;
            else if (comp < 0) x = x.left;
            else               return true;
        }   
        return false;
    }

    private           Node pointToNode(Point2D p)       // Debugging tool      
    {
        Node x = root;
        int comp;
        while (x != null)
        {
            comp = compare(p, x.point, x.ifUsingXCoord);
            if      (comp > 0) x = x.right;
            else if (comp < 0) x = x.left;
            else               return x;
        }   
        return null;
    }

    private int compare(Point2D p1, Point2D p2, boolean ifUsingXCoord)
    {
        int temp;
        if (ifUsingXCoord) temp = Double.compare(p1.x(), p2.x()); // Compare by x coordinates
        else               temp = Double.compare(p1.y(), p2.y()); // Compare by y coordinates

        if (temp == 0) return p1.compareTo(p2);      // Important!
        return temp;                                 // If the preferred coordinates are identical,
                                                     // compare by the other.
    }

    public              void draw()                         // draw all points to standard draw 
    {
        for (Point2D p: points())
            p.draw();
    }

    private Iterable<Point2D> points()
    {
        Stack<Point2D> points = new Stack<Point2D>();
        inorder(root, points);
        return points;
    }

    private void inorder(Node x, Stack<Point2D> points) 
    // Add all the points in the tree rooted at x
    {
        if (x == null) return ;
        inorder(x.left, points);
        points.add(x.point);
        inorder(x.right, points);
    }

    public Iterable<Point2D> range(RectHV rect)             // all points that are inside the rectangle (or on the boundary) 
    {
        if (rect == null) throw new java.lang.IllegalArgumentException();

        Stack<Point2D> pointsInside = new Stack<Point2D>();
        if (root == null) return pointsInside;

        findInside(root, rect, pointsInside);
        return pointsInside;
    }

    private void findInside(Node x, RectHV rect, Stack<Point2D> pointsInside)
    // Push all the points inside the rectangular and in the tree rooted at x
    {       
        if (x.left != null && rect.intersects(x.left.rect)) 
            findInside(x.left, rect, pointsInside);
        if (rect.contains(x.point)) 
            pointsInside.add(x.point);
        if (x.right != null && rect.intersects(x.right.rect)) 
            findInside(x.right, rect, pointsInside);    
    }

    public           Point2D nearest(Point2D p)             // a nearest neighbor in the set to point p; null if the set is empty 
    {
        if (p == null) throw new java.lang.IllegalArgumentException();

        if (root == null) return null;

        Point2D currentNearest = root.point;
        currentNearest = findNearest(root, p, currentNearest);
        return currentNearest;
    }


    private Point2D findNearest(Node x, Point2D p, Point2D currentNearest)
    // Find the a nearer point in the subtree rooted at x. Return the new one.
    {               
        // System.out.println(x.point); // Debugging Info
        if (x.right != null && x.right.rectDivideIntoTwo.contains(p)) 
        // Query point is in the same part with x.right, therefore search x.right first.
        {
            if (x.point.distanceTo(p) < currentNearest.distanceTo(p)) // Parent first. 
                                                                      // Avoid recursion whenever possible
                currentNearest = x.point; 
            if (x.right != null && x.right.rect.distanceTo(p) < currentNearest.distanceTo(p))
                currentNearest = findNearest(x.right, p, currentNearest);
            if (x.left != null && x.left.rect.distanceTo(p) < currentNearest.distanceTo(p)) 
                currentNearest = findNearest(x.left, p, currentNearest);
        }
        else
        // Query point is in the same part with x.left, therefore search x.left first.
        {
            if (x.point.distanceTo(p) < currentNearest.distanceTo(p)) 
                currentNearest = x.point; 
            if (x.left != null && x.left.rect.distanceTo(p) < currentNearest.distanceTo(p)) 
                currentNearest = findNearest(x.left, p, currentNearest);
            if (x.right != null && x.right.rect.distanceTo(p) < currentNearest.distanceTo(p))
                currentNearest = findNearest(x.right, p, currentNearest);
        }
        return currentNearest;
    }

    public static void main(String[] args)                  // unit testing of the methods (optional) 
    {
        System.out.println("Client starts:");

        KdTree kt = new KdTree();
        System.out.println(kt.isEmpty());

        Point2D p1 = new Point2D(0.7, 0.2); kt.insert(p1);

        Point2D p2 = new Point2D(0.5, 0.4); kt.insert(p2);
        Point2D p3 = new Point2D(0.2, 0.3); kt.insert(p3);

        Point2D p4 = new Point2D(0.4, 0.7); kt.insert(p4);
        Point2D p5 = new Point2D(0.9, 0.6); kt.insert(p5);  

        System.out.println(kt.size());
        System.out.println(kt.isEmpty());

        StdDraw.setXscale(0, 1);
        StdDraw.setYscale(0, 1);
        StdDraw.setPenRadius(.01);

        // kt.draw();

        RectHV rect = new RectHV(0.1,0.2,0.8,0.7);
        rect.draw();        
        Iterable<Point2D> pointsInside = kt.range(rect);
        for (Point2D p : pointsInside)
        {   p.draw();   }

        Point2D pTest = new Point2D(0.3, 0.4);
        kt.nearest(pTest).drawTo(pTest);

        Iterable<Point2D> points = kt.points();
        for (Point2D p : points)
        {   System.out.print(kt.pointToNode(p).count+" ");  }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值