您的位置:首页 > 编程语言 > Go语言

Week3 Assignment - Princeton-Algorithms-PartI

2014-02-16 14:50 441 查看

题注

Princeton Algorithm Part I中Week 3的Assignment实际上也是很早前就做完了,不过因为懒,一直没放到网上来… 正好新周期的Algorithm课也督促我复习一遍算法知识,另一方面也有机会把所有的Assignment做完吧。上个周期中,Week 4的Assignment没有拿到满分,就萌生退意了。这回卷土重来,希望能拿到一个Perfect的结果(当然了,Princeton的公开课是没有Accomplishment的,做这个只是学习和课外消遣而已)。

Week 3的题目是图论中的一个很典型的问题,在LeetCode中也有相应的算法题,也算是为刷LeetCode图论的题打点基础吧!

题目

Programming Assignment 3: Pattern Recognition

Write a program to recognize line patterns in a given set of points.

Computer vision involves analyzing patterns in visual images andreconstructing the real-world objects that produced them. The processin often broken up into two phases:
feature detection andpattern recognition. Feature detection involves selectingimportant features of the image; pattern recognition involvesdiscovering patterns in the features. We will investigate aparticularly clean pattern recognition problem
involving points andline segments. This kind of pattern recognition arises in many otherapplications such as statistical data analysis.

The problem.Given a set of N distinct points in the plane, draw every (maximal) line segment that connects a subset of 4 or more of the points.



Point data type.Create an immutable data type Point that represents a point in the planeby implementing the following API:

public class Point implements Comparable<Point> {
public final Comparator<Point> SLOPE_ORDER;        // compare points by slope to this point

public Point(int x, int y)                         // construct the point (x, y)

public   void draw()                               // draw this point
public   void drawTo(Point that)                   // draw the line segment from this point to that point
public String toString()                           // string representation

public    int compareTo(Point that)                // is this point lexicographically smaller than that point?
public double slopeTo(Point that)                  // the slope between this point and that point
}


To get started, use the data typePoint.java,which implements the constructor and thedraw(),
drawTo(), and toString() methods.Your job is to add the following components.
The compareTo() method should compare points by their y-coordinates,breaking ties by their
x-coordinates.Formally, the invoking point(x0, y0)is
less than the argument point(x1, y1)if and only if either
y0 < y1 or ify0 =
y1 and x0 < x1.
The slopeTo() method should return the slope between the invoking point(x0,
y0) and the argument point(x1, y1), which is given by the formula(y1 −
y0) / (x1 − x0).Treat the slope of a horizontal line segment as positive zero [added 7/29];treat the slope of a vertical line segment as positive infinity;treat the
slope of a degenerate line segment (between a point and itself) as negative infinity.
The SLOPE_ORDER comparator should compare points by the slopes theymake with the invoking point (x0,
y0).Formally, the point (x1, y1) is
less thanthe point (x2, y2) if and only if the slope(y1 −
y0) / (x1 − x0) is less than the slope(y2 −
y0) / (x2 − x0).Treat horizontal, vertical, and degenerate line segments as in the
slopeTo() method.
Brute force.Write a program Brute.java that examines 4 points at a time and checks whetherthey all lie on the same line segment, printing out any such linesegments to standard output and drawing them using standard drawing.To check
whether the 4 points p, q, r, and s are collinear,check whether the slopes between
p and q, between p and r, and between p and
sare all equal.

The order of growth of the running time of your program should beN4 in the worst case and it should use space proportional to
N.

A faster, sorting-based solution.Remarkably, it is possible to solve the problem much faster than thebrute-force solution described above.Given a point
p, the following method determines whether pparticipates in a set of 4 or more collinear points.

Think of p as the origin.
For each other point q, determine the slope it makes with p.
Sort the points according to the slopesthey makes with p.
Check if any 3 (or more) adjacent points in the sorted order have equalslopes with respect to
p.If so, these points, together with p, are collinear.

Applying this method for each of the N points in turn yields anefficient algorithm to the problem.The algorithm solves the problem because points that have equal slopes with respect to
p are collinear, and sorting brings such points together.The algorithm is fast because the bottleneck operation is sorting.



Write a program Fast.java that implements this algorithm.The order of growth of the running time of your program should beN2 log
N in the worst case and it should use space proportional to N.

APIs. [Added 7/25]Each program should take the name of an input file as a command-line argument,read the input file (in the format specified below),print to standard output the line segments discovered (in
the format specified below),and draw to standard draw the line segments discovered (in the format specified below).Here are the APIs.

public class Brute {
public static void main(String[] args)
}

public class Fast {
public static void main(String[] args)
}


Input format.Read the points from an input file in the following format:An integer
N, followed by Npairs of integers (x, y), each between 0 and 32,767.Below are two examples.

% more input6.txt       % more input8.txt
6                       8
19000  10000             10000      0
18000  10000                 0  10000
32000  10000              3000   7000
21000  10000              7000   3000
1234   5678             20000  21000
14000  10000              3000   4000
14000  15000
6000   7000


Output format.Print to standard output the line segments that your program discovers, one per line.Print each line segment as an
ordered sequence of its constituent points,separated by " -> ".

% java Brute input6.txt
(14000, 10000) -> (18000, 10000) -> (19000, 10000) -> (21000, 10000)
(14000, 10000) -> (18000, 10000) -> (19000, 10000) -> (32000, 10000)
(14000, 10000) -> (18000, 10000) -> (21000, 10000) -> (32000, 10000)
(14000, 10000) -> (19000, 10000) -> (21000, 10000) -> (32000, 10000)
(18000, 10000) -> (19000, 10000) -> (21000, 10000) -> (32000, 10000)

% java Brute input8.txt
(10000, 0) -> (7000, 3000) -> (3000, 7000) -> (0, 10000)
(3000, 4000) -> (6000, 7000) -> (14000, 15000) -> (20000, 21000)

% java Fast input6.txt
(14000, 10000) -> (18000, 10000) -> (19000, 10000) -> (21000, 10000) -> (32000, 10000)

% java Fast input8.txt
(10000, 0) -> (7000, 3000) -> (3000, 7000) -> (0, 10000)
(3000, 4000) -> (6000, 7000) -> (14000, 15000) -> (20000, 21000)


Also, draw the points using draw() and draw the line segmentsusing drawTo().Your programs should call
draw() once for each point in the input file andit should call drawTo() once for each line segment discovered.Before drawing, use
StdDraw.setXscale(0, 32768) and StdDraw.setYscale(0, 32768) to rescale the coordinate system.For full credit, do not print
permutations of points ona line segment (e.g., if you outputp→q→r→s,do not also output eithers→r→q→p orp→r→q→s).Also, for full credit in
Fast.java,do not print or plot subsegments of a line segment containing5 or more points (e.g., if you output
p→q→r→s→t,do not also output eitherp→q→s→t orq→r→s→t);you may print out such subsegments in
Brute.java.

分析

这道题本身就是算法中的一个很经典的问题。Princeton对于题目的介绍已经非常详细了,包括两种算法的描述,一些特定的问题,一些特殊情况的注意事项等等。我们一个一个来看这些问题。

Point类的实现

不管用什么方法,Point类终究是要实现的,因此我们先来看如何实现Point类。题目中已经给出了一个Point.java的模板,示例如下:

/*************************************************************************
* Name:
* Email:
*
* Compilation: javac Point.java
* Execution:
* Dependencies: StdDraw.java
*
* Description: An immutable data type for points in the plane.
*
*************************************************************************/

import java.util.Comparator;

public class Point implements Comparable<Point> {

// compare points by slope
public final Comparator<Point> SLOPE_ORDER; // YOUR DEFINITION HERE

private final int x; // x coordinate
private final int y; // y coordinate

// create the point (x, y)
public Point(int x, int y) {
/* DO NOT MODIFY */
this.x = x;
this.y = y;
}

// plot this point to standard drawing
public void draw() {
/* DO NOT MODIFY */
StdDraw.point(x, y);
}

// draw line between this point and that point to standard drawing
public void drawTo(Point that) {
/* DO NOT MODIFY */
StdDraw.line(this.x, this.y, that.x, that.y);
}

// slope between this point and that point
public double slopeTo(Point that) {
/* YOUR CODE HERE */
}

// is this point lexicographically smaller than that one?
// comparing y-coordinates and breaking ties by x-coordinates
public int compareTo(Point that) {
/* YOUR CODE HERE */
}

// return string representation of this point
public String toString() {
/* DO NOT MODIFY */
return "(" + x + ", " + y + ")";
}

// unit test
public static void main(String[] args) {
/* YOUR CODE HERE */
}
}我们的目标是首先实现Point类的几个核心功能;然后来实现SLOPE_ORDER内部类,用于对两个Point进行对比。

slopeTo()

slopeTo类相当于来计算两点间连线的斜率了。初中数学我们就知道,有如下几种情况。

(1)两个点的y坐标相同,即连线与x轴平行。这种情况的斜率为0。但是我们要注意一个问题,在Java中实际上有两种0,即+0和-0,并且Java内置的比较函数认为+0>-0。这是怎么来的呢?在题目的CheckList中已经给出了相关的介绍,我们放在这里供大家参考:

What does it mean for slopeTo() to return positive zero?Java (and the IEEE 754 floating-point standard) define two representations of zero: negative zero and positive zero.

double a = 1.0;
double x = (a - a) /  a;   // positive zero ( 0.0)
double y = (a - a) / -a;   // negative zero (-0.0)


Note that while (x == y) is guaranteed to be true,Arrays.sort() treatsnegative zero as strictly less than positive zero.Thus, to make the
specification precise, we require you to return positive zero for horizontal line segments.Unless your program casts to the wrapper type
Double (either explicitly or via autoboxing),you probably will not notice any difference in behavior;but, if your program does cast to the wrapper type and fails only on (some) horizontal line segments, this may bethe cause.
(2)两个点的x坐标相同,即连线与y轴平行。根据题目要求,我们要把这种斜率定义为正无穷大。在Java中,Double类给出了双精度浮点数正无穷大的表示,为Double.POSITIVE_INFINITY,因此用这个值代表正无穷大即可。

(3)两个点相同。这是一个极为特殊的情况,在CheckList中也有过说明,内容如下:

Can the same point appear more than once as input to Brute or Fast?You may assume the input to
Brute and Fast are N distinct points.Nevertheless, the methods implemented as part of the
Point data typemust correctly handle the case when the points are not distinct:for the
slopeTo() method, this requirement is explicitly stated in the API;for the comparison methods, this requirement is implict in the contracts for
Comparable and Comparator.

对于这种情况,我的处理方法是将其结果设置为负无穷大,即Double.NEGATIVE_INFINITY。

(4)其他一般情况。这种情况下,我们直接计算斜率即可,计算公式很简单,为:(y_1 - y_0) / (x_1 - x_0)。不过注意到点的坐标表示是int,斜率为double,因此我们还需要一个强制转换来保证计算的精度。

compareTo()

这个类的实现也很容易,直接按照题目要求来实现即可。当y_0 < y_1或者当y_0 = y_1且x_0 < x_1时,返回-1。

SLOPE_ORDER

这个类的实现也很容易,分别计算两个点对于第三个点的斜率(调用slopeTo()),然后返回两个结果的比较值即可。因为在slopeTo()中我们已经处理过了3种特殊的情况,因此直接返回比较值就是正确的结果。

蛮力搜索的实现和注意事项

蛮力搜索的实现很简单,相当于把所有点四个四个的比较,看看斜率是不是一样,如果一样就在一条直线上。蛮力搜索需要注意的情况是不要重复比较,举个例子:p,q,r,s点比较了,就不要比较p,q,s,r等类似的四组点了。因此,直观地看我们相当于从所有N个点中,依次取出4个点逐个比较了。为了保证比较不重复,用如下的循环来处理比较:

for (int i=0; i<N; i++){
for (int j=i+1; j<N; j++){
for (int k=j+1; k<N; k++){
for (int l=k+1; l<N; l++){
//进行比较
}
}
}
}

实现实例数据读入的main函数

蛮力搜索实现完毕后,怎么进行测试呢?这就涉及到实例数据读取的问题了。读取的顺序是:读取Point总数N;依次读取每一个Point的x坐标和y坐标;运行算法得出结果。不得不说,Princeton自己实现的一个stdlib真是非常好用,以至于我现在实现自己的一些程序都会用这个jar,也推荐给大家好好看看,可以减轻很多开发工作呢。

实例数据读入main函数的实例如下,供大家参考:

public static void main(String[] args){
// rescale coordinates and turn on animation mode
StdDraw.setXscale(0, 32768);
StdDraw.setYscale(0, 32768);

String filename = args[0];
In in = new In(filename);
int N = in.readInt();
Point[] pointArray = new Point
;
for (int i = 0; i < N; i++) {
int x = in.readInt();
int y = in.readInt();
Point p = new Point(x, y);
pointArray[i] = p;
p.draw();
}
//在这里调用算法
}

Fast Algorithm的实现

Fast Algorithm的原理很简单,实际上是一种快速找到与自己slope相同的点的方法。算法本身确实没什么说的,不过有一些细节问题需要注意。

1. 如何处理类似p,q,r,s和p,q,s,r的问题?这个问题刚开始我还觉得是一个问题,结果最后发现这根本不是个问题… 因为p->q的斜率和q->p的斜率根本就是不一样的,互为相反数。所以这种算法可以直接处理连续5个点以及以上的问题。举例来说,如果p,q,r,s,t点在一个直线上,那么排序结果p->q,p->r,p->s,p->t的斜率都一样,这五个点在一个直线上。

2. 如果p,q,r,s,t这5个点在一条直线上,那么从q搜索的时候q,r,s,t这4个点也在一条直线上,如何处理这种情况呢(意味着两组结果,还是只能输出q,r,s,t这一种结果)?这是一个值得考虑的问题。一种简单的解法是,开辟一个新的存储空间,对于5个点以上在一条直线的情况,把其子情况存储在新的存储空间中。当找到新的解时,对比新解的起始点和斜率是否与存储的解和斜率是否相同(这是因为一个点和一个斜率唯一确定一条直线)。不过,这个问题实际上也不需要考虑,在CheckList中也给出了类似的解释:

I'm having trouble avoiding subsegments Fast.java when there are 5 or more points on a linesegment. Any advice?Not handling the 5-or-more case is a bit tricky,so don't kill yourself over it.

代码

Point.java

import java.util.Comparator;

public class Point implements Comparable<Point> {

// compare points by slope
public final Comparator<Point> SLOPE_ORDER = new SlopeOrder(); // YOUR DEFINITION HERE

private final int x; // x coordinate
private final int y; // y coordinate

private class SlopeOrder implements Comparator<Point>{

public int compare(Point p1, Point p2) {
double slopeP1 = slopeTo(p1);
double slopeP2 = slopeTo(p2);
if (slopeP1 == slopeP2) return 0;
if (slopeP1 < slopeP2) return -1;
else return +1;
}

}
// create the point (x, y)
public Point(int x, int y) {
/* DO NOT MODIFY */
this.x = x;
this.y = y;
}

// plot this point to standard drawing
public void draw() {
/* DO NOT MODIFY */
StdDraw.point(x, y);
}

// draw line between this point and that point to standard drawing
public void drawTo(Point that) {
/* DO NOT MODIFY */
StdDraw.line(this.x, this.y, that.x, that.y);
}

// slope between this point and that point
public double slopeTo(Point that) {
int dx = that.x - this.x;
int dy = that.y - this.y;
if (dx == 0 && dy == 0) return Double.NEGATIVE_INFINITY;
if (dx == 0) return Double.POSITIVE_INFINITY;
if (dy == 0) return +0;
else return (double)dy / (double)dx;
}

// is this point lexicographically smaller than that one?
// comparing y-coordinates and breaking ties by x-coordinates
public int compareTo(Point that) {
if (this.y < that.y) return -1;
if (this.y == that.y && this.x < that.x) return -1;
if (this.y == that.y && this.x == that.x) return 0;
else return +1;
}

// return string representation of this point
public String toString() {
/* DO NOT MODIFY */
return "(" + x + ", " + y + ")";
}

// unit test
public static void main(String[] args) {
/* YOUR CODE HERE */
}
}

Brute.java
public class Brute {
public static void main(String[] args){
// rescale coordinates and turn on animation mode
StdDraw.setXscale(0, 32768);
StdDraw.setYscale(0, 32768);

String filename = args[0];
In in = new In(filename);
int N = in.readInt();
Point[] pointArray = new Point
;
for (int i = 0; i < N; i++) {
int x = in.readInt();
int y = in.readInt();
Point p = new Point(x, y);
pointArray[i] = p;
p.draw();
}
Quick3way.sort(pointArray);
BruteForce(pointArray);
StdDraw.show(0);
}

private static void BruteForce(Point[] pointArray){
int N = pointArray.length;
for (int i=0; i<N; i++){
for (int j=i+1; j<N; j++){
for (int k=j+1; k<N; k++){
for (int l=k+1; l<N; l++){
if (pointArray[i].slopeTo(pointArray[j]) == pointArray[i].slopeTo(pointArray[k]) &&
pointArray[i].slopeTo(pointArray[j]) == pointArray[i].slopeTo(pointArray[l])){
StdOut.println(pointArray[i] + " -> " + pointArray[j] + " -> " + pointArray[k] + " -> " + pointArray[l]);
pointArray[i].drawTo(pointArray[l]);
}
}
}
}
}
}
}

Fast.java
public class Fast {
public static void main(String[] args){
// rescale coordinates and turn on animation mode
StdDraw.setXscale(0, 32768);
StdDraw.setYscale(0, 32768);

String filename = args[0];
In in = new In(filename);
int N = in.readInt();
if (N < 4){
return;
}
Point[] pointArray = new Point
;
for (int i = 0; i < N; i++) {
int x = in.readInt();
int y = in.readInt();
Point p = new Point(x, y);
pointArray[i] = p;
p.draw();
}
Quick3way.sort(pointArray);
FastMethod(pointArray);
StdDraw.show(0);
}

private static void FastMethod(Point[] pointArray){
int N = pointArray.length;
for (int i=0; i<N; i++){
Point origPoint = pointArray[i];
Point[] otherPoint = new Point[N-1];
for (int j=0; j<pointArray.length; j++){
if (j < i) otherPoint[j] = pointArray[j];
if (j > i) otherPoint[j-1] = pointArray[j];
}
Arrays.sort(otherPoint, origPoint.SLOPE_ORDER);

int count = 0;
int index = 0;
double tempSlope = origPoint.slopeTo(otherPoint[0]);
for (int j=0; j<otherPoint.length;j++){
if (Double.compare(origPoint.slopeTo(otherPoint[j]), tempSlope) == 0){
count++;
continue;
}else{
if (count >=3){
if (otherPoint[index].compareTo(origPoint) >=0){
StdOut.print(origPoint + " -> ");
for (int k=index; k<j-1; k++){
StdOut.print(otherPoint[k] + " -> ");
}
StdOut.println(otherPoint[j-1]);
origPoint.drawTo(otherPoint[j-1]);
}
}
count = 1;
index = j;
tempSlope = origPoint.slopeTo(otherPoint[j]);
}
}
if (count >= 3){
if (otherPoint[index].compareTo(origPoint) >= 0){
StdOut.print(origPoint + " -> ");
for (int k=index; k<otherPoint.length - 1; k++){
StdOut.print(otherPoint[k] + " -> ");
}
StdOut.println(otherPoint[otherPoint.length-1]);
origPoint.drawTo(otherPoint[otherPoint.length-1]);
}
}
}
StdDraw.show(0);
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Algorithm Coursera Java