8.2。サマリーチャートの実装
1.グラフとは?
- ノード
- エッジ
2.エッジとノードを作成する方法は?
- エッジとノードは間違いなくグラフの内部クラスです
- ノード:値とそれで終わるエッジ、およびそれで始まるエッジが含まれます
- エッジ:エッジにはウェイトがある場合があります。つまり、上の図のウェイトには頂点(to)と開始頂点(from)が含まれている必要があります
エッジと頂点の作成
//顶点内部类
private static class Vertex<V, E> {
//节点存储的值
V value;
//从节点出去的边,用set来存储,去重
Set<Edge<V, E>> outEdges = new HashSet<>();
//指向节点的边
Set<Edge<V, E>> inEdges = new HashSet<>();
public Vertex(V value) {
this.value = value;
}
@Override
public boolean equals(Object o) {
return Objects.equals(value, ((Vertex<V, E>)o).value);
}
@Override
public int hashCode() {
return value == null ? 0 : value.hashCode();
}
@Override
public String toString() {
return value == null ? "null" : value.toString();
}
}
//边
private static class Edge<V, E> {
Vertex<V, E> from; //边的起点
Vertex<V, E> to; //边的终点
E weight; //边的权重
public Edge(Vertex<V, E> from, Vertex<V, E> to) { //构造方法
this.from = from;
this.to = to;
}
@Override
public boolean equals(Object obj) { //重写 equals和hashCode方法,因为边不能重复存储在哈希表中
// if (this == o) return true;
// if (this.getClass() != o) return false;
// Edge<?, ?> edge = (Edge<?, ?>) o;
// return from.equals(edge.from) &&
// to.equals(edge.to);
if (this == obj) return true;
if (this.getClass() != obj.getClass()) return false;
Edge<V, E> edge = (Edge<V, E>) obj;
return Objects.equals(from, edge.from) && Objects.equals(to, edge.to);
}
@Override
public int hashCode() {
return from.hashCode() * 31 + to.hashCode();
}
@Override
public String toString() { //toSting方法,from会调用它自己的toString
return "Edge{" +
"from=" + from +
", to=" + to +
", weight=" + weight +
'}';
}
}
- イチジク
- グラフインターフェイスで、マップを使用して頂点を格納し、エッジを格納するように設定します
//Map中的key,对应 它的顶点,用来存储顶点
private Map<V, Vertex<V, E>> vertices = new HashMap<>();
//set用来存储边!
private Set<Edge<V, E>> edges = new HashSet<>();
1.エッジと頂点を作成する
@Override
public void addVertex(V v) {
if (vertices.get(v) != null) {
return;
}
vertices.put(v, new Vertex<>(v));
}
@Override
public void addEdge(V from, V to, E weight) { //创建边要传递起点和终点
Vertex<V, E> vertexFrom = vertices.get(from);
//如果顶点为空,则我们创建顶点
if (vertexFrom == null) {
vertexFrom = new Vertex<>(from);
vertices.put(from, vertexFrom);
}
Vertex<V, E> vertexTo = vertices.get(to);
if (vertexTo == null) {
vertexTo = new Vertex<>(to);
vertices.put(to, vertexTo);
}
Edge<V, E> edge = new Edge<>(vertexFrom, vertexTo);
edge.weight = weight;
//这里再起点和终点添加上这条边,先删除再添加!,因为更新了权值
if (vertexFrom.outEdges.remove(edge)) {
vertexTo.inEdges.remove(edge);
edges.remove(edge);
}
//更新了weight,真正添加
vertexFrom.outEdges.add(edge);
vertexTo.inEdges.add(edge);
edges.add(edge);
}
@Override
public void addEdge(V from, V to) {
addEdge(from, to, null);
}
2.エッジと頂点を削除する
- エッジを削除
@Override
public void removeEdge(V from, V to) {
//怎么删除边?边是怎么确定:from 和 to确定一条边,两点确定一条边
//我们需要再from的outEdges中删了,和to的inEdges上了,还有总集合 edges上也删了!
//Vertex<V, E> vertexFrom = vertices.get(from);
Vertex<V, E> vertexFrom = vertices.get(from);
if (vertexFrom == null) return;
Vertex<V, E> vertexTo = vertices.get(to);
if (vertexTo == null) return;
//先创造出来再删除
Edge<V, E> edge = new Edge<>(vertexFrom, vertexTo);
if (vertexFrom.outEdges.remove(edge)) {
vertexTo.inEdges.remove(edge);
edges.remove(edge);
}
}
要約:
グラフのエッジを削除するには、3つの場所でエッジを削除します。
- OutEdgesの出発点
- 最後にInEdges
- グラフ全体の端
そして、最初に作成し、削除するときに削除する方法を使用しました!
- 頂点を削除
@Override
public void removeVertex(V v) {
//删除顶点,要把它包含的边都删了
//怎么删? 遍历 OutEdges集合:删除它到达顶点的InEdges
Vertex<V, E> vertex = vertices.remove(v);
if (vertex == null)
return;
Set<Edge<V, E>> outEdges = vertex.outEdges;
Set<Edge<V, E>> inEdges = vertex.inEdges;
for (Iterator<Edge<V, E>> iterator = outEdges.iterator(); iterator.hasNext();) {
Edge<V, E> edge = iterator.next();
edge.to.inEdges.remove(edge);
// 将当前遍历到的元素edge从集合vertex.outEdges中删掉
iterator.remove();
edges.remove(edge);
}
for (Iterator<Edge<V, E>> iterator = inEdges.iterator(); iterator.hasNext();) {
Edge<V, E> edge = iterator.next();
edge.from.outEdges.remove(edge);
// 将当前遍历到的元素edge从集合vertex.outEdges中删掉
iterator.remove();
edges.remove(edge);
}
}
要約:頂点を削除するには、頂点が占有しているすべてのエッジを削除します。つまり、inEdgesコレクションとOutEdgesコレクションのエッジを削除します。
削除するには?InEdgeは、outEdgeの各エッジの終点を通過し、このエッジを削除します。inEdgeは次のようになります。
Set<Edge<V, E>> outEdges = vertex.outEdges;
Set<Edge<V, E>> inEdges = vertex.inEdges;
for (Iterator<Edge<V, E>> iterator = outEdges.iterator(); iterator.hasNext();) {
Edge<V, E> edge = iterator.next();
edge.to.inEdges.remove(edge);
// 将当前遍历到的元素edge从集合vertex.outEdges中删掉
iterator.remove();
edges.remove(edge);
}
for (Iterator<Edge<V, E>> iterator = inEdges.iterator(); iterator.hasNext();) {
Edge<V, E> edge = iterator.next();
edge.from.outEdges.remove(edge);
// 将当前遍历到的元素edge从集合vertex.outEdges中删掉
iterator.remove();
edges.remove(edge);
}
3.トラバース
- bfs:幅優先検索
アイデア:
バイナリツリー、アプリケーションキュー、レイヤーごとのトラバーサルのシーケンストラバーサルに非常に似ています。
この図では、次のレイヤーは、このノードが1つのステップで到達できる位置を指していることに注意してください。
ただし、ここでは、頂点を通過するoutEdgeが順番にキューに追加されますが、グラフにループがある場合、Siループが繰り返し表示される可能性があるため、set to repeatを使用します。
- コード
//遍历图,使用DFS算法!
public void bfs(V value) {
Vertex<V, E> vertex = vertices.get(value); //遍历的起点
if (vertex == null) return;
Queue<Vertex<V, E>> queue = new LinkedList<>(); //使用队列,java中队列由双向链表实现
Set<Vertex<V, E>> set = new HashSet<>(); //用set去重,怎么去?入队的同时在set中加上
queue.offer(vertex);
set.add(vertex);
while (!queue.isEmpty()) {
Vertex<V, E> poll = queue.poll();
System.out.println(poll.value);
for (Edge<V, E> outEdge : poll.outEdges) { //outEdges的终点就是下一层节点
if (set.contains(outEdge.to)) continue;
queue.offer(outEdge.to);
set.add(outEdge.to);
}
}
}
- DFS:デプスフリスト検索
- 再帰的な実装
アイデア:再帰したいときは、深く考えないでください。実際には非常に簡単です。
- コード:
//深度优先搜素,相当于前序遍历!
@Override
public void dfs(V begin) {
Vertex<V, E> vertex = vertices.get(begin);
if (vertex == null)
return;
dfs(vertex, new HashSet<>());
}
private void dfs(Vertex<V,E> vertex, Set<Vertex<V, E>> set) {
if (vertex == null)
return;
System.out.println(vertex.value);
set.add(vertex);
for (Edge<V, E> outEdge : vertex.outEdges) {
if (set.contains(outEdge.to)) continue;
dfs(outEdge.to, set);
}
}
set to deduplicationの使用は、上記の重複排除のアイデアに似ています!
まとめ:ループ内の再帰を理解する方法:
上の画像の場合、ノード1からトラバースを開始します。ノードが表示されると、forループに入り、トラバースします。
dfs(3)、dfs(5)、dfs(2)、dfs(0)
dfs(3)の入力は、深さ優先の検索で、7をトラバースし、3をトラバースした後、dfs(5)をトラバースします。
ただそれを理解してください!
3. DFS非再帰的実装
すべての再帰は反復に変換できますが、一部は困難です
なんで?
再帰は関数JVMをスタックにプッシュするプロセスであり、データ構造のスタックにもこの機能があるため、スタックと反復を使用して多くの複雑な再帰を実装できます。
コード:主なアイデアは、スタックを使用して、深い思考のためにスタックの高度な機能を組み合わせることです!
public void dfs(V begin, Visitor<V, E> visitor) {
if (visitor == null) return;
Vertex<V, E> vertex = vertices.get(begin);
if (vertex == null) {
return;
}
Stack<Vertex<V, E>> stack = new Stack<>();
Set<Vertex<V, E>> visited = new HashSet<>();
stack.push(vertex);
if (visitor.visit(vertex.value)) return;
visited.add(vertex);
while (!stack.isEmpty()) {
Vertex<V, E> pop = stack.pop();
for(Edge<V, E> edge : pop.outEdges) {
if (visited.contains(edge.to)) continue;
stack.push(pop);
stack.push(edge.to);
if (visitor.visit(edge.to.value)) return;
visited.add(edge.to);
break;
}
}
}
まとめ:セットは順序付けされていないため、そのエッジを最初にトラバースする理由がわかりません。なぜfromとtoの両方がBreakに置かれるのか、毎回toノードの値にアクセスするだけです