遞迴——深度優先搜尋(DFS)——以滑雪問題為例(自頂而下)

chanxe發表於2022-03-11

一、問題:滑雪

問題描述:小明喜歡滑雪,為了獲得速度,滑的區域必須向下傾斜,而且當你滑到坡底,你不得不再次走上坡或者等待升降機來載你。小明想知道在一個區域中最長底滑坡。區域由一個二維陣列給出。陣列的每個數字代表點的高度。下面是一個例子:

1  2  3  4 5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9

 

一個人可以從某個點滑向上下左右相鄰四個點之一,當且僅當高度減小。在上面的例子中,一條可滑行的滑坡為 24-17-16-1 . 當然 25-24-23-...-3-2-1 更長。事實上,這是最長的一條.

 

  • 輸入描述:輸入的第一行表示區域的行數 R和列數 C (1 ≤R,C≤100). 下面是 RR 行,每行有 C 個整數,代表高度 h ,0≤h≤10000.

  • 輸出描述:輸出最長區域的長度.

  • 樣例輸入

 

5 5
1 2 3 4 5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9

  

  • 樣例輸出:25

二、問題分析

  • 簡述:從二維陣列中,找到一條滿足條件(一個人可以從某個點滑向上下左右相鄰四個點之一,當且僅當高度減小)節點的個數。

  • 可以採用DFS演算法,搜尋出以一個節點為起點,最遠可以抵達的地方,並記錄長度(其中節點個數)

三、問題的圖解

1 2 3 4 5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9

  

依此輸入為例(以21所在位置為起點)

下面展示三張搜尋的過程

 

 

#include<cstdio>
#include<algorithm>
using namespace std;
const int N=105,mod=1e9+7;
int a[N][N];
int n,m;
int tmp;
 
int dx[4]={1,0,-1,0};
int dy[4]={0,1,0,-1};
int h[N][N];//記錄座標(i,j)的答案,以(i,j)為起點的路徑最長多少 
 
int dfs(int x,int y){//以(x,y)為起點的遍歷
    int mx=0;
    if(h[x][y])return h[x][y];// 記錄為0的路徑避免重複計算 
    for(int i=0;i<4;i++){
        int nx=x+dx[i],ny=y+dy[i];
        if(nx>=1&&nx<=n&&ny>=1&&ny<=m&&a[nx][ny]<a[x][y]){//遞迴出口:找不到更低的去處 
            mx=max(mx,dfs(nx,ny));//遞迴體:只要能在周圍找到能去的路徑,遞迴呼叫去找能去路徑的最大值 
        } 
    }
    return h[x][y]=mx+1;//最終求出周圍路徑最大值+1就是(x,y)為起點的最長滑坡長度 
}
// dfs: 這個演算法會盡可能深的搜尋樹的分支 ,#include<cstdio>
#include<algorithm>
using namespace std;
const int N=105,mod=1e9+7;
int a[N][N];
int n,m;
int tmp;
 
int dx[4]={1,0,-1,0};
int dy[4]={0,1,0,-1};
int h[N][N];//記錄座標(i,j)的答案,以(i,j)為起點的路徑最長多少 
 
int dfs(int x,int y){//以(x,y)為起點的遍歷
    int mx=0;
    if(h[x][y])return h[x][y];// 記錄為0的路徑避免重複計算 
    for(int i=0;i<4;i++){
        int nx=x+dx[i],ny=y+dy[i];
        if(nx>=1&&nx<=n&&ny>=1&&ny<=m&&a[nx][ny]<a[x][y]){//遞迴出口:找不到更低的去處 
            mx=max(mx,dfs(nx,ny));//遞迴體:只要能在周圍找到能去的路徑,遞迴呼叫去找能去路徑的最大值 
        } 
    }
    return h[x][y]=mx+1;//最終求出周圍路徑最大值+1就是(x,y)為起點的最長滑坡長度 
}
// dfs: 這個演算法會盡可能深的搜尋樹的分支 ,時間複雜度為O(N) 
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            scanf("%d",&a[i][j]);
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            dfs(i,j);
        }
    }
    int ans=0;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            ans=max(ans,h[i][j]);
        }
    }
    printf("%d\n",ans);
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            scanf("%d",&a[i][j]);
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            dfs(i,j);
        }
    }
    int ans=0;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            ans=max(ans,h[i][j]);
        }
    }
    printf("%d\n",ans);
}

  

五、遞迴思想總結

  • 歸納假設:一個節點為起點的最深路徑為周圍節點最深路徑加一

  • 遞迴模型

    • f(x,y):路徑的長度

    • f(x,y)==1 當四周找不到更低的地方(無處可去)(遞迴出口)

    • f(x,y)==max(f(x-1,y),f(x+1,y),f(x,y+1),f(x,y-1))+1(遞迴體)

六、感悟:

先將大問題分解成一個基礎問題+一個小一層級問題,並用遞迴模型表示出來,利用圖文結合方法加快效率。最後落地。

 

相關文章