Lab1主要分为 1.Magic Squares 2.Turtle Graphics 3.Social Network,下面简单记录各个任务中的一些思考总结。
1.Magic Squares(文件输入输出操作)
第一项任务首要一点是Java语言的文件输入输出操作,刚好在这一部分也不熟悉,就简单总结一下Java中的基本的文件操作。
首先:在Java中不论文件输入还是输出都会用到 File类 :File类是一个代表文件或文件目录(俗称文件夹)的类,通常使用路径来初始化,允许使用绝对路径和相对路径,如:
在Java程序中,对于数据的输入/输出操作以 “流” 的方式进行:
字节流和字符流这两种读取方式的差别主要体现在读ascii码范围之外的字符时,字符流能够正常读取,而字节流可能会没办法正常读取汉字等字符。Java的IO设计多个类,但实际上基本都是由上面这些抽象基类派生的,如:FileInputStream,BufferedReader,PrintWriter 。而对于访问文件常见的有FileInputStream、FileOutputStream、FileReader、FileWriter四个类。
1.1.FileReader读入数据的操作
读入的文件必须存在,否则会报FileNotFoundException。
步骤:
1. 提供File对象,指明要操作的文件。
2. 提供FileReader对象,用于数据的读入。
read() 返回读入的一个字符的字节码,如果到达文件末尾则返回-1。 3. 文件读完后进行流资源关闭
1.2.FileWriter写出数据的操作
如果对应的文件在硬盘中不存在,则创建此文件。
如果对应的文件在硬盘中存在:
如果使用的构造器是 FileWriter(file) 或 FileWriter(file, false),则对原来的文件覆盖。
如果使用的构造器是 FileWriter(file, true),则在原来的文件追加内容。
1.3.FileInputStream写入数据操作
1.常用构造方法:
1.1.FileInputStream(File file),参数传入一个File类型的对象。
1.2.FileInputStream(String name),参数传入文件的路径。
2.FileInputStream常用方法
2.1.read()方法:从文件的第一个字节开始,read()方法每执行一次,就会将一个字节读取,并返回该字节ASCII码,如果读出的数据是空的,即读取的地方是没有数据,则返回-1。
2.2.int read(byte b[]):方法与int read()方法不一样,该方法将字节一个一个地往byte数组中存放,直到数组满或读完,然后返回读到的字节的数量,如果一个字节都没有读到,则返回-1。
1.4FileOutputStream写出数据操作
1.常用构造方法
1.1FileOutputStream(File file),该流向File对象表示的文件写出数据。
1.2FileOutputStream(String filename),该流向指定文件名的文件写出数据。
2.FileOutputStream常用write()方法
2.1void write(int d),将指定字节写入此文件输出流,参数是只给定的int值的"低八位",也就是一个字节
2.2void write(byte[] b),将指定的byte数组中的数据写入此文件输出流中
2.Turtle Graphics(Convex Hulls)
任务二要根据MIT的实验要求和代码注释,完成TurtleSoup.java中待实现的方法,操作一个叫Turtle的绘图工具进行绘图。其中有Problem 7:Convex Hulls(凸包问题)涉及礼品包装算法(Gift Wrapping)。
2.1凸包问题
凸包(Convex Hull),是一个计算几何(图形学)中的概念。简而言之,就是给定平面点集,找出该点集中最外围的点构成凸多边形,使得该平面点集中的点全都在该凸多边行内部或者边上。其他凸包还分为最大凸包和最小凸包;最大凸包:既构成该凸多边形的顶点数最多;最小凸包:既构成该凸多边形的顶点数最少。某些情况下,最大凸包和最小凸包是可以相等的。如下,红色圈圈部分即为该平面点集的凸包。
2.2Gift Wrapping算法
基本思路:首先,先找到凸包上的一点,然后以此点开始,顺时针(或逆时针)寻找下一个点,然后再从下一个点寻找下下一个点,依此循环,直至寻找到凸包集上所以点。
前提条件:默认输入的是平面点集上的各点的坐标。
问题1:如何确定第一个点?
依照凸包的定义,我们可以选取最左上(x轴坐标最小且y轴坐标最大)、左下、右上或者右下的点作为第一个点,该点一定是在凸包上的。如下:搜索输入点集中的每一个点(p),找出最左上的一个点(start)加入凸包集,并以此点开始(ptr),寻找下一个点。
//搜索输入点集中的每一个点(p),找出最左上的一个点(start)加入凸包集,并以此点开始(ptr),寻找下一个点。
for(Point p:points) {
if( (p.x()<start.x()) || (p.x()==start.x()&&p.y()>start.y()) )
start = p;
}
result.add(start);
Point ptr = start;
问题2:如何在已知点上确定下一个点?
计算该点(P0)与其他点的极角并进行比较,选择极角最小的点作为下一个点,该点必在凸包上。倘若有两个点(P1,P2)极角大小相同,则根据所求的最大/最小凸包来选取:若求最大闭包,则选取距离P0点最近的点;若求最小闭包,则选取距离P0点最远的点。
须注意:calculateBearingToPoint()方法计算极角值,其第一个参数确定以哪个朝向作为极轴正方法(下面以0°代表正上方),且每次找出一个点后,计算下一个的极角值时,应将每次的极角值累加作为下次计算的朝向。否则,计算可能会陷进死循环。
while(true){
//首先,找出任意一个不同点,计算极角值
for(Point p:points){
if(p!=ptr){
target=p;
break;
}
}
mindegree=calculateBearingToPoint(degree,(int)ptr.x(),(int)ptr.y(),(int)target.x(),(int)target.y());
//之后,搜索整个平面点集,找出极角值最小的点
for(Point p :points){
if(p==ptr)
continue;
tempdegree=calculateBearingToPoint(degree, (int)ptr.x(), (int)ptr.y(), (int)p.x(), (int)p.y());
if(tempdegree<mindegree){
mindegree=tempdegree;
target=p;
}
//若极角值相等,判断距离
else if(tempdegree==mindegree){
double dist1=TurtleSoup.calculateDistance(ptr.x(), ptr.y(), target.x(), target.y());
double dist2=TurtleSoup.calculateDistance(ptr.x(), ptr.y(), p.x(), p.y());
if(dist2>dist1)
target = p;
}
}
//当再次找到start点时,退出循环
if(target==start)
break;
result.add(target);
ptr=target;
//累加极角值
degree=(degree+mindegree)%360.0;
}
return result;
//throw new RuntimeException("implement me!");
}
问题3:如何确定已找完所有的点?
按找问题2的思路寻找,若求出的下一个点与初始确定的第一个点相同,则已找到全部的凸包集上的点。
Gift Wrapping算法过程说白了,就是往钉子板上围皮筋的过程,先将皮筋固定在最外面的一个点上,然后顺时针(或逆时针)转动皮筋,每次碰到的点就是凸包集上的一点,直至将全部钉子包围起来。
3.Social Network(BFS求最短路径)
任务三实现Person和FriendshipGraph两个类,模拟社交网络,实现添加节点以及节点之间添加边,通过BFS计算两节点之间最短路径的方法。其中难点在于利用设计好的两个类,求两点间的最短路径(getDistance())方法的实现。
3.1实现getDistance()方法的类属性设计
首先,Person类中:
private boolean visit; //标识person是否在BFS中访问过
private List<Person> friends;//person的朋友表(边集)
private int distance; //BFS中表示源点离该person最短距离
然后,FriendShipGragh类中:
//图中已有的人名列表(顶点集)
private ArrayList<Person> personlist = new ArrayList<Person>();
3.2 getDistance()方法
基本思路:首先,进行初始化(消除多次调用该方法之间的影响),将社交网络图中各顶点(person)设置为未被访问状态,和与源点的距离(distance)为0。然后,利用BFS搜索,将源点(person1)入队列,走遍源点的朋友圈,将各个朋友入队,设置各个朋友的访问状态(true)到源点的距离(队首点的距离+1),检测是否到达目标点(person2),若无,则将当前队首点出队,计算下个队首点及其朋友。循环直至person2被访问,返回该距离;或者队列为空,源点不可达person2。
须注意:该方法存在缺陷,因为在社交网络图中,各顶点间的边关系(既边集)属性应该存储于类FriendshipGragh类中较合适,而该实现方案将之存储于Person类中的List<Person> friends中;这将导致在连续创建多个社交网络图时,若在第一个社交网络图(gragh1)中添加点person1及其相应的部分朋友(person2,person3等)的边关系后,再创建第二个社交网络图(gragh2)添加顶点person1 ,person2 ,person3等时,他们之间的边关系无需添加,也已经出现在gragh2图中,这将造成难以预料的结果。但是,由于考虑到本题限制条件无重名的person和一个人的朋友无论在哪个社交网络图中,其原有的朋友关系是不会消失的,所以在此题中该方法是可行的。
具体代码实现:
public int getDistance(Person person1, Person person2){
int distance=0;
if(person1==person2)
return distance;
//初始化
for(int i=0;i<personlist.size();i++){
personlist.get(i).setVisit(false);
personlist.get(i).setdis(0);
}
Queue<Person> queue= new LinkedList<Person>();
queue.offer(person1);
person1.setVisit(true);
//BFS求最短路径
while(!queue.isEmpty()){
Person temp=queue.poll();
for(int i=0;i<temp.getFriends().size();i++){
if(!temp.getFriends().get(i).getVisit()){
temp.getFriends().get(i).setVisit(true);
temp.getFriends().get(i).setdis(1+temp.getdis());
queue.offer(temp.getFriends().get(i));
}
}
if(person2.getVisit()){
return person2.getdis();
}
}
return -1;
}