图的存储结构实现+深度优先+广度优先+拓扑排序算法+最小生成树(Kruskal+prim)算法+Dijkstra算法
【图的存储结构】
分别由:图结构+边结构+结点结构组成
【Code】
public class GraphAchieve {
public static 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 static 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 static class Gragh
{
public HashMap<Integer, node>nodes;
public HashSet<Edge>edges;
public Gragh()
{
nodes = new HashMap<>();
edges = new HashSet<>();
}
}
}
【图的广度优先遍历】
【思路】
使用队列结构,先将头结点放入队列,当列队不为空时每次从队列取队头元素,将其打印并将其所有邻接点都加入队列
【Code】
public static void bfs(node node)
{
if(node == null)
return;
Queue<node> queue = new LinkedList<>();//队列
HashSet<node>flag = new HashSet<>();//标记某个节点是否已遍历过
queue.add(node);
flag.add(node);
while(!queue.isEmpty())
{
node cur = queue.poll();
System.out.println(cur.value);
for(node next : cur.nexts)
{
//将所有未遍历过的直接可达点加入队列
if(!flag.contains(next))
{
flag.add(next);
queue.add(next);
}
}
}
}
【图的深度优先遍历】
【思路】
使用栈结构,先将头结点打印并压入栈中,当栈不为空时每次从栈中取栈顶元素,找其未被访问过的邻接点,如果有,则先将刚取出的栈顶元素压回栈,再压入刚找到的满足条件的邻接点,跳出寻找循环,重新取栈顶元素重复以上步骤
【Code】
public static void Dfs(node node)
{
if(node == null)
return;
Stack<node>stack = new Stack<GraphAchieve.node>();
HashSet<node>flag = new HashSet<>();
stack.add(node);
flag.add(node);
System.out.println(node.value);
while(!stack.isEmpty())
{
node cur = stack.pop();
for(node next : cur.nexts)
{
if(!flag.contains(next))
{
stack.push(cur);
stack.push(next);
flag.add(next);
System.out.println(next.value);
break;//每次压入一个邻接点就跳出for循环
}
}
}
}
【拓扑排序算法】
前提:有向无环图
【思路】
先选择入度为0的节点取出,从图中删掉这些节点。再次选择删掉后变成入度为0的节点,依次类推直到输出所有节点。
具体地,使用队列存储入度为0的节点,使用哈希表存储每个节点的入度。遍历图的所有节点,将入度为0的节点加入队列,如果该队不空,每次出队头元素,将队头节点射出可达边的相邻节点的入度-1,此时如果此节点入度变为0,则将其加入队列,重复以上操作。
【Code】
public static List<node> sortedTopology(Graph graph) {
HashMap<node, Integer> inMap = new HashMap<>();
Queue<node> zeroInQueue = new LinkedList<>();
for (node node : graph.nodes.values()) {
inMap.put(node, node.in);
if (node.in == 0) {
zeroInQueue.add(node);
}
}
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;
}
【最小生成树】
传入的图一定是无向图,且保证图中全部节点都连通的情况下代价最小(每条边都有权重)
【kruskal算法】
从最小代价边开始选择,每次选择最小的,选择之前判断选择后是否形成回路,如无形成回路则可选,如形成回路则该边放弃。直到所有的点都包含进来,算法停止。
具体地,使用并查集实现判断选择该边后是否会形成回路,使用小根堆实现每次选择都选择最小边,不必每次都遍历全部边找最小。
【Code】
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) {
priorityQueue.add(edge);
}
Set<Edge> result = new HashSet<>();
while (!priorityQueue.isEmpty()) {
//每次从小根堆弹出一条边
//如果该边连接的两个点已经属于同个集合(存在回路),丢弃该边,如果不属于,取该边并将两节点合并
Edge edge = priorityQueue.poll();
if (!unionFind.isSameSet(edge.from, edge.to)) {
result.add(edge);
unionFind.union(edge.from, edge.to);
}
}
return result;
}
【prim算法】
通过逐渐解锁点来解锁可选择的边,从初始点出发,每次从所有与已达到点相连的边即可选边里选代价最小
具体地,使用小根堆存储可选择边
【Code】
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> set = new HashSet<>();
Set<Edge> result = new HashSet<>();
//for循环是因为默认传进来的图有很多个图,是一个森林,求所有的连通树
for (Node node : graph.nodes.values()) {
//一个连通图只需要一次for循环就够了
if (!set.contains(node)) {
set.add(node);
//将node所有边加到小根堆z中
for (Edge edge : node.edges) {
priorityQueue.add(edge);
}
while (!priorityQueue.isEmpty()) {
//从优先级队列中弹出最小的边
Edge edge = priorityQueue.poll();
Node toNode = edge.to;
//查看该边的to节点是否已经在set中,如不在,将其加进来
if (!set.contains(toNode)) {
set.add(toNode);
result.add(edge);
for (Edge nextEdge : node.edges) {
priorityQueue.add(nextEdge);
}
}
}
}
}
return result;
}
最短路径:Dijkstra算法
通过不断运行广度优先算法找可见点,计算可见点到源点的距离长度,并从当前已知的路径中选择长度最短的将其顶点加入路径作为确定找到的最短路径的顶点。