當(dāng)前位置:首頁(yè) > 芯聞號(hào) > 充電吧
[導(dǎo)讀]首先說明一下,此博文來自我在CSDN上看到的一篇哈密頓回路(有向圖中)的位運(yùn)算算法,出自GDTZX大神之手,(侵刪),雖然剛從校園畢業(yè),但腦子已經(jīng)完全僵住了,花了許久才看懂了這個(gè)算法。 哈密頓回路,

首先說明一下,此博文來自我在CSDN上看到的一篇哈密頓回路(有向圖中)的位運(yùn)算算法,出自GDTZX大神之手,(侵刪),雖然剛從校園畢業(yè),但腦子已經(jīng)完全僵住了,花了許久才看懂了這個(gè)算法。

哈密頓回路,具體到本題之中即從某一個(gè)點(diǎn)開始經(jīng)過所有的點(diǎn)一次后再回到該點(diǎn)的不同路徑數(shù)。對(duì)于這個(gè)不同需要注意兩點(diǎn):

如果我們將路徑經(jīng)過的點(diǎn)按順序?qū)懴?,比如?dāng)n=3時(shí),若存在123和231。此時(shí),我們認(rèn)為這兩條路徑是同一條哈密頓回路。而123和213則是不同的哈密頓回路。

若兩個(gè)點(diǎn)之間有多條邊,經(jīng)過不同的邊的路徑仍然看作同一條哈密頓回路。不同哈密頓回路只和經(jīng)過的點(diǎn)有關(guān)。因此對(duì)于多條邊的情況我們可以將其合并為一條邊來考慮。

對(duì)于哈密頓回路,一個(gè)簡(jiǎn)單的想法就是枚舉所有可能的路徑,判定這個(gè)路徑是否存在。即時(shí)間復(fù)雜度為O(n!)。而題目給定的數(shù)據(jù)范圍為:n <= 12,所以最大可能的枚舉次數(shù)為12! = 479,001,600。

極限的數(shù)據(jù)不到5億,所以我們可以考慮使用暴力來枚舉所有的哈密頓回路。直接采用DFS枚舉我們的每一步,最后判定是否走回到起點(diǎn)。

偽代碼如下:

DFS(int nowVertex, bool visitedVertex, int path, int length)
    visitedVertex[ nowVertex ] = True;
    If (all Vertex is visited) Then
        Count = Count + 1
    Else 
        For (u is next vertex of nowVertex)
            If (not visitedVertex[ u ]) Then
                path[ length ] = u
                DFS(u, visitedVertex, path, length + 1)
            End If
        End For    
    End If
    visitedVertex[ nowVertex ] = False

由于哈密頓回路始終會(huì)經(jīng)過每一個(gè)點(diǎn),所以我們只以1為起點(diǎn)就一定可以找出所有的哈密頓回路。

那么這樣是否能夠解決這道題目呢?我只能說不一定能夠解決。

雖然偽代碼相同,但是根據(jù)實(shí)現(xiàn)的方法會(huì)有不同的運(yùn)行效率,在某些實(shí)現(xiàn)方法下就能夠通過所有的數(shù)據(jù)點(diǎn),在某些實(shí)現(xiàn)方法下就會(huì)超過時(shí)限。

這里我們介紹一種利用位運(yùn)算的實(shí)現(xiàn),能夠使得整個(gè)程序的效率提高很多倍。

首先來看看代碼:

const int MAXN = 14;

int edge[ MAXN ];
int p[1 << MAXN];
int cnt;

void dfs(int nowVertex, int unused) {
    if (!unused) {
        cnt += (edge[nowVertex] & 1);
        return ;
    }
    int rest = unused & edge[ nowVertex ];
    while (rest) {
        int tp = rest & (-rest);
        dfs(p[ tp ], unused - tp);
        rest -= tp;
    }
    return ;
}

int main()
{
    int n, m;
    scanf("%d %d", &n, &m);
    for (int i = 0; i < n; ++i)
        p[ 1 << i ] = i + 1;
    while (m--) {
        int u, v;
        scanf("%d %d", &u, &v);
        edge[u] |= 1 << (v - 1);
    }
    dfs(1, (1 << n) - 2);
    printf("%dn", cnt);
    return 0;
}

我們一個(gè)一個(gè)來解釋每一個(gè)變量的含義:

edge[i]表示點(diǎn)i的next節(jié)點(diǎn)情況,我們用二進(jìn)制表示edge[i],比如當(dāng)edge[i]=01011時(shí):

+---+---+---+---+---+
| 5 | 4 | 3 | 2 | 1 | 右起第j位
+---+---+---+---+---+
| 0 | 1 | 0 | 1 | 1 | 二進(jìn)制位的值
+---+---+---+---+---+

從右起第j位,若為1,則表示存在i到j(luò)的邊;若為0,則表示不存在i到j(luò)的邊。所以edge[i]=01011就表示節(jié)點(diǎn)i可以到達(dá)節(jié)點(diǎn)1,2,4。

p[i]是為了方便查找點(diǎn)的編號(hào)。在edge[i]中若存在01000,我們可以根據(jù)01000=8, 而p[8]=4來快速的通過二進(jìn)制位來定位節(jié)點(diǎn)編號(hào)。

所以有初始化:

for (int i = 0; i < n; ++i)
    p[ 1 << i ] = i + 1;

而通過節(jié)點(diǎn)編號(hào)來找到二進(jìn)制位,則可以直接使用1 << (i - 1)實(shí)現(xiàn)。

我們?cè)谧x入數(shù)據(jù)時(shí),通過位運(yùn)算邊可以記錄下所有點(diǎn)之間的連邊情況:

while (m--) {
    int u, v;
    scanf("%d %d", &u, &v);
    edge[u] |= 1 << (v - 1);    // 記錄u有后繼節(jié)點(diǎn)v
}

unused該二進(jìn)制數(shù)表示我們尚未訪問的節(jié)點(diǎn)集合,同樣的右起第i位表示節(jié)點(diǎn)i,比如unused = 01100

+---+---+---+---+---+
| 5 | 4 | 3 | 2 | 1 | 右起第i位
+---+---+---+---+---+
| 0 | 1 | 1 | 0 | 0 | 二進(jìn)制位的值
+---+---+---+---+---+

表示我們現(xiàn)在深度優(yōu)先搜索已經(jīng)經(jīng)過了節(jié)點(diǎn)1,2,5,而節(jié)點(diǎn)3,4還尚未經(jīng)過。

由于我們是以節(jié)點(diǎn)1作為起點(diǎn),初始化的unused也就要去掉最右邊的1,所以代碼為dfs(1, (1 << n) - 2)

接下來我們逐行解釋dfs函數(shù):

if (!unused) {
    cnt += (edge[nowVertex] & 1);
    return ;
}

當(dāng)我們所有的節(jié)點(diǎn)都經(jīng)過一次之后,unused中一定不存在1,因此有!unused = true。但是此時(shí)并不一定找到了哈密頓回路,我們還需要判斷當(dāng)前節(jié)點(diǎn)是否能回到起點(diǎn),也就是節(jié)點(diǎn)1。若nowVertex可以到達(dá)節(jié)點(diǎn)1,則edge[nowVertex]最右邊1位一定是1,那么就一定有edge[nowVertex] & 1 = 1

int rest = unused & edge[ nowVertex ];

rest表示當(dāng)前節(jié)點(diǎn)還可以走到的未訪問節(jié)點(diǎn)。由于&運(yùn)算的性質(zhì),只有當(dāng)unusededge[ nowVertex ]對(duì)應(yīng)二進(jìn)制位同時(shí)為1時(shí),rest對(duì)應(yīng)的二進(jìn)制位才會(huì)為1。其含義就是該節(jié)點(diǎn)尚未訪問,且節(jié)點(diǎn)nowVertex可以到達(dá)該節(jié)點(diǎn)。

while (rest) {
    int tp = rest & (-rest);
    dfs(p[ tp ], unused - tp);
    rest -= tp;
}

該循環(huán)的作用是枚舉每一個(gè)可以到達(dá)的點(diǎn)。

int tp = rest & (-rest);

這里利用了一個(gè)性質(zhì),即p & -p等于取出p的最右邊的一個(gè)1。舉個(gè)例子p=10110:

   +---+---+---+---+---+
p  | 1 | 0 | 1 | 1 | 0 | 
   +---+---+---+---+---+
-p | 0 | 1 | 0 | 1 | 0 | 
   +---+---+---+---+---+
&  | 0 | 0 | 0 | 1 | 0 | 
   +---+---+---+---+---+

我們不斷的利用這個(gè)性質(zhì)從rest里面取出可以使用的二進(jìn)制位,進(jìn)行dfs(p[ tp ], unused - tp);。同時(shí)再枚舉完一個(gè)節(jié)點(diǎn)后,將其從rest中刪除,即rest -= tp;。

最后我們?cè)偈褂?code>printf("%dn", cnt);來輸出我們找到的方案數(shù)。


在上面DFS的基礎(chǔ)上,我們還可以進(jìn)一步優(yōu)化。

遞歸的過程中,unused很有可能會(huì)出現(xiàn)重復(fù)的情況,比如說從1->3->2->4和從1->2->3->4,對(duì)于dfs(4, unused)來說,此時(shí)的unused值都是相同的。因此我們可以采用記憶化搜索的方式進(jìn)一步降低復(fù)雜度。

定義數(shù)組f[n][unused]表示當(dāng)前節(jié)點(diǎn)為n,且節(jié)點(diǎn)訪問情況為unused時(shí)的方案數(shù)量。

那么有:

f[n][unused] = sigma(f[p][unused + (1 << (n - 1))] | (unused & (1 << (n - 1)) == 0) and (p != n) and (edge[p] & (1 << (n - 1)) != 0))

這對(duì)應(yīng)的是原dfs函數(shù)中下面這段代碼的逆過程。

while (rest) {
    int tp = rest & (-rest);
    dfs(p[ tp ], unused - tp);
    rest -= tp;
}

三個(gè)條件分別為:

(unused & (1 << (n - 1)) == 0)表示當(dāng)前狀態(tài)中右起第n位為0(p != n)表示前驅(qū)結(jié)點(diǎn)不等于n(edge[p] & (1 << (n - 1)) != 0)表示節(jié)點(diǎn)p能夠到達(dá)節(jié)點(diǎn)n

在計(jì)算f[n][unused]的過程中,假設(shè)unused的二進(jìn)制表示中有i個(gè)1,則我們需要事先計(jì)算出所有i+1個(gè)1的狀態(tài)才能夠保證unused + (1 << (p - 1))是正確的結(jié)果。

因此我們?cè)诿杜e過程中,需要按照狀態(tài)中1的個(gè)數(shù)來枚舉。

其偽代碼:

For numberOfOnes = n-1 .. 0
    For (the number of ones of unused equals numberOfOnes)
        For nowVertex = 1 .. n
            For prevVertex = 1 .. n
                If (unused & (1 << (nowVertex - 1)) == 0) and (prevVertex != nowVertex) and (edge[ prevVertex ] & (1 << (nowVertex - 1)) != 0) Then
                    f[ nowVertex ][ unused ] = f[ nowVertex ][ unused ] + f[ prevVertex ][unused + (1 << (nowVertex - 1))]
                End If
            End For
        End For
    End For
End For

對(duì)于f[n][ unused ]數(shù)組,其初始條件為f[1][(1 << n) - 2] = 1。

最后需要將所有的f[n][0]中能夠到達(dá)節(jié)點(diǎn)1的節(jié)點(diǎn)n累加起來,即可得到所有的方案數(shù)。該算法的理論時(shí)間復(fù)雜度為O(2^n*n^2)。

結(jié)果分析

該題目一共只有7名選手通過,大部分提交過該題的選手也都有一定的部分分值。

本題直接采用搜索就能夠通過,拉開差距的主要原因是對(duì)于偽代碼實(shí)現(xiàn)方式的不同,而導(dǎo)致通過的測(cè)試點(diǎn)數(shù)量不同。

另外還有一個(gè)易錯(cuò)點(diǎn)是走完所有節(jié)點(diǎn)之后一定要判斷是否可以回到起點(diǎn)。




本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫?dú)角獸公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

倫敦2024年8月29日 /美通社/ -- 英國(guó)汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認(rèn)證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時(shí)1.5...

關(guān)鍵字: 汽車 人工智能 智能驅(qū)動(dòng) BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務(wù)能7×24不間斷運(yùn)行,同時(shí)企業(yè)卻面臨越來越多業(yè)務(wù)中斷的風(fēng)險(xiǎn),如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報(bào)道,騰訊和網(wǎng)易近期正在縮減他們對(duì)日本游戲市場(chǎng)的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國(guó)國(guó)際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)開幕式在貴陽(yáng)舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國(guó)國(guó)際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機(jī) 衛(wèi)星通信

要點(diǎn): 有效應(yīng)對(duì)環(huán)境變化,經(jīng)營(yíng)業(yè)績(jī)穩(wěn)中有升 落實(shí)提質(zhì)增效舉措,毛利潤(rùn)率延續(xù)升勢(shì) 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長(zhǎng) 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競(jìng)爭(zhēng)力 堅(jiān)持高質(zhì)量發(fā)展策略,塑強(qiáng)核心競(jìng)爭(zhēng)優(yōu)勢(shì)...

關(guān)鍵字: 通信 BSP 電信運(yùn)營(yíng)商 數(shù)字經(jīng)濟(jì)

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺(tái)與中國(guó)電影電視技術(shù)學(xué)會(huì)聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會(huì)上宣布正式成立。 活動(dòng)現(xiàn)場(chǎng) NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長(zhǎng)三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會(huì)上,軟通動(dòng)力信息技術(shù)(集團(tuán))股份有限公司(以下簡(jiǎn)稱"軟通動(dòng)力")與長(zhǎng)三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉