遞迴_CH0301_遞迴實現指數型列舉_遞迴演算法正確性證明範例
簡而言之本題要求列印集{1, 2,..., n}的所有子集(列印時每個子集中的所有元素位於同一行, 每行中的元素遞增列印, 空集對應空行)
先給出如下AC程式碼, 然後給出其正確性的形式化證明
//CH0301_遞迴實現指數型列舉 #include <iostream> #include <cstdio> #include <vector> using namespace std; int n;//輸出{1, ... n}的所有子集 vector<int> choosn;//當前已經選擇的元素集 void solve(int cur){ if(cur > n){ for(int i = 0; i < choosn.size(); ++i) cout << choosn[i] << " "; cout << endl; return; } solve(cur + 1); choosn.push_back(cur), solve(cur + 1), choosn.pop_back(); } int main(){ scanf("%d", &n); solve(1); return 0; }
對於上述演算法正確性的證明:
設集 = { x | x為對於solve(i)的呼叫, 且x滿足本次solve(i)執行初始時刻choosn[0...choosn.size() - 1]嚴格遞增(如果choosn非空)且對應於{1,...,i - 1}的一個子集 }
歸納起點: 對於集中的所有元素, 直接觀察其程式碼邏輯可得: 中的所有元素均可打印出當前choosn對應集合和集{n}的所有子集的並集, 且滿足同一個子集中的元素按遞增順序列印在同一行中, 並於執行完畢後還原choosn為初始狀態
遞推: 假設對於對於集
綜上述: 對於集中的所有元素均可打印出當前choosn對應集合(此時為空集)和集{1,...,n}的所有子集的並集, 且滿足同一個子集中的元素按遞增順序列印在同一行中, 並於執行完畢後還原choosn為初始狀態, 這也正是題目要求的結果, 從而演算法正確性得證.
下面提出一個擴充套件性問題, 將每個子集中的元素按照遞增排序後(視為一個字串), 如何按照字典序遞增列印{1,...,n}的所有子集?(約定: 空集對應空行且列印在第一行), 下面先給出實現程式碼, 隨後對該演算法正確性證明的關鍵部分給予說明, 具體證明過程不再贅述.
//CH0301_遞迴實現指數型列舉_按字典序遞增列印每個子集
#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
int n;
vector<int> choosn;
void solve(int cur, bool show){
if(show){
for(int i = 0; i < choosn.size(); ++i) cout << choosn[i] << " "; cout << endl;
}
if(cur > n) return;
choosn.push_back(cur), solve(cur + 1, true), choosn.pop_back();
solve(cur + 1, false);
}
int main(){
scanf("%d", &n), solve(1, true);
return 0;
}
為證明該程式的正確性, 依舊定義集合:
設集 = { x | x為對於solve(i)的呼叫, 且x滿足本次solve(i)執行初始時刻choosn[0...choosn.size() - 1]嚴格遞增(如果choosn非空)且對應於{1,...,i - 1}的一個子集 }
歸納起點: 對於集中的元素, 直接觀察其程式碼邏輯可得: 若初始引數show為true, 則中對應元素均可打印出當前choosn對應集合和集{n}的所有子集(包括空集)的並集, 且滿足同一個子集中的元素按遞增順序列印在同一行中, 並於執行完畢後還原choosn為初始狀態, 若初始引數show為false, 則中對應元素均可打印出當前choosn對應集合和集{n}的所有子集(不包括空集)的並集.
遞推: 假設對於對於集 (2 =< k <= n)中的元素, 若初始引數show為true, 則中對應元素均可打印出當前choosn對應集合和集{k,...,n}的所有子集(包括空集)的並集, 且滿足同一個子集中的元素按遞增順序列印在同一行, 並於執行完畢後還原choosn為初始狀態. 若初始引數show為false, 則中對應元素均可打印出當前choosn對應集合和集{k,...,n}的所有子集(不包括空集)的並集. 那麼對於中的任意元素, 其是否列印初始choosn對的應集合, 可由第9行正確處理, 將所有choosn對應集合和集{k - 1,...,n}的所有子集(不包括空集)的並集分為兩類, 第一類包含元素k - 1, 第二類不包含k - 1, 易知第二類所有集合的字典序大於第一類所有集合, 因此程式第13行先列印第一類集合, 第14行後列印第二類集合是正確的,據此對於集合中的元素, 若初始引數show為true, 則中對應元素均可打印出當前choosn對應集合和集{k - 1,...,n}的所有子集(包括空集)的並集, 且滿足同一個子集中的元素按遞增順序列印在同一行, 並於執行完畢後還原choosn為初始狀態. 若初始引數show為false, 則中對應元素均可打印出當前choosn對應集合和集{k - 1,...,n}的所有子集(不包括空集)的並集.
綜上述: 對於集中的元素, 若初始引數show為true, 則中對應元素均可打印出當前choosn對應集合和集{1,...,n}的所有子集(包括空集)的並集, 且滿足同一個子集中的元素按遞增順序列印在同一行, 並於執行完畢後還原choosn為初始狀態. 若初始引數show為false, 則中對應元素均可打印出當前choosn對應集合和集{1,...,n}的所有子集(不包括空集)的並集, 也即solve(1, true)按字典序遞增列印集合{1,...,n}的所有子集(包括空集), 從而程式正確性得證.