一、图的表示方式
1、邻接矩阵
如果是带权图,将1换成权值。
2、邻接表
表示与1 关联的有 1,2,3,4
二、图的实现。
图:
/**
* 图。
* notes:所有的节点。
* edges:所有的边。
*/
static class Graph {
HashMap<Integer, Note> notes;
HashSet<Edge> edges;
public Graph() {
notes = new HashMap<>();
edges = new HashSet<>();
}
}
节点:
/**
* 图的节点。
* value:序号
* in:入度
* out:出度
* nexts:后续节点们
* edges:当前节点发出的边
*/
static class Note {
int value;
int in;
int out;
ArrayList<Note> nexts;
ArrayList<Edge> edges;
public Note() {
}
public Note(int value) {
this.value = value;
this.in = 0;
this.out = 0;
this.nexts = new ArrayList<>();
this.edges = new ArrayList<>();
}
}
边:
/**
* 边。
* weight:边的权重
* from:起始节点
* to:终点
*/
static class Edge {
int weight;
Note from;
Note to;
public Edge() {
}
public Edge(int weight, Note from, Note to) {
this.weight = weight;
this.from = from;
this.to = to;
}
}
图的初始化:
/**
* 图的初始化。
*
* @param arr {{weight, 起点序号, 终点序号}, {},{}}
*/
public static void generatorGraph(int[][] arr) {
Graph graph = new Graph();
for (int i = 0; i < arr.length; i++) {
// 创建起点,终点,边。
int weight = arr[i][0];
int from = arr[i][1];
int to = arr[i][2];
if (!graph.notes.containsKey(from)) {
graph.notes.put(from, new Note(from));
}
if (!graph.notes.containsKey(to)) {
graph.notes.put(to, new Note(to));
}
Note noteF = graph.notes.get(from);
Note noteT = graph.notes.get(to);
Edge edge = new Edge(weight, noteF, noteT);
//初始化节点
noteF.out++;
noteT.in++;
noteF.nexts.add(noteT);
noteF.edges.add(edge);
//边加入图中
graph.edges.add(edge);
}
}
三、图的常用算法。
1、遍历搜索算法
(1)、广度优先搜索(bfs)
将节点的每一个下一个节点加入队列中,用set避免重复。
//用set去重,加入过队列中的节点不再加入队列
public static void bfs(Note root) {
Note note = root;
Queue<Note> queue = new LinkedList<>();
HashSet<Note> set = new HashSet<>();
queue.add(note);
set.add(note);
while (!queue.isEmpty()) {
note = queue.poll();
for (Note next : note.nexts) {
if (!set.contains(next)) {
set.add(next);
queue.add(next);
}
}
System.out.println(note.value);
}
}
(2)、深度优先搜索(dfs)
栈中弹出节点,当前节点有下节点不在set中,就输出下节点,并将当前节点和下节点加入栈中。
public static void dfs(Note note) {
Stack<Note> stack = new Stack<>();
HashSet<Note> set = new HashSet<>();
stack.add(note);
set.add(note);
System.out.println(note);
while (!stack.isEmpty()) {
Note cur = stack.pop();
for (Note next : cur.nexts) {
if (!set.contains(next)) {
stack.add(cur);
stack.add(next);
set.add(next);
System.out.println(next);
break;
}
}
}
}
2、最小生成树算法
(1)、kruskal算法(无向图)
每次选择权重最小的能够连接新节点的边,直到所有节点连接在一起。
代码:用优先级队列将边以权重排列,每次弹出权重最小的边 并将边的端点用并查集连接起来。
public static Set<Edge> kruskal(Graph graph) {
UnionSet unionSet = new UnionSet(graph.notes.values());
PriorityQueue<Edge> priorityQueue = new PriorityQueue<>((Edge e1, Edge e2) -> e1.weight - e2.weight);
for (Edge edge : graph.edges) {
priorityQueue.add(edge);
}
Set<Edge> resultSet = new HashSet<>();
while (!priorityQueue.isEmpty()) {
Edge edge = priorityQueue.poll();
Note fromNote = edge.from;
Note toNote = edge.to;
if (!unionSet.isSameSet(fromNote, toNote)) {
resultSet.add(edge);
unionSet.union(fromNote, toNote);
}
}
return resultSet;
}
并查集:
static class UnionSet {
HashMap<Note, Note> fatherMap = new HashMap<>();
HashMap<Note, Integer> sizeMap = new HashMap<>();
public UnionSet(Collection<Note> notes) {
for (Note note : notes) {
fatherMap.put(note, note);
sizeMap.put(note, 1);
}
}
public Note getHead(Note note) {
Note father = fatherMap.get(note);
while (father != note) {
father = getHead(father);
}
fatherMap.put(note, father);
return father;
}
public boolean isSameSet(Note note1, Note note2) {
Note head1 = getHead(note1);
Note head2 = getHead(note2);
return head1 == head2;
}
public void union(Note note1, Note note2) {
Note head1 = getHead(note1);
Note head2 = getHead(note2);
int size1 = sizeMap.get(head1);
int size2 = sizeMap.get(head2);
if (size1 < size2) {
fatherMap.put(head1, head2);
sizeMap.put(head2, size1 + size2);
} else {
fatherMap.put(head2, head1);
sizeMap.put(head1, size1 + size2);
}
}
}
(2)、prim算法(无向图)
每次选择当前节点所拥有的边中,最小的边,直到所有节点连接在一起。
用set存放已经连在一起的节点,优先队列存放现有节点所有边。每次弹出一个 最小边,若toNote 不在set中,就加入set
public static Set<Edge> prim(Graph graph) {
HashSet<Note> set = new HashSet<>();
PriorityQueue<Edge> priorityQueue = new PriorityQueue<>((Edge edge1, Edge edge2) -> edge1.weight - edge2.weight);
HashSet<Edge> resultSet = new HashSet<>();
for (Note note : graph.notes.values()) {
if (!set.contains(note)) {
set.add(note);
for (Edge edge : note.edges) {
priorityQueue.add(edge);
}
}
while (!priorityQueue.isEmpty()) {
Edge edge = priorityQueue.poll();
Note toNote = edge.to;
if (!set.contains(toNote)) {
resultSet.add(edge);
set.add(toNote);
for (Edge edge1 : toNote.edges) {
priorityQueue.add(edge1);
}
break;
}
}
}
return resultSet;
}
3、拓扑排序(有向无环图)
思路:用map记录每个节点的入度,将入度为0的加入队列中。队列弹出节点,将节点加入结果集,此节点下的每一个节点的入度都减一,并将入度变为0的下节点加入队列中。
public static List<Note> tuopuSort(Graph graph) {
Queue<Note> queue = new LinkedList<>();
HashMap<Note, Integer> inMap = new HashMap<>();
for (Note note : graph.notes.values()) {
inMap.put(note, note.in);
if (note.in == 0) {
queue.add(note);
}
}
ArrayList<Note> result = new ArrayList<>();
while (!queue.isEmpty()) {
Note cur = queue.poll();
result.add(cur);
for (Note next : cur.nexts) {
inMap.put(next, inMap.get(next) - 1);
if (inMap.get(next) == 0) {
queue.add(next);
}
}
}
return result;
}