數據結構圖之定義與遍歷

我是自動化專業的應屆研究生,最終拿到了tplink、華爲、vivo等公司的ssp的offer,分享自己學習過的計算機基礎知識(C語言+操作系統+計算機網絡+linux)以及數據結構與算法的相關知識,保證看完讓你有所成長。
歡迎關注我,學習資料免費分享給你哦!還有其他超多學習資源,都是我自己學習過的,經過過濾之後的資源,免去你還在因爲擁有大量資源不知如何入手的糾結,讓你體系化學習。
在這裏插入圖片描述

圖的概念

圖就是有一些頂點,頂點之間有一些邊,這樣就構成了一個圖。所以對於圖這種數據結構,關鍵點就是頂點和邊,就是描述頂點和邊的關係。然後對於邊呢,對於某些問題來說,還可能有權重,以城市之間的航班爲例,各個城市就是頂點,航線就是邊,不同的航線票價不同,這個票價就是權重,所以根據邊有無權重,分爲有權圖和無權圖。根據邊是否是單方向,就是頂點A和B之間有一條邊,如果只能沿着這條邊單方向的從A到B或者從B到A,而不是既可以從A到B也可以從B到A的,這種圖就是有向圖。

無向圖:

在這裏插入圖片描述

有向圖:
在這裏插入圖片描述

有權圖:

在這裏插入圖片描述

在一個無向圖中,如果任意的兩個頂點,都可以找到一個路徑,讓他倆連通,那麼這個圖就是連通圖。否則就不是。

連通圖:任意兩個節點之間都可以找到通路

在這裏插入圖片描述

非連通圖:ABCD任意一個頂點都不能找到到達E和F的通路。但是ABCD是一個連通圖。

在這裏插入圖片描述

圖的鄰接矩陣的實現

所謂鄰接矩陣就是用二維矩陣(就是二維數組)來表示頂點之間的關係。二維矩陣是一個方陣,大小就是頂點數*頂點數,如果頂點i和頂點j之間有一條邊,如果是無向圖則,G[i][j]=1,G[j][i]=1。如果是有向圖,假設只是從i指向j那麼G[i][j]=1。對於有權圖,那麼這個矩陣的值就是權值,然後兩個頂點之間沒有邊,可以讓G[i][j]等於一個權值永遠不會相等的數來表示沒有邊。

代碼如下

typedef struct EdgeNode //邊的定義,方便爲圖插入邊,如果是有權圖,添加一個權重變量即可
{
	int begin; 
	int end;
}* Edge;
#define MAX_NV 11 //定義圖的最大頂點數目,當然也可以使用動態數組生成一個二維數組
typedef struct Graph //如果是有權圖,添加一個權重變量即可
{
	int Nv;//頂點數
	int Ne;//邊數
	int G[11][11];//邊的關係,兩個頂點i和j之間有邊,則置1
}* Graph;
Graph creatGraph(int Nv,int Ne)//輸入頂點數和邊數,創建一個圖,返回圖的指針。
{
	Graph graph;
	int i,j;
	graph=(Graph)malloc(sizeof(struct Graph));
	if(graph==NULL)
	{
		printf("內存不足\n");
		return NULL;
	}
	graph->Ne=Ne;
	graph->Nv=Nv;
	for(i=0;i<graph->Nv;i++)//全部初始化爲沒有邊
	{
		for(j=0;j<graph->Nv;j++)
		{
			graph->G[i][j]=0;
		}
	}
	return graph;
}
void insert(Graph graph,Edge e)//插入邊,這裏是無權圖,無向圖
{
	if(graph==NULL||e==NULL)
	{
		return;
	}
	graph->G[e->begin][e->end]=1;
	graph->G[e->end][e->begin]=1;
}
void destroy(Graph graph)//釋放圖的內存
{
	free(graph);
}
圖的鄰接表實現

前面的鄰接矩陣對於頂點很多,邊特別少的情況(被叫做稀疏矩陣),矩陣中的很多元素都是0,是一種巨大的浪費。圖的鄰接表表示法,其實說到底就是一個散列而已,定義了一個頭部,頭部中包含圖中一共有多少頂點和邊,還有一個元素是指向各個頂點頭部,用來指示頂點之間的邊的關係。

在這裏插入圖片描述

與代碼對應一下:

typedef struct Gnode
{
	int end;
    struct Gnode* next;
}* Gnode;
typedef struct Graph
{
	int Nv;//頂點數
	int Ne;//邊數
	struct Gnode G[20];
}* Graph;

struct Graph就是散列的管理結構,struct Gnode G[20];就是散列表的頭部,struct Gnode就是節點。

然後圖中的頂點與邊的對應關係是這樣的:G[i]就是表示第i頂點,然後向後遍歷這個鏈表,每個節點struct Gnode結構中都有end元素,它表示的就是第end頂點,就是i到end頂點之間存在一個邊。

在這裏插入圖片描述

假設有上圖所示的一個無向圖,那麼表示一個圖的鄰接表就是:

在這裏插入圖片描述
圖中的方框表示的就是struct Gnode節點。比如0與1和3之間有邊,那麼對於圖的管理結構G[0]這個鏈表就是表示的頂點0的邊,上圖可以看到這個鏈表是1,3.也就是表示0與1,0與3之間有邊。就這樣就可以表示出來了一個圖的頂點與邊的關係。

代碼實現:

Graph creatGraph(int Nv,int Ne)
{
	Graph graph;
	int i;
	graph=(Graph)malloc(sizeof(struct Graph));
	if(graph==NULL)
	{
		printf("內存不足");
		return NULL;
	}
	graph->Ne=Ne;
	graph->Nv=Nv;
    for(i=0;i<Nv;i++)
	{
		graph->G[i].next=NULL;
	}
	return graph;
}
void insert(Graph graph,int begin,int end)//無向圖i與j有邊
//那麼在G[i]的鏈表裏插入邊,也得在G[j]的鏈表裏插入邊,如果是有向邊就可以只插入一個邊了。
{
	Gnode g;
	g=(Gnode)malloc(sizeof(struct Gnode));
	if(g==NULL)
	{
		return;
	}
	g->next=graph->G[begin].next;
    graph->G[begin].next=g;
	g->end=end;

    g=(Gnode)malloc(sizeof(struct Gnode));
	if(g==NULL)
	{
		return;
	}
	g->next=graph->G[end].next;
    graph->G[end].next=g;
	g->end=begin;
}

void destroyGraph(Graph graph) //釋放圖的空間
{
	int i;
	Gnode g;
	if(graph==NULL)
	{
		return;
	}
	else
	{
		for(i=0;i<graph->Nv;i++)
		{
			while(graph->G[i].next!=NULL)
			{
                g=graph->G[i].next;
                graph->G[i].next=g->next;
				free(g);
			}
		}
	}
	free(graph);
}
圖的深度優先遍歷

圖的深度優先遍歷,就是從一個頂點A開始出發,然後找到一個與A有邊的頂點B,將A標記爲已經訪問,然後去訪問B,將B標記爲訪問完成,然後找到一個與B有邊的頂點C,在去訪問C,然後標記已經訪問完成。之後發現C已經沒有可以訪問的頂點了,那麼就回退到B,繼續找與B相連,且沒有訪問的頂點,最後回退到A,在尋找,最後直到找不到合適的頂點,結束遍歷。

在這裏插入圖片描述

舉個例子來說,如上圖所示的一個圖,首先從0開始遍歷。將0標記爲已經訪問,然後找到與它相連的頂點1.

在這裏插入圖片描述

然後將1標記爲已經訪問,找到與它相連的2,

在這裏插入圖片描述

在2處,把2標記爲已經訪問,然後繼續搜尋,發現與它相連的1已經被訪問了,所以繼續尋找,找到了3

在這裏插入圖片描述

在3點,標記爲已訪問,然後發現與之相連的頂點0,1,2都訪問了,只剩下4了,所以去頂點4.

在這裏插入圖片描述

到達4點的時候,標記爲已經訪問,然後發現與之相連的3點訪問過了。沒有其他的邊了,那麼就回退到剛纔訪問4頂點之前的3頂點,看看還有沒有與之相連的頂點沒有訪問,發現3也沒有了,那就在回退到訪問3之前的節點2,發現2有節點5沒有訪問,然後訪問,與4一樣,沒有其他頂點可以訪問,那麼就回退到2,此時2的頂點也都被訪問過了,在回退到訪問2之前的1,最後回退到0,發現都沒有空餘節點訪問了,所以完成了深度優先遍歷。這個過程是不是與之前學過的一種數據結構很像,先進後出,沒錯就是棧,函數實現棧,就是遞歸,所以圖的深度優先遍歷,就是不斷遞歸的過程。

代碼如下:

void DFS(Graph graph,int *visit,int k)//從k頂點開始遍歷,visit爲訪問標記,訪問
//過了就置1
{
	int i;
	visit[k]=1;
	printf(" %d",k);
	for(i=0;i<graph->Nv;i++)
	{
		if(visit[i]==0&&graph->G[k][i]==1)
		{
			DFS(graph,visit,i);
		}
	}
}
圖的廣度優先遍歷

圖的廣度優先遍歷就比較簡單了,其實就是二叉樹的層次遍歷的思想,需要使用隊列這個數據結構,基本思路是這樣的:首先將第一個頂點入隊,標記爲已經訪問(切記一定是入隊就標記已經訪問,不可以出隊在標記訪問,否則會造成頂點的重複入隊)然後開始遍歷,循環結束的條件是隊列爲空,將隊列首個元素彈出,然後判斷與它相連的頂點是否被訪問過,沒有,則入隊,並且標記已經訪問。重複這個過程,就完成了遍歷。

代碼如下:

void BFS(Graph graph,int *visit,int k)
{
	int i,j;
	queue q;
	q=createQueue();
	if(q==NULL)
	{
		return;
	}
	printf("{");
	add_que(q,k);
	visit[k]=1;
	while(!isEmpty(q))
	{
		i=pop_que(q);
		printf(" %d",i);
		for(j=0;j<graph->Nv;j++)
		{
			if(visit[j]==0&&graph->G[i][j]==1)
			{
				add_que(q,j);
				visit[j]=1;
			}
		}
	}
	printf(" }\n");
	destroyQueue(q);
}
圖的練習題

對於圖的基礎定義和圖的遍歷,大家可以通過下面這道題練習一下:
列出連通集

補充

補充:廣度優先遍歷中使用隊列的實現代碼如下:

typedef int Element;
typedef struct Qnode
{
  Element data[50];
  int rear;
  int front;
};

#define True 1
#define False 0
typedef struct Qnode* queue;

queue createQueue();
int isFull(queue que);
int isEmpty(queue que);
void add_que(queue que,Element BT);
Element pop_que(queue que);
void destroyQueue(queue que);
queue createQueue()
{
  queue que;
  que=(queue)malloc(sizeof(struct Qnode));
  if(que==NULL)
  {
    printf("空間不足");
    return NULL;
  }
  que->rear=0;
  que->front=0;
  return que;
}

int isFull(queue que)
{
  return ((que->rear+1)%50)==(que->front);
}
int isEmpty(queue que)
{
  return ((que->rear==que->front));
}

void add_que(queue que,Element BT)
{
  if(isFull(que)==True)
  {
    printf("隊列滿\n");
    return;
  }
  que->rear=(que->rear+1)%50;
  que->data[que->rear]=BT;
}
Element pop_que(queue que)
{
  if(isEmpty(que)==True)
  {
    printf("隊列空\n");
    return -1;
  }
//注意這個出隊,是先front指向的位置加1
  que->front=(que->front+1)%50;
  return que->data[que->front];
}

void destroyQueue(queue que)
{
  free(que);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章