數據結構之圖(java語言版)
圖是比樹更復雜的結構,樹是一對多的關係,圖是多對多的關係。
一、基本概念
1、定義:圖(graph)是由一些點(vertex)和這些點之間的連線(edge)所組成的;其中,點通常被成爲"頂點(vertex)",而點與點之間的連線則被成爲"邊或弧"(edege)。通常記爲,G=(V,E)。
2、根據邊是否有方向,將圖可以劃分爲:無向圖和有向圖。
3、度,在無向圖中,某個頂點的度是鄰接到該頂點的邊(或弧)的數目。
在有向圖中,度還有"入度"和"出度"之分。
某個頂點的入度,是指以該頂點爲終點的邊的數目。而頂點的出度,則是指以該頂點爲起點的邊的數目。
頂點的度=入度+出度。
4、弧頭和弧尾
有向圖中:用<A,B>,<B,C>,<B,F>,A->B,A是弧尾,B是 弧頭。
無向圖中:用(A,B),(A,C),(B,C),弧頭和弧尾沒有區別。
5、權
弧如果有值的話,稱爲權。
二、圖的存儲結構
圖的存儲結構有許多種,有鄰接矩陣,鄰接表,十字鏈表等。
鄰接矩陣
用矩陣表示,用線性表存儲數據,直觀簡單,但是浪費空間。
鄰接表
用數組和鏈表表示結構,節省空間,可伸縮。
數組中存儲了鏈表,鏈表的頭節點代表定點,存儲着數據及下一個頂點的引用,後面的節點存儲着下標和下一個頂點的引用。
圖來自《大話數據結構》
鄰接表實現圖
頂點及索引結構
頂點存儲着數據和索引。
索引結構存儲着頂點在數組中的下標。
class VexNode{//頂點
String data;//頂點的數據
EdgeNode fnext;//指向下一個頂點
public VexNode(){}
public VexNode(String data){
this.data=data;
}
}
class EdgeNode{//存儲索引的節點
int index;//索引
//int weight;//權重
EdgeNode next;//指向下一個頂點
}
建立圖
使用數組存儲鏈表。
構造方法傳入頂點數和弧數。
public class Graph{
public VexNode[] vexTable;//表
private int numNode;//頂點數量
private int size;//弧數
public Graph(int numNode,int size){//構造函數,確定頂點數量
this.numNode=numNode;
this.size=size;
vexTable=new VexNode[numNode];
}
public VexNode[] init(){//建立鄰接表
Scanner scanner =new Scanner(System.in);
for(int i=0;i<numNode;i++){
System.out.println("輸入頂點數據");
String data=scanner.next();
VexNode node=new VexNode(data);
vexTable[i]=node;
}
System.out.println("輸入弧數據:");
for(int k=0;k<size;k++){
EdgeNode eNode=new EdgeNode();
System.out.println("輸入弧頭:");
int x=scanner.nextInt();
System.out.println("輸入弧尾:");
int y=scanner.nextInt();
eNode.index=y;
eNode.next=vexTable[x].fnext;//頭插法插入鏈中
vexTable[x].fnext=eNode;
eNode=new EdgeNode();//無向表需要弧頭弧尾相同
eNode.index=x;//如果建立的是有向表,將這四行代碼去除即可
eNode.next=vexTable[y].fnext;
vexTable[y].fnext=eNode;
}
return vexTable;
}
}
查看弧
圖的遍歷比較複雜,我們可以先通過弧來查看圖是否正確。
public void display(VexNode[] vexTable){
System.out.println("打印表:");
for(int i=0;i<numNode;i++){
EdgeNode v=vexTable[i].fnext;
while(v!=null){
System.out.printf("(%s %s) ",vexTable[i].data,vexTable[v.index].data);
v=v.next;
}
System.out.println();
}
}
用以下圖來表示,數據輸入順序代表數據在數組中的位置。所以頂點輸入順序是
abcde
弧的輸入順序代表方向,不過我們建立的表是無向圖,所以無所謂。
主函數中測試:
public static void main(String[] args) {
Graph graph=new Graph(5,5);
VexNode[] vexTable=graph.init();
graph.display(vexTable);
}
輸入數據:
輸入頂點數據
a
輸入頂點數據
b
輸入頂點數據
c
輸入頂點數據
d
輸入頂點數據
e
輸入弧數據:
輸入弧頭:
0
輸入弧尾:
1
輸入弧頭:
1
輸入弧尾:
3
輸入弧頭:
3
輸入弧尾:
4
輸入弧頭:
4
輸入弧尾:
2
輸入弧頭:
2
輸入弧尾:
0
得到結果:
無向圖會打印兩次,有向圖只有一個指向只會打印一次。
打印表:
(a c) (a b)
(b d) (b a)
(c a) (c e)
(d e) (d b)
(e c) (e d)
遍歷
圖的遍歷有深度優先和廣度優先。
深度優先遍歷是從圖中某個頂點出發,訪問此頂點,然後從它未被訪問到的鄰接點出發深度優先遍歷圖,直到圖中所有和它有路徑相通的頂點都被訪問到.,類似樹的先序遍歷。
廣度優先遍歷從某個頂點出發,訪問其所有相鄰元素,再從某個相鄰元素開始廣度優先遍歷,類似樹的層級遍歷。
深度優先遍歷
對遍歷過的點需要標記,以免重複遍歷。
同樣可以遞歸來遍歷。
private int[] visit;//標記頂點是否遍歷,1爲已遍歷,0爲未遍歷
//深度優先遍歷
public void DFS(VexNode[] vexTable){
this.visit=new int[numNode];//默認所有頂點都未遍歷,數組中值都爲0
System.out.println("深度優先遍歷:");
for(int i=0;i<numNode;i++){
if(visit[i]==0){
DFSVisit(vexTable, i);
}
}
System.out.println();
}
public void DFSVisit(VexNode[] vexTable,int i){//對爲遍歷的數據輸出
EdgeNode eNode;
visit[i]=1;//該頂點已經遍歷
System.out.print(" "+vexTable[i].data);
eNode=vexTable[i].fnext;
while(eNode!=null){
if(visit[eNode.index]==0){
DFSVisit(vexTable, eNode.index);
}
eNode=eNode.next;
}
}
廣度優先遍歷
廣度優先遍歷的特點是我們需要一個先進先出的容器來保存頂點。
使用隊列即可,這裏的隊列可以採用之前的隊列,也可以通過java已經提供好的LinkedList類來實現。
我們採用之前的鏈表隊列:
public class Queue{
SingleLinkList list;
public Queue(){
list=new SingleLinkList();
}
public void enQueue(Object e) {
list.add(e);
System.out.println("入隊");
}
public Object deQueue() {
Object e=list.get(1);
list.remove(1);
System.out.println("出隊");
return e;
}
public void display() {
list.display();
}
public int getSize(){
return list.getSize();
}
}
廣度優先如下:
private Queue queue;
//廣度優先遍歷
public void BFS(){
this.visit=new int[numNode];//默認所有頂點都未遍歷,數組中值都爲0
System.out.println("廣度優先遍歷:");
this.queue=new Queue();//建立隊列
for(int i=0;i<numNode;i++){
if(visit[i]==0){
BFSVisit();
}
}
System.out.println();
}
public void BFSVisit(){//遍歷操作
for(int i=0;i<numNode;i++){
if(visit[i]==0){
visit[i]=1;
System.out.print(" "+vexTable[i].data);
queue.enQueue(i);//入隊
EdgeNode eNode;
while(!queue.isEmpty()){
queue.deQueue();//出隊
eNode=vexTable[i].fnext;
while(eNode!=null){
if(visit[eNode.index]==0){
visit[eNode.index]=1;
System.out.print(" "+vexTable[eNode.index].data);
queue.enQueue(eNode.index);
}
eNode=eNode.next;
}
}
}
}
}
還是採用這個圖:使用同樣的輸入數據在主方法中測試:
public static void main(String[] args) {
Graph graph=new Graph(5,5);
VexNode[] vexTable=graph.init();
graph.DFS(vexTable);
graph.BFS();
}
深度優先遍歷:
a c e d b
廣度優先遍歷:
a c b d e