百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

图-从入门到会用

toyiye 2024-06-21 12:02 9 浏览 0 评论


什么是图

一些理论

  • 在线性结构中,数据元素之间满足唯一的线性关系,每个数据元素(除第一个和最后一个外)只有一个直接前驱和一个直接后继;
  • 在树形结构中,数据元素之间有着明显的层次关系,并且每个数据元素只与上一层中的一个元素(parent node)及下一层的多个元素(孩子节点)相关;
  • 在图形结构中,节点之间的关系是任意的,图中任意两个数据元素之间都有可能相关。

图G由两个集合V(顶点)和E(边)组成,定义为G=(V,E)。



图的分类


无向图

有向图

无权图

连接线上没有数值的图可以认为是无权图

有权图

其实所有的图都可以认为是有向图,无向图可以看为是两个方向的有向图

无向图


节点V的度是3


有向图分为入度和出度。

V的入度2,出度1。


对应现实生活中图的系统建模


比如对交通流量建模,顶点可以表示街道的十字路口,边表示街道。加权的边可以表示限速或者车道的数量。建模人员可以用这个系统来判断最佳路线及最有可能堵车的街道。

任何运输系统都可以用图来建模。比如,航空公司可以用图来为其飞行系统建模。将每个机场看成顶点,将经过两个顶点的每条航线看作一条边。加权的边可以看作从一个机场到另一个机场的航班成本,或两个机场之间的距离,这取决与建模的对象是什么。

包含局域网和广域网(如互联网)在内的计算机网络,同样经常用图来建模。

可以用图来建模的实现系统是消费市场,顶点可以用来表示供应商和消费者。

还有比如朋友圈,朋友的互相关系。

图在内存中的实现

概要

  • 节点(Vertex) 与 边(Edge)
  • 图的表示: 邻接表邻接矩阵(平时工作中很少这样表示)
    • 这里可以分为 有向图 和无向图
      无向图是一种特殊的有向图
    • 有权图 和 无权图

邻接表的表示



邻接矩阵表示


图的遍历

对节点表示

public class Node {
    public int value;
    public int in; //入度
    public int out; //出度
    public ArrayList<Node> nexts; //相邻节点
    public ArrayList<Edge> edges; //相邻边


    public Node(int value) {
        this.value = value;
        in = 0;
        out = 0;
        nexts = new ArrayList<>();
        edges = new ArrayList<>();
    }
}

对边的表示

public class Edge {
    public int weight;//权重
    public Node from; //开始节点
    public Node to; // 结束节点


    public Edge(int weight, Node from, Node to) {
        this.weight = weight;
        this.from = from;
        this.to = to;
    }

对整个图的表示

public class Graph {
    public HashMap<Integer,Node> nodes; // 所有的点
    public HashSet<Edge> edges; // 所有的边


    public Graph() {
        nodes = new HashMap<>();
        edges = new HashSet<>();
    }
}

广度优先遍历(BFS)

public class GraphBFS {
    // 从node 出发,进行广度
    public static void bfs(Node start){
        if (start ==null){
            return;
        }
        Queue<Node> queue = new LinkedList<>();
        HashSet<Node> set =  new HashSet<>();
        queue.add(start);
        set.add(start);
        System.out.println(start.value);
        while (!queue.isEmpty()){
            Node cur = queue.poll();
            for (Node node:cur.nexts){
                if (!set.contains(node)){
                    queue.add(node);
                    set.add(node);
                    System.out.println(start.value);
                }
            }
        }
    }

深度优先遍历(DFS)

//一条路走到底
    public static void dfs(Node node){
        if (node==null){
            return;
        }
        Stack<Node> stack = new Stack<>();
        HashSet<Node> set = new HashSet<>();
        stack.add(node);
        set.add(node);
        System.out.println(node.value);
        while (!stack.isEmpty()){
            Node cur = stack.pop();
            for (Node find:cur.nexts){
                if (!set.contains(find)){
                    stack.add(cur);
                    stack.add(find);
                    System.out.println(find.value);
                    break;
                }
            }
        }
    }


最小生成树

一般最小生成树有三种解决思想

  • kruska:从点开始找到最小的那棵树
  • Prim:边开始找最小的那棵树
  • Dijkstra:指定一个节点,计算 这个节点到其他节点的最短路径,有点类似全局最优解

代码

kruska 解法

// Union-Find Set
  public static class UnionFind {
    // key 某一个节点, value key节点往上的节点
    private HashMap<Node, Node> fatherMap;
    // key 某一个集合的代表节点, value key所在集合的节点个数
    private HashMap<Node, Integer> sizeMap;


    public UnionFind() {
      fatherMap = new HashMap<Node, Node>();
      sizeMap = new HashMap<Node, Integer>();
    }
    
    public void makeSets(Collection<Node> nodes) {
      fatherMap.clear();
      sizeMap.clear();
      for (Node node : nodes) {
        fatherMap.put(node, node);
        sizeMap.put(node, 1);
      }
    }


    private Node findFather(Node n) {
      Stack<Node> path = new Stack<>();
      while(n != fatherMap.get(n)) {
        path.add(n);
        n = fatherMap.get(n);
      }
      while(!path.isEmpty()) {
        fatherMap.put(path.pop(), n);
      }
      return n;
    }


    public boolean isSameSet(Node a, Node b) {
      return findFather(a) == findFather(b);
    }


    public void union(Node a, Node b) {
      if (a == null || b == null) {
        return;
      }
      Node aDai = findFather(a);
      Node bDai = findFather(b);
      if (aDai != bDai) {
        int aSetSize = sizeMap.get(aDai);
        int bSetSize = sizeMap.get(bDai);
        if (aSetSize <= bSetSize) {
          fatherMap.put(aDai, bDai);
          sizeMap.put(bDai, aSetSize + bSetSize);
          sizeMap.remove(aDai);
        } else {
          fatherMap.put(bDai, aDai);
          sizeMap.put(aDai, aSetSize + bSetSize);
          sizeMap.remove(bDai);
        }
      }
    }
  }
  


  public static class EdgeComparator implements Comparator<Edge> {


    @Override
    public int compare(Edge o1, Edge o2) {
      return o1.weight - o2.weight;
    }


  }


  public static Set<Edge> kruskalMST(Graph graph) {
    UnionFind unionFind = new UnionFind();
    unionFind.makeSets(graph.nodes.values());
    // 从小的边到大的边,依次弹出,小根堆!
    PriorityQueue<Edge> priorityQueue = new PriorityQueue<>(new EdgeComparator());
    for (Edge edge : graph.edges) { // M 条边
      priorityQueue.add(edge);  // O(logM)
    }
    Set<Edge> result = new HashSet<>();
    while (!priorityQueue.isEmpty()) { // M 条边
      Edge edge = priorityQueue.poll(); // O(logM)
      if (!unionFind.isSameSet(edge.from, edge.to)) { // O(1)
        result.add(edge);
        unionFind.union(edge.from, edge.to);
      }
    }
    return result;
  }


Prim解法

public static class EdgeComparator implements Comparator<Edge> {


    @Override
    public int compare(Edge o1, Edge o2) {
      return o1.weight - o2.weight;
    }


  }


  public static Set<Edge> primMST(Graph graph) {
    // 解锁的边进入小根堆
    PriorityQueue<Edge> priorityQueue = new PriorityQueue<>(new EdgeComparator());


    // 哪些点被解锁出来了
    HashSet<Node> nodeSet = new HashSet<>();
    
    
    
    Set<Edge> result = new HashSet<>(); // 依次挑选的的边在result里


    for (Node node : graph.nodes.values()) { // 随便挑了一个点
      // node 是开始点
      if (!nodeSet.contains(node)) {
        nodeSet.add(node);
        for (Edge edge : node.edges) { // 由一个点,解锁所有相连的边
          priorityQueue.add(edge);
        }
        while (!priorityQueue.isEmpty()) {
          Edge edge = priorityQueue.poll(); // 弹出解锁的边中,最小的边
          Node toNode = edge.to; // 可能的一个新的点
          if (!nodeSet.contains(toNode)) { // 不含有的时候,就是新的点
            nodeSet.add(toNode);
            result.add(edge);
            for (Edge nextEdge : toNode.edges) {
              priorityQueue.add(nextEdge);
            }
          }
        }
      }
      // break;
    }
    return result;
  }

Dijkstra 普通解法

public static HashMap<Node, Integer> dijkstra1(Node from) {
        HashMap<Node, Integer> distanceMap = new HashMap<>();
        distanceMap.put(from, 0);
        // 打过对号的点
        HashSet<Node> selectedNodes = new HashSet<>();
        Node minNode = getMinDistanceAndUnselectedNode(distanceMap, selectedNodes);
        while (minNode != null) {
            //  原始点  ->  minNode(跳转点)   最小距离distance
            int distance = distanceMap.get(minNode);
            for (Edge edge : minNode.edges) {
                Node toNode = edge.to;
                if (!distanceMap.containsKey(toNode)) {
                    distanceMap.put(toNode, distance + edge.weight);
                } else { // toNode
                    distanceMap.put(edge.to, Math.min(distanceMap.get(toNode), distance + edge.weight));
                }
            }
            selectedNodes.add(minNode);
            minNode = getMinDistanceAndUnselectedNode(distanceMap, selectedNodes);
        }
        return distanceMap;
    }


    public static Node getMinDistanceAndUnselectedNode(HashMap<Node, Integer> distanceMap, HashSet<Node> touchedNodes) {
        Node minNode = null;
        int minDistance = Integer.MAX_VALUE;
        for (Map.Entry<Node, Integer> entry : distanceMap.entrySet()) {
            Node node = entry.getKey();
            int distance = entry.getValue();
            if (!touchedNodes.contains(node) && distance < minDistance) {
                minNode = node;
                minDistance = distance;
            }
        }
        return minNode;
    }

Dijkstra 自定义堆优化解法

public static class NodeRecord {
    public Node node;
    public int distance;


    public NodeRecord(Node node, int distance) {
      this.node = node;
      this.distance = distance;
    }
  }


  public static class NodeHeap {
    private Node[] nodes; // 实际的堆结构
    // key 某一个node, value 上面堆中的位置
    private HashMap<Node, Integer> heapIndexMap;
    // key 某一个节点, value 从源节点出发到该节点的目前最小距离
    private HashMap<Node, Integer> distanceMap;
    private int size; // 堆上有多少个点


    public NodeHeap(int size) {
      nodes = new Node[size];
      heapIndexMap = new HashMap<>();
      distanceMap = new HashMap<>();
      size = 0;
    }


    public boolean isEmpty() {
      return size == 0;
    }


    // 有一个点叫node,现在发现了一个从源节点出发到达node的距离为distance
    // 判断要不要更新,如果需要的话,就更新
    public void addOrUpdateOrIgnore(Node node, int distance) {
      if (inHeap(node)) {
        distanceMap.put(node, Math.min(distanceMap.get(node), distance));
        insertHeapify(node, heapIndexMap.get(node));
      }
      if (!isEntered(node)) {
        nodes[size] = node;
        heapIndexMap.put(node, size);
        distanceMap.put(node, distance);
        insertHeapify(node, size++);
      }
    }


    public NodeRecord pop() {
      NodeRecord nodeRecord = new NodeRecord(nodes[0], distanceMap.get(nodes[0]));
      swap(0, size - 1);
      heapIndexMap.put(nodes[size - 1], -1);
      distanceMap.remove(nodes[size - 1]);
      nodes[size - 1] = null;
      heapify(0, --size);
      return nodeRecord;
    }


    private void insertHeapify(Node node, int index) {
      while (distanceMap.get(nodes[index]) < distanceMap.get(nodes[(index - 1) / 2])) {
        swap(index, (index - 1) / 2);
        index = (index - 1) / 2;
      }
    }


    private void heapify(int index, int size) {
      int left = index * 2 + 1;
      while (left < size) {
        int smallest = left + 1 < size && distanceMap.get(nodes[left + 1]) < distanceMap.get(nodes[left])
            ? left + 1
            : left;
        smallest = distanceMap.get(nodes[smallest]) < distanceMap.get(nodes[index]) ? smallest : index;
        if (smallest == index) {
          break;
        }
        swap(smallest, index);
        index = smallest;
        left = index * 2 + 1;
      }
    }


    private boolean isEntered(Node node) {
      return heapIndexMap.containsKey(node);
    }


    private boolean inHeap(Node node) {
      return isEntered(node) && heapIndexMap.get(node) != -1;
    }


    private void swap(int index1, int index2) {
      heapIndexMap.put(nodes[index1], index2);
      heapIndexMap.put(nodes[index2], index1);
      Node tmp = nodes[index1];
      nodes[index1] = nodes[index2];
      nodes[index2] = tmp;
    }
  }


  // 改进后的dijkstra算法
  // 从head出发,所有head能到达的节点,生成到达每个节点的最小路径记录并返回
  public static HashMap<Node, Integer> dijkstra2(Node head, int size) {
    NodeHeap nodeHeap = new NodeHeap(size);
    nodeHeap.addOrUpdateOrIgnore(head, 0);
    HashMap<Node, Integer> result = new HashMap<>();
    while (!nodeHeap.isEmpty()) {
      NodeRecord record = nodeHeap.pop();
      Node cur = record.node;
      int distance = record.distance;
      for (Edge edge : cur.edges) {
        nodeHeap.addOrUpdateOrIgnore(edge.to, edge.weight + distance);
      }
      result.put(cur, distance);
    }
    return result;
  }

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码