图论

一、预备知识

1.图的种类

  • 有向图
  • 无向图
  • 带权图

2.图的表示方法

  • 邻接矩阵
    • 表示顶点之间相邻关系的矩阵。用一个二维数组来表示图的相关信息,即用二维数组单元edga[i][j]来表示结点i和结点j的关系。
    • 优点
      • 原理易懂、用法简单
      • 在确定某对结点之间是否存在关系时,只需访问二维数组中的相关单元即可,耗时较少
    • 缺陷
      • 空间复杂度为O(n*n),n为图中结点个数
      • 当表示的图为稀疏图时,大量的空间被浪费
        • 只有表示的图为稠密图,且频繁地判断某特定的结点对是否相邻时,用邻接矩阵较为适宜
  • 邻接链表
    • 为图的每个结点建立一个单链表,第i个单链表中保存与结点Vi相邻的所有结点(无向图)或所有以结点Vi为弧尾的弧指向的结点(有向图)及其相关信息
    • 当用其表示带权图时,各链表单元在保存结点信息的同时也保存相应的边权信息
    • 优点
      • 效率高。遍历某个结点的相邻结点(无向图)或某个结点为弧尾的弧的弧头指向结点(有向图)时,不用遍历不存在关系的其他结点
      • 空间复杂度为O(n+e),较邻接矩阵而言空间利用率较高
    • 缺陷
      • 当需要判断结点Vi与Vj间是否存在关系时比较繁琐
        • 有向图:遍历Vi和Vj所有的邻接结点
        • 无向图:选择Vi或Vj遍历其所有邻接结点
        • 当存在大量遍历邻接结点的操作而较少的判断两个特定结点的关系时,使用邻接链表要好一些

二、并查集

1.并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。常常在使用中以森林来表示。

2.解决问题:

  • 某个元素是否属于某个集合
  • 某个元素和另一个元素是否同属于一个集合
  • 所有元素属于几个不同的集合

3.示例代码

public class UnionFind {
    int size;
    int[] unionset;
    int[] weight;
    public UnionFind(int size){
        this.size = size;
        unionset = new int[size];
        weight = new int[size];
        for (int i=0;i<size;i++){
            unionset[i]=i;
            weight[i] = 1;
        }
    }

    public int find(int element){
        if (element==unionset[element]){return element;}
        else {
            int root = find(unionset[element]);
            unionset[element]=root;//将element的父节点设置为查找到的根结点,缩短路径
            return root;
        }
//        while (element!=unionset[element]){//当element有自己的父亲结点时
//            unionset[element]=unionset[unionset[element]];//将element中的值设置为其父节点的父节点,缩短路径
//            element = unionset[element];
//        }
//        return element;
    }

    public boolean isConnected(int firstelement,int secondelement){return find(firstelement)==find(secondelement);}

    public void unionElements(int firstelement,int secondelement){
        int firstroot = find(firstelement);
        int secondroot = find(secondelement);
        if (firstroot!=secondroot){
            if (weight[firstroot]>weight[secondroot]){unionset[secondroot]=firstroot;weight[firstroot]+=weight[secondroot];}
            else {unionset[firstroot]=secondroot;weight[secondroot]+=weight[firstroot];}

        }
    }
    private void printArr(int[] intlist) {
        for (int id : intlist) {
            System.out.print(id + "\t");
        }
        System.out.println();
    }

    public static void main(String[] args){
        UnionFind union = new UnionFind(10);
        System.out.println("初始parent:");
        union.printArr(union.unionset);
        System.out.println("初始height:");
        union.printArr(union.weight);

        System.out.println("连接了5 6 之后的parent:");
        union.unionElements(5, 6);
        union.printArr(union.unionset);
        System.out.println("连接了5 6 之后的weight:");
        union.printArr(union.weight);

        System.out.println("连接了1 2 之后的parent:");
        union.unionElements(1, 2);
        union.printArr(union.unionset);
        System.out.println("连接了1 2 之后的weight:");
        union.printArr(union.weight);

        System.out.println("连接了2 3 之后的parent:");
        union.unionElements(2, 3);
        union.printArr(union.unionset);
        System.out.println("连接了2 3 之后的weight:");
        union.printArr(union.weight);

        System.out.println("连接了1 4 之后的parent:");
        union.unionElements(1, 4);
        union.printArr(union.unionset);
        System.out.println("连接了1 4 之后的weight:");
        union.printArr(union.weight);

        System.out.println("连接了1 5 之后的parent:");
        union.unionElements(1, 5);
        union.printArr(union.unionset);
        System.out.println("连接了1 5 之后的weight:");
        union.printArr(union.weight);

        System.out.println("连接了5 8 之后的parent:");
        union.unionElements(5, 8);
        union.printArr(union.unionset);
        System.out.println("连接了5 8 之后的weight:");
        union.printArr(union.weight);

        System.out.println("连接了8 9 之后的parent:");
        union.unionElements(8, 9);
        union.printArr(union.unionset);
        System.out.println("连接了8 9 之后的weight:");
        union.printArr(union.weight);

        System.out.println("1  6 是否连接:" + union.isConnected(1, 6));
        System.out.println("5  9 是否连接:" + union.isConnected(1, 8));

    }
}

三、最小生成树MST

1.在一个无向连通图中,如果存在一个连通子图,它包含原图中的所有结点和部分边,且这些边不构成回路,则称这个连通子图为原图的一个生成树。

在带权图中,所有生成树中权值和最小的那棵(或几棵)树称为最小生成树。

2.求最小生成树

  • Krustal算法
    • 初始时所有结点属于孤立的集合
    • 按照边权递增的顺序遍历所有的边,若遍历到的边的两个顶点仍分属于两个不同的集合,该边则是连接这两个不同集合的最短的边,确定这条边为最小生成树的一条边,之后将这两个集合合并
    • 遍历完所有边后,原图上所有结点属于同一集合则被选取的边和原图上所有结点构成一个最小生成树;否则原图不连通,最小生成树不存在
  • Prime算法
    • 清空生成树,选取任意一个结点加入生成树
    • 在那些一个端点在生成树里,另一个端点不在生成树里的边中,选择权值最小的一条边加入生成树
    • 重复2步骤,直至所有结点都加入到生成树中

四、最短路径

寻找图中某两个特定结点间的最短路径长度。

计算最短路径的算法:

  • Floyd算法
    • 最简单的最短路径算法
    • 计算图中任意两点间的距离,全源最短路径
    • 时间复杂度O(N^3)
    • 代码实现
      • void floyd()
        {
            for(int k = 0; k < n; k ++){ //作为循环中间点的k必须放在最外一层循环 
                for(int i = 0; i < n; i ++){
                    for(int j = 0; j < n; j ++){
                        if(dist[i][j] > dist[i][k] + dist[k][j]){
                            dist[i][j] = dist[i][k] + dist[k][j];    //dist[i][j]得出的是i到j的最短路径 
                        }     
                    }    
                }    
            }    
        }

         

  • Dijkstra算法
    • 计算某一结点到图中其他所有点间的距离,单源最短路径问题
    • 时间复杂度O(N^2)
    • 算法流程
      • 初始化,集合K中加入结点1,结点1到结点1最短距离为0,到其他结点为无穷/不确定
      • 遍历与集合K中直接相邻的边(U,V,C),其中U属于集合K,V不属于集合K,C为边的权值。计算由结点1出发按照已经得到的最短路径到达U,再由U经过该边到达V的路径长度,其中路径长度最短的结点V被确定为下一个最短路径所确定的结点V,其最短路径长度即为该最短路径长度,最后将V加入到集合K
      • 重复步骤2,,至集合K中已经包含了所有的结点,算法结束
    • 代码实现
      •  LinkedList<Node>[] linkedLists = new LinkedList[N+1];//创建邻接链表
                    for (int i=1;i<=N;i++){linkedLists[i]= new LinkedList<Node>();}//!!!创建单链表,此句容易忘记
                    boolean[] marked = new boolean[N + 1];//用于标记结点是否已经在集合K中
                    int[] weights = new int[N + 1];//存储开始结点到结点j的权值和
                    for (int n = 1; n <= N; n++) {weights[n] = -1; }
                    for (int m = 1; m <= M; m++) {
                        int i = scanner.nextInt();
                        int j = scanner.nextInt();
                        int weight = scanner.nextInt();
                        Node node_i = new Node(j, weight);
                        Node node_j = new Node(i, weight);
                        linkedLists[i].add(node_i);
                        linkedLists[j].add(node_j);
                    }
                    weights[1] = 0;
                    int nextNode = 1;//继结点i之后加入到K中的结点,此结点与结点i间有最短路径
                    marked[1]=true;
                    for (int i=1;i<=N;i++){
                        for (int j = 0; j < linkedLists[nextNode].size(); j++) {
                            LinkedList<Node> visitedlist = linkedLists[nextNode];
                            int adjnode = visitedlist.get(j).nodeNum;
                            int weight = visitedlist.get(j).weight;
                            if (marked[adjnode]) { continue; }
                            else {
                                if (weights[adjnode] == -1 | weights[adjnode] > weights[nextNode] + weight) {
                                    weights[adjnode] = weights[nextNode] + weight;
                                }
                            }
                        }//在选定下一个加入K的结点后,先更新与nextNode相邻且不在K集合中的结点距离结点1的路径和
        
                        //在更新完路径和后,在不属于K集合的结点中,选择拥有距离结点1最短的路径和的结点作为下一个结点nextNode,
                        // 并将其加入到集合K中
                        int min_weight = 1000;//结点i到所有相邻结点的最短路径
                        for (int j = 2; j <= N; j++) {
                            if (marked[j]) {
                                continue;
                            } else {
                                int weight = weights[j];
                                if (weight < min_weight) {
                                    min_weight = weight;
                                    nextNode = j;
                                }
                            }
                        }
                        marked[nextNode]=true;
                    }

         

 

五、拓扑排序

无论何时,当需要判断一个图是否是有向无环图(DAG)时,都需要想到拓扑排序。

  • 若一个图存在符合拓扑次序的结点序列,则称其为有向无环图
  • 若一个图不存在符合拓扑次序的结点序列,则称其为非有向无环图

队列Queue

  • 先进先出FIFO型数据结构
  • 两个基本操作
    • 在队尾添加一个元素
    • 从队头删除一个元素
  • LinkedList类实现了Queue接口,可以把LinkedList当成Queue来用
  • 方法
    • 使用offer()来添加元素
    • 使用peek()来查看队头元素
    • 使用poll()来查看并删除队头元素
      • booleanadd(E e)

        Inserts the specified element into this queue if it is possible to do so immediately without violating capacity restrictions, returning true upon success and throwing an IllegalStateException if no space is currently available.

        Eelement()

        Retrieves, but does not remove, the head of this queue.Throws NoSuchElementException if the queue is empty.

        booleanoffer(E e)

        Inserts the specified element into this queue if it is possible to do so immediately without violating capacity restrictions.

        Epeek()

        Retrieves, but does not remove, the head of this queue, or returns null if this queue is empty.

        Epoll()

        Retrieves and removes the head of this queue, or returns null if this queue is empty.

        Eremove()

        Retrieves and removes the head of this queue.

示例代码:

    题目:Leagal or Not

   

import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;

public class Main {
    public static void main(String[] args){
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()){
            int N = scanner.nextInt();
            int M = scanner.nextInt();
            if (N==0&&M==0){break;}
            LinkedList<Integer>[] relationship = new LinkedList[N];
            for (int n=0;n<N;n++){relationship[n]=new LinkedList<>();}
            int[] indigrees = new int[N];
            for (int m=0;m<M;m++){
                int master = scanner.nextInt();
                int prentice = scanner.nextInt();
                relationship[master].add(prentice);
                indigrees[prentice]++;
            }
            Queue<Integer> queue = new LinkedList<>();//保存入度为0的结点
            for (int cnt=0;cnt<N;cnt++){//遍历N次,每次找到一个入度为0的结点
                for (int i=0;i<N;i++){
                        queue.offer(i);
                        for (int prentice:relationship[i]){
                            indigrees[prentice]--;
                        }
                        indigrees[i]=-1;//更改结点i的入度,防止再次遍历时重新加入queue
                        break;//找到结点后立即退出遍历
                    }
                }
            }
            if (queue.size()==N){System.out.println(queue);System.out.println("YES");}
            else {System.out.println("NO");}


        }
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值