資料結構之--圖的講解與C語言實現
資料結構–圖
圖是研究資料元素之間的多對多的關係。在這種結構中,任意兩個元素之間可能存在關係。即結點之間的關係可以是任意的,圖中任意元素之間都可能相關。圖的應用極為廣泛,已滲入到諸如語言學、邏輯學、物理、化學、電訊、電腦科學以及數學的其它分支。
圖的定義
一個圖(G)定義為一個偶對(V,E) ,記為G=(V,E) 。其中: V是頂(Vertex)的非空有限集合,記為V(G);E是無序集V&V的一個子集,記為E(G) ,其元素是圖的弧(Arc)。將頂點集合為空的圖稱為空圖。其形式化定義為:G=(V ,E),V={v|v in data object},
E={(v,w)| v,w in V and p(v,w)},P(v,w)表示從頂點v到頂點w有一條直接通路。
圖的結構
圖的儲存結構比較複雜,其複雜性主要表現在:
任意頂點之間可能存在聯絡,無法以資料元素在儲存區中的物理位置來表示元素之間的關係。
圖中頂點的度不一樣,有的可能相差很大,若按度數最大的頂點設計結構,則會浪費很多儲存單元,反之按每個頂點自己的度設計不同的結構,又會影響操作。
圖的常用的儲存結構有:鄰接矩陣、鄰接連結串列、十字連結串列、鄰接多重表和邊表。
在圖的鄰接連結串列表示中,所有頂點結點用一個向量 以順序結構形式儲存,可以隨機訪問任意頂點的連結串列,該向量稱為表頭向量,向量的下標指示頂點的序號。
用鄰接連結串列儲存圖時,對無向圖,其鄰接連結串列是唯一的,對有向圖,其鄰接連結串列有兩種形式。
圖的遍歷
深度優先搜尋(DFS):深度優先搜尋(Depth First Search–DFS)遍歷類似樹的先序遍歷,是樹的先序遍歷的推廣。
設初始狀態時圖中的所有頂點未被訪問,則:
⑴ :從圖中某個頂點vi出發,訪問vi;然後找到vi的一個鄰接頂點vi1 ;
⑵:從vi1出發,深度優先搜尋訪問和vi1相鄰接且未被訪問的所有頂點;
⑶:轉⑴ ,直到和vi相鄰接的所有頂點都被訪問為止
⑷ :繼續選取圖中未被訪問頂點vj作為起始頂點,轉(1),直到圖中所有頂點都被訪問為止。
廣度優先搜尋(BFS):廣度優先搜尋(Breadth First Search–BFS)遍歷類似樹的按層次遍歷的過程。
設初始狀態時圖中的所有頂點未被訪問,則:
⑴ :從圖中某個頂點vi出發,訪問vi;
⑵:訪問vi的所有相鄰接且未被訪問的所有頂點vi1,vi2,…,vim;
⑶:以vi1,vi2, …,vim的次序,以vij(1≦j≦m)依此作為vi ,轉⑴;
⑷ :繼續選取圖中未被訪問頂點vk作為起始頂點,轉⑴,直到圖中所有頂點都被訪問為止。
程式碼實現–C語言實現
Graph.c
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "Queue.h"
#define MAX_VERTEX_NUM 10
#define INFINITY 32768
typedef enum{DG,DN,UDG,UDN} GraphKind;
#define ERROR 0
#define TRUE 1
typedef int status;
typedef char ElemType;
typedef struct MNode{
ElemType vertex[MAX_VERTEX_NUM]; /*頂點*/
int Arc[MAX_VERTEX_NUM][MAX_VERTEX_NUM]; /*弧*/
int vexnum,arcnum; /*弧個數,頂點個數*/
GraphKind kind;
}GraphNode,*Graph;
int IsRead[MAX_VERTEX_NUM]; //標記頂點是否被遍歷過
/*初始化*/
status Init(Graph *G){
(*G) = (GraphNode *)malloc(sizeof(GraphNode));
(*G)->vexnum = 0;
(*G)->arcnum = 0;
if((*G))return TRUE;
else{
printf("初始化出錯...\n");
}
return ERROR;
}
/*查詢位置*/
void FindPos(Graph G,char a,char b,int *pos1,int *pos2){
int i = 0;
*pos1 = -1; *pos2 = -1; //初值
for(i = 0; i<G->vexnum;i++){
if(G->vertex[i]==a){
(*pos1) = i;
continue;
}else if(G->vertex[i] == b){
(*pos2) = i;
continue;
}
}
}
/*建立圖*/
void BuildGraph(Graph G){
int choice = 0,num = 0,pos1,pos2,i,j,weight;
char a,b,ch;
printf("請選擇建立的圖的型別:1:有向圖,2:有向網,3:無向圖,無向網:\n");
scanf("%d",&choice);
getchar();
printf("\n"); //下一行
if(choice == 1){ //有向圖
for(i = 0;i<MAX_VERTEX_NUM;i++){ //初始化弧
for(j = 0;j<MAX_VERTEX_NUM;j++){
G->Arc[i][j] = 0;
}
}
G->kind = DG; //設定圖的型別
printf("請輸入頂點(不超過10個,以#結束):\n");
scanf("%c",&ch);
while(ch!='#' && num <10){
G->vertex[num] = ch;
scanf("%c",&ch);
num++;
}
G->vexnum = num; //頂點個數
getchar(); //清除鍵盤緩衝區
printf("請輸入對應的弧以a->b格式輸入(以#->#結束):\n");
scanf("%c->%c",&a,&b);
while(a!='#' && b!='#'){
printf("%c,%c",a,b);
FindPos(G,a,b,&pos1,&pos2);
printf("位置a:%d,位置b:%d\n",pos1,pos2);
if(pos1!= -1 && pos2!= -1){ //忽略不存在的頂點
G->Arc[pos1][pos2] = 1;
G->arcnum++;
}
scanf("%c->%c",&a,&b);
}
getchar(); //清空
}
else if(choice==2){ //有向網
num = 0; //個數初始化
for(i = 0;i < MAX_VERTEX_NUM; i++){ //初始化弧
for(j = 0;j<MAX_VERTEX_NUM; j++){
G->Arc[i][j] = INFINITY;
}
}
G->kind = DN; //設定圖的型別
printf("請輸入頂點(不超過10個,以#結束):\n");
scanf("%c",&ch);
while(ch!='#' && num <10){
G->vertex[num] = ch;
scanf("%c",&ch);
num++;
}
G->vexnum = num; //頂點個數
getchar(); //清除鍵盤緩衝區
printf("請輸入對應的弧以a->b:weight格式輸入(以#->#:0結束):\n");
scanf("%c->%c:%d",&a,&b,&weight);
while(a!='#' && b!='#'){
printf("%c,%c",a,b);
FindPos(G,a,b,&pos1,&pos2);
printf("位置a:%d,位置b:%d\n",pos1,pos2);
if(pos1!= -1 && pos2!= -1){ //忽略不存在的頂點
G->Arc[pos1][pos2] = weight;
G->arcnum++;
}
scanf("%c->%c:%d",&a,&b,&weight);
}
getchar(); //清空
}
else if(choice == 3){//無向圖
num = 0;
for(i = 0;i<MAX_VERTEX_NUM;i++){ //初始化弧
for(j = 0;j<MAX_VERTEX_NUM;j++){
G->Arc[i][j] = 0;
}
}
G->kind = UDG; //設定圖的型別
printf("請輸入頂點(不超過10個,以#結束):\n");
scanf("%c",&ch);
while(ch!='#' && num <10){
G->vertex[num] = ch;
scanf("%c",&ch);
num++;
}
G->vexnum = num; //頂點個數
getchar(); //清除鍵盤緩衝區
printf("請輸入對應的弧以a-b格式輸入(以#-#結束):\n");
scanf("%c-%c",&a,&b);
while(a!='#' && b!='#'){
printf("%c,%c",a,b);
FindPos(G,a,b,&pos1,&pos2);
printf("位置a:%d,位置b:%d\n",pos1,pos2);
if(pos1!= -1 && pos2!= -1){ //忽略不存在的頂點
G->Arc[pos1][pos2] = 1;
G->Arc[pos2][pos1] = 1;
G->arcnum++;
}
scanf("%c-%c",&a,&b);
}
getchar(); //清空
}
else if(choice == 4){ //無向網
num = 0; //個數初始化
for(i = 0;i < MAX_VERTEX_NUM; i++){ //初始化弧
for(j = 0;j<MAX_VERTEX_NUM; j++){
G->Arc[i][j] = INFINITY;
}
}
G->kind = DN; //設定圖的型別
printf("請輸入頂點(不超過10個,以#結束):\n");
scanf("%c",&ch);
while(ch!='#' && num <10){
G->vertex[num] = ch;
scanf("%c",&ch);
num++;
}
G->vexnum = num; //頂點個數
getchar(); //清除鍵盤緩衝區
printf("請輸入對應的弧以a-b:weight格式輸入(以#-#:0結束):\n");
scanf("%c->%c:%d",&a,&b,&weight);
while(a!='#' && b!='#'){
printf("%c,%c",a,b);
FindPos(G,a,b,&pos1,&pos2);
printf("位置a:%d,位置b:%d\n",pos1,pos2);
if(pos1!= -1 && pos2!= -1){ //忽略不存在的頂點
G->Arc[pos1][pos2] = weight;
G->Arc[pos2][pos1] = weight;
G->arcnum++;
}
scanf("%c-%c:%d",&a,&b,&weight);
}
getchar(); //清空
}
else { //非法輸入的選擇
printf("輸入非法,請輸入正確的數字!!\n");
return;
}
}
void DepthFS(Graph G,int pos){ /*深度優先搜尋for圖*/
int i = 0,j = 0;
if(IsRead[pos] == 0){
IsRead[pos] = 1; //從第一個開始
printf("遍歷頂點%c\n",G->vertex[pos]);
}
for(i = 0;i<G->vexnum;i++){
if(G->Arc[pos][i] == 1 && IsRead[i] ==0){ //存在弧
DepthFS(G,i);
}
}
}
void DepthFS1(Graph G,int pos){ /*深度優先搜尋for網*/
int i = 0,j = 0;
if(IsRead[pos] == 0){
IsRead[pos] = 1; //從第一個開始
printf("遍歷頂點%c\n",G->vertex[pos]);
}
for(i = 0;i<G->vexnum;i++){
if(G->Arc[pos][i] !=INFINITY && IsRead[i] ==0){ //存在弧且未被遍歷
DepthFS1(G,i);
}
}
}
/*深度優先搜尋*/
void DFS(Graph G){
if(G->kind == DG || G->kind == UDG){ //圖
DepthFS(G,0);
}else if(G->kind == DN || G->kind == UDN){ //網
DepthFS1(G,0);
}
}
void BFS1(Graph G,int pos){ //廣度優先搜尋for圖
int i = 0,temp;
Queue Q;
InitQueue(&Q);
for(i = 0; i <G->vexnum;i++){ //清零
IsRead[i] = 0;
}
if(IsRead[pos] ==0){
IsRead[pos] = 1;
printf("遍歷頂點:%c\n",G->vertex[pos]);
}
EnterQueue(Q,pos);
while(!isEmpty(Q)){//當佇列不為空
OutQueue(Q,&temp);
for(i = 0; i< G->vexnum;i++){
if(G->Arc[temp][i] == 1 && IsRead[i] == 0){
IsRead[i] = 1;
printf("遍歷頂點:%c\n",G->vertex[i]);
EnterQueue(Q,i);
}
}
}
free(Q);
}
void BFS2(Graph G,int pos){ //廣度優先搜尋for圖
int i = 0,temp;
Queue Q;
InitQueue(&Q);
for(i = 0; i <G->vexnum;i++){ //清零
IsRead[i] = 0;
}
if(IsRead[pos] ==0){
IsRead[pos] = 1;
printf("遍歷頂點:%c\n",G->vertex[pos]);
}
EnterQueue(Q,pos);
while(!isEmpty(Q)){//當佇列不為空
OutQueue(Q,&temp);
for(i = 0; i< G->vexnum;i++){
if(G->Arc[temp][i] != INFINITY && IsRead[i] == 0){
IsRead[i] = 1;
printf("遍歷頂點:%c\n",G->vertex[i]);
EnterQueue(Q,i);
}
}
}
free(Q);
}
void BFS(Graph G){
if(G->kind == DG || G->kind == UDG){ //圖
BFS1(G,0);
}else if(G->kind == DN || G->kind == UDN){ //網
BFS2(G,0);
}
}
void main(){
int i = 0;
Graph G = NULL;
Init(&G);
BuildGraph(G);
for(i = 0; i<MAX_VERTEX_NUM; i++){
IsRead[i] = 0; //未被遍歷
}
printf("\n深度優先搜尋:\n");
DFS(G,0);
printf("\n廣度優先搜尋:\n");
BFS(G,0);
}
Queue.h
#include <stdio.h>
#include <stdlib.h>
#define TRUE 1
#define ERROR 0
#define MAX_SIZE 20
typedef int status;
/*定義佇列*/
typedef struct QNode{
int Qarr[MAX_SIZE];
int tail,head;
int size;
}QNode,*Queue;
void InitQueue(Queue *Q){
(*Q) = (QNode *)malloc(sizeof(QNode));
if(*Q){
(*Q)->size = 0;
(*Q)->head = 0;
(*Q)->tail = 0;
}
}
/*入佇列*/
void EnterQueue(Queue Q, int data){
if((Q->tail +1) % MAX_SIZE == Q->head){
printf("佇列已經滿!!\n");
return;
}
Q->Qarr[Q->tail] = data;
Q->size++;
Q->tail = (Q->tail +1) % MAX_SIZE;
return;
}
void OutQueue(Queue Q, int *data){
if(Q->head == Q->tail){
printf("佇列為空!!\n");
return;
}
(*data) = Q->Qarr[Q->head];
Q->size--;
Q->head = (Q->head +1) % MAX_SIZE;
return;
}
int isEmpty(Queue Q){
if(Q->head == Q->tail)return 1;
return 0;
}