回溯與遞歸的區別

遞歸算法是爲了描述問題的某一狀態,必須用到該狀態的上一狀態,而描述上一狀態,又必須用到上一狀態的上一狀態……這種用自已來定義自己的方法,稱爲遞歸定義。比如最出名的一個問題,求斐波那契數列的第i位,如果用遞歸算法做,就需要不斷遞歸得到前兩位的值。

而回溯算法的本質是爲了得到可能存在的所有情況,當一個分支走到底之後,就返回頂點繼續遍歷下一個分支。在這個返回頂點的過程,就需要不斷調用自身函數,這裏的調用自身函數是爲了窮舉所有可能的情況。

之前對遞歸的理解有點狹隘,認爲只要是函數調用自身就算遞歸。所以對於回溯算法來說,回溯的過程函數也會調用自身,就容易將回溯與遞歸混淆。當然,對於有的特殊的問題,這兩者可能沒有明確的界限。

我們來具體地看一到題,用遞歸和回溯兩種方法解答,來看看遞歸和回溯的區別:

子集

給定一組不含重複元素的整數數組 nums,返回該數組所有可能的子集(冪集)。

說明:解集不能包含重複的子集。
示例:
輸入: nums = [1,2,3]
輸出:
[
  [3],
  [1],
  [2],
  [1,2,3],
  [1,3],
  [2,3],
  [1,2],
  []
]

遞歸:

首先我們來看遞歸的做法。假設要求數組[1,2,3]的子集,我們需要先求得[1,2]的子集A1,然後向[1,2]子集的每個元素裏添加3這個元素,得到的新子集A2,再加上原來的子集A1,即爲[1,2,3]的子集。

所以我們開始假設輸出子集爲空,每一步都向子集添加新的整數,並生成新的子集。

          

c++實現如下:

class Solution {
public:
    vector<vector<int>> subsets(vector<int>& nums) {
        vector<vector<int>>v={{}};
        for(int i=0;i<nums.size();++i)
        {
            int n=v.size();
            for(int j=0;j<n;++j)
            {
                v.emplace_back(v[j]);
                v[n+j].emplace_back(nums[i]);
            }
        }
        return v;
    }
};

 

回溯:

以示例爲例,畫出樹狀圖。每次添加一個元素,每次只添加上一節點右邊的元素。每一個節點即爲一個子集。

                         

c++實現如下:

class Solution {
public:
    void dfs(vector<int>&nums,vector<vector<int>>& v,vector<int>& tmp,int start)
    {
        for(int i=start;i<nums.size();++i)
        {
            tmp.emplace_back(nums[i]);//向當前子集添加元素
            v.emplace_back(tmp);//將新子集放入子集數組
            dfs(nums,v,tmp,i+1);
            tmp.pop_back();//上一節點下所有節點遍歷完成,將元素取出以便更換
        }
    }
    vector<vector<int>> subsets(vector<int>& nums) {
        vector<vector<int>>v={{}};//存放所有子集
        vector<int>tmp;//存放一個子集
        dfs(nums,v,tmp,0);
        return v;
    }
};

 

題目來源:leetcode

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章