实验九

学号 2019-2020-1823 《数据结构与面向对象程序设计》实验六报告

班级: 1823

姓名: 杨凯涵

学号:20182321

实验教师:王志强

实验日期:2019年10月22日

必修/选修: 必修

1.实验内容

(1) 初始化:根据屏幕提示(例如:输入1为无向图,输入2为有向图)初始化无向图和有向图(可用邻接矩阵,也可用邻接表),图需要自己定义(顶点个数、边个数,建议先在草稿纸上画出图,然后再输入顶点和边数)(2分)

(2) 图的遍历:完成有向图和无向图的遍历(深度和广度优先遍历)(4分)

(3) 完成有向图的拓扑排序,并输出拓扑排序序列或者输出该图存在环(3分)

(4) 完成无向图的最小生成树(Prim算法或Kruscal算法均可),并输出(3分)

(5) 完成有向图的单源最短路径求解(迪杰斯特拉算法)(3分)

PS:本题12分。目前没有明确指明图的顶点和连通边,如果雷同或抄袭,本次实验0分。

2实验过程及结果

实验一

初始化:根据屏幕提示(例如:输入1为无向图,输入2为有向图)初始化无向图和有向图(可用邻接矩阵,也可用邻接表),图需要自己定义(顶点个数、边个数,建议先在草稿纸上画出图,然后再输入顶点和边数)(2分)

首先我们现在稿纸上设计好要花的图

接着,我们开始初始化图,以下为主程序的代码(初始化图的代码与之前的周总结内容和实验相同,再次不做过多论述)

Vertex a = main.new Vertex("V0",0);//0到第一个节点的最短路径设置为0
            Vertex b = main.new Vertex("V1");
            Vertex c = main.new Vertex("V2");
            Vertex d = main.new Vertex("V3");
            Vertex e = main.new Vertex("V4");
            Vertex f = main.new Vertex("V5");
            vertexs.add(a);
            vertexs.add(b);
            vertexs.add(c);
            vertexs.add(d);
            vertexs.add(e);
            vertexs.add(f);
            int[][] edges = {
                    {5,3,Integer.MAX_VALUE,Integer.MAX_VALUE,3,Integer.MAX_VALUE},
                    {Integer.MAX_VALUE,Integer.MAX_VALUE,4,Integer.MAX_VALUE,Integer.MAX_VALUE,Integer.MAX_VALUE},
                    {6,2,Integer.MAX_VALUE,3,Integer.MAX_VALUE,6},
                    {Integer.MAX_VALUE,9,7,Integer.MAX_VALUE,4,5},
                    {Integer.MAX_VALUE,5,2,1,Integer.MAX_VALUE,7},
                    {Integer.MAX_VALUE,Integer.MAX_VALUE,Integer.MAX_VALUE,3,Integer.MAX_VALUE,Integer.MAX_VALUE}

            };
            Graph graph = main.new Graph(vertexs, edges);

            List<Vertex> vertexs1 = new ArrayList<Vertex>();
            Vertex a1 = main.new Vertex("V0",0);//0到第一个节点的最短路径设置为0
            Vertex b1 = main.new Vertex("V1");
            Vertex c1 = main.new Vertex("V2");
            Vertex d1 = main.new Vertex("V3");
            Vertex e1 = main.new Vertex("V4");
            Vertex f1 = main.new Vertex("V5");
            vertexs1.add(a1);
            vertexs1.add(b1);
            vertexs1.add(c1);
            vertexs1.add(d1);
            vertexs1.add(e1);
            vertexs1.add(f1);
            int[][] edges1 = {
                    {5,3,0,0,3,0},
                    {0,0,4,0,0,0},
                    {6,2,0,3,0,6},
                    {0,9,7,0,4,5},
                    {0,5,2,1,0,1},
                    {0,0,0,3,0,0}

            };
            Graph graph1 = main.new Graph(vertexs1,edges1);

接着输出结果为:

有向图

无向图

实验二

(2) 图的遍历:完成有向图和无向图的遍历(深度和广度优先遍历)(4分)

有向图和无向图的深度遍历和广度遍历的核心代码为

深度遍历

   public void  DFS(String vertexName){
            int id=getIdOfVertexName(vertexName);
            if(id==-1)return;
            vertexs.get(id).setMarked(true);
            System.out.print(vertexs.get(id).getName()+" ");
            List<Vertex> neighbors = getNeighbors(vertexs.get(id));
            for(int i=0;i<neighbors.size();i++){
                if(!neighbors.get(i).isMarked()){
                    DFS(neighbors.get(i).getName());
                }
            }
        }

广度遍历

 public void BFS(String vertexName){
            int startID=getIdOfVertexName(vertexName);
            if(startID==-1) return;
            List<Vertex> q=new ArrayList<Vertex>();
            q.add(vertexs.get(startID));
            vertexs.get(startID).setMarked(true);
            while(!q.isEmpty()){
                Vertex curVertex=q.get(0);
                q.remove(0);
                System.out.print(curVertex.getName()+" ");
                List<Vertex> neighbors = getNeighbors(curVertex);
                for(int i=0;i<neighbors.size();i++){
                    if(!neighbors.get(i).isMarked()){
                        neighbors.get(i).setMarked(true);
                        q.add(neighbors.get(i));
                    }
                }

            }

        }

运行结果如下

实验三

(3) 完成有向图的拓扑排序,并输出拓扑排序序列或者输出该图存在环(3分)

 拓扑排序定义: 对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若<u,v> ∈E(G),则u在线性序列中出现在v之前。
 通常,这样的线性序列称为满足拓扑次序(TopoiSicai Order)的序列,简称拓扑序列。

部分代码

VertexNode v0 = new VertexNode(0, 1, null);
        EdgeNode v0e0 = new EdgeNode(1, 0, null);
        EdgeNode v0e1 = new EdgeNode(2, 0, null);

        v0.setFirstEdge(v0e0);
        v0e0.setNext(v0e1);

        VertexNode v1 = new VertexNode(0, 1, null);
        EdgeNode v1e0 = new EdgeNode(3, 0, null);
        EdgeNode v1e1 = new EdgeNode(4, 0, null);

        v1.setFirstEdge(v1e0);
        v1e0.setNext(v1e1);

        VertexNode v2 = new VertexNode(1, 2, null);
        EdgeNode v2e0 = new EdgeNode(3, 0, null);
        EdgeNode v2e1 = new EdgeNode(5, 0, null);

        v2.setFirstEdge(v2e0);
        v2e0.setNext(v2e1);


        VertexNode v3 = new VertexNode(2, 3, null);
        EdgeNode v3e0 = new EdgeNode(5, 0, null);

运行结果

实验四

(4) 完成无向图的最小生成树(Prim算法或Kruscal算法均可),并输出(3分)

求最小生成树的方法由下面的图来演示

通过以上的算法,我们的实现代码为

   for(int i=0;i<vertexs.size();i++){
                if(vertexs.get(i).getAnotherIDinminEdge()!=-1){
                    minTree[getIdOfVertexName(vertexs.get(i).getName())][vertexs.get(i).getAnotherIDinminEdge()]=
                            edges[getIdOfVertexName(vertexs.get(i).getName())][vertexs.get(i).getAnotherIDinminEdge()];
                    minTree[vertexs.get(i).getAnotherIDinminEdge()][getIdOfVertexName(vertexs.get(i).getName())]=
                            edges[vertexs.get(i).getAnotherIDinminEdge()][getIdOfVertexName(vertexs.get(i).getName())];
                }
            }//设置生成最小生成树
 for(int i=0;i<this.vertexs.size();i++){
                for(int j=0;j<this.vertexs.size();j++){
                    this.minTree[i][j]=Integer.MAX_VALUE;
                }//初始化最小生成树
            
  initMinTree();//初始化最小生成树
            while(!allVisited()){
                Vertex vertex = vertexs.get(getNotMarkedMinVertex());//设置处理节点
                //System.out.println("处理:节点"+vertex.getName());
                //顶点已经计算出最短路径,设置为"已访问"
                vertex.setMarked(true);
                //获取所有"未访问"的邻居
                List<Vertex> neighbors = getNeighbors(vertex);
                //System.out.println("邻居个数为:"+neighbors.size());
                //更新最小生成树
                updateMinEdge(vertex, neighbors);
            }
            //System.out.println("search over");
            setMinTree();

            return minTree;//获得最小生成树
   public void  updateMinEdge(Vertex vertex, List<Vertex> neighbors){
            //参数检测
            if(!isInGraph(vertex)){
                System.out.println("当前节点不在图中");
                return ;
            }

            for(Vertex neighbor: neighbors){
                int distance = edges[getIdOfVertexName(neighbor.getName())][getIdOfVertexName(vertex.getName())];
                if(neighbor.getAnotherIDinminEdge()==-1){
                    neighbor.setAnotherIDinminEdge(getIdOfVertexName(vertex.getName()));
                    //System.out.println(neighbor.getName()+" setEdge To "+vertex.getName()+edges[neighbor.getAnotherIDinminEdge()][getIdOfVertexName(neighbor.getName())]);
                }
                else if(distance <  edges[getIdOfVertexName(neighbor.getName())][neighbor.getAnotherIDinminEdge()]){
                    neighbor.setAnotherIDinminEdge(getIdOfVertexName(vertex.getName()));
                    //System.out.println(neighbor.getName()+" setEdge To "+vertex.getName()+edges[neighbor.getAnotherIDinminEdge()][getIdOfVertexName(neighbor.getName())]);
                }
            }
        }//更新最小生成树

实验五

(5) 完成有向图的单源最短路径求解(迪杰斯特拉算法)(3分)

单源最短路径问题,即在图中求出给定顶点到其它任一顶点的最短路径。在弄清楚如何求算单源最短路径问题之前,必须弄清楚最短路径的最优子结构性质。

一.最短路径的最优子结构性质

该性质描述为:如果P(i,j)={Vi....Vk..Vs...Vj}是从顶点i到j的最短路径,k和s是这条路径上的一个中间顶点,那么P(k,s)必定是从k到s的最短路径。下面证明该性质的正确性。

假设P(i,j)={Vi....Vk..Vs...Vj}是从顶点i到j的最短路径,则有P(i,j)=P(i,k)+P(k,s)+P(s,j)。而P(k,s)不是从k到s的最短距离,那么必定存在另一条从k到s的最短路径P'(k,s),那么P'(i,j)=P(i,k)+P'(k,s)+P(s,j)<P(i,j)。则与P(i,j)是从i到j的最短路径相矛盾。因此该性质得证。

二.Dijkstra算法

由上述性质可知,如果存在一条从i到j的最短路径(Vi.....Vk,Vj),Vk是Vj前面的一顶点。那么(Vi...Vk)也必定是从i到k的最短路径。为了求出最短路径,Dijkstra就提出了以最短路径长度递增,逐次生成最短路径的算法。譬如对于源顶点V0,首先选择其直接相邻的顶点中长度最短的顶点Vi,那么当前已知可得从V0到达Vj顶点的最短距离dist[j]=min{dist[j],dist[i]+matrix[i][j]}。根据这种思路,

假设存在G=<V,E>,源顶点为V0,U={V0},dist[i]记录V0到i的最短距离,path[i]记录从V0到i路径上的i前面的一个顶点。

1.从V-U中选择使dist[i]值最小的顶点i,将i加入到U中;

2.更新与i直接相邻顶点的dist值。(dist[j]=min{dist[j],dist[i]+matrix[i][j]})

3.知道U=V,停止。

代码如下


        /**
         * 搜索各顶点最短路径
         */
        public void search(){
            while(!unVisited.isEmpty()){
                Vertex vertex = unVisited.element();
                //顶点已经计算出最短路径,设置为"已访问"
                vertex.setMarked(true);
                //获取所有"未访问"的邻居
                List<Vertex> neighbors = getNeighbors(vertex);
                //更新邻居的最短路径
                updatesDistance(vertex, neighbors);
                pop();
            }
            System.out.println("最短路径");
        }


        /**
         * 获取顶点到目标顶点的距离
         */
        private int getDistance(Vertex source, Vertex destination) {
            if(!isInGraph(source)||!isInGraph(destination)){
                System.out.println("当前节点不在图中");
                return -1;
            }


   /**
         * 获取顶点所有(未访问的)邻居
         */
        public List<Vertex> getNeighbors(Vertex v) {
            //参数检测
            if(!isInGraph(v)){
                System.out.println("当前节点不在图中");
                return null;
            }
            List<Vertex> neighbors = new ArrayList<Vertex>();
            int position = vertexs.indexOf(v);
            Vertex neighbor = null;
            int distance;
            for (int i = 0; i < vertexs.size(); i++) {
                if (i == position) {
                    //顶点本身,跳过
                    continue;
                }
                distance = edges[position][i];    //到所有顶点的距离
                if (distance < Integer.MAX_VALUE) {
                    //是邻居(有路径可达)
                    neighbor = getVertex(i);
                    if (!neighbor.isMarked()) {
                        //如果邻居没有访问过,则加入list;
                        neighbors.add(neighbor);
                    }
                }
            }
            return neighbors;
        }


        /**
         * 根据顶点位置获取顶点
         */
        private Vertex getVertex(int index) {
            if(index<0||index>vertexs.size()+1){
                System.out.println("获取ID为"+index+"失败");
                return null;
            }
            return vertexs.get(index);
        }

运行结果如图

3. 实验过程中遇到的问题和解决过程

  • 问题1:实现迪杰斯特拉算法
  • 问题1解决方法:

一、算法思想

Dijkstra算法是最短路径算法中为人熟知的一种,是单起点全路径算法。该算法被称为是“贪心算法”的成功典范。

1、令G = (V,E)为一个带权无向图。G中若有两个相邻的节点,i和j。aij(在这及其后面都表示为下标,请注意)为节点i到节点j的权值,在本算法可以理解为距离。每个节点都有一个值di(节点标记)表示其从起点到它的某条路的距离。

  2、算法初始有一个数组V用于储存未访问节点的列表,我们暂称为候选列表。选定节点1为起始节点。开始时,节点1的d1=0, 其他节点di=无穷大,V为所有节点。
初始化条件后,然后开始迭代算法,直到V为空集时停止。具体迭代步骤如下:

   将d值最小的节点di从候选列表中移除。(本例中V的数据结构采用的是优先队列实现最小值出列,最好使用斐波那契对,在以前文章有过介绍,性能有大幅提示)。对于以该节点为起点的每一条边,不包括移除V的节点, (i, j)属于A, 若dj > di + aij(违反松弛条件),则令

  dj = di + aij , (如果j已经从V中移除过,说明其最小距离已经计算出,不参与此次计算)

  可以看到在算法的运算工程中,节点的d值是单调不增的。

代码如下

public class Dijkstra {
	class Item
	{   String endString;
		ArrayList<String>lujingArrayList=new ArrayList<String>();
		int distance;
	}
	public void GetShortWay(Graph graph,String startpoint,String endpoint) {
	int startindex=graph.vertexList.indexOf(startpoint);
	ArrayList<Item>Sarray=new ArrayList<Dijkstra.Item>();//S列表中存已知最短路径的对象,U列表中存未知最短路径的对象
	ArrayList<Item>Uarray=new ArrayList<Dijkstra.Item>();//S列表中存已知最短路径的对象,U列表中存未知最短路径的对象
	//初始化U列表
	for(int i=0;i<graph.getNumofVertex();i++)
	{
		Item tempItem=new Item();
		tempItem.endString=graph.vertexList.get(i);
		tempItem.lujingArrayList.add(graph.vertexList.get(startindex));
		tempItem.lujingArrayList.add(graph.vertexList.get(i));
		tempItem.distance=graph.edges[startindex][i];
		Uarray.add(tempItem);
	}
	while (!Uarray.isEmpty()) {
		int t=0;
		int tempdistance=Uarray.get(0).distance;
		for(int i=0;i<Uarray.size();i++)
		{
			if(tempdistance>Uarray.get(i).distance)
			{
				t=i;
				tempdistance=Uarray.get(i).distance;
			}
		}
		Sarray.add(Uarray.remove(t));
		for(int i=0;i<Uarray.size();i++)
		{
			int index1=graph.vertexList.indexOf(Uarray.get(i).endString);
			for(int j=0;j<Sarray.size();j++)
			{
				int index2=graph.vertexList.indexOf(Sarray.get(j).lujingArrayList.get(Sarray.get(j).lujingArrayList.size()-1));
				if(graph.edges[index1][index2]==Integer.MAX_VALUE)
				{
					continue;
				}
				else {
					int newdistance=Sarray.get(j).distance+graph.edges[index1][index2];
					if(newdistance<Uarray.get(i).distance)
					{
						Uarray.get(i).distance=newdistance;
						Uarray.get(i).lujingArrayList=new ArrayList<String>( Sarray.get(j).lujingArrayList);
						Uarray.get(i).lujingArrayList.add(graph.vertexList.get(index1));
					}
				}
			}
		}
		
	}
	for(int i=0;i<Sarray.size();i++)
	{
		System.out.print( startpoint+"-->"+Sarray.get(i).endString+"的最短路径为:");
		for(int j=0;j<Sarray.get(i).lujingArrayList.size();j++)
		{System.out.print( Sarray.get(i).lujingArrayList.get(j)+"  ");}
		System.out.println("长度为:"+ Sarray.get(i).distance);
	}
	}

}

  • 问题2:如何代码打出广度遍历和深度遍历
  • 问题2解决方法:
    在上面的基础上,我们进行编写广度遍历和深度遍历的方法,这两个方法用到了堆栈的方式来实现,以下给出代码

深度遍历

 public String DFS(String startnode,ArrayList<String> vertexList,int [][]edges) {
        if (!vertexList.contains(startnode)) {
            System.out.print("输入节点不在该图内");
            return null;
        }
        int startindex=vertexList.indexOf(startnode);
        int numOfNodes=vertexList.size();
        boolean[]visted=new boolean[numOfNodes];
        StringBuilder resultBuilder=new StringBuilder();
        Stack<Integer> stack=new Stack<Integer>();
        stack.push(startindex);
        visted[startindex]=true;
        while (!stack.isEmpty()) {
            int v=stack.pop();
            resultBuilder.append(vertexList.get(v)+",");
            for(int i=0;i<numOfNodes;i++)
            {
                //当edges【v】【i】的值不为0,不为最大,且没有被访问时,将其压入栈中
                if((edges[v][i]!=0)&&(edges[v][i]!=Integer.MAX_VALUE)&&!visted[i])
                {
                    stack.push(i);
                    visted[i]=true;
                }
            }
        }


        return resultBuilder.length()>0?resultBuilder.substring(0,resultBuilder.length()-1):null;
    }

广度遍历

  public String BFS(String startnode,ArrayList<String> vertexList,int [][]edges) {
        if (!vertexList.contains(startnode)) {
            System.out.print("输入节点不在该图内");
            return null;
        }
        StringBuilder resultBuilder=new StringBuilder();
        boolean []visited=new boolean[vertexList.size()];
        int startIndex=vertexList.indexOf(startnode);
        Queue<Integer>queue=new LinkedList<Integer>();
        queue.offer(startIndex);
        visited[startIndex]=true;
        while (!queue.isEmpty()) {
            int v=queue.poll();
            resultBuilder.append(vertexList.get(v)+",");
            for(int i=0;i<vertexList.size();i++)
            {
                if((edges[v][i]!=0) &&( edges[v][i]!=Integer.MAX_VALUE)&&!visited[i])
                {
                    queue.offer(i);
                    visited[i]=true;
                }
            }

        }
        return resultBuilder.length()>0?resultBuilder.substring(0,resultBuilder.length()-1):null;
    }

其他(感悟、思考等)

  • 很高兴能圆满完成这学期的所有实验,感悟最深的不是学了多少的数据结构、多少的java算法、多少的关于安卓、关于java的各种各样的知识,是感觉自己自学能力的提高,自己对于解决苦难的能力的提高,在困境中的不懈努力和寻求解决难题的方法,才是我觉得这门课给我最多的东西
  • 完成这个实验后还有这最后一个难度最大的app在等着我们,加油!

参考资料

《Java程序设计与数据结构教程(第二版)》

《Java程序设计与数据结构教程(第二版)》学习指导

posted @ 2019-12-08 19:47  楊某人  阅读(171)  评论(0编辑  收藏  举报