图论 邻接链表存储 BFS DFS 拓扑排序 最小生成树 KRUSKAL PRIM

来源:互联网 发布:apache spark 安装 编辑:程序博客网 时间:2024/05/10 20:56
package Algorithms;import java.util.ArrayList;import java.util.Arrays;import java.util.Comparator;import java.util.LinkedList;import java.util.PriorityQueue;import java.util.Stack;import java.util.TreeSet;import javax.swing.text.html.HTMLDocument.Iterator;class MinProQueue<T extends Comparable<? super T>>{    int heapSize;    T[] heap;    int capacity;    public MinProQueue(int capaticty)    {        this.capacity=capaticty;        heapSize=0;        //因为泛型擦除,泛型不能实例化,只能创建Object,然后再强制类型转换为数组        //这里不能使用new Object 因为没有comparable,要使用直接父类comparable        heap=(T[])new Comparable[capaticty];    }    public boolean isEmpty()    {        if(heapSize==0)            return true;        return false;    }    /**     * 最小优先队列的维护     */    public  void heapfy(int i)    {        if(i>=heapSize&&i<0)        {            System.out.println("要维护的节点错误");            return ;        }        int left=2*i+1;        int right=2*i+2;        int min=i;        //寻找i与其两个孩子的最小值        if(left<heapSize&&heap[left].compareTo(heap[min])==-1)            min=left;        if(right<heapSize&&heap[right].compareTo(heap[min])==-1)            min=right;        if(min!=i)        {            T temp=heap[min];            heap[min]=heap[i];            heap[i]=temp;            heapfy(min);        }    }    /**     * 建立最小优先队列     */    public void insert(T ele)    {        if(heapSize>=capacity)        {            System.out.println("最小优先队列已满!");            return ;        }        heap[heapSize]=ele;        heapSize++;        int child=heapSize-1;        int parent=(heapSize/2)-1;        while(parent>=0&&heap[parent].compareTo(heap[child])>0)        {            T temp=heap[parent];            heap[parent]=heap[child];            heap[child]=temp;            child=parent;            parent=(child+1)/2-1;        }    }    public T extractMin()    {        if(heapSize<=0)        {            System.out.println("没有元素");            return null;        }        T min=heap[0];        heapSize--;        heap[0]=heap[heapSize];        heap[heapSize]=min;        heapfy(0);        return min;    }    public void decreaseKey(T a)    {        int child;        for( child=0;child<capacity;child++)            if(a==heap[child])                break;        if(child==capacity)        {            System.out.println("没有此元素");            return ;        }        int parent=((child+1)/2)-1;        while(parent>=0&&heap[parent].compareTo(heap[child])>0)        {            T temp=heap[parent];            heap[parent]=heap[child];            heap[child]=temp;            child=parent;            parent=((child+1)/2)-1;        }    }}public class Graphic {    public static class Vertex implements Comparable<Vertex>{        public int num;//节点编号        public int weight;//边的权重 或者最小生成树中节点到此顶点的权重最小值                           //在最短路径中存放距离源节点的最短距离        public Vertex next;//指向顶点的下一个弧尾,即下一条边        public Vertex(int num,int weight,Vertex next)        {            this.num=num;            this.weight=weight;            this.next=next;        }        @Override        public int compareTo(Vertex o) {            if(this.weight>o.weight)                return 1;            else if(this.weight==o.weight)                return 0;            else                return -1;        }    }    //边节点应用于 KRUSKAL的最小生成树 的边按权值排序    public static class Edge{        public int head;//弧头        public int tail;//弧尾        public int w;//边的权重        public Edge(int head,int tail,int weight){            this.head=head;            this.tail=tail;            this.w=weight;        }    }    //数组比较器    class edgeComparator implements Comparator<Edge>{        @Override        public int compare(Edge o1, Edge o2) {            // TODO Auto-generated method stub            if(o1.w==o2.w)               return 0;            else if(o1.w>o2.w)                return 1;            else                return -1;        }            }        //创建存放边的数组 创建从大到小排序的比较器    private Edge arc[];    private int size;//图的顶点数    private int Esize;//边的数目    private int countE;//边计数器 用于arc数组的添加元素    private boolean isDirection;//是否为有向图true,    private boolean visit[];//标志遍历时节点是否被访问    private boolean[] inQueue;//prim算法每个顶点是否在队列中的标志位,初始状态顶点都在队列    private Vertex  p[];//存放每个顶点的前驱    private int distance[];//广度优先遍历时存放与根节点的距离    private Vertex adj[];    public int startT[];//深度优先遍历的开始时间    public int endT[];//深度优先遍历的结束时间  用于拓扑排序    int time;//深度优先遍历时记录每个节点的开始和结束时间的计时器    LinkedList<Vertex>topologicalSort=new LinkedList<Vertex>();//存放拓扑排序的数组            public Graphic(int verNum,int Esize ,boolean isDirection){        adj=new Vertex[verNum];        p=new Vertex[verNum];        size=verNum;//顶点个数        this.Esize=Esize;//边的数目        countE=0;        this.isDirection=isDirection;        arc=new Edge[Esize];        inQueue=new boolean[size];        visit=new boolean[verNum];        distance=new int [verNum];         endT=new int [size];        startT=new int [size];        //构造顶点,防止下面的有向图中顶点不是弧头,从而顶点数组会为空指针        for(int i=0;i<size;i++)        {            adj[i]=new Vertex(i, 1000, null);        }    }    /**     * 创建图  只插入所有节点即可创建 图的连接表表示     * x y分别表示弧头,和 弧尾     */    public void insertE(int x,int y,int w)    {        Vertex temp=adj[x];        while(temp.next!=null)            temp=temp.next;        temp.next=new Vertex(y, w, null);        if(isDirection==false)        {            Vertex temp1=adj[y];            while(temp1.next!=null)                temp1=temp1.next;            temp1.next=new Vertex(x, w, null);        }        //这个只是为了使用kruskal算法,prim算法用不到        arc[countE++]=new Edge(x, y, w);    }    /**   广度优先遍历     * @param x:图的根节点 开始遍历      * 存放在队列中的节点都是将要访问的所以设置为true ,以免重复添加      * 可用于求无权图上的最短路径     */    public void BFS(int x)    {        //初始化        Arrays.fill(visit, false);        Arrays.fill(distance, -1);        Arrays.fill(p, null);        java.util.Queue<Integer> q=new LinkedList<Integer>();        distance[x]=0;//初始化距离根节点的深度        visit[x]=true;        q.add(x);        while(!q.isEmpty())        {            //这里遍历顶点的边,要找到定点数组中的引用,才能找到相应的邻接表,否则            //只在一个顶点的边上乱转,所以 使用顶点的编号 在队列中表示 不要用引用            //使用引用,还要找到相应的定点数组上的引用            Vertex parent=adj[q.poll()];            System.out.print(parent.num+" ");            Vertex temp=parent.next;            while(temp!=null)//在向队列中添加节点时就把此节点的前驱和深度进行赋值            {                if(visit[temp.num]==false)                {                    p[temp.num]=parent;                    visit[temp.num]=true;//添加访问标志进入队列之后就要添加标志防止                                          //重复进入 重复访问                    distance[temp.num]=distance[p[temp.num].num]+1;                    q.add(temp.num);                }                temp=temp.next;            }        }    }    /**     * 深度优先遍历 与拓扑排序     */    public void DFS()    {        //初始化        Arrays.fill(visit, false);        Arrays.fill(distance, -1);        Arrays.fill(p, null);        Arrays.fill(endT, 0);        Arrays.fill(startT, 0);        time=0;        for(int i=0;i<size;i++)//避免有的图为不强连通的        {            if(visit[i]==false)//注意不能把这个条件写到for循环中,因为遇到2                               //已经访问退出循环不再迭代,应该放在循环体中               DFSvisit3(i);        }    }    //递归实现    public void DFSvisit(int i)    {        time++;//开始访问的时间        startT[i]=time;        visit[i]=true;        System.out.print(i+" ");        Vertex temp=adj[i].next;        while(temp!=null)        {            if(visit[temp.num]==false)               DFSvisit(temp.num);            temp=temp.next;        }        time++;//访问完成+1 ,记录 结束的时间  如果不加就和开始的时间一样了        endT[i]=time;        topologicalSort.addFirst(adj[i]);    }    //使用栈实现    public void DFSvisit2(int i)    {        Stack<Integer> s=new Stack<Integer>();        s.push(i);        while(!s.isEmpty())        {            time++;            int num=s.pop();            visit[num]=true;            System.out.print(num+" ");            Vertex temp=adj[num].next;            while(temp!=null)            {                if(visit[temp.num]==false)                    s.push(temp.num);                temp=temp.next;            }        }    }    //使用栈实现    public void DFSvisit3(int i)    {        Stack<Integer> s=new Stack<Integer>();        s.push(i);        System.out.print("深度优先遍历:");        while(!s.isEmpty())        {            time++;            int num=s.peek();            if(visit[num]==false)            {                    visit[num]=true;                startT[num]=time;               System.out.print(num+" ");                Vertex temp=adj[num].next;                while(temp!=null)                {                   if(visit[temp.num]==false)                      s.push(temp.num);                    temp=temp.next;                 }            }            else            {                endT[num]=time;                s.pop();                topologicalSort.addFirst(adj[num]);            }        }        System.out.println();    }    /**     * 拓扑排序(对于无环图)     * 深度优先遍历的按 结束时间从大到小排序     * 结束时间大的表示  为最前面的节点     */    public void topologicalSort()    {        DFS();        for(int i=0;i<topologicalSort.size();i++)        {            int num=topologicalSort.get(i).num;            System.out.println("第"+i+"执行的"+"节点"+num+": "+startT[num]+"/"+endT[num]);        }    }    /**     * MST :最小生成树的生成     * kruskal算法,初始状态为 含有顶点个数的森林,     * 每个顶点存放在8个不相交数据结构中(对于发现的最短边,用于判断有无回路),     * 判断是否有无回路  如果一条边所包含的 两个顶点 在一个集合中,说明两个顶点已经     * 连接,此边若在添加就会产生回路     */    public String MSTKRUSKAL()    {        String mst="";        //对每一个顶点  创建不相交集合        ////参见 http://blog.csdn.net/jiaomingliang/article/details/41322285        DisjointSet<Integer>a=new DisjointSet<Integer>(size);        for(int i=0;i<size;i++)            //第一个参数为这个集合所在的数组中的位置,第二个参数为此元素的标识            a.makeSet(i, i);        Arrays.sort(arc, new edgeComparator());        for(int i=0;i<Esize;i++)        {            Edge temp=arc[i];            if(a.findSet(temp.head)!=a.findSet(temp.tail))//不在一个集合说明为安全边            {                mst+="E("+temp.head+","+temp.tail+") ";                a.union(temp.head, temp.tail);            }        }        return mst ;    }    /**     * 最小生成树的prim算法(借助  最小优先队列实现)还可以使用两个数组实现,     *    道理一样,这里就不实现了     * 先将所有节点放入最小优先队列,然后距离生成树距离最近的出队,更新还     * 没在树中节点(在队列中的)到现有生成树的距离,     * 不管是用 邻接矩阵还是 邻接链表  都必须记录每个树外的结点距离树中最近     * 的那个节点这里称为他的父亲,输出在最小生成树中的边      */    public String MSTPRIM(int x)    {        String mst="";        Arrays.fill(p, null);        Arrays.fill(inQueue, true);        //这个队列作用找出最小的权重的节点        MinProQueue<Vertex>q=new MinProQueue<Graphic.Vertex>(size);        adj[x].weight=0;        p[x]=adj[x];        for(int i=0;i<size;i++)           q.insert(adj[i]);        while(!q.isEmpty())        {            int num=q.extractMin().num;            mst+="E("+p[num].num+","+num+")  ";            inQueue[num]=false;            Vertex temp=adj[num].next;            while(temp!=null)//遍历所有的边            {//顶点不在树中 并且顶点距离树的最短距离比旧树的小 则更新距离                if(inQueue[temp.num]==true&&temp.weight<adj[temp.num].weight)                {                    p[temp.num]=adj[num];//用于输出最小生成树中的边                    adj[temp.num].weight=temp.weight;                    q.decreaseKey(adj[temp.num]);                }                temp=temp.next;            }        }        return mst;    }    /**     *对数组节点中的顶点,进行单源路径 初始化     *前驱节点为null   距离源节点的距离(weight)为正无穷     *并且令源节点 的weight=0     */    public void initialzieSource(int s)    {        for(int i=0;i<size;i++)        {            adj[i].weight=10000;            p[i]=null;        }        adj[s].weight=0;        }        /**松弛操作     * 对此边进行松弛操作:     *    也是更新节点weight 不同于prim算法,更新的是此节点距离树中结点的最短距离)     *    这里是更新此节点 距离源节点的最短路径     *     v.d>u.d+w 则更新为 v.d=u.d+w      * @param u v:表示一条边  对v到s的最短距离进行松弛     * @param w:此边的权重      *      */    public void relax(int u,int v,int w)    {        if(adj[v].weight>adj[u].weight+w)        {            adj[v].weight=adj[u].weight+w;            p[v]=adj[u];        }    }    /**单元最短路径的 Bellman-Ford算法   时间复杂度:O(VE)     * 思路:对条边进行size-1次松弛操作,然后在判断是否存在环,     * 若存在则返回false,否则为true     * s:为根节点        */    public boolean bellmanFord(int s)    {        initialzieSource(s);        for(int i=0;i<size-1;i++)        {            for(int j=0;j<size;j++)//遍历每一条边            {                Vertex temp=adj[j].next;                while(temp!=null)                {                    relax(j,temp.num,temp.weight);                    temp=temp.next;                }            }        }        for(int j=0;j<size;j++)//遍历每一条边判断是否含有权值为负的回路 回路的权值        {                     //之和为负            Vertex temp=adj[j].next;            while(temp!=null)            {                if(adj[temp.num].weight>adj[j].weight+temp.weight)                    return false;                temp=temp.next;            }        }        return true;    }    /**     * 有向无环图的最短路径问题       * 有环图不能进行拓扑排序     * 利用拓扑排序的顶点顺序只对 每条边进行一次松弛就可以了     * 是线性时间算法 :v+E     */    public void dagshorttestpath(int s)    {        DFS();        int i;        //图中每个节点都必须算出距离 源节点s的最短距离        initialzieSource(s);        for(i=0;i<topologicalSort.size();i++)        {            int num=topologicalSort.get(i).num;            Vertex temp=adj[num].next;            while(temp!=null)            {                relax(num,temp.num,temp.weight);                temp=temp.next;            }        }    }        /**     * 贪心算法 DIJKSTRA算法     * 要求  :所有边的权重都为非负值。若有负值不满足贪心算法     * S集合为出队列组成的集合,即已经求出最短路径的顶点,然后再对其作为弧头     * 的边进行松弛,在寻找队列中此时距离s最小的值,此对应节点一定是求出最短路径的     * 这种贪心策略一定正确。     */    public void dijkstra(int s)    {        initialzieSource(s);        MinProQueue<Vertex>q=new MinProQueue<Graphic.Vertex>(size);        for(int i=0;i<size;i++)           q.insert(adj[i]);        //S=空;        while(!q.isEmpty())        {            Vertex u =q.extractMin();//出队列就是为了更新寻找要松弛的边            //S=S∪{u};            Vertex v=u.next;            while(v!=null)            {                relax(u.num,v.num,v.weight);                q.decreaseKey(adj[v.num]);                v=v.next;            }        }    }    public void printPath(int s,int v)    {        System.out.print("最短路径:");        print(s,v);    }    public void print(int s,int v)    {        if(v==s)            System.out.print(s+" ");        else        {            print(s, p[v].num);            System.out.print(v+" ");        }    }    public static void main(String []args)    {        Graphic g=new Graphic(5, 9,true);        g.insertE(0, 1,6);        g.insertE(0, 2,7);        g.insertE(1, 2,8);        g.insertE(2, 3,9);        g.insertE(3, 4,7);        //g.insertE(4, 1,-2);        g.insertE(1, 4,5);        g.insertE(3, 0,2);        //g.insertE(2, 4,-3);        //g.insertE(1, 3,-4);        g.dijkstra(0);        g.printPath(0, 3);        //System.out.println(g.bellmanFord(0));            }}

最短单源路径图:



1 0