小白學演算法 DFS排列組合問題

2021-10-03 18:24:25 字數 3439 閱讀 1407

一些用語及事項的說明,方便大家理解。

1.陣列從一號索引開始用,不用0號索引。

2.dfs遞迴零次時稱為深度1,遞迴一次稱為深度2,以此類推。

3.每個深度dfs要進行一些操作,統稱某深度運算空間中的計算。

4.以圖的遍歷講解組合排列的求解

問題:給定乙個含有n個元素的數表,從中選定k個數,可以構成多少種排列

輸出每種排列 和總的排列 數,每個數三個場寬。

上**:

#include

#include

using

namespace std;

int number[

100]

;//儲存數表

int book[

100]

;//哨兵 ,初始為0,代表未使用過

int num[

100]

;//儲存找到的排列

int n,k,count;

//n為數表的長度,k為每個排列數包含的元素個數,count為總的排列數

void

dfs(

int step)

for(

int i =

1;i<=n;i++)}

return;}

intmain()

數的組合問題:

給定乙個含有n個元素的數表,從中選定k個數,可以構成多少個組合

輸出每種組合和總的組合數,每個數三個場寬

上**:

#include

#include

using

namespace std;

int number[

100]

;//存放數表

int num[

100]

;//存放查詢到的組合數

int count,n,k;

//count記錄總的組合數個數,n為數表長度,k為每個組合數包含元素的個數

void

dfs(

int step,

int startx)

//step為深度,startx表示當前深度迴圈開始的索引

cout << endl;

count++

;//組合數個數 +1

return;}

for(

int i=startx;i<=n;i++)}

intmain()

分析的重點在於,這兩個**不同的原因。通過**原因,深入理解迴圈和哨兵的作用。

我們很容易看出來,這兩份**的差異所在:

1.迴圈的起點不同。

2.乙個使用了哨兵,乙個沒有使用。

下面我們通過圖來**他們不同的原因。

問題引入:我們將排列組合的問題,放在圖中思考。如下圖;

問題:在圖中的3個元素中,任意選取兩個元素構成乙個排列,總共有多少種選項方案?

解決:第一步:當我們從 1 開始走的時候,我們可以得到 12 和13兩種排列。

第二步:當我從2開始走的時候,依照排列的原則,我可以有21和23兩種排列方案。

那麼問題來了(記為問題x):

當我從2開始走的時候,我如何保證在深度為2的運算空間裡,我可以遍歷除了已經遍歷過的元素(這裡就是2)之外的所有元素?

我設定迴圈為遍歷1~n的元素,那麼我就可以在每個深度的運算空間裡遍歷所有的元素。這裡是所有的元素,當然也包括了已經遍歷過的元素(這裡就是2)。那麼如何剔除遍歷過的元素呢?

當我們每遍歷乙個元素,就把這個元素標記為已用,那麼在下乙個深度空間裡,通過哨兵的報告,我們就可以避免再次遍歷已經遍歷過的元素了。這就是標記點a的作用。至此,問題x不解決了嗎?

那麼,還有乙個問題:標記點b是做什麼的?為什麼在回溯時要將當前元素又標記為未用呢?

繼續**,我們來舉個栗子:

假如沒有標記點b

當我們以1為起點,找到12和13後,回溯到深度為1的運算空間,然後大問題來了,哨兵報告說:「已經沒有可用元素了!」。

這明顯不對啊,這時候標記點b的作用就體現出來了。

我們來看看有標記點b的情況:

找到12和13後,我們又回溯到了深度為1的運算空間,這時候,由於標記點b的存在,1號和2號元素又被標記為未用哨兵報告你說:「2號元素可用!」,然後我們就可以以2為起點,繼續查詢新的排列方案。

滿迴圈:即指遍歷所有元素,保證在每個深度的運算空間空間裡可以遍歷所有的元素。

哨兵:標記點a,保證在查詢單個排列時,不會使用當前排列已經使用過的元素;

標記點b,保證在查詢另乙個排列方案時,可以使用已經找到的排列時用過的元素。

標記點a:當我從1開始,遞迴進入深度為2的運算空間時,我不能再使用1號元素。12(或者13)就是當前排列,1號元素就是當前排列已經使用過的元素。

**標記點b:**當我找到12和13的時候(這時候12和13就是已經找到的排列,2就是已經找的排列用過的元素),我仍然可以以2為起點再次查詢新的排列方案。

之前我們說了。組合和排列的**塊在迴圈和哨兵的使用方面存在差異,

對比排列,我們發現組合沒有使用哨兵,並且不是滿迴圈。顯而易見,組合的**使用的迴圈,他的起點就是當前dfs的深度(暫且稱這種迴圈為變起點式迴圈)。

又到了舉栗子的時間(仍然使用上面那幅圖):

假如我們使用的是哨兵+滿迴圈組合:

當我們從2開始遍歷,,那麼我們就會得到21和23,(從1遍歷時得到了12)那麼21和12顯然是重複的組合。

我們先來**為什麼會出現21這種重複的組合。顯而易見,是因為我們遍歷了起點2之前的元素1,如果我們不遍歷起點之前的元素,不就不會出現重複組合了嗎?如何做到呢?

通過變起點迴圈,我們很容易就保證了絕對不會遍歷起點之前的元素。

變起點式迴圈只遍歷數表中起點之後餘下的數,所以標記點a標記點b所解決的問題在這裡都不會出現,自然也就不需要哨兵了。(不再贅述)。

dfs排列組合問題

這幾天回顧了一下dfs演算法。這個演算法我覺得挺重要的,思想很基礎,是以後學習很多演算法的基礎吧,簡而言之就是遞迴,才開始接觸肯定會覺得,哇,很神奇,不知道怎麼做到的,其實只需要多加練習就可以了,當初我開始刷題的時候也是懵了很久。深度優先首先接觸來自於學資料結構的圖。深度優先是一種遍歷搜尋的方法。它...

DFS排列組合

初學,加強一下理解 每次dfs都是從1到n中 選出乙個數放在排列中第x個位置 void dfs int x int main void for int i 1 i r i dfs 1 關於回溯 我們可以以乙個1到3的全排列為例,則全排列過程可以想象為你穿過三層牆,每層牆都有三個編號為1 2 3的門,...

演算法 排列組合問題

從m個數裡面選n個 m n 全部用到 棧的儲存結構 遞迴方法 用棧儲存選取的那n個數,選取就push,換乙個就pop再push 遞迴方法 要選取n個數,已經選取了k個,k n時表示一種組合結果完成 選擇乙個再之後遞迴 k 1 這是最簡單的問題,選擇n個數,每個數有m個選擇 每次選擇時只需要迴圈這m個...