软件构造lab1

 目录

完成Magic Squares、Turtle Graphics和Social Network三个任务。

一、Magic Squares

实现两个方法,isLegalMagicSquare用于判断是否是幻方,generateMagicSquare用于生成行列数为奇数的幻方。

1.isLegalMagicSquare

从文件中读取数据判断是否为Magic Square,并检查输入是否合法。

这个方法的声明

public static boolean isLegalMagicSquare(String fileName)

实现过程分为三步:文件读入、合法性检查和判断是否为幻方。

文件读入:定义一个ArrayList并按行读取字符串保存在这个ArrayList中。

		ArrayList<String> list = new ArrayList<String>();
		  String path = "src/P1/txt/" + fileName;
		  try 
		  {
		   BufferedReader br = new BufferedReader(new FileReader(path));
		   String line = "";
		   while ((line = br.readLine()) != null) 
		   {
		    list.add(line);
		   }
		   br.close();
		  } 
		  catch (IOException e) 
		  {
		    e.printStackTrace();
		    return false;
		  }

 合法性检查:检查行列数是否相等、是否用\t分割和是否所有元素都是正整数。

		  int row;//行
		  int column;//列
		  row = list.size();
		  String f0 = list.get(0);
		  String[] l0 = f0.split("\t");
		  column = l0.length;
		  if(row!=column)
		  {
		   System.out.print("rows and columns are not equal ");
		   return false;
		  }
		  int[][] matrix = new int[row][column];
		  try 
		  {
		   for(int i=0;i<row;i++)
		   {
		    String fn = list.get(i);
		    String[] ln = fn.split("\t"); 
		    for(int j =0;j<column;j++)
		    {
		     if(Integer.valueOf(ln[j])<=0) 
		     {
		      System.out.print("Contains non-positive integers ");
		      return false;
		     }
		     if(ln.length!=column)
		     {
		      System.out.print("Not a \\t split matrix ");
		      return false;
		     }
		     matrix[i][j] =  Integer.valueOf(ln[j]);
		    }
		   }
		  } 
		  catch(Exception e) 
		  {
		   System.out.print("Not a \\t split matrix");
		   return false;
		  }

判断是否为幻方:计算二维数组的各行各列和两个对角线的和是否相等。

          int sumr[] = new int[row];
		  int sumc[] = new int[column];
		  int sumdia[] = new int[2];
		  for(int i=0;i<row;i++) 
		  {
		   sumdia[0]+=matrix[i][i];
		   sumdia[1]+=matrix[i][row-1-i];
		   for(int j=0;j<row;j++)
		   {
		    sumr[i]+=matrix[i][j];
		    sumc[i]+=matrix[j][i];
		   }
		  }
		  int x=sumr[0];
		  if(sumr[0]!=x||sumdia[0]!=x||sumdia[1]!=x)
		  {
			  return false;
		  }
		  for(int i=1;i<row-1;i++) 
		  {
		   if((sumr[i]!=x)||(sumc[i]!=x)||(sumdia[0]!=x)||(sumdia[1]!=x))
		   {
		    return false;
		   }
		  }
		  return true;

2.generateMagicSquare

输入一个奇数n生成一个n*n的Magic Square并写入到文件中,并检查输入是否合法。

异常分析:

如果输入的 n 为偶数,函数运行之后在控制台产生以下输出:

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 12

at MagicSquare.generateMagicSquare(MagicSquare.java:17)

at MagicSquare.main(MagicSquare.java:121)

原因:数组越界

如果输入的 n 为负数,函数运行之后在控制台产生以下输出:

Exception in thread "main" java.lang.NegativeArraySizeException

at MagicSquare.generateMagicSquare(MagicSquare.java:11)

at MagicSquare.main(MagicSquare.java:121)

原因:数组长度为负

原理分析:采用了罗伯法生成幻方

数字 1 放在首行最中间的格子中。向右上角斜行,依次填入数字。如果右上方向出了上边界,就以出框后的虚拟方格位置为基准,将数字竖直降落至底行对应的格子中。同上,向右出了边界,就以出框后的虚拟方格位置为基准,将数字平移至最左列对应的格子中。如果数字{N} 右上的格子已被其它数字占领,就将{N+1} 填写在{N}下面的格子中。如果朝右上角出界,和“重复”的情况做同样处理。

实现思路:

这个方法的声明

public static boolean generateMagicSquare(int n)

实现过程分为三步:合法性检查、生成幻方和写入文件。

合法性检查

		if(n%2==0)
		{
			System.out.println("n为偶数");
			return false;
		}
		else if(n<0)
		{
			System.out.println("n为负数");
			return false;
		}

生成幻方

		int magic[][] = new int[n][n];
		int row = 0, col = n / 2, i, j, square = n * n;
		for (i = 1; i <= square; i++) 
		{
		magic[row][col] = i;
		if (i % n == 0)//每n个为一组
		row++;//进行下一组的赋值
		else 
		{
		if (row == 0)//第0行赋值后进行最后一行的赋值
		row = n - 1;
		else
		row--;//按行递减赋值
		if (col == (n - 1))//最后一列赋值后进行第0列的赋值
		col = 0;
		else
		col++;//按列递增赋值
		} 
		}

写入文件

		FileWriter out = null;
        try {
            out = new FileWriter("src/P1/txt/6.txt");//二维数组按行存入到文件中
            for (int u = 0; u < magic.length; u++) 
            {
                for (int v = 0; v < magic.length; v++) 
                {
                    String content = String.valueOf(magic[u][v]) + "";//将整数转换为字符串
                    out.write(content + "\t");
                }
                out.write("\r\n");
            }
            out.close();
        } 
        catch (IOException e) 
        {
            e.printStackTrace();
        }
		for (i = 0; i < n; i++) 
		{
		for (j = 0; j < n; j++)
		System.out.print(magic[i][j] + "\t");
		System.out.println();
		}
		return true;

二、Turtle Graphics

1.Turtle graphics and drawSquare

调用forward沿当前方向移动sideLenghth像素,再调用turn旋转90度。重复四次,则可以画出一个边长为sideLength的正方形。

public static void drawSquare(Turtle turtle, int sideLength) {
    	turtle.forward(sideLength);
		turtle.turn(90);
		turtle.forward(sideLength);
		turtle.turn(90);
		turtle.forward(sideLength);
		turtle.turn(90);
		turtle.forward(sideLength);
		turtle.turn(90);
    }

2.Drawing polygons

画出一个正多边形。

根据多边形的边角关系完成calculateRegularPolygonAngle方法和calculatePolygonSidesFromAngle方法。

public static double calculateRegularPolygonAngle(int sides) {
    	return (double)((sides-2)*180)/sides;
    }
public static int calculatePolygonSidesFromAngle(double angle) {
		return (int) Math.round(360/(180-angle));
    }

输入需要满足边数大于等于3、边长大于等于0,否则报错退出。由于每次默认是向北方向的,所以第一次要转的角度需要判断内角是锐角还是钝角,如果是锐角的话,第一次向右旋转(90-内角)°,如果是钝角的话,第一次向右旋转(450-内角)°,然后每次旋转(180-内角)°。每次旋转后前进固定长度,总共前进次数为边数,则可以得到一个正多边形。

public static void drawRegularPolygon(Turtle turtle, int sides, int sideLength) {
    	if (sides>=3) 
    	{                                      
			if(sideLength>=0)
			{
			double Dside = calculateRegularPolygonAngle(sides);
			if(Dside>90)
			{
				turtle.turn(450- Dside);
				turtle.forward(sideLength);
			}
			else 
			{
				turtle.turn(90 - Dside);
				turtle.forward(sideLength);
			} 
			for (int i = 1; i<sides; i++) 
			{
					turtle.turn(180- Dside);
					turtle.forward(sideLength);
			}
			}
			else
			{
				System.out.println("sideLength should be greater than or equal to 0");
				System.exit(0);
			}
		} 

3.Calculating Bearings

calculateBearingToPoint函数:
当两点纵坐标相等时,判断横坐标的大小关系来计算出需要旋转的角度;
纵坐标不相等时,调用arctan计算倾斜角,调用toDegrees将弧度制转为角度制,再计算需要旋转的角度。如果算出的结果不在[0,360)范围内,则通过加减360使其在范围内。

public static double calculateBearingToPoint(double currentBearing, int currentX, int currentY,int targetX, int targetY) {
        double r;
    	if(currentY==targetY)
        {
        	if(currentX>targetX)
        	{
        		r=270-currentBearing;
        	}
        	else if(currentX<targetX)
        	{
        		r=90-currentBearing;
        	}
        	else
        	{
        		r=0;
        	}
        }
    	else if(targetX==currentX&&targetY<currentY)
    	{
    		return 180;
    	}
        else
        {
        	double k=((double)(targetX-currentX))/((double)(targetY-currentY));
        	r=Math.toDegrees(Math.atan(k))-currentBearing;
        }
    	while(r>=360)
    	{
    		r-=360;
    	}
    	while(r<0)
    	{
    		r+=360;
    	}
    	return r;
    }

calculateBearings函数
使用temp记录当前角度,调用calculateBearingToPoint函数计算每次需要旋转的角度保存在列表Angle中。

public static List<Double> calculateBearings(List<Integer> xCoords, List<Integer> yCoords) {
    	List<Double> Angle = new ArrayList<Double>();
    	int n=xCoords.size();
    	double temp=0;
    	for(int i=0;i<n-1;i++)
    	{
    		Angle.add(calculateBearingToPoint(temp,xCoords.get(i),yCoords.get(i),xCoords.get(i+1),yCoords.get(i+1)));
    		temp=Angle.get(i);
    	}
    		return Angle;
    }

4.Convex Hulls

算法思想:集合大小小于等于3时凸包就是这个集合。大于3时,首先找出左下角的点,并记录为初始点和极点。找到极角最小的点加入凸包集合(极角相同的点选择距离极点最远的点),并将该点更新为极点,并且在原集合中删除该点。重复寻找极角最小的点的操作,直到极角最小的点为初始点时,返回凸包集合。
实现:
循环寻找极角最小的点时,要使用do-while循环,避免开始时初始点和当前点相同直接跳出循环。

public static Set<Point> convexHull(Set<Point> points) {
    	if(points.size()<=3)
    	{
    		return points;
    	}
    	Set<Point> convexHull = new HashSet<Point>();
    	List<Point> temp = new ArrayList<Point>();
    	for (Point point: points)
    	{
    		temp.add(point);
    	}
    	Point pole=temp.get(0);//记录极点
    	Point first=temp.get(0);//记录初始点
    	Point now=temp.get(1);//记录当前极角最小的点
    	for(int i=1;i<temp.size();i++)//找到左下角的点
    	{
    		now=temp.get(i);
    		if((pole.x()>now.x())||((pole.x()==now.x())&&(pole.y()>now.y())))
    		{
    			pole=now;
    		}
    	}
    	first=pole;
    	convexHull.add(pole);
    	double degree;
    	do
    	{
    		now=temp.get(0);
    		degree=calculateBearingToPoint(0,(int)pole.x(),(int) pole.y(),(int) temp.get(0).x(),(int) temp.get(0).y());
    		int n=temp.size();
	    	if(n>1)
	    	{
	    		for(int i=1;i<n;i++)
	    	{
	    			if((degree>calculateBearingToPoint(0,(int)pole.x(), (int)pole.y(), (int)temp.get(i).x(), (int)temp.get(i).y()))&&!((now==first)&&(pole==first)))
	    			{
		    			now=temp.get(i);
		    			degree=calculateBearingToPoint(0,(int)pole.x(), (int)pole.y(), (int)temp.get(i).x(), (int)temp.get(i).y());
		    		}
		    		else if((degree==calculateBearingToPoint(0,(int)pole.x(), (int)pole.y(), (int)temp.get(i).x(), (int)temp.get(i).y()))&&!((now==first)&&(pole==first)))
		    		{
		    			if(Math.abs(pole.x()-now.x())<Math.abs(pole.x()-temp.get(i).x())||Math.abs(pole.y()-now.y())<Math.abs(pole.y()-temp.get(i).y()))
		    			{
		    				now=temp.get(i);
		        			degree=calculateBearingToPoint(0,(int)pole.x(), (int)pole.y(), (int)temp.get(i).x(), (int)temp.get(i).y());
		    			}
		    		}
	    	}
	    	}
	    		convexHull.add(now);
	    		pole=now;
	    		temp.remove(now);
    	}while(now!=first);
    	return convexHull;
    }

5.Personal art

利用循环,循环中使用旋转、前进和改变颜色绘制而成。

public static void drawPersonalArt(Turtle turtle) {
    	for(int j=0;j<180;j++)
    	{
    		for(int i=0;i<5;i++)
    	{
    		switch (i%5) 
    		{
		    case 0:
			turtle.color(PenColor.GREEN);
		    case 1:
			turtle.color(PenColor.YELLOW);
			break;
		    case 2:
			turtle.color(PenColor.BLUE);
			break;
		    case 3:
			turtle.color(PenColor.RED);
		    case 4:
			turtle.color(PenColor.GREEN);

			break;
		    }
    		turtle.forward(250);
    		turtle.turn(144);
    	}
    		turtle.turn(100);
    		if(j%9==0)
    		{
    			turtle.turn(77);
    		}
    	}
    }

三、Social Network

通过一个图模拟社交网络,顶点代表人,边代表两个人之间有社交关系。实际是考察对图的各种操作,添加顶点、添加边和计算最短距离。

1.设计/实现FriendshipGraph

List<Person> vertexList = new ArrayList<Person>();//使用List保存顶点

addEdge函数用于在两个顶点之间加入边,并在有不存在的顶点、为同一个顶点或边已经存在时,报错退出。

public void addVertex(Person man)//加入顶点
	{
		if (vertexList.contains(man))//判断顶点是否重复
		{
			System.out.println("Duplicate names");
			System.exit(0);
		} 
		else 
		{
			vertexList.add(man);
		}
	}

addVertex函数用于图中加入顶点,并判断顶点名是否重复,有重复时报错退出。

public void addEdge(Person x, Person y)//加入边
	{
		if (vertexList.contains(x)&& vertexList.contains(y))//判断是否有不存在的顶点 
		{
			if (x!=y)//判断是否为同一个顶点
			{
				if(x.exist(y)==false)//判断边是否已经存在
				{
					x.addFriend(y);
				}
				else 
				{
					System.out.println("There's already an edge between these two vertices");
					System.exit(0);
				}
			} 
			else 
			{
				System.out.println("Repeated vertices");
				System.exit(0);
			}
		} 
		else 
		{
			System.out.println("Vertices don't exist");
			System.exit(0);
		}
	}

getDistance函数把图转化为邻接矩阵后,根据该矩阵使用Dijkstra算法计算并返回两个顶点之间的距离。

public int getDistance(Person x, Person y)//Dijkstra算法计算两个顶点的距离
	{
		if (vertexList.contains(x)&&vertexList.contains(y)) 
		{
			int size = vertexList.size();//计算顶点数量
			int[][] Adjacency = new int[size][size];//保存邻接矩阵
			List<Integer> Distance = new ArrayList<Integer>();//存储该顶点到其他顶点的最短距离
			boolean[] flag = new boolean[size];//用来记录是否被访问过
			int w = 0;
			int sum = 0;
			for (int i = 0; i < size; i++)
			{
				Person temp = vertexList.get(i);
				for (int j = 0; j < size; j++) 
				{
					if (i == j) 
					{
						Adjacency[i][j] = 0;
					} 
					else if (temp.exist(vertexList.get(j)))
					{
						Adjacency[i][j] = 1;
					} 
					else 
					{
						Adjacency[i][j] = 1000000;//用1000000来代表不连通
					}
				}
			}
			//将所有点默认初始化为没有访问过
			int n = vertexList.indexOf(x);//获得x在存储顶点的vertexList中的下标
			for (int i = 0; i < size; i++) 
			{
				flag[i] = false;
				Distance.add(Adjacency[n][i]);
				
			}
			// 利用Dijkstra算法,算出一个点到所有点的单源最短路径
			for (int i = 0; i < size; i++) 
			{
				w = min(flag,Distance);
				flag[w] = true;//w是当前List中距离该点距离最短的点,并且是没有被访问过的
				for (int j = 0; j < size; j++)
				{
					if (flag[j] == false && j != n)
					{
						sum = Distance.get(w) + Adjacency[w][j];
						if (sum < Distance.get(j))//如果经过其他点到达另一个顶点的距离更小,那么最短距离便是经由其他点到达的距离
						{
							Distance.set(j, sum);
						}
					}
				}
				sum = 0;
			}
			if (Distance.get(vertexList.indexOf(y)) >=1000000)
				{
					return -1;
				}
			else
				{
					return Distance.get(vertexList.indexOf(y));
				}
		}
		else 
		{
			System.out.println("Vertices don't exist");
			System.exit(0);
			return -2;
		}
	}
	public int min(boolean[] flag,List<Integer> x) // 算出当前List中距离该点距离最短的点,并且是没有被访问过的
	{
		int temp = 2000;
		int m = 0;
		int size = x.size();
		for (int i = 0; i < size; i++)
			if (flag[i] == false&&x.get(i)!=0&&x.get(i)<temp)
			{
				temp = x.get(i);
				m = i;
			}
		return m;
	}

2设计/实现Person

由于person中的名字和邻接关系都应该是私有的,所以要在其内部声明方法。这样的private在面向对象的编程中是十分重要的。保证用户可以访问的权限。

public class Person//Person是基本的对象类型
{
	 private String name;
	 private List<Person> edgeList=new ArrayList<>();//保存和该顶点相连接的所有顶点
	 public String getname(Person friend)//获取该Person的名字 
	 {
		 return this.name;
	 }
	 public boolean exist(Person friend)//判断是否存在边
	 {
		 if(edgeList.contains(friend)) 
		{
			 return true;
		}
		 else 
		{
			 return false;
		}
	 }
	 public void addFriend(Person bfriend)//加入边 
	 {
		 edgeList.add(bfriend);
	 }
	 public Person(String friend) 
	 {
		 this.name=friend;
	 }
}

3.设计/实现客户端代码main()

public static void main(String[] args) 
	{
		FriendshipGraph graph = new FriendshipGraph();
		Person rachel = new Person("Rachel");
		Person ross = new Person("Ross");
		Person ben = new Person("Ben");
		Person kramer = new Person("Kramer");
		graph.addVertex(rachel);
		graph.addVertex(ross);
		graph.addVertex(ben);
		graph.addVertex(kramer);
		graph.addEdge(rachel, ross);
		graph.addEdge(ross, rachel);
		graph.addEdge(ross, ben);
		graph.addEdge(ben, ross);
		System.out.println(graph.getDistance(rachel, ross));
		// should print 1
		System.out.println(graph.getDistance(rachel, ben));
		// should print 2
		System.out.println(graph.getDistance(rachel, rachel));
		// should print 0
		System.out.println(graph.getDistance(rachel, kramer));
		// should print -1
	}

4.设计/实现测试用例

创建一个五个顶点五条边的有向图。测试每个方法的功能是否符合yu'q

class FriendshipGraphTest 
{
    @Test
    void friendshipGraphtest() 
    {
        FriendshipGraph graph = new FriendshipGraph();
        Person Curry = new Person("Curry");
        Person Green = new Person("Green");
        Person Thompson = new Person("Thompson");
        Person Wiggins = new Person("Wiggins");
        Person Looney = new Person("Looney");
        graph.addVertex(Curry);
        graph.addVertex(Green);
        graph.addVertex(Thompson);
        graph.addVertex(Wiggins);
        graph.addVertex(Looney);
        graph.addEdge(Curry, Green);
        graph.addEdge(Wiggins, Curry);
        graph.addEdge(Wiggins, Looney);
        graph.addEdge(Thompson, Looney);
        graph.addEdge(Green,Thompson);
        assertEquals(3,graph.getDistance(Curry, Looney));
        assertEquals(-1,graph.getDistance(Green,Wiggins));
        assertEquals(3,graph.getDistance(Wiggins, Thompson));
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值