圖的遍歷

圖的遍歷方法一般有兩種:深度優(yōu)先搜索(DFS)和廣度優(yōu)先搜索(BFS)

采用深度優(yōu)先搜索(DFS)遍歷圖

沿著一條路徑直到無法繼續(xù)前進,才退回到路徑上離當前頂點最近的還存在未訪問分支頂點的岔道口象迎,并前往訪問那些未訪問分支頂點揽趾,直到遍歷完整個圖。
兩個概念:
連通分量:在無向圖中亭姥,如果兩個頂點之間可以相互到達(可以是通過一定路徑間接到達)稼钩,那么就稱這兩個頂點連通。如果G(V,E)的任意兩個頂點都連通达罗,則稱圖G為連通圖坝撑;否則,稱圖G為非連通圖粮揉,且稱其中的極大連通子圖為連通分量巡李。
強連通分量:在有向圖中,如果兩個頂點可以各自通過一條有向路徑到達另一個頂點扶认,就稱這兩個頂點強連通侨拦。如果圖G(V,E)的任意兩個頂點都強連通,則稱圖G為強連通圖蝠引;否則阳谍,稱圖G為非強連通圖,且稱其中的極大強連通子圖為強連通分量螃概。
把連通分量和強連通分量均稱為連通塊
如果要遍歷整個圖矫夯,就需要對所有連通塊分別進行遍歷。
基本思路:將經(jīng)歷過的頂點設置為已訪問吊洼,在下次遞歸碰到這個頂點時就不再去處理训貌,直到整個圖的頂點都被標記為已訪問。
如已知給定的圖時一個連通圖冒窍,則只需要一次DFS就能完成遍歷
偽代碼:

DFS(u) {    //訪問頂點u 
    vis[u] = true;  //設置u已被訪問 
    for(從u出發(fā)能到達的所有頂點v)  //枚舉從u出發(fā)可以到達的所有頂點v
        if vis[v] == false      //如果v未被訪問 
            DFS(v);     //遞歸訪問v 
}

DFSTrave(G) {   //遍歷圖G 
    for(G的所有頂點u)    //對G的所有頂點u
        if vis[u] == false  //如果u未被訪問
            DFS(u);     //訪問u所在的連通塊 
} 

實現(xiàn)鄰接矩陣和鄰接表前需要先定義MAXV為最大頂點數(shù)递沪、INF為一個很大的數(shù)字

const int MAXV = 1000;    //最大頂點數(shù)
const int INF = 1000000000;  //設INF為一個很大的數(shù)

鄰接矩陣版本

int n, G[MAXV][MAXV];       //n為頂點數(shù),MAXV為最大頂點數(shù)
bool vis[MAXV] = {false};   //如果頂點i已被訪問综液,則vis[i] == true款慨。初值為false

void DFS(int u, int depth) {    //u為當前訪問的頂點標號,depth為深度 
    vis[u] = true;      //設置u已被訪問
    //如果需要對u進行一些操作谬莹,可以在這里進行
    //下面對所有從u出發(fā)能到達的分支頂點就像枚舉
    for(int v = 0; v < n; v++) {
        if(vis[v] == false && G[u][v] != INF) {     //如果v未被訪問檩奠,且u可到達v 
            DFS(v, depth + 1);      //訪問v桩了, 深度加一 
        }
    } 
} 

void DFSTrave() (
    for(int u = 0; u < n; u++) {        //對每個頂點u 
        if(vis[u] == false) {   //如果u未被訪問    
            DFS(u, 1);      //訪問u和u所在的連通塊,1表示初始為第一層 
        }
    }
) 

鄰接表版本

vector<int> Adj[MAXN];  //圖G的鄰接表
int n;      //n為頂點數(shù)埠戳,MAXV為最大頂點數(shù)
bool vis[MAXV] = {false};   //如果頂點i已被訪問井誉,則vis[i] == true。初值為false
 
void DFS(int u, int depth) {    //u為當前訪問的頂點標號整胃,depth為深度 
    vis[u] = true;  //設置u已被訪問 
    //如果需要對u進行一些操作颗圣,可以在這里進行
    for(int i = 0; i < Adj[u].size(); i++) {
        int v = Adj[u][i];
        if(vis[v] == false){
            DFS(v, depth + 1);  //訪問v, 深度加一 
        }
    } 
} 

void DFSTrave() (
    for(int u = 0; u < n; u++) {        //對每個頂點u 
        if(vis[u] == false) {   //如果u未被訪問    
            DFS(u, 1);      //訪問u和u所在的連通塊屁使,1表示初始為第一層 
        }
    }
) 

實例:
給出若干人之間的通話長度(視為無向邊)在岂,這些通話將他們分為若干組。每個組的總邊權設為該組內(nèi)的所有通話的長度之和屋灌,而每個人的點權設為該人參與的通話長度之和〗喽危現(xiàn)在給定一個閥值K,且只要一個組的總邊權超過K共郭,并滿足成員人數(shù)超過2祠丝,則將該組視為”犯罪團伙(Gang)“,而該組內(nèi)點權最大的人視為頭目除嘹。要求輸出”犯罪團伙“的個數(shù)写半,并按頭目姓名字典序從小到大的順序輸出每個”犯罪團伙“的頭目姓名和成員人數(shù)。
思路:

  1. 解決姓名和編號的對應關系尉咕〉可使用map<string, int>直接建立字符串與整型的映射關系,也可使用字符串hash的方法將字符串轉(zhuǎn)換為整型年缎。編號與姓名的對應關系則可以直接使用string數(shù)字進行定義悔捶,或者使用map<int, string>也是可以的
  2. 需要獲得每個人的點權,即與之相關的通話記錄的時長之和单芜,可以在讀入時進行處理蜕该。該步在求與某個點相連的邊的邊權之和
  3. 圖的遍歷。使用DFS遍歷每個連通塊洲鸠,目的是獲取每個連通塊的頭目(即連通塊內(nèi)點權最大的結點)堂淡、成員個數(shù)、總邊權
  4. 通過步驟3可以獲得連通塊的總邊權totalValue扒腕。如果totalValue大于給定的閥值K绢淀,且成員人數(shù)大于2,則說明該連通塊是一個團伙瘾腰,將該團伙的信息存儲下來
    可以定義map<string, int>皆的,來建立團伙頭目的姓名與成員人數(shù)的映射關系。由于map中元素自動按鍵從小到大排序蹋盆。

注意點:

  1. 由于通話記錄的條數(shù)最多有1000條祭务,意味著不同的人可能有2000人内狗,因此數(shù)組大小必須在2000以上
  2. 可以使用使用結構體來存放頭目姓名與成員人數(shù)怪嫌,但會增加一定代碼量
struct Gang {
    string head;    //團伙頭目
    int numMember;  //成員數(shù)量
}arrayGang[maxn];
int numGang = 0;    //團伙個數(shù)
bool cmp(Gang a, Gang b) {
    return a.head < b.head;     //按頭目姓名的字典序從小到大排序 
}
  1. 由于每個結點在訪問后不應再次被訪問义锥,但是圖中可能有環(huán),即遍歷過程中發(fā)生一條邊連接已訪問結點 的情況岩灭。此時為了邊權不被漏加拌倍,需要先累加邊權,再去考慮結點遞歸訪問的問題
  2. 本題也可用并查集解決
//輸入1
8 59
AAA BBB 10
BBB AAA 20
AAA CCC 40
DDD EEE 5
EEE DDD 70
FFF GGG 30
GGG HHH 20
HHH FFF 10
//輸出1
2
AAA 3
GGG 3
//輸入2
8 70
AAA BBB 10
BBB AAA 20
AAA CCC 40
DDD EEE 5
EEE DDD 70
FFF GGG 30
GGG HHH 20
HHH FFF 10
//輸出2
0
#include<iostream>
#include<string>
#include<map>
using namespace std;
const int maxn = 2010;  //總人數(shù)
const int INF = 1000000000; //無窮大

map<int, string> intToString;   //編號->姓名
map<string, int> stringToInt;   //姓名->編號
map<string, int> Gang;          //head->人數(shù)
int G[maxn][maxn] = {0}, weight[maxn] = {0};    //鄰接矩陣G噪径,點權weight
int n, k, numPerson = 0;    //邊數(shù)n柱恤、下限k、總人數(shù)numPerson
bool vis[maxn] = {false};   //標記是否被訪問

//DFS函數(shù)訪問單個連通塊找爱,nowVisit為當前訪問的編號
//head為頭目梗顺,numMember為成員編號,totalValue為連通塊的總邊權/
void DFS(int nowVisit, int& head, int& numMember, int& totalValue) {
    numMember++;    //成員人數(shù)+1
    vis[nowVisit] = true;   //標記已被訪問
    if(weight[nowVisit] > weight[head]) {
        head = nowVisit;    //更新頭目 
    } 
    for(int i = 0; i < numPerson; i++) {
        if(G[nowVisit][i] > 0) {    //如果nowVisit能到達i 
            totalValue += G[nowVisit][i];   //連通塊的總邊權增加該邊權
            G[nowVisit][i] = G[i][nowVisit] = 0;    //刪除這條邊车摄,防止回頭
            if(vis[i] == false) {
                DFS(i, head, numMember, totalValue);
            } 
        }
    }
} 

//DFSTrave函數(shù)遍歷真?zhèn)€圖寺谤,獲得每個連通塊的信息
void DFSTrave() {
    for(int i = 0; i < numPerson; i++) {
        if(vis[i] == false) {
            int head = 1, numMember = 0, totalValue = 0;
            DFS(i, head, numMember, totalValue);
            if(numMember > 2 && totalValue > k) {
                Gang[intToString[head]] = numMember;
            }
        }
    }
} 

//change函數(shù)返回姓名str對應的編號
int change(string str) {
    if(stringToInt.find(str) != stringToInt.end()) {  //如果str已經(jīng)出現(xiàn)過, 
        return stringToInt[str];            //返回編號 
    } else {
        stringToInt[str] = numPerson;   //str的編號為numPerson
        intToString[numPerson] = str;   //numPerson對應str
        return numPerson++; 
    }
} 

int main() {
    int w;
    string str1, str2;
    cin >> n >> k;
    for(int i = 0; i < n ; i++) {
        cin >> str1 >> str2 >> w;
        int id1 = change(str1);
        int id2 = change(str2);
        weight[id1] += w;
        weight[id2] += w;
        G[id1][id2] += w;
        G[id2][id1] += w;
    } 
    DFSTrave();
    cout << Gang.size() << endl;
    map<string, int>::iterator it;
    for(it = Gang.begin(); it != Gang.end(); it++) {
        cout << it->first << " " << it->second << endl;
    }
    return 0;
}

采用廣度優(yōu)先搜索(BFS)遍歷圖

通過反復取出隊首頂點吮播,將該頂點可到達的未曾加入過隊列(而不是未訪問)的頂點全部入隊变屁,直到隊列為空時遍歷結束。
偽代碼:

BFS(u) {    //遍歷u所在的連通塊 
    queue q; //定義隊列q
    將u入隊;
    inq[u] = true;  //設置u已入過隊
    while(q非空) {    //只要隊列非空 
        取出q的隊首元素u進行訪問
        for(從u出發(fā)可達的所有頂點v) { //枚舉從u能直接到達的頂點v 
            if(inq[v] == false) {   //如果v未曾加入過隊列 
                將v入隊意狠;
                inq[v] = true;  //設置v已加入過隊列 
            }
        } 
    } 
} 

BFSTrave(G) {   //遍歷圖G 
    for(G的所有頂點u)        //枚舉G的所有頂點 
        if(inq[u] == false) {   //如果u未曾入過隊列 
            BFS(u);     //遍歷u所在的連通塊 
        } 
} 

鄰接矩陣版本

bool inq[MAXV] = {false};   //記錄頂點是否入過隊列 
 
void BFS(int u) {
    queue<int> q;
    q.push(u);
    inq[u] = true;
    while(!q.empty()) {
        int u = q.front();
        q.pop();
        for(int v = 0; v < n; v++) {
            if(inq[v] == false && G[u][v] != INF){
                q.push(v);
                inq[v] = true;
            }
        }
    }
}

void BFSTrave() {
    for(int u = 0; u < n; u++) {
        if(inq[u] == false) {
            BFS(u);
        }
    }
}

鄰接表版本

vector<int> Adj[MAXN];
int n;
bool inq[MAXV] = {false};

void BFS(int u) {
    queue<int> q;
    q.push(u);
    inq[u] = true;
    while(!q.empty()) {
        int u = q.front();
        q.pop();
        for(int i = 0; i < Adj[u].size();i++) {
            int v = Adj[u][i];
            if(inq[v] == false) {
                q.push(v);
                inq[v] = true;
            }
        }
    }
} 

void BFSTrave() {
    for(int u = 0; u < n; u++) {
        if(inq[u] == false) {
            BFS(u);
        }
    }
}

如果需要存放頂點層號粟关,需要定義結構體Node,存放頂點的編號和層號

struct Node {
    int v;
    int layer;
};

vector<Node> Adj[N];

void BFS(int s) {
    queue<Node> q;
    Node start;
    start.v = s;
    start.layer = 0;
    q.push(start);
    inq[start.v] = true;
    inq(!q.empty()) {
        Node topNode = q.front();
        q.pop();
        int u = topNode.v;
        for(int i = 0; i < Adj[u].size(); i++) {
            Node next = Adj[u][i];
            next.layer = topNode.layer + 1;
            if(inq[next.v] == false) {
                q.push(next);
                inq[next.v] = true;`
            }
        }
    } 
}

實例:
在微博中环戈,每個用戶都可能被若干個其他用戶關注闷板。而當該用戶發(fā)布一條信息時,他的關注者就可以看到這條信息并選擇是否轉(zhuǎn)發(fā)它院塞,且轉(zhuǎn)發(fā)的信息也可以被轉(zhuǎn)發(fā)者的關注者再次轉(zhuǎn)發(fā)遮晚,但同一用戶最多只能轉(zhuǎn)發(fā)該信息一次(信息的最初發(fā)布者不會轉(zhuǎn)發(fā)該信息)。現(xiàn)在給出N個用戶的關注情況(即他們各自關注了哪些用戶)以及一個轉(zhuǎn)發(fā)層數(shù)上限L迫悠,并給出最初發(fā)布消息的用戶編號鹏漆,求在轉(zhuǎn)發(fā)層上限內(nèi)消息最多會被多少用戶轉(zhuǎn)發(fā)

//輸入
7 3
3 2 3 4
0
2 5 6
2 3 1
2 3 4
1 4
1 5
2 2 6
//輸出
4
5
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
using namespace std;
const int MAXV = 1010;

struct Node {
    int id;
    int layer;
};

vector<Node> Adj[MAXV]; //鄰接表
bool inq[MAXV] = {false};

int BFS(int s, int L) {
    int numForward = 0;
    queue<Node> q;
    Node start;
    start.id = s;
    start.layer = 0;
    q.push(start);
    inq[start.id] = true;
    while(!q.empty()) {
        Node topNode = q.front();
        q.pop();
        int u = topNode.id;
        for(int i = 0; i < Adj[u].size(); i++) {
            Node next = Adj[u][i];
            next.layer = topNode.layer + 1;
            if(inq[next.id] == false && next.layer <= L) {
                q.push(next);
                inq[next.id] = true;
                numForward++;
            }
        }
    }
    return numForward;
} 

int main() {
    Node user;
    int n, L, numFollow, idFollow;
    scanf("%d%d", &n, &L);
    for(int i = 1; i <= n; i++) {
        user.id = i;
        scanf("%d", &numFollow);
        for(int j = 0; j < numFollow; j++) {
            scanf("%d", &idFollow);
            Adj[idFollow].push_back(user);
        }
    }
    int numQuery, s;
    scanf("%d", &numQuery); //查詢個數(shù)
    for(int i = 0; i < numQuery; i++) {
        memset(inq, false, sizeof(inq));    //inq數(shù)組初始化 
        scanf("%d", &s);                //起始結點編號 
        int numForward = BFS(s, L);         //BFS,返回轉(zhuǎn)發(fā)數(shù) 
        printf("%d\n", numForward);         //輸出轉(zhuǎn)發(fā)數(shù) 
    } 
    return 0;
}
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末创泄,一起剝皮案震驚了整個濱河市艺玲,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌鞠抑,老刑警劉巖饭聚,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異搁拙,居然都是意外死亡秒梳,警方通過查閱死者的電腦和手機法绵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來酪碘,“玉大人朋譬,你說我怎么就攤上這事⌒丝眩” “怎么了徙赢?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長探越。 經(jīng)常有香客問我狡赐,道長,這世上最難降的妖魔是什么钦幔? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任枕屉,我火速辦了婚禮,結果婚禮上鲤氢,老公的妹妹穿的比我還像新娘搀擂。我一直安慰自己,他們只是感情好铜异,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布哥倔。 她就那樣靜靜地躺著,像睡著了一般揍庄。 火紅的嫁衣襯著肌膚如雪咆蒿。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天蚂子,我揣著相機與錄音沃测,去河邊找鬼。 笑死食茎,一個胖子當著我的面吹牛蒂破,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播别渔,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼附迷,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了哎媚?” 一聲冷哼從身側響起喇伯,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎拨与,沒想到半個月后稻据,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡买喧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年捻悯,在試婚紗的時候發(fā)現(xiàn)自己被綠了匆赃。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡今缚,死狀恐怖算柳,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情荚斯,我是刑警寧澤埠居,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站事期,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏纸颜。R本人自食惡果不足惜兽泣,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望胁孙。 院中可真熱鬧唠倦,春花似錦、人聲如沸涮较。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽狂票。三九已至候齿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間闺属,已是汗流浹背慌盯。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留掂器,地道東北人亚皂。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像国瓮,于是被迫代替她去往敵國和親灭必。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容