描述相连的结点所表示的模型所起到了关键的作用,这些结点之间的连接也会相继引发能否到达,最短路径。。。问题的抽象数学对象就是图.
图的分类:
无向图,有向图,加权图,加权有向图。
无向图:由一组顶点和一组能够将两个顶点相连的边组成
如下图
图的表示方式
当然也有其他的表示形式,但是本质都是一样的,就不做过多描述。
先说几个和图有关的概念:
- 路径:由边顺序连接的一系列顶点,简单路径是一条没有重复顶点的路径。环是一条至少含有一条边且起点和终点相同的路径。简单环是一条不含有重复顶点和边的环,路径或者环的长度是其中所包含的遍数。
- 连通图:如果从任意一个顶点都存在一条路径到达另一个任意顶点。我们称这幅图是连通图。一副非连通的图由若干个连通的部分组成,他们都是其极大连通子图。
- 无环图:树是一副无环连通图。互不相连的树组成的集合称为森林。连通图的生成树是它的一副子图,它含有图中所有顶点,但它是一个树。
图的表示形式:
- 邻接矩阵:用矩阵来表示图中各个顶点的关系,所需空间为v*v,仅适合顶点较少的图。
- 边的数组
- 邻接表数组:使用一个以顶点为索引的列表数组,其中的每个元素都是和该顶点相邻的顶点列表。这种表示方式能够满足:1.为可能在应用中碰到的各种类型的图预留出足够的空间。2.Graph实例方法的实现一定要快。
邻接表表示的图
我们采用邻接表的数据结构来表示。它将每个顶点所有相邻顶点都保存在该顶点对应的元素所指向的一张链表中,这里用 Bag 抽象数据类型来实现这个链表,可以在常数时间内添加新的边或遍历任意顶点的所有相邻顶点。
下面先看看 Bag 数据结构:
public class Bag<T> implements Iterable<T>{
private Node first;//链表首节点
private class Node {
T data;
Node next;
}
public void add(T t){//添加节点O(1)
Node oldfirst = first;
first = new Node();
first.data = t;
first.next = oldfirst;
}
@Override
public Iterator<T> iterator() {//迭代
return new ListIterator();
}
private class ListIterator implements Iterator<T> {
private Node current = first;
@Override
public boolean hasNext() {
return current != null;
}
@Override
public T next() {
T data = current.data;
current = current.next;
return data;
}
}
}
图的数据结构表示
/**
* 用邻接表实现的图数据结构
*/
public class Graph {
private final int V; //顶点数目
private int E; //边的数目
private Bag<Integer>[] adj; //邻接表
public Graph(int V) {
this.V = V;
this.E = 0;
adj = new Bag[V]; // 创建邻接表
for(int v = 0;v<V;v++){//将所有链表初始化为空
adj[v] = new Bag<Integer>();
}
}
public int V() {
return V;
}
public int E() {
return E;
}
public void addEdge(int v,int w){
adj[v].add(w); //将w添加到v的链表中
adj[w].add(v); //将v添加到w的链表中
E++;
}
public Iterable<Integer> adj(int v){
return adj[v];
}
}
深度优先搜索:
public class DepthFirstSearch {
private boolean[] marked;
private int count;
public DepthFirstSearch(Graph G, int s){
marked = new boolean[G.V()];
dfs(G, s);
}
private void dfs(Graph G, int v){
marked[v] = true;
count++;
for (int w : G.adj(v))
if(!marked[w]) dfs(G, w);
}
public boolean marked(int w){
return marked[w];
}
public int count(){
return count;
}
}
应用:1.使用深度优先搜索查找图中路径
/**
* 深度搜索图中路径
*/
public class DepthFirstPaths {
private boolean[] marked;//这个顶点上调过dfs()了吗?
private int[] edgeTo; //从起点到一个顶点的已知路径上的最后一个顶点
private final int s; //起点
public DepthFirstPaths(Graph G, int s){
marked = new boolean[G.V()];
edgeTo = new int[G.V()];
this.s = s;
dfs(G,s);
}
private void dfs(Graph g, int s) {
marked[s] = true;
for (int w: g.adj(s))
if(!marked[w]){
edgeTo[w] = s;
dfs(g,w);
}
}
public boolean hasPathTo(int v){
return marked[v];
}
public Iterable<Integer> pathTo(int v){
if (!hasPathTo(v)) return null;
Stack<Integer> path = new Stack<Integer>();
for (int x = v;x != s; x = edgeTo[x]){
path.push(x);
}
path.push(s);
return path;
}
}
2.找出一幅图的所有连通分量
CC的实现使用了marked[]数组来寻找一个顶点作为每个连通分量中深度优先搜索的起点。递归的深度优先搜索第一次调用的参数是顶点0–它会标记所有与0连通的顶点。然后构造函数中的 for 循环会查找每个没有标记的顶点并递归调用dfs()来标记和它相邻的所有顶点。另外还使用一个以顶点作为索引的数组id[],将同一连通分量中的顶点和连通分量的标识符关联起来。
public class CC {
private boolean[] marked;
private int[] id;
private int count;
public CC(Graph G) {
marked = new boolean[G.V()];
id = new int[G.V()];
for(int s = 0;s < G.V(); s++){
if(!marked[s]){
dfs(G,s);
count++;
}
}
}
private void dfs(Graph G, int v){
marked[v] = true;
id[v] = count;
for(int w : G.adj(v))
if(!marked[w]){
dfs(G, w);
}
}
public boolean connected(int v, int w){
return id[v] == id[w];
}
public int id(int v){
return id[v];
}
public int count(){
return count;
}
}
广度优先搜索:用广度优先搜索查找图中的路径
/**
* 广度优先搜索
*/
public class BreadFirstPaths {
private boolean[] marked;//到达该顶点的最短路径已知吗?
private int[] edgeTo;//到达该顶点的已知路径上的最后一个顶点
private final int s;//起点
public BreadFirstPaths(Graph G,int s) {
marked = new boolean[G.V()];
edgeTo = new int[G.V()];
this.s = s;
bfs(G,s);
}
private void bfs(Graph G, int s) throws InterruptedException {
Queue<Integer> queue= new Queue<Integer>();
marked[s] = true; //标记起点
queue.enqueue(s); //将它加入队列
while(!queue.isEmpty()){
int v = queue.dequeue(); //从队列中删去下一个节点
for(int w : G.adj(v)){
if(!marked[w]) //对于每个未被标记的相邻顶点
{
edgeTo[w] = v;//保存最短路径的最后一条边
marked[w] = true;//标记它,因为最短路径已知
queue.enqueue(w);//并将它添加到队列中
}
}
}
}
public boolean hasPathTo(int v){
return marked[v];
}
public Iterable<Integer> pathTo(int v){
if (!hasPathTo(v)) return null;
Stack<Integer> path = new Stack<>();
for (int x = v; x != s; x = edgeTo[x]){
path.push(x);
}
path.push(s);
return path;
}
}