基于C中一個行壓縮圖的簡單實現(xiàn)代碼
首先簡單說一下什么是行壓縮圖,其實嚴格意義上應(yīng)該是行壓縮矩陣。正常情況下,矩陣是用二維數(shù)組簡單存儲的,但是如果是稀疏矩陣,也就是零很多的時候,這樣比較浪費空間。所以就有各種節(jié)省空間的存儲方式,三元組存儲就是其中一種。
什么是三元組呢?一個三元組就是(row,col,value),這樣把所有不為零的值組成一個向量。這種存儲方式比二維數(shù)組節(jié)省了不少空間,當然還可以進一步節(jié)省,因為三元組里面row或者col重復(fù)存儲了,一行或者一列存一次就行了,按這種思路走下去就是行壓縮存儲了。
那具體什么是行壓縮存儲呢?行壓縮存儲的思想就是,把所有不為零的值按行訪問的順序組成一個向量,然后再把每一行值不為0的列的下標存下來,這個兩個向量的大小和稀疏矩陣中不為0的值得個數(shù)相同,當然要實現(xiàn)對行壓縮矩陣的訪問,還要把每一行的不為0的列的下標在第二個向量中開始的位置存下來,有人把這個叫做指針。有了這三個向量就可以實現(xiàn)對矩陣實現(xiàn)高效的按行訪問了。行壓縮存儲比三元組優(yōu)秀的不僅是空間的壓縮,還有就是行訪問時的高效。三元組如果是有序的,可以二分查找來訪問一行,但是行壓縮存儲按行訪問時的時間復(fù)雜度是常數(shù)級的。 大家可以參考下面這個行壓縮矩陣示意圖:


可能你會有疑問,你明明實現(xiàn)的行壓縮圖,怎么扯了這么多行壓縮矩陣呢?其實圖和矩陣是等價的,矩陣的一行可以看做是圖一個節(jié)點的出邊,矩陣的一列可以看做圖一個節(jié)點的入邊。當然這里需要滿足兩個條件:第一個就是圖節(jié)點編號必須是從0或者1開始的連續(xù)數(shù)值(這個可以通過對圖的節(jié)點做一次映射解決),第二個就是圖必須至少是弱連通的(非連通圖可以拆成連圖片)。實現(xiàn)了稀疏矩陣的高效存儲訪問,也就實現(xiàn)了圖的高效存儲訪問。
下面來說一下,我的實現(xiàn)。我的實現(xiàn)跟經(jīng)典的行壓縮矩陣有兩個不同:第一個就是經(jīng)典的行壓縮矩陣沒有考慮一行全為0的情況,我對這種情況做了處理(之所以處理當然不是因為我無聊,而是因為有這個需求)。第二個就是經(jīng)典的行壓縮圖對按列訪問比較慢(當然是相對于按行訪問的速度而言),對行壓縮圖按列訪問時,時間復(fù)雜度是線性的。我也對這種情況做了處理。
這里簡單說一下我的思路:
第一個問題,我是通過將所有指針默認設(shè)為-1,即表示該行可能全為0,只有當有非零值時才設(shè)置為其正確的指針。當然訪問時也要做相應(yīng)的處理。
第二個問題,我是這樣解決的。我按列壓縮存儲的方式,存了一份每一列不為0的行下標,以及每一列不為0的行下標開始的位置。這樣我的實現(xiàn)中就多了兩個向量,浪費了存儲空間,但是提高了按列訪問時的效率。
好了,talk is cheap,show me the code。下面是我的代碼(可能有錯,我只做了簡單的測試)
利用邊向量構(gòu)造壓縮圖
/*
* buildGraph 利用邊向量 構(gòu)造壓縮圖
* 對邊分別按第一個頂點、第二個頂點排序
* 然后分別按行壓縮圖和列壓縮圖構(gòu)造行、列索引和指針
* 全零行和全零列,指針置為-1
*/
private void buildGraph(Vector<Edge> edges) {
int edgeSize = edges.size();
weight = new Vector<Float>(edgeSize);
rowIndex = new Vector<Integer>(edgeSize);
rowPtr = new Vector<Integer>(nodeCount + 1);
colIndex = new Vector<Integer>(edgeSize);
colPtr = new Vector<Integer>(nodeCount + 1);
// set default value as -1
for (int i = 0; i < nodeCount; ++i) {
rowPtr.add(-1);
colPtr.add(-1);
}
rowPtr.add(edges.size());
colPtr.add(edges.size());
// sort the edge based on first node
EdgeBasedOnFirstNodeComparator cmp = new EdgeBasedOnFirstNodeComparator();
Collections.sort(edges, cmp);
// build row index and pointer
int curNode = edges.elementAt(0).getFirstNode();
int curPtr = 0;
for (int i = 0; i < edgeSize; ++i) {
Edge e = edges.elementAt(i);
// System.out.println("curNode" + curNode + "firstNode: "
// + e.getFirstNode());
weight.add(e.getWeight());
rowIndex.add(e.getSecondNode());
if (curNode != e.getFirstNode()) {
rowPtr.set(curNode, curPtr);
curNode = e.getFirstNode();
curPtr = i;
}
}
rowPtr.set(curNode, curPtr);
// sort the edge based on second node
EdgeBasedOnSecondNodeComparator cmp2 = new EdgeBasedOnSecondNodeComparator();
Collections.sort(edges, cmp2);
// build column index and pointer
curNode = edges.elementAt(0).getSecondNode();
curPtr = 0;
for (int i = 0; i < edgeSize; ++i) {
Edge e = edges.elementAt(i);
colIndex.add(e.getFirstNode());
if (curNode != e.getSecondNode()) {
colPtr.set(curNode, curPtr);
curNode = e.getSecondNode();
curPtr = i;
}
}
colPtr.set(curNode, curPtr);
}
獲得一個節(jié)點的出邊
/*
* getOutEdges 返回結(jié)點所有的出邊(即所有由結(jié)點指出的邊)
*
* @param node 要查找的結(jié)點
*
* @return 返回結(jié)點所有的出邊組成的向量
*/
@Override
public Vector<Edge> getOutEdges(int node) {
Vector<Edge> res = new Vector<Edge>();
int startIndex = getStartIndex(node, true);
if (startIndex == -1) {
// 沒有出邊的點
return null;
}
int endIndex = getEndIndex(node, true);
float value;
Edge e;
int outNode;
for (int index = startIndex; index < endIndex; ++index) {
value = weight.elementAt(index);
outNode = rowIndex.elementAt(index);
e = new Edge(node, outNode, value);
res.add(e);
}
return res;
}
獲得一個節(jié)點的入邊
?
/*
* getInEdges 獲取結(jié)點所有的入邊(即所有指向結(jié)點的邊)
*
* @param node 要查找的結(jié)點
*
* @return 返回所有由結(jié)點入邊組成的向量
*/
@Override
public Vector<Edge> getInEdges(int node) {
Vector<Edge> res = new Vector<Edge>();
int startIndex = getStartIndex(node, false);
// 沒有入邊的點
if (startIndex == -1) {
return null;
}
int endIndex = getEndIndex(node, false);
float value;
Edge e;
int inNode;
for (int index = startIndex; index < endIndex; ++index) {
inNode = colIndex.elementAt(index);
value = getWeight(inNode, node);
e = new Edge(inNode, node, value);
res.add(e);
}
return res;
}
這里訪問方式就跟按行訪問不一樣了,行訪問時,直接讀weight向量里面對應(yīng)的值就行了,這里不行,應(yīng)該weight向量是按行訪問順序存的。我的解決方法,獲取入節(jié)點,然后對整個節(jié)點對按行訪問獲得對應(yīng)值。這樣雖然繞了一下,但是對于稀疏圖來說,基本上也是常數(shù)級的。下面是getWeight的代碼
/*
* getWeight 獲得特定邊的weight
*/
private float getWeight(int row, int col) {
int startIndex = getStartIndex(row, true);
if(startIndex==-1)
return 0;
int endIndex = getEndIndex(row, true);
for (int i = startIndex; i < endIndex; ++i) {
if (rowIndex.elementAt(i) == col)
return weight.elementAt(i);
}
return 0;
}
最后是對行或者列全0時的特殊處理,這里處理,體現(xiàn)在從指針向量獲取開始和結(jié)束位置的函數(shù)上
/*
* getStartIndex 獲取特定頂點的開始索引
*/
private int getStartIndex(int node, boolean direction) {
// true : out edge
if (direction)
return rowPtr.elementAt(node);
else
return colPtr.elementAt(node);
}
?
/*
* getEndIndex 獲取特定頂點的結(jié)束索引
*/
private int getEndIndex(int node, boolean direction) {
// true:out edge
if (direction) {
int i = 1;
while ((node + i) < nodeCount) {
if (rowPtr.elementAt(node + i) != -1)
return rowPtr.elementAt(node + i);
else
++i;
}
return rowPtr.elementAt(nodeCount);
} else {
int i = 1;
while ((node + i) < nodeCount) {
if (colPtr.elementAt(node + i) != -1)
return colPtr.elementAt(node + i);
else
++i;
}
return colPtr.elementAt(nodeCount);
}
}
這里我只實現(xiàn)了兩個最簡單的功能,獲取入邊和出邊。一方面是因為,對于我做的東西,這兩個函數(shù)就夠了,另一方面,對于一個圖來說,有這兩個函數(shù),其他函數(shù)都可以相應(yīng)實現(xiàn)。
相關(guān)文章
C++高性能服務(wù)器框架之協(xié)程調(diào)度模塊
這篇文章主要介紹了C++高性能服務(wù)器框架中的協(xié)程調(diào)度模塊,文中通過代碼示例介紹的非常詳細,對我們的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2023-06-06
C++中4種強制類型轉(zhuǎn)換的區(qū)別總結(jié)
C++風(fēng)格的類型轉(zhuǎn)換提供了4種類型轉(zhuǎn)換操作符來應(yīng)對不同場合的應(yīng)用。下面這篇文章主要給大家介紹了C++中4種強制類型轉(zhuǎn)換的區(qū)別,有需要的朋友們可以參考借鑒,下面來一起看看吧。2016-12-12
C++實現(xiàn)LeetCode(189.旋轉(zhuǎn)數(shù)組)
這篇文章主要介紹了C++實現(xiàn)LeetCode(189.旋轉(zhuǎn)數(shù)組),本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下2021-07-07
C++實現(xiàn)猜數(shù)小游戲的實現(xiàn)
這篇文章主要介紹了C++實現(xiàn)猜數(shù)小游戲的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02

