数据结构 -- 图与图存储
我們在使用像QQ ,微信,微博,快手,抖音等社交軟件的過程中經(jīng)常需要添加好友,關(guān)注好友和被好友關(guān)注。這個過程中 這樣的社交網(wǎng)絡(luò)中的好友關(guān)系就需要被存儲下來,存儲在各個公司的后臺服務(wù)器之上,都會作為每個公司的數(shù)據(jù)資產(chǎn)來進(jìn)行自己核心業(yè)務(wù)的開發(fā)(視頻推薦、好友推薦。。。)
這個用來保存好友關(guān)系的數(shù)據(jù)結(jié)構(gòu)就是 圖,接下來探索一下這個非線性數(shù)據(jù)結(jié)構(gòu)的基本實現(xiàn)。
圖的一些基本概念如上導(dǎo)圖,已經(jīng)描述的很清楚了,這里重點說的是圖的圖存儲方式。
基本的存儲方式有兩種:
- 鄰接矩陣
- 鄰接表
鄰接矩陣的底層依賴一個二維數(shù)組。對于無向圖來說,如果頂點i與頂點j之間有邊,我們就將A[i][j]和A[j][i]
標(biāo)記為1;對于有向圖來說,如果頂點i到頂點j之間,有 一條箭頭從頂點i指向頂點j的邊,那我們就將A[i][j]標(biāo)記為1。同理,如果有一條箭頭從頂點j指向頂點i的邊,我們就將A[j][i]標(biāo)記為1。對于帶權(quán)圖,數(shù)組中就存儲 相應(yīng)的權(quán)重。
用數(shù)組表示的底層數(shù)據(jù)結(jié)構(gòu)能夠提供高效的查找能力,但是缺點也很明顯,就是浪費空間過于嚴(yán)重,針對無向圖的存儲中只需要保存一個A[i][j]即可。
鄰接表底層依賴鏈表進(jìn)行數(shù)據(jù)存儲,能夠解決鄰接矩陣?yán)速M空間嚴(yán)重的問題,鏈表只有添加了新節(jié)點才會分配空間。整體的實現(xiàn)有點像散列表。
對于無向圖來說,每一個頂點表示一個鏈表,與該頂點相連的其他頂點依次添加到該頂點的鏈表之上,類似如下:
有向圖也是類似的,鏈表上僅保存該頂點指向的頂點
鏈表本身的查找效率比較低,需要順鏈訪問,在鄰接表中可以將底層的鏈表存儲結(jié)構(gòu)用更加高效的動態(tài)查找數(shù)據(jù)結(jié)構(gòu)來代替(鏈表和紅黑樹)。
以上數(shù)據(jù)結(jié)構(gòu)都是存放在內(nèi)存中的,但是實際的線上環(huán)境社交關(guān)系數(shù)據(jù)量以TB級別進(jìn)行存儲,這個時候數(shù)據(jù)量遠(yuǎn)超內(nèi)存,便需要有持久化能力,需要對圖的內(nèi)存數(shù)據(jù)結(jié)構(gòu)進(jìn)行編碼。
以鄰接表距離,遍歷鄰接表,將頂點和對應(yīng)鏈接頂點別編碼為(user_id, follower_id),后續(xù)的關(guān)系依次追加。像常見的分布式圖存儲,大體也是類似的方式進(jìn)行存儲,只不過底層磁盤的編碼格式更為復(fù)雜一點。可以類似與sst中的data_block,index block,footer 依次索引到datablock中,datablock保存實際的user_id和follower_id信息。
同時圖存儲 系統(tǒng)也希望將依賴于圖的眾多查找算法(bfs,dfs,djs,djkstra,A*,啟發(fā)式搜索等)下沉到存儲層,從而節(jié)省計算層的CPU開銷。這時候,一個高效的分布式圖存儲系統(tǒng)的雛形邊有了(只是單機的,分布式能力還是一大部分工作量),當(dāng)然為用戶提供可以訪問的圖語言也是完善系統(tǒng)的一部分。
接下來簡單實現(xiàn)一下鄰接表和鄰接矩陣的圖存儲功能:
- 鄰接表
graph_list.c#include <stdio.h> #include <stdlib.h> #include <string.h>typedef struct Graph_vertex vertex;typedef struct Graph_edge { // 邊struct Graph_vertex *v; // 該邊鏈接的頂點struct Graph_edge *next; // 用鏈表管理的鏈接同一頂點的下一條邊 }edge;typedef struct Graph_vertex { // 頂點int data; // 頂點的值edge *e; // 頂點的邊 鏈表管理的相連接的邊 }vertex;#define MAX_GRAPH (1 << 8) typedef struct Graph{vertex *vxs[MAX_GRAPH]; }graph;void init_graph(graph *g){int i = 0;for (;i < MAX_GRAPH; i++) {g->vxs[i] = NULL;} }/* 創(chuàng)建頂點 */ vertex *create_vertex(int data) {if(data < 0) {return NULL;}vertex * v = NULL;v = (vertex *)malloc(sizeof(vertex));if(v == NULL) {return NULL;}v->data = data+1;v->e = NULL;return v; }/* 創(chuàng)建邊 */ edge *create_edge(vertex *v){if(v == NULL) {return NULL;}edge *e;e = (edge *)malloc(sizeof(edge));if(e == NULL) {return NULL;}e->v = v;e->next = NULL;return e; }/* 為頂點v1 插入邊 */ void insert_edge(vertex *v1, vertex *v2) {if (v1 == NULL || v2 == NULL) {return;}edge **e;e = &v1->e;while(*e) {e = &(*e)->next;}*e = create_edge(v2); }/* 打印鄰接表 */ void dump_graph(graph *g) {int i;for(i = 0;i < MAX_GRAPH; ++i) {vertex *v = g->vxs[i];edge *e;if(v == NULL) {continue; }printf("Vertex[%d]:%2d->",i+1, v->data);e = v->e;while(e) {if(e->next != NULL) {printf("%2d->", e->v->data);}else {printf("%2d", e->v->data);}e = e->next; }printf("\n");} }/* 1 ----- 2 ----- 3| / | /| / | / | / | / | / | / | / | / 4 ----- 5 */ void create_graph(graph *g) {int i = 0;init_graph(g);for (;i < 5; ++i) {g->vxs[i] = create_vertex(i);}// 鏈接1--2, 1--4insert_edge(g->vxs[0],g->vxs[1]);insert_edge(g->vxs[0],g->vxs[3]);// 鏈接2--1,2--3,2--4,2--5insert_edge(g->vxs[1],g->vxs[0]);insert_edge(g->vxs[1],g->vxs[2]);insert_edge(g->vxs[1],g->vxs[3]);insert_edge(g->vxs[1],g->vxs[4]);// 鏈接3--2,3--5insert_edge(g->vxs[2],g->vxs[2]);insert_edge(g->vxs[2],g->vxs[4]);// 鏈接4--1,4--2,4--5insert_edge(g->vxs[3],g->vxs[0]);insert_edge(g->vxs[3],g->vxs[1]);insert_edge(g->vxs[3],g->vxs[5]);// 鏈接5--2,5--3,5--4insert_edge(g->vxs[4],g->vxs[1]);insert_edge(g->vxs[4],g->vxs[2]);insert_edge(g->vxs[4],g->vxs[3]); }int main() {graph g;create_graph(&g);dump_graph(&g);return 0; } - 鄰接矩陣
graph_matrix.c#include <stdio.h> #include <stdlib.h> #include <string.h>#define MAX_GRAPH (1 << 8)int *graph[MAX_GRAPH];void init_graph(int size) {if (size > MAX_GRAPH) {return ;}int i = 0;int j = 0;for (; i < size; ++i) {graph[i] = (int*)malloc(size*sizeof(int));for (;j < size; ++j){graph[i][j] = 0;}} }void add_edge(int v1, int v2) {graph[v1][v2] = 1;graph[v2][v1] = 1; }void print_graph(int size) {int i = 0;int j = 0;for (; i < size; ++i) {for (; j < size; ++j) {printf("%d ", graph[i][j]);}printf("\n");} }/* 1 ----- 2 ----- 3| / | /| / | / | / | / | / | / | / | / 4 ----- 5 */ void create_graph(int size) {init_graph(size);add_edge(0,1);add_edge(0,3);add_edge(1,0);add_edge(1,2);add_edge(1,3);add_edge(1,4);add_edge(2,1);add_edge(2,4);add_edge(3,0);add_edge(3,1);add_edge(3,4);add_edge(4,1);add_edge(4,2);add_edge(4,3); }int main() {create_graph(6);print_graph(6);return 0; }
總結(jié)
以上是生活随笔為你收集整理的数据结构 -- 图与图存储的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 数据结构 -- 散列表
- 下一篇: 一文带你看透 GDB 的 实现原理 --