邻接表实现相对简单
// 邻接表实现(adjacency list)
// B----A
// | |
// D----C
public class AdjList {
private VexNode[] vexs;// 顶点数组
// 顶点表
private class VexNode{
int data;// 顶点值
Edge firstEdge;// 指向第一条边
public VexNode(int data){
this.data = data;
this.firstEdge = null;
}
}
// 边表
private class Edge{
int ivex;// 顶点下标
Edge next;// 指向顶点附属的下一条边
public Edge(int ivex){
this.ivex = ivex;
this.next = null;
}
}
// 查找顶点在顶点表中的位置
private int findVertexIndex(int vex){
for (int i=0;i<this.vexs.length;i++)
if (this.vexs[i].data==vex) return i;
return -1;
}
// 输入的方式创建邻接表
public void createAdjList(){
Scanner input = new Scanner(System.in);
System.out.print("please input vertex num:");
int vlen = input.nextInt();
this.vexs = new VexNode[vlen];
System.out.print("please input edge num:");
int elen = input.nextInt();
// 初始化顶点
for (int i=0;i<vlen;i++){
System.out.printf("input vertex%d value:",i+1);
this.vexs[i] = new VexNode(input.nextInt());
}
// 创建边
for (int i=0;i<elen;i++){
System.out.print("input edge vertex(vi,vj):");
int vi = input.nextInt();
int vj = input.nextInt();
int viIndex = this.findVertexIndex(vi);// 查找顶点vi在顶点表中位置
int vjIndex = this.findVertexIndex(vj);// 查找顶点vj在顶点表中位置
if (viIndex<0) System.out.printf("not find vertex:%d in vexs\n",vi);
if (vjIndex<0) System.out.printf("not find vertex:%d in vexs\n",vj);
// 头插法创建vi顶点的附属边
Edge viedge = new Edge(vjIndex);
viedge.next = this.vexs[viIndex].firstEdge;
this.vexs[viIndex].firstEdge = viedge;
// 头插法创建vj顶点的附属边
Edge vjedge = new Edge(viIndex);
vjedge.next = this.vexs[vjIndex].firstEdge;
this.vexs[vjIndex].firstEdge = vjedge;
}
}
}
有向图
有向图本身带方向属于,对于邻接表实现来说,只可以用于有向图的入边或者其出边表示,如果需要完整表示一个有向图则需要实例化两个邻接表,有没有一种方法同时既保存出度也表示入度?十字链表就是如此,同时存储顶点的入(in)和出(out)
// 有向图十字链表实现(Orthogonal正交 List)
// 由于用邻接表表示有向图只能表示有向图的出边
// 或者入边,如果需要完整的表示有向图需要两个
// 邻接表实例。十字链表既表示入边又表示出边,
// 可以解决这个问题
public class OrthogonalList {
public VexNode[] vexs;
// 顶点表
private class VexNode{
int data;// 数据域
ArcNode firstin;// 第一条入边
ArcNode firstout;// 第一条出边
public VexNode(int data){
this.data = data;
}
}
// 弧表(有向图中边称为弧)
private class ArcNode{
int headvex;// 弧头在顶点表中的位置
int tailvex;// 弧尾在顶点表中的位置
ArcNode headlink;// 指向弧头相同的下一条边
ArcNode taillink;// 指向弧尾相同的下一条弧
}
// 获取顶点在顶点表中位置
private int findVexPos(int vex){
for (int i=0;i<this.vexs.length;i++)
if (this.vexs[i].data==vex) return i;
return -1;
}
// 输入的方式创建十字链表
public void createOList(){
Scanner input = new Scanner(System.in);
// 初始化顶点表
System.out.print("please input vertex num:");
int vexNum = input.nextInt();
this.vexs = new VexNode[vexNum];
for (int i=0;i<vexNum;i++){
System.out.printf("input vertex%d value:",i+1);
this.vexs[i] = new VexNode(input.nextInt());
}
// 弧的个数
System.out.print("please input arc num:");
int arcNum = input.nextInt();
for (int i=0;i<arcNum;i++){
System.out.print("input arc head and tail <vh,vt>:");
int vh = input.nextInt();
int vt = input.nextInt();
// 查找两个顶点在顶点表中位置
int hi = this.findVexPos(vh);
int ti = this.findVexPos(vt);
if (hi<0) System.out.println("vh not found in vertex table");
if (ti<0) System.out.println("ti not found int vertex table");
// 创建弧
ArcNode arc = new ArcNode();
arc.headvex = hi;
arc.tailvex = ti;
// 头插法firstin
arc.headlink = this.vexs[hi].firstin;
this.vexs[hi].firstin = arc;
// 头插法firstout
arc.taillink = this.vexs[ti].firstout;
this.vexs[ti].firstout = arc;
}
}
}
无向图
对于无向图来说,如果只关心顶点的操作,那么邻接表的实现就已经比较好了。但是如果需要涉及边的操作的时候对于边的操作都会涉及两个顶点,显然是不太方便的,邻接多重表可以很好的解决这个问题同样可以用十字链表的思想来实现,邻接多重表既保存顶点i的下一条附属边,也保存顶点j的下一条附属边
import java.util.Scanner;
// 邻接多重表实现(无向图的十字链表实现方式)
// 如果更加关注顶点操作,那么普通邻接表就是比较好的
// 实现方式,但是如果涉及到边的操作或者删除时,使用
// 邻接表实现需要同时操作两个边结点,相对麻烦,邻接
// 多重表可以很好的解决这个问题
public class AdjMultiList {
// 顶点表
private Vertex[] vexs;
// 顶点
private class Vertex{
int data;// 数据域
Edge firstEdge;// 第一条边
public Vertex(int data){
this.data = data;
}
}
// 边
private class Edge{
int i;// 顶点i位置
int j;// 顶点j位置
Edge ilink;// 顶点i相同的附属的下一条边
Edge jlink;// 顶点j相同的附属的下一条边
}
// 获取顶点data在顶点表中的位置
private int findVexPos(int data){
for (int i=0;i<this.vexs.length;i++)
{
if (this.vexs[i].data==data) return i;
}
return -1;
}
// 根据输入创建邻接多重表
public void createAdjMultiList(){
Scanner input = new Scanner(System.in);
System.out.print("please input vertex num:");
int vLen = input.nextInt();
this.vexs = new Vertex[vLen];
// 创建顶点表
for (int i=0;i<vLen;i++){
System.out.printf("input vertex%d value:",i+1);
Vertex vex = new Vertex(input.nextInt());
this.vexs[i] = vex;
}
// 创建边表
System.out.print("please input edge num:");
int eLen = input.nextInt();
for (int i=0;i<eLen;i++){
System.out.print("input edge vertex (vi,vj):");
int vi = input.nextInt();
int vj = input.nextInt();
int viIndex = this.findVexPos(vi);
int vjIndex = this.findVexPos(vj);
if (viIndex<0) System.out.printf("not found vertex %d in vertex table\n",vi);
if (vjIndex<0) System.out.printf("not found vertex %d in vertex table\n",vj);
Edge edge = new Edge();
edge.i = viIndex;
edge.j = vjIndex;
// 创建顶点i相同的附属边
edge.ilink = this.vexs[viIndex].firstEdge;
this.vexs[viIndex].firstEdge = edge;
// 创建顶点j相同的附属边
edge.jlink = this.vexs[vjIndex].firstEdge;
this.vexs[vjIndex].firstEdge = edge;
}
}
public static void main(String[] args){
AdjMultiList adjMulList = new AdjMultiList();
adjMulList.createAdjMultiList();
int index = adjMulList.findVexPos(2);
// 获取2的所有边
Edge edge = adjMulList.vexs[index].firstEdge;
while (edge!=null){
if (adjMulList.vexs[edge.i].data==2){
System.out.println(adjMulList.vexs[edge.j].data);
edge = edge.ilink;
}else{
System.out.println(adjMulList.vexs[edge.i].data);
edge = edge.jlink;
}
}
}
}
遍历
图的遍历是指不重复的遍历所有顶点,主要有两种遍历方式,因为遍历只关心顶点,所以对于有向图或者无向图来说都是一致的,使用邻接表方式举例
深度优先遍历(depth first search)
深度优先遍历先遍历某一方向(firstEdge)到达最深处后然后回退遍历其他方向边,类似二叉树中的前序遍历
假设从A开始遍历,只往顶点的右边开始遍历,则顺序未ABC…到达F然后到达A,A已经遍历过了,则回退到F,遍历F的下一条边,以此类推
public class AdjList {
private VexNode[] vexs;// 顶点数组
private int vLen;// 顶点长度
private boolean[] visited;// 遍历时保存是否遍历
// 顶点表
private class VexNode{
int data;// 顶点值
Edge firstEdge;// 指向第一条边
public VexNode(int data){
this.data = data;
this.firstEdge = null;
}
}
// 边表
private class Edge{
int ivex;// 顶点下标
Edge next;// 指向顶点附属的下一条边
public Edge(int ivex){
this.ivex = ivex;
this.next = null;
}
}
// 深度优先遍历递归操作
private void dfs(int i){
System.out.println(this.vexs[i].data);
this.visited[i] = true;
Edge edge = this.vexs[i].firstEdge;
while (edge!=null){
// 如果没有遍历过则继续dfs
if (!this.visited[edge.ivex]){
dfs(edge.ivex);
}
// 查找下一条
edge = edge.next;
}
}
// 深度优先遍历
public void dfs(){
this.visited = new boolean[this.vLen];
for (int i=0;i<this.vLen;i++)
this.visited[i] = false;
for (int i=0;i<this.vLen;i++)// 假设是连通图,则只需要一次dfs(i)即可
if (!this.visited[i])
dfs(i);
}
}
广度优先遍历(breadth first search)
广度优先遍历则类似于二叉树的层序遍历
// 广度优先遍历(breadth first search)
public void bfs(){
this.visited = new boolean[this.vLen];
for (int i=0;i<this.vLen;i++)
this.visited[i] = false;
LinkedList<Integer> queue = new LinkedList<>();
for (int i=0;i<this.vLen;i++){
if (!this.visited[i]){
System.out.println(this.vexs[i].data);
this.visited[i] = true;
queue.add(i);
while (!queue.isEmpty()){
int index = queue.removeFirst();
Edge edge = this.vexs[index].firstEdge;
while (edge!=null){
if (!this.visited[edge.ivex]){
System.out.println(this.vexs[edge.ivex].data);
this.visited[edge.ivex] = true;
queue.add(edge.ivex);
}
edge = edge.next;
}
}
}
}
}