目录
1 图的存储方式
2 图的宽度优先遍历和广度优先遍历
2.1 图的宽度优先遍历
2.2 图的广(深)度优先遍历
3 拓扑排序算法
4 kruskal算法
5 prim算法
6 Dijkstra算法
1 图的存储方式
(1)邻接表
邻接表,存储方法跟树的孩子链表示法相类似,是一种顺序分配和链式分配相结合的存储结构。
(2)邻接矩阵
逻辑结构分为两部分:V和E集合,其中,V是顶点,E是边。因此,用一个一维数组存放图中所有顶点数据;用一个二维数组存放顶点间关系(边或弧)的数据。
(3)在实际解题的时候图有各种各样的表达形式。可以将其先转化成自己常用的一种模板,再用自己熟悉的算法去完成。
public class Graph{
//大图的结构:点集和边集
public HashMap<Integer, Node> nodes;
public HashSet<Edge> edges;
public Graph(){
nodes = new HashMap<>();
edges = new HashSet<>();
}
}
public class Node{
public int value; //点上的值,例如城市“a”或者城市1
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;
}
}
假设给一个图的类型的结构,将其转化成自己的模板
//matrix 所有的边
//N*3的矩阵
//[weight, from节点上面的值, to节点上面的值]
public static Graph createGraph(Integer[][] matrix){
Graph graph = new Graph();
for(int i = 0; i < matrix.length; i++){
Integer from = matrix[i][0];
Integer to = matrix[i][1];
Integer weight = matrix[i][2];
if(!graph.nodes.containsKey(from)){//如果该城市是第一次遇见,则将其加入图中
graph.nodes.put(from, new Node(from));//在图的点集中,将该城市新建出来。
//编号及其对应的城市挂成记录放入点集
}
if(!graph.nodes.containsKey(to)){//同上
graph.nodes.put(to, new Node(to));
}
Node fromNode = graph.nodes.get(from);
Node toNode = graph.nodes.get(to);
Edge newEdge = new Edge(weight, fromNode, toNode); //将边建立起来
fromNode.next.add(toNode); //将该边加入到fromNode中的next里
fromNode.out++;
toNode.in++;
fromNode.edges.add(newEdge); //新建的这条边是属于from点的
graph.edges.add(newEdge); //将该边放入图里的边集
}
return graph;
}
2 图的宽度优先遍历和广度优先遍历
2.1 图的宽度优先遍历
(1)利用队列实现
(2)从源节点开始依次按照宽度进队列,然后弹出
(3)每弹出一个点,把该节点所有没有进过队列的邻接点放入队列
(4)直到队列变空
//从node出发,进行宽度优先遍历
public static void bfs(Node node){
if (node == null){
return;
}
Queue<Node> queue = new LinkedList<>();
HashSet<Node> set = new HashSet<>();
queue.add(node);
set.add(node);
while(!queue.isEmpty()){
Node cur = queue.poll();
System.out.println(cur.value);
for(Node next : cur.nexts){
if(!set.contains(next)){
set.add(next);
queue.add(next);
}
}
}
}
2.2 图的广(深)度优先遍历
(1)利用栈实现
(2)从源节点开始把节点按照深度放入栈,然后弹出
(3)每弹出一个点,把该节点下一个没有进过栈的邻接点放入栈
(4)直到栈变空
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 next : cur.nexts){
if(!set.contains(next)){
stack.push(cur);
stack.push(next);
set.add(next);
System.out.println(next.value);
break;
}
}
}
}
3 拓扑排序算法
适用范围:要求有向图,且有入度为0的节点,且没有环
解题思路:
①首先找到入度为0的点
②将该点打印出来,随后将其与其影响从图中抹去
③回到①
//directed graph and no loop
public static List<Node> sortedTopology(Graph graph){
//key: 某一个node
//value: 剩余的入度
HashMap<Node, Integer> inMap = new HashMap<>();
//入度为0的点,才能进这个队列
Queue<Node> zeroInQueue = new LinkedList<>();
for(Node node : graph.nodes.values()){
inMap.put(node, node.in);
if(node.in == 0){
zeroInQueue.add(node);
}
}
//拓扑排序的结果,依次加入result
List<Node> result = new ArrayList<>();
while(!zeroInQueue.isEmpty()){
Node cur = zeroInQueue.poll();
result.add(cur);
for(Node next : cur.nexts){
inMap.put(next, inMap.get(next) - 1);
if(inMap.get(next) == 0){
zeroInQueue.add(next);
}
}
}
return result;
}
4 kruskal算法
适用范围:要求无向图
问题:求图中能把所有点连起来的最小的边和
解题思路:
①将每个点当成一个独立的集合
②从小到大依次考察每条边,from跟to是否在一个集合里
③如果不在,将两点合在一个集合里,且选择该边;如果在,则该边不要
④回到②
哈希集结构:
public static class MySets{
public HashMap<Node,List<Node>> setMap;
public MySets(List<Node> nodes){
for(Node cur : nodes){
List<Node> set = new ArrayList<Node>();
set.add(cur);
setMap.put(cur,set);
}
}
public boolean isSameSet(Node from, Node to) {
List<Node> fromSet = setMap.get(from);
List<Node> toSet = setMap.get(to);
return fromSet == toSet;
}
public void union(Node from, Node to){
List<Node> fromSet = setMap.get(from);
List<Node> toSet = setMap.get(to);
for(Node toNode : toSet){
fromSet.add(toNode);
setMap.put(toNode,fromSet);
}
}
}
并查集结构:
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;
}
5 prim算法
适用范围:要求无向图
算法思路:
①从任意一点出发,然后将与该点相连的边解锁
②取最小的边,连接到下一个点
③使用过的边不许再使用,从新的点解锁与新点相连的边
④回到②
public static class EdgeComparator implements Comparator<Edge>{
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> set = new HashSet<>();
Set<Edge> result = new HashSet<>(); //依次挑选的边在result里
for(Node node : graph.nodes.values()){ //随便挑了一个点
// node 是开始点
if(!set.contains(node)){
set.add(node);
for(Edge edge: node.edges){ //由一个点,解锁所有相连的边
priorityQueue.add(edge);
}
while(!priorityQueue.isEmpty()){
Edge edge = priorityQueue.poll(); //弹出解锁的边中,最小的边
Node toNode = edge.to;
if(!set.contains(toNode)){ //不含有的时候,就是新的点
set.add(toNode);
result.add(edge);
for(Edge nextEdge : toNode.edges){
priorityQueue.add(nextEdge);
}
}
}
}
}
return result;
}
6 Dijkstra算法
适用范围:没有累加和为负数的环
算法思路:
(1)从其中一点出发(必须规定出发点),分别计算起点距离各个点的距离
(2)一开始赋值起点为0,其余均为正无穷
(3)从该点开始选择各条边,看是否更新其余距离使其更小,有的话即更新
(4)使用完该点后,从起点到该点的距离锁死不用。
(5)从表中剩下的记录中选取最小的值,回到(3)
public static HashMap<Node, Integer> dijkstra1(Node head){
//从head出发到所有点的最小距离
//key : 从head出发到达key
//value : 从head出发到达key的最小距离
//如果在表中,没有T的记录,含义是从head出发到T这个点的距离为正无穷
HashMap<Node, Integer> distanceMap = new HashMap<>();
distanceMap.put(head,0);
//已经求过距离的节点,存在selectedNodes中,以后再也不碰
HashSet<Node> selectedNodes = new HashSet<>();
Node minNode = getMinDistanceAndUnselectedNode(distanceMap, selectedNodes);
while( minNode != null){
int distance = distanceMap.get(minNode);
for(Edge edge : minNode.edges){
Node toNode = edge.to;
if(!distanceMap.containsKey(toNode)){
distanceMap.put(toNode, distance + edge.weight);
}
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> entry : distanceMap,
HashSet<Node> touchedNodes){
Node minNode = null;
int minDistance = Integer.MAX_VALUE;
for(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;
}