图概述
图是一种重要的数据结构,基本概念包括:顶点,边,有向,无向,权,路径回路,连通域,邻接点,度,入边,出边,入度,出度等等,很好理解,不赘述,可以参考这篇博客:http://www.cnblogs.com/skywang12345/p/3691463.html
图的存储结构
图可以使用两种存储结构,分别是邻接矩阵和邻接表。
邻接矩阵以矩阵的形式存储图所有顶点间的关系。邻接矩阵具有以下特点:
1,邻接矩阵是正矩阵,即横纵维数相等。
2,矩阵的每一行或一列代表一个顶点,行与列的交点对应这两个顶点的边。
3,矩阵的点代表边的属性,1代表有边,0代表无边,所以矩阵的对角线都是0,因为对角线上对应的横纵轴代表相同的顶点,边没有意义。
4,如果是无向图,那么矩阵是对称矩阵;如果是有向图则不一定。
5,如果是有权图,矩阵点数值可以是权值。
6,邻接矩阵表示图的关系非常清晰,但消耗空间较大。
邻接表是以一组链表来表示顶点间关系,有以下特点:
1,邻接表示一个有但链表组成的数组
2,图中的每一个顶点都有一个链,数组的大小等于图中顶点的个数。
3,无向图的链的第一个元素是本顶点,后继分别连接着和这个顶点相连的顶点;有向图的链第一个顶点是本顶点,后继是以本顶点为起点的边的终点。
4,如果是有权图,可以在节点元素中设置权值属性
5,邻接链表关系表示不如邻接矩阵清晰,数据结构相对复杂,但节省空间。
Java实现
邻接矩阵无向图
public class MatrixNDG {
int size;//图顶点个数
char[] vertexs;//图顶点名称
int[][] matrix;//图关系矩阵
public MatrixNDG(char[] vertexs,char[][] edges){
size=vertexs.length;
matrix=new int[size][size];//设定图关系矩阵大小
this.vertexs=vertexs;
for(char[] c:edges){//设置矩阵值
int p1 = getPosition(c[0]);//根据顶点名称确定对应矩阵下标
int p2 = getPosition(c[1]);
matrix[p1][p2] = 1;//无向图,在两个对称位置存储
matrix[p2][p1] = 1;
}
}
//图的遍历输出
public void print(){
for(int[] i:matrix){
for(int j:i){
System.out.print(j+" ");
}
System.out.println();
}
}
//根据顶点名称获取对应的矩阵下标
private int getPosition(char ch) {
for(int i=0; i<vertexs.length; i++)
if(vertexs[i]==ch)
return i;
return -1;
}
public static void main(String[] args) {
char[] vexs = {'A', 'B', 'C', 'D', 'E', 'F', 'G','H','I','J','K'};
char[][] edges = new char[][]{
{'A', 'C'},
{'A', 'D'},
{'A', 'F'},
{'B', 'C'},
{'C', 'D'},
{'E', 'G'},
{'D', 'G'},
{'I','J'},
{'J','G'},};
MatrixNDG pG;
// 自定义"图"(输入矩阵队列)
// 采用已有的"图"
long start=System.nanoTime();
for(int i=0;i<10000;i++){
pG = new MatrixNDG(vexs, edges);
//pG.print(); // 打印图
}
long end=System.nanoTime();
System.out.println(end-start);
}
}
邻接矩阵有向图
邻接矩阵有向图和邻接矩阵无向图结构一样,只是在存储矩阵值时只存储对应方向的点。
public class MatrixDG {
int size;
char[] vertexs;
int[][] matrix;
public MatrixDG(char[] vertexs,char[][] edges){
size=vertexs.length;
matrix=new int[size][size];
this.vertexs=vertexs;
//和邻接矩阵无向图差别仅仅在这里
for(char[] c:edges){
int p1 = getPosition(c[0]);
int p2 = getPosition(c[1]);
matrix[p1][p2] = 1;
}
}
public void print(){
for(int[] i:matrix){
for(int j:i){
System.out.print(j+" ");
}
System.out.println();
}
}
private int getPosition(char ch) {
for(int i=0; i<vertexs.length; i++)
if(vertexs[i]==ch)
return i;
return -1;
}
public static void main(String[] args) {
char[] vexs = {'A', 'B', 'C', 'D', 'E', 'F', 'G','H','I','J','K'};
char[][] edges = new char[][]{
{'A', 'C'},
{'A', 'D'},
{'A', 'F'},
{'B', 'C'},
{'C', 'D'},
{'E', 'G'},
{'D', 'G'},
{'I','J'},
{'J','G'},};
MatrixDG pG;
// 自定义"图"(输入矩阵队列)
//pG = new MatrixUDG();
// 采用已有的"图"
pG = new MatrixDG(vexs, edges);
pG.print();
}
}
输出结果是非对称矩阵。
0 0 1 1 0 1 0 0 0 0 0
0 0 1 0 0 0 0 0 0 0 0
0 0 0 1 0 0 0 0 0 0 0
0 0 0 0 0 0 1 0 0 0 0
0 0 0 0 0 0 1 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 1 0
0 0 0 0 0 0 1 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
邻接表无向图
public class ListNDG {
Vertex[] vertexLists;//邻接表数组
int size;
class Vertex{//邻接表节点类,单链表数据结构
char ch;
Vertex next;
Vertex(char ch){//初始化方法
this.ch=ch;
}
void add(char ch){//加到链表尾
Vertex node=this;
while(node.next!=null){
node=node.next;
}
node.next=new Vertex(ch);
}
}
public ListNDG(char[] vertexs,char[][] edges){
size=vertexs.length;
this.vertexLists=new Vertex[size];//确定邻接表大小
//设置邻接表头节点
for(int i=0;i<size;i++){
this.vertexLists[i]=new Vertex(vertexs[i]);
}
//存储边信息
for(char[] c:edges){
int p1=getPosition(c[0]);
vertexLists[p1].add(c[1]);
int p2=getPosition(c[1]);
vertexLists[p2].add(c[0]);
}
}
//跟据顶点名称获取链表下标
private int getPosition(char ch) {
for(int i=0; i<size; i++)
if(vertexLists[i].ch==ch)
return i;
return -1;
}
//遍历输出邻接表
public void print(){
for(int i=0;i<size;i++){
Vertex temp=vertexLists[i];
while(temp!=null){
System.out.print(temp.ch+" ");
temp=temp.next;
}
System.out.println();
}
}
public static void main(String[] args){
char[] vexs = {'A', 'B', 'C', 'D', 'E', 'F', 'G','H','I','J','K'};
char[][] edges = new char[][]{
{'A', 'C'},
{'A', 'D'},
{'A', 'F'},
{'B', 'C'},
{'C', 'D'},
{'E', 'G'},
{'D', 'G'},
{'I','J'},
{'J','G'},};
ListNDG pG;
long start=System.nanoTime();
for(int i=0;i<10000;i++){
pG = new ListNDG(vexs, edges);
//pG.print(); // 打印图
}
long end=System.nanoTime();
System.out.println(end-start);
}
}
邻接表有向图
和邻接矩阵一样,邻接表有向图和邻接表无向图数据结构一样,只是在存储时一个需要存两次,一个需要存一次。
public class ListDG {
Vertex[] vertexLists;
int size;
class Vertex{
char ch;
Vertex next;
Vertex(char ch){
this.ch=ch;
}
void add(char ch){
Vertex node=this;
while(node.next!=null){
node=node.next;
}
node.next=new Vertex(ch);
}
}
public ListDG(char[] vertexs,char[][] edges){
size=vertexs.length;
this.vertexLists=new Vertex[size];
for(int i=0;i<size;i++){
this.vertexLists[i]=new Vertex(vertexs[i]);
}
for(char[] c:edges){
int p=getPosition(c[0]);
vertexLists[p].add(c[1]);
}
}
private int getPosition(char ch) {
for(int i=0; i<size; i++)
if(vertexLists[i].ch==ch)
return i;
return -1;
}
public void print(){
for(int i=0;i<size;i++){
Vertex temp=vertexLists[i];
while(temp!=null){
System.out.print(temp.ch+" ");
temp=temp.next;
}
System.out.println();
}
}
public static void main(String[] args){
char[] vexs = {'A', 'B', 'C', 'D', 'E', 'F', 'G','H','I','J','K'};
char[][] edges = new char[][]{
{'A', 'C'},
{'A', 'D'},
{'A', 'F'},
{'B', 'C'},
{'C', 'D'},
{'E', 'G'},
{'D', 'G'},
{'I','J'},
{'J','G'},};
ListDG pG;
long start=System.nanoTime();
for(int i=0;i<10000;i++){
pG = new ListDG(vexs, edges);
//pG.print(); // 打印图
}
long end=System.nanoTime();
System.out.println(end-start);
}
}
邻接表再讨论
在邻接表存储结构的实现中,我创建了一个内部类Vertex来实现单链表。我稍微感觉到有些不优雅,因为在图数据结构中又引入了一个数据结构,而单链表是可以用Java原生的LinkedList来实现的,于是便有了邻接表有向图的第二次实现:
import java.util.LinkedList;
public class ListDG2 {
LinkedList<Character>[] vertexLists;
int size;
public ListDG2(char[] vertexs,char[][] edges){
size=vertexs.length;
this.vertexLists=new LinkedList[size];
for(int i=0;i<size;i++){
this.vertexLists[i]=new LinkedList<Character>();
vertexLists[i].add(vertexs[i]);
}
for(char[] c:edges){
int p=getPosition(c[0]);
this.vertexLists[p].add(c[1]);
}
}
private int getPosition(char ch) {
for(int i=0; i<size; i++)
if(vertexLists[i].get(0)==ch)
return i;
return -1;
}
public void print(){
for(int i=0;i<size;i++){
LinkedList<Character> temp=vertexLists[i];
for(int j=0;j<temp.size();j++){
System.out.print(temp.get(j)+" ");
}
System.out.println();
}
}
public static void main(String[] args){
char[] vexs = {'A', 'B', 'C', 'D', 'E', 'F', 'G','H','I','J','K'};
char[][] edges = new char[][]{
{'A', 'C'},
{'A', 'D'},
{'A', 'F'},
{'B', 'C'},
{'C', 'D'},
{'E', 'G'},
{'D', 'G'},
{'I','J'},
{'J','G'},};
ListDG2 pG;
long start=System.nanoTime();
for(int i=0;i<10000;i++){
pG = new ListDG2(vexs, edges);
//pG.print(); // 打印图
}
long end=System.nanoTime();
System.out.println(end-start);
}
}
和之前大同小异,只是用LinkedList来代替自定义的数据结构,我也尝试实用HashMap来代替getPosition方法,避免每加入一条变信息都要从头遍历寻找下标。我使用了类似下面的代码来简单测试图初始化的效率。实验的结果发现,效率并没有提升反而下降,当然这不是一个全面的测试,没有考虑其他相关的图数据结构操作,也没有考虑图的大小和边的数量的影响,但总体来说,使用自建的内部类和遍历方法的效率在使用LinkedList和HashMap的两倍以上。这略微有些反直觉,想来可能是因为实际是Java集合类因为要实现通用强大的功能,变得比较重型,反而不如自建的数据结构和算法快捷轻巧。
long start=System.nanoTime();
for(int i=0;i<10000;i++){
pG = new ListDG2(vexs, edges);
//pG.print(); // 打印图
}
long end=System.nanoTime();