模擬遞迴法
相較於計算法,在單一執行緒上處理速度較快,但也只能用單一執行緒來跑,此例中使用 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; }
沒有留言:
張貼留言