模擬遞迴法
相較於計算法,在單一執行緒上處理速度較快,但也只能用單一執行緒來跑,此例中使用 std::list、std::vector 及 size_t 使得最大處理數量受其限制
排列(Permutation)
#include <list> #include <vector> #include <iostream> #include <iomanip> template <typename T> std::ostream& operator <<(std::ostream& os, std::vector<T>& vt) { os << "{ "; for(std::vector<T>::iterator cit = vt.begin(); cit != vt.end(); ++cit) { os << *cit << ' '; } os << "}"; return os; } void Permutation(const size_t n, const size_t r, void (*pFn)(std::vector<size_t>&)) { if(n == 0 || n < r) return; std::list<size_t> Samples; // 要排列的樣本 std::vector<size_t> Index(r); for(size_t i = 0; i < n; ++i) Samples.push_back(i+1); std::vector<size_t> result(r); size_t Level = 1; bool up = true; while(true) { if(Level == r) { for(size_t i = Samples.size(); i > 0; --i) { result[Level-1] = Samples.front(); Samples.pop_front(); pFn(result); Samples.push_back(result[Level-1]); } --Level; up = false; } else if(Level == 0) { return; } else { if(up) { Index[Level-1] = Samples.size(); result[Level-1] = Samples.front(); Samples.pop_front(); ++Level; } else { --Index[Level-1]; Samples.push_back(result[Level-1]); if(Index[Level-1] > 0) { result[Level-1] = Samples.front(); Samples.pop_front(); ++Level; up = true; } else --Level; } } } } unsigned long long m = 1; // 回叫函數 void Fn(std::vector<size_t>& result) { std::cout << std::setw(10) << m++ << ": " << result << std::endl; } int main(int argc, char* argv[]) { size_t n, r; std::cout << "Permutation nPr" << std::endl; while (std::cout << "Enter n r? " << std::flush, std::cin >> n >> r) { m = 1; Permutation(n, r, Fn); } return 0; }
組合(Combination)
#include <vector> #include <iostream> #include <iomanip> template <typename T> std::ostream& operator <<(std::ostream& os, std::vector<T>& vt) { os << "{ "; for(std::vector<T>::iterator cit = vt.begin(); cit != vt.end(); ++cit) { os << *cit << ' '; } os << "}"; return os; } void Combination(const size_t n, const size_t r, void (*pFn)(std::vector<size_t>& vt)) { if(n == 0 || n < r) return; std::vector<size_t> result(r); bool up = true; size_t Level = 0; while(Level < r) { result[Level] = Level + 1; ++Level; } size_t b = n-r+1; while(true) { if(Level == r) { pFn(result); --Level; up = false; } else if(Level == 0) { if(up == false) { if(result[0] < b) { up = true; ++result[0]; ++Level; } else return; } } else { if(up == false) { if(result[Level] < (b+Level)) { up = true; ++result[Level]; ++Level; } else --Level; } else // if(up == true) { result[Level] = result[Level-1]+1; ++Level; } } } } unsigned long long m = 1;
// 回叫函數 void Fn(std::vector<size_t>& result) { std::cout << std::setw(10) << m++ << ": " << result << std::endl; } int main(int argc, char* argv[]) { size_t n, r; std::cout << "Calculate nCr" << std::endl; while(std::cout << "Enter n r? " << std::flush, std::cin >> n >> r) { m = 1; Combination(n, r, Fn); } return 0; }
計算法
這方法雖然處理費時,但每組計算可獨立運作,很適合用平行處理來加速,即可用多個 CPU 或多台電腦一起計算。另外因可以指定要取得哪一個組合的這個特點,可作為不重覆取得亂數的應用。
組合(Combination)
這方法先由 sflam(Raymond) 提出,也做了一般版及大數版,但他的計算法我看不懂,所以我也做了一套,不過我沒寫大數版。另外 sflam(Raymond) 也提供 Python 版,可在討論中找到
sflam(Raymond) 的一般版
--------------------------------------
[參考資料: https://msdn.microsoft.com/en-us/library/aa289166.aspx] 組合程式. 程式用了一些 C++11 的語法如 auto, 編譯器如不支援自己轉 成 std::vector<T>::const_iterator. 因為用的是內建類型, 計算會有上限, unsigned long long 應該可以 到 64C32, 再大就 overflow 了. [code: C++11] #include <vector> #include <string> #include <iostream> #include <iomanip> template <typename T> std::ostream& operator <<(std::ostream& os, const std::vector<T>& vt) { os << "{ "; for (auto cit = vt.cbegin(); cit != vt.cend(); ++cit) { os << *cit << ' '; } os << "}"; return os; } namespace combination { unsigned long long choose(const size_t n, const size_t r) { if(n < r) return 0; else if (n == r) return 1; // choose(100, 97) ==> choose(100, 3) size_t k2 = (r < n - r) ? r : n - r; // n * (n-1) * (n-2) ... (n-k2+1) // ------------------------------- // 1 * 2 * ... k2 unsigned long long ans = n; for (size_t i = 2; i <= k2; ++i) { ans = (ans * (n - i + 1))/i; } return ans; } size_t LargestV(size_t a, size_t b, unsigned long long x) { long v = a - 1; while (choose(v, b) > x) --v; return v; } void GetMthElement(const size_t n, const size_t r, unsigned long long m, std::vector<size_t>& result) { result.resize(r); size_t a = n; size_t b = r; unsigned long long x = (choose(n, r) - 1) - m; for (size_t i = 0; i < r; ++i) { result[i] = LargestV(a, b, x); x = x - choose(result[i], b); a = result[i]; b = b - 1; } for (size_t i = 0; i < r; ++i) { result[i] = n - result[i]; } } void C(const size_t n, const size_t r) { std::vector<size_t> result(r); const unsigned long long mMax = choose(n, r); for (unsigned long long m = 0; m < mMax; ++m) { GetMthElement(n, r, m, result); std::cout << std::setw(10) << m+1 << ": " << result << std::endl; } } } int main() { size_t n, r; std::cout << "Calculate nCr" << std::endl; while (std::cout << "Enter n r? " << std::flush, std::cin >> n >> r) { combination::C(n, r); } } [/code: C++11] [執行:] Calculate nCr Enter n r? 5 3 1: { 1 2 3 } 2: { 1 2 4 } 3: { 1 2 5 } 4: { 1 3 4 } 5: { 1 3 5 } 6: { 1 4 5 } 7: { 2 3 4 } 8: { 2 3 5 } 9: { 2 4 5 } 10: { 3 4 5 } Enter n r?
sflam(Raymond) 的大數版
--------------------------------------
[code: C++11] #include <vector> #include <string> #include <iostream> #include <iomanip> #include <boost/multiprecision/cpp_int.hpp> using namespace boost::multiprecision; namespace combination { cpp_int choose(const size_t n, const size_t r) { if (n < r) return 0; else if (n == r) return 1; // choose(100, 97) ==> choose(100, 3) size_t k2 = (r < n - r) ? r : n - r; // n * (n-1) * (n-2) ... (n-k2+1) // ------------------------------- // 1 * 2 * ... k2 cpp_int ans = n; for (size_t i = 2; i <= k2; ++i) { ans = (ans * (n - i + 1))/i; } return ans; } size_t LargestV(size_t a, size_t b, cpp_int x) { size_t v = a - 1; while (choose(v, b) > x) --v; return v; } void GetMthElement(const size_t n, const size_t r, cpp_int m, std::vector<size_t>& result) { result.resize(r); size_t a = n; size_t b = r; cpp_int x = (choose(n, r) - 1) - m; for (size_t i = 0; i < r; ++i) { result[i] = LargestV(a, b, x); x = x - choose(result[i], b); a = result[i]; b = b - 1; } for (size_t i = 0; i < r; ++i) { result[i] = n - result[i]; } } void C(const size_t n, const size_t r) { std::vector<size_t> result(r); const cpp_int mMax = choose(n, r); for (cpp_int m = 0; m < mMax; ++m) { GetMthElement(n, r, m, result); std::cout << std::setw(10) << m+1 << ": " << result << std::endl; } } } [/code: C++11]
我的版本
--------------------------------------
--------------------------------------
#include <vector> #include <string> #include <iostream> #include <iomanip> template <typename T> std::ostream& operator <<(std::ostream& os, std::vector<T>& vt) { os << "{ "; for (std::vector<T>::iterator cit = vt.begin(); cit != vt.end(); ++cit) { os << *cit << ' '; } os << "}"; return os; } unsigned long long choose(const size_t n, const size_t r) { if (n < r) return 0; else if (n == r) return 1; // choose(100, 97) ==> choose(100, 3) size_t k2 = (r < n - r) ? r : n - r; // n * (n-1) * (n-2) ... (n-k2+1) // ------------------------------- // 1 * 2 * ... k2 unsigned long long ans = n; for (size_t i = 2; i <= k2; ++i) { ans = (ans * (n - i + 1))/i; } return ans; } size_t V(size_t a, size_t b, long long &m) { if(b == 1) return (size_t)m+1; size_t l = 0; while(true) { long long z = choose(a-l-1,b-1); if(z < (m+1)) { m = m - z; ++l; } else return l+1; } } void GetMthElement(const size_t n, const size_t r, unsigned long long mm, std::vector<size_t>& result) { long long m = mm; result.resize(r); size_t a = n; size_t b = r; size_t c = 0; for(size_t i = 0; i < r; ++i) { result[i] = V(a, b, m) + c; c = result[i]; a = n - c; --b; } } void C(const size_t n, const size_t r) { std::vector<size_t> result(r); const unsigned long long mMax = choose(n, r); for (unsigned long long m = 0; m < mMax; ++m) { GetMthElement(n, r, m, result); std::cout << std::setw(10) << m+1 << ": " << result << std::endl; } } int main(int argc, char* argv[]) { size_t n, r; std::cout << "Calculate nCr" << std::endl; while (std::cout << "Enter n r? " << std::flush, std::cin >> n >> r) { C(n, r); } return 0; }
排列(Permutation)
Permutation 的 C++ 程式碼, 配合之前的 combination, 就可以打出 nPr 所有 組合的排列. NextPermutation() 的邏輯參 考 http://en.wikipedia.org/wiki/Permutation#Generation_in_lexicographic_order. [code: C++] #include <algorithm> #include <iterator> namespace permutation { bool NextPermutation(std::vector<size_t>& result) { if (result.size() < 2) return false; size_t k = result.size() - 2; while (k != 0 && result[k] >= result[k+1]) { --k; } if (k == 0 && result[k] >= result[k+1]) return false; size_t l = result.size() - 1; while (l > k && result[k] >= result[l]) { --l; } std::swap(result[k], result[l]); std::reverse(result.begin() + k + 1, result.end()); return true; } void P(const size_t n, const size_t r) { std::vector<size_t> result(r); const unsigned long long mMax = combination::choose(n, r); for (unsigned long long line = 1, m = 0; m < mMax; ++m) { combination::GetMthElement(n, r, m, result); do { std::cout << std::setw(10) << line++ << ": " << result << std::endl; } while (NextPermutation(result)); } } } int main() { size_t n, r; std::cout << "Calculate nPr" << std::endl; while (std::cout << "Enter n r? " << std::flush, std::cin >> n >> r) { permutation::P(n, r); } } [/code: C++] [執行:] Enter n r? 4 3 1: { 1 2 3 } 2: { 1 3 2 } 3: { 2 1 3 } 4: { 2 3 1 } 5: { 3 1 2 } 6: { 3 2 1 } 7: { 1 2 4 } 8: { 1 4 2 } 9: { 2 1 4 } 10: { 2 4 1 } 11: { 4 1 2 } 12: { 4 2 1 } 13: { 1 3 4 } 14: { 1 4 3 } 15: { 3 1 4 } 16: { 3 4 1 } 17: { 4 1 3 } 18: { 4 3 1 } 19: { 2 3 4 } 20: { 2 4 3 } 21: { 3 2 4 } 22: { 3 4 2 } 23: { 4 2 3 } 24: { 4 3 2 } Enter n r?
量子演算法:
這裡的思路是以 thread 代替遞廻呼叫,若 thread 的數量可以沒上限,而且 thread 的處理又得到硬體的支援速度極快(比如量子電腦),那麼一瞬間就可以得到所有答案。
因沒有量子電腦所以只能像徵性的意思一下,先由遞廻版本了解這個演算法的實作方法。
遞廻版:
#include <iostream>
#include <iomanip>
struct resul_t { resul_t(resul_t &previous_arg, size_t value_arg) :previous(previous_arg) { value = value_arg; } resul_t &previous; size_t value; }; class Combination { static void(*m_pFn)(const resul_t &); static void v(resul_t &pre, const size_t n, const size_t r) { if (r == 1) { for (size_t i = n; i > 0; --i) m_pFn(resul_t(pre, i - 1)); return; } for (size_t i = n - 1; i >= r - 1; --i) v(resul_t(pre, i), i, r - 1); } public: static void C(const size_t n, const size_t r, void(*pFn)(const resul_t &)) { m_pFn = pFn; v(*(resul_t*)NULL, n, r); } }; void(*Combination::m_pFn)(const resul_t &); unsigned long long m = 1; void Fn(const resul_t &resul_arg) { std::cout << std::setw(10) << m++ << '=' << '{'; for (const resul_t *resul = &resul_arg; resul != (resul_t*)NULL; resul = &resul->previous ) std::cout << resul->value << ' '; std::cout << '}' << std::endl; } int main(int argc, char* argv[]) { size_t n, r; std::cout << "Calculate nCr" << std::endl; while (std::cout << "Enter n r? " << std::flush, std::cin >> n >> r) { m = 1; Combination::C(n, r, Fn); } return 0; }
接著用 std::thread 將遞廻版改寫。
std::thread 版:
#include <mutex> #include <iostream> #include <iomanip> #include <memory> #include <thread> using namespace std; struct result_t { result_t(const shared_ptr<result_t> &previous_arg, const size_t value_arg) :previous_ptr(previous_arg), value(value_arg) { } const shared_ptr<result_t> previous_ptr; const size_t value; }; class Combination { static void(*m_pFn)(const shared_ptr<result_t> &); static void v(const shared_ptr<result_t> &pre, const size_t n, const size_t r) { if (r == 1) { for (size_t i = n; i > 0; --i) { thread m(m_pFn, shared_ptr<result_t>(new result_t(pre, i - 1))); m.detach(); } return; } for (size_t i = n - 1; i >= r - 1; --i) { thread m(v, shared_ptr<result_t>(new result_t(pre, i)), i, r - 1); m.detach(); } } public: static void C(const size_t n, const size_t r, void(*pFn)(const shared_ptr<result_t> &)) { m_pFn = pFn; v(shared_ptr<result_t>((result_t*)NULL), n, r); } }; void(*Combination::m_pFn)(const shared_ptr<result_t> &); unsigned long long m = 1; void Fn(const shared_ptr<result_t> &result_arg) { static mutex io_mutex; lock_guard<mutex> lk(io_mutex); cout << setw(10) << m++ << '=' << '{'; for (shared_ptr<result_t> result_ptr = result_arg; result_ptr.get() != (result_t*)NULL; result_ptr = result_ptr->previous_ptr ) cout << result_ptr->value << ' ';
cout << '}' << endl; } int main(int argc, char* argv[]) { size_t n, r; cout << "Calculate nCr" << endl; cout << "Enter n r?" << endl; while (cin >> n >> r) { m = 1; Combination::C(n, r, Fn); } return 0; }
因 std::thread 可同時產生的數量是有上限的,所以若超過上限,程式會被強制結束,因此面對這種大量吃 thread 的東西,應該先經過一層控管機制來產生 thread 才能確保安全。
所以再用 CxxlMan2 程式庫裡的 ThreadLimit 改寫一次
所以再用 CxxlMan2 程式庫裡的 ThreadLimit 改寫一次
ThreadLimit 版:
#include <THREADMGR.HPP> // CxxlMan2::ThreadLimit
#include <iostream> #include <iomanip> using namespace std; using namespace CxxlMan2; struct result_t { result_t(const shared_ptr<result_t> &previous_arg, const size_t value_arg ) :previous_ptr(previous_arg), value(value_arg) { } const shared_ptr<result_t> previous_ptr; const size_t value; }; class Combination { static void(*m_pFn)(const shared_ptr<result_t> &); static ThreadLimit TL; static void v(const shared_ptr<result_t> &pre, const size_t n, const size_t r) { if (r == 1) { for (size_t i = n; i > 0; --i) TL.Thread(m_pFn, shared_ptr<result_t>(new result_t(pre, i - 1))); return; } for (size_t i = n - 1; i >= r - 1; --i) TL.Thread(v, shared_ptr<result_t>(new result_t(pre, i)), i, r - 1); } public: static void C(const size_t n, const size_t r, void(*pFn)(const shared_ptr<result_t> &)) { m_pFn = pFn; v(shared_ptr<result_t>((result_t*)NULL), n, r); } }; void(*Combination::m_pFn)(const shared_ptr<result_t> &); // 視自己電腦的 CPU 做修改,這裡假設是 4 核心, // 設定太大不會比較快 ThreadLimit Combination::TL(4); unsigned long long m = 1; void Fn(const shared_ptr<result_t> &result_arg) { static mutex io_mutex; lock_guard<mutex> lk(io_mutex); cout << setw(10) << m++ << '=' << '{'; for (shared_ptr<result_t> result_ptr = result_arg; result_ptr.get() != (result_t*)NULL; result_ptr = result_ptr->previous_ptr ) cout << result_ptr->value << ' '; cout << '}' << endl; } int main() { size_t n, r; cout << "Calculate nCr" << endl; cout << "Enter n r?" << endl; while (cin >> n >> r) { m = 1; Combination::C(n, r, Fn); } return 0; }
沒有留言:
張貼留言