数据结构算法之图

这次数据结构的实验是建立一个无向非连通图的邻接表存储结构 。

让同学发了一个代码给我赖得打字了。。这二货写的是邻接矩阵。。

还得自己写

先说一下,还有一种图是邻接矩阵。是通过存储各顶点之间的邻接关系而形成图,还有邻接多重表等好几种。

由于明天实验要用邻接表。所以就写这个了

刚才有人问我什么是接口,我一时半会也没答上来,虽然心里知道是怎么用。

正好在这里就是一个接口的典范。

public interface GGraph<E>                           
{                                                    
    int vertexCount();                               
    E get(int i);                                    
    boolean insertVertex(E vertex);                  
    boolean insertEdge(int i, int j, int weight);    
    boolean removeVertex(int v);                     
    boolean removeEdge(int i, int j);                
    int getFirstNeighbor(int v);                     
    int getNextNeighbor(int v, int w);               
}            

这个是图的接口,图有好几种实现的方式, 上边说了,有表啊有矩阵什么的。但是这个接口都要有,接口就是一个方法的备忘录。类里边再根据需求实现不同的东西。


邻接表,顾名思义是使用单链表做为单位存储结构,可以想像一下,把很多条单链表往地上一撒,这种结构就是图了、


图包括顶点和边表

顶点类

public class Vertex<T> {
	public T data;
	public SortedSinglyLinkedList<Edge> adjlink;
	public Vertex(T data){
		this.data=data;
		this.adjlink=new SortedSinglyLinkedList<Edge>();
	}
	public String toString(){
		return "\n"+this.data.toString()+":"+this.adjlink.toString();
	}
}

SinglyLinkedList是单链表,还完书的数据表示之后 ,书上说只是链表里多了两个元素,一个权值一个next。

所以果断继承SinglyLinkedList。写了一个SortedSinglyLinkedList。如下

public class SortedSinglyLinkedList<T> extends SinglyLinkedList<T> {
	public Node<T> weight;
	public Node<T> next;
	public void SinglyLinkedList(){this.head=new Node<T>();}
	public SortedSinglyLinkedList(T[] element){
		this();
		Node<T> rear=this.head;
		for (int i = 0; i < element.length; i++) {
			rear.next=new Node<T>(element[i],null);
			rear=rear.next;
		}
	}
	public SortedSinglyLinkedList() {
		this.head=new Node<T>();
		// TODO Auto-generated constructor stub
	}

}


下面还会用到一个顺序表是用来放顶点的,这里注意思路要清楚,顶点里放单链表,放顶点的是顺序表

边表

public class Edge implements Comparable<Edge> 
{
    public int start;                         
    public int dest;                          
    public int weight;                        
    
    public Edge(int start, int dest, int weight)
    {
        this.start = start;
        this.dest = dest;
        this.weight = weight;
    }

    public String toString() 
    {
        return "("+start+","+dest+","+weight+")";
    }

    public int compareTo(Edge e)               
    {
        if (this.start!=e.start)
            return this.start - e.start;
        else
            return this.dest - e.dest;
    }
    
}   
 
三个属性 ,起点 终点 权值

现在基本的辅助数据结构已经摆好了。就相当于菜买齐了,可以开炒了

为了能更清楚的表达这个运行过程,我就以单步调试的思路来解释代码

public class WeightedUndiGraph3 {
	public static void main(String args[]) {
		String[] vertices = { "A", "B", "C", "D", "E" };
		Edge edges[] = { new Edge(0, 1, 5), new Edge(0, 3, 2),
				new Edge(1, 0, 5), new Edge(1, 2, 7), new Edge(1, 3, 6),
				new Edge(2, 1, 7), new Edge(2, 3, 8), new Edge(2, 4, 3),
				new Edge(3, 0, 2), new Edge(3, 1, 6), new Edge(3, 2, 8),
				new Edge(3, 4, 9), new Edge(4, 2, 3), new Edge(4, 3, 9) };
		AdjListGraph<String> graph = new AdjListGraph<String>(vertices, edges);
		System.out.println("带权无向图" + graph.toString());
		System.out.println("深度优先遍历");
		 for (int i=0; i<graph.vertexCount(); i++)
		 graph.DFSTraverse(i);
		//
		 System.out.println("广度优先遍历");
		 for (int i=0; i<graph.vertexCount(); i++)
		 graph.BFSTraverse(i);
		//        
		//
		// graph.shortestPath(1);

	}
}
这是运行入口类,这里我们声明了一个边的数组和顶点数组,传入了AdjListGraph类。看看它的构造函数

public AdjListGraph(T[] veritces,Edge[] edges){
		this(veritces.length*2);
		if (veritces==null) {
			return;
		}
		for (int i = 0; i < veritces.length; i++) {
			insertVertex(veritces[i]);
		}
		if (edges!=null) {
			for (int i = 0; i < edges.length; i++) {
				insertEdge(edges[i]);
			}
		}
	}
在这个方法它首先调用的是自己的另一个构造函数 参数是顶点基数的2倍。再看这个方法

public AdjListGraph(int size){
		size=size<10?10:size;
		this.vertexlist=new SeqList<Vertex<T>>(size);
	}
如果传入的参数小于10就设置为10,否则不变。然后初始化AdjListGraph类里的属性
protected SeqList<Vertex<T>> vertexlist;
这个边表是一个seqlist,是顺序表

好了,现在回到声明了一个边的数组和顶点数组,传入了AdjListGraph类。下面它通过 两个for循环分别插入顶点和边

插入顶点

public boolean insertVertex(T x) {
		// TODO Auto-generated method stub
		this.vertexlist.append(new Vertex<T>(x));
		return false;
	}
顶点这里调用了seqlist类里边append方法。

	public void append(E x) {
		insert(Integer.MAX_VALUE, x);
	}

	private void insert(int maxValue, E x) {
		// TODO Auto-generated method stub
		if (x == null) {
			return;
		}
		if (this.n == table.length) {
			Object[] temp = this.table;
			this.table = new Object[temp.length * 2];
			for (int i = 0; i < temp.length; i++) {
				this.table[i] = temp[i];
			}
		}
		if (maxValue<0) {
			maxValue=0;
		}
		if (maxValue>this.n) {
			maxValue=this.n;
		}
		for (int j = this.n-1;j>=maxValue;j--) {
			this.table[j+1]=this.table[j];
		}
		this.table[maxValue]=x;
		this.n++;
	}

在append方法里边调用了本类的insert方法,传入的值是2的31次方-1 的常量,和这个要插入的顶点。为什么直接 传入这个在编程里边最大的数,如果是原来的insert(this.length(),x),就需要遍历两次链表,因为检测这个长度就需要一次,这样直接传入最大的数,就只需要一次了,有人会以为这样需要遍历更多次么?看这个方法里边存在一个容错判断,如果大于本类长度则设置为本类长度

插入边

private void insertEdge(Edge edge) {
		// TODO Auto-generated method stub
		SortedSinglyLinkedList<Edge> adjlink=this.vertexlist.get(edge.start).adjlink;
		Node<Edge> front=adjlink.head,p=front.next;
		while(p!=null&&p.data.compareTo(edge)<0){
			front=p;
			p=p.next;
		}
		if (p!=null&&p.data.compareTo(edge)==0) {
			return;
		}	
		front.next=new Node<Edge>(edge,p);
		
	}

这个就是遍历一下找到要插入的位置,这个compareto是edge类里边的比较方法,找到了这个位置并且不为空就插入进去。

这样一个图就建造好了

继续回到文章最开始的地方,那个运行入口 。当这个图建好之后 下一行代码 是

System.out.println("带权无向图" + graph.toString());
打印出来这个图。别看只有一行代码。问题可多了

warning:这个toString不是java.lang.object里边的方法是AdjListGraph类里边自己写的方法

	public String toString(){
		return "出边表:\n"+this.vertexlist.toString()+"\n";
	}
这个方法又调用了。seqlist类里边的toString方法
	public String toString() {
		String str = "(";
		if (this.n != 0) {
			for (int i = 0; i < this.n - 1; i++)
				str += this.table[i].toString()+ ".";
			str += this.table[this.n - 1].toString();
		}
		return str + ")";
	}

这个方法是一个tostirng方法也不是那个java.lang.object里边的东西,想想我们存在seqlist存的是什么东西,是顶点,所以它调用的是顶点的toString方法。 

	public String toString(){
		return "\n"+this.data.toString()+":"+this.adjlink.toString();
	}
在这里第一个tostring方法是java的。第二个是SortedSinglyLinkedList里边的方法,如果到这儿你还没乱的话,真是挺厉害的了。。下面是单链表里边的tostring方法

public String toString(){
		String str="(";
		Node<T> p=this.head.next;
		while(p!=null){
			str+=p.data.toString();
			if (p.next!=null) {
				str+=",";
			}
			p=p.next;
		}
		return str+")";
	}
现在这个图已经能正常的打出来了。。

结果应该是这样的

(
A:((0,1,5),(0,3,2)).
B:((1,0,5),(1,2,7),(1,3,6)).
C:((2,1,7),(2,3,8),(2,4,3)).
D:((3,0,2),(3,1,6),(3,2,8),(3,4,9)).
E:((4,2,3),(4,3,9)))

 for (int i=0; i<graph.vertexCount(); i++)
		 graph.DFSTraverse(i);
		//
		 System.out.println("广度优先遍历");
		 for (int i=0; i<graph.vertexCount(); i++)
		 graph.BFSTraverse(i);
		//        
		 graph.shortestPath();
		 graph.shortestPath("A","E",10);
调用了graph的三个方法分别是深度优先,广度优先和打印所有对顶点的最短路径,还有找A和E的最短路径是不是10.

先看深度遍历

 public void DFSTraverse(int v)                       
    {
        boolean[] visited = new boolean[this.vertexCount()];   
        int i=v;
        do
        {
            if (!visited[i])                              
            {
                System.out.print("{ ");
                depthfs(i, visited);                      
                System.out.print("} ");
            }
            i = (i+1) % vertexCount();                    
        } while (i!=v);
        System.out.println();
    }

    private void depthfs(int v, boolean[] visited)        
    {                                                     
        System.out.print(this.get(v)+" ");                
        visited[v] = true;                                
        int w = getNextNeighbor(v,-1);                      
        while (w!=-1)                                     
        {
            if(!visited[w])                               
                depthfs(w, visited);                      
            w = getNextNeighbor(v, w);                    
        }
    }

先声明了一个布尔型数组,用于存储所有顶点是否被遍历过,下面一个循环,若没有被 遍历过则调用下面的depthfs方法,这个方法是一个递归方法,它调用 一个getnextneightbor就是取得它的邻接顶点,这个图是一个邻接表嘛,返回这个顶点有dest,就是终点序号。

@Override
	public int getNextNeighbor(int i, int j) {
		// TODO Auto-generated method stub
		int n = this.vertexCount();
		if (i >= 0 && i < n && j >= -1 && j < n && i != j) {
			Node<Edge> p = this.vertexlist.get(i).adjlink.head.next;
			while (p != null) {
				if (p.data.dest > j) {
					return p.data.dest;
				}
				p = p.next;
			}
		}
		return -1;
	}
广度优先遍历就是借助一个队列来控制递归的层数,让其每次都只递归一层。

    public void BFSTraverse(int v)                        
    {
        boolean[] visited = new boolean[vertexCount()];   
        int i=v;
        do
        {
            if (!visited[i])                              
            {
                System.out.print("{ ");
                breadthfs(i, visited);                    
                System.out.print("} ");
            }
            i = (i+1) % vertexCount();                    
        } while (i!=v);
        System.out.println();
    }
        
    private void breadthfs(int v, boolean[] visited)      
    {                                                     
        System.out.print(this.get(v)+" ");
        visited[v] = true;
        SeqQueue<Integer> que = new SeqQueue<Integer>(vertexCount());    
        que.enqueue(new Integer(v));                      
        while (!que.isEmpty())                            
        {
            v = que.dequeue().intValue();                 
            int w = getFirstNeighbor(v);                  
            while (w!=-1)                                 
            {
                if (!visited[w])                          
                {
                    System.out.print(this.get(w)+" ");    
                    visited[w] = true;
                    que.enqueue(new Integer(w));          
                }
                w = getNextNeighbor(v, w);                
            }
        }

第三个是判断从一个顶点到另一个顶点的最短路径是不是k,若是k打印出所有的路径

public void shortestPath(String string, String string2, int l) {
		// TODO Auto-generated method stub
		int n = this.vertexCount(), i, j;
		int dist[][] = new int[n][n];
		int path[][] = new int[n][n];
		for (i = 0; i < n; i++) {
			for (j = 0; j < n; j++) {
				dist[i][j]=this.getWeight(i,j);
				path[i][j]=(i!=j&&dist[i][j]<MAX_WEIGHT)?i:-1;
			}
		}
		for (int k = 0; k < n; k++) {
			for ( i = 0; i < n; i++) {
				if (k!=i) {
					for (j = 0; j < n; j++) {
						if (k!=j&&i!=j&&dist[i][j]>dist[i][k]+dist[k][j]) {
							dist[i][j]=dist[i][k]+dist[k][j];
							path[i][j]=path[k][j];
						}
					}
				}
			}
		}
		for (i = 0; i < n; i++) {
			for (j = 0; j < n; j++) {
				if (i!=j) {
					String pathstr="";
					for (int k = path[i][j]; k!=i&&k!=j&&k!=-1; k=path[i][k]) {
						pathstr=","+this.get(k)+pathstr;
					}
					if (this.get(i).equals(string)&&this.get(j).equals(string2)) {
						if (dist[i][j]==l) {
							System.out.println("存在最短路径为:"+l);
							pathstr="("+this.get(i)+pathstr+","+this.get(j)+"长度为:"+(dist[i][j]==MAX_WEIGHT?"无穷":dist[i][j]);
							System.out.println(pathstr);
						}
						else if (dist[i][j]!=l) {
							System.out.println("存在最短路径为:但不为"+l);
							pathstr="("+this.get(i)+pathstr+","+this.get(j)+"长度为:"+(dist[i][j]==MAX_WEIGHT?"无穷":dist[i][j]);
							System.out.println(pathstr);
						}
						else{
							System.out.println("不存在最短路径");
						}
					}
					
				}
			}
		}
	}

感觉 这个是最有意思 的,首先声明两个n X n的整数数组,一个存权重一个存 可连通的路径,第一次两层循环取得所有权重和路径,第二次算出所有路径的所有权重,第三次循环打印出来 。



这个是图的工程的下载地址:点击打开链接

少传了一个队列的接口。。

下面附上

public interface QQueue<T> {
	boolean isEmpty();
	boolean enqueue(T x);
	T dequeue();
}










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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值