图的的存储方式有两种:1、邻接表,2、邻接矩阵
示例:
1、邻接表法:
A的直接邻居:C、D
B的直接邻居:C
C的直接邻居:A、B、D
D的直接邻居:A、C
用链表表示这种形式:
2、邻接矩阵法:一个二维数组,里面的数值是边的权值
接下来说几个概念
1、入度:就是有多少条边指向自己,比如上面那个简单的图:A的入度是2,B的入度1
2、出度:就是从该点出发,指出去几条边。A的出度是2,B的出度是1
无向图的出度和入度相同
代码实现:
//点集
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 graphic {
HashMap<Integer,Node> nodes;
HashSet<Edge> edges;
public graphic(){
nodes=new HashMap<>();
edges=new HashSet<>();
}
}
将题目中的图结构转化成自己熟悉的图结构
例如,题中给一个二维数组arr[[5,0,1],[3,1,2],[7,0,2]],arr[0][0]表示边的权值,arr[0][1]和arr[0][2]表示从0节点出发到1节点,然后边的权值是5。
public static Graphic createGraphic(Integer[][] matrix){
Graphic graphic=new Graphic();
for(int i=0;i<matrix.length;i++){
Integer weight=matrix[i][0];
Integer from=matrix[i][1];
Integer to=matrix[i][2];
if(!graphic.nodes.containsKey(from)){ //如果在图中当前from节点不存在,则新建一个节点存在hashmap中
graphic.nodes.put(from,new Node(from));
}
if(!graphic.nodes.containsKey(to)){ //当前图中to节点不存在,则新创建一个
graphic.nodes.put(to,new Node(to));
}
Node fromNode=graphic.nodes.get(from);
Node toNode=graphic.nodes.get(to);
Edge newEdge=new Edge(weight,fromNode,toNode);
fromNode.nexts.add(toNode);
fromNode.out++;
toNode.in++;
fromNode.edges.add(newEdge);
graphic.edges.add(newEdge);
}
return graphic;
}
图的遍历
1、宽度优先遍历(利用队列实现)
从源节点开始依次按照宽度进队列,然后弹出
每弹出一个节点,把该节点所有没有进过队列的邻接点放入队列
循环直到队列为空
public static void bfs(Node node){
if(node==null){
return ;
}
Queue<Node> queue=new LinkedList<>();
Set<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、深度优先遍历
从源节点开始把节点按照深度入栈,然后弹出
每弹出一个点,检查该点的下一个节点是否在set集合中,若不在,把该点和该点的下一个节点一起再压入栈,若在,不做任何处理。
循环直到栈空
public static void dfs(Node node){
if(node==null){
return ;
}
Stack<Node> stack=new Stack<>();
Set<Node> set=new HashSet<>();
stack.push(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;
}
}
}
}
拓扑排序算法
拓扑结构:就是只有从前指向后的边,没有从后面指向前面的边,也就是说在一个图中没有环,类似于下面这样。
有向无环图一定是拓扑结构
有向有环图一定不是拓扑结构
无向图没有拓扑结构
拓扑排序算法(要求有向图,有入度为0的节点,无环):可以理解成Java的依赖关系,不能有循环依赖
1、找到入度为0的节点
2、把这个点以及它的影响擦掉,会出现另一个入度为0的节点
3、循环直到最后一个节点
public static List<Node> sortedTopology(Graphic graphic){
//key:某一个node
//value:剩余的入度
HashMap<Node,Integer> inMap=new HashMap<>();
//入度为0的点才能进这个队列
Queue<Node> zeroInQueue=new LinkedList<>();
for(Node node:graphic.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;
}
最小生成树算法(要求无向图)
什么是最小生成树?在一个图中,保证连通且权值最小,例如:
1、k算法(kruskal算法)
从最小的边开始找,如果没有形成环就加上,如果形成环就不加
怎么判断有没有形成环呢,初始化的时候将每个节点都放在各自集合中{A},{B},{C},{D},然后开始选最小的边,最小的边为B和D之间的边,判断B所在的集合和D所在的集合不是同一个集合,然后把B和D放在一个集合中,所以当前集合为{B,D},{A},{C},然后是A和B之间的边,所以集合为{A,B,D},{C},然后是B到C之间的边,此时集合为{A,B,C,D}。然后是A到D的边,但是A所在的集合和D所在的集合是同一个集合,所以就形成了环。以此类推,会出现最小生成树。
代码实现:
public class MySets {
public HashMap<Node, List<Node>> setMap;
//给每一个节点创建集合,然后把该点和该点所在的集合以map形式放入setMap中
public MySets(List<Node> list){
for(Node cur:list){
List<Node> set=new ArrayList<Node>();
set.add(cur);
setMap.put(cur,set);
}
}
//判断from节点和to节点是否在一个集合中
public boolean isSame(Node from,Node to){
List<Node> fromList=setMap.get(from);
List<Node> toList=setMap.get(to);
return fromList==toList;
}
//把from节点所在集合和to节点所在集合合并
public void unionList(Node from,Node to){
List<Node> fromList=setMap.get(from);
List<Node> toList=setMap.get(to);
for(Node cur:toList){
fromList.add(cur);
setMap.put(cur,fromList);
}
}
}
private static class EdgeComparator implements Comparator<Edge> {
@Override
public int compare(Edge o1,Edge o2) {
return o1.weight-o2.weight;
}
}
public static Set<Edge> kruskalMST(Graphic graphic){
MySets mySets=new MySets((List<Node>) graphic.nodes.values());
PriorityQueue<Edge> priorityQueue=new PriorityQueue<>(new EdgeComparator());
for(Edge edge:graphic.edges){
priorityQueue.add(edge);
}
Set<Edge> result=new HashSet<>();
while(!priorityQueue.isEmpty()){
Edge edge=priorityQueue.poll();
if(!mySets.isSame(edge.from,edge.to)){
result.add(edge);
mySets.unionList(edge.from,edge.to);
}
}
return result;
}
p算法(prim算法)
public static Set<Edge> primMst(Graphic graphic){
//解锁的边进入小根堆
PriorityQueue<Edge> priorityQueue=new PriorityQueue<>(new EdgeComparator());
HashSet<Node> set=new HashSet<>();//依次选的节点放进set里
Set<Edge> result=new HashSet<>();//依次挑选的边在result里
for(Node node:graphic.nodes.values()){ //随便挑了一个节点
if(!set.contains(node)){ //如果set里没有这个节点,将该节点放进set中并且将该点的边放进小根堆
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;
}
Dijkstra算法(要求没有权值为负数的边)
Dijkstra算法解决特定问题:规定一个出发点,计算这个出发点到所有点的最短距离
以这个图为例:
代码实现:
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;
}
public static HashMap<Node,Integer> Dijkstra(Node head){
//从head出发到所有点的最小距离
//key:从head出发到达key
//value:从head出发到达key的最小距离
//如果在表中,没有T的记录,含义是从head出发到T这个点的距离为正无穷
HashMap<Node,Integer> distanceMap=new HashMap<>();
distanceMap.put(head,0);
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;
}