图-从入门到会用

什么是图

一些理论

  • 在线性结构中,数据元素之间满足唯一的线性关系,每个数据元素(除第一个和最后一个外)只有一个直接前趋和一个直接后继;

  • 在树形结构中,数据元素之间有着明显的层次关系,并且每个数据元素只与上一层中的一个元素(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;  }

本文使用 文章同步助手 同步

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,496评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,407评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,632评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,180评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,198评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,165评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,052评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,910评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,324评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,542评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,711评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,424评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,017评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,668评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,823评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,722评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,611评论 2 353

推荐阅读更多精彩内容