您的位置:首页 > 其它

用回溯法(backtracking)实现数学排列和组合

2009-07-16 18:28 239 查看
回溯法是基本算法的一种,可以用于解决大致这样的问题:假设我们有一个N个元素的集合{N},现在要依据该集合生成M个元素的集合{M},每一个元素的生成都依据一定的规则CHECK。

用回溯法解决此问题,我们可以划分为三个重要组成部分。
步骤
从第一步开始至第M步,每一步都从{N}中选取一个元素放入结果{M}中。
界定
每次选择一个元素时,我们都要用规则CHECK来界定{N}中的元素谁合适。界定规则的描述将决定算法的效率和性能。
回溯
如果第k步不能找到合适的元素或者需要得到更多的结果,返回到第k-1步,继续选择下一个第k-1步的元素。

让我们来运用以上的描述和C++语言来解决数学排列和组合问题。
问题1:从N个元素中选择M个元素进行排列,列出所有结果。


// permutation.h : List all the permutation subsets of a certian set.


//




#pragma once




#include <iostream>


#include <iomanip>


using namespace std;




// Compute P(N, M). N is the total number, M is the selected number.


template<int N, int M>


class CPermutation






{


private:


int** m_result; // two-dimension array of [m_nCount][M]


int m_nCount; // how many results


int m_nIndex; // 0 - m_nCount - 1




public:


// List all possible results by nesting


void Count()






{


m_nIndex = 0;


int result
, used
;


for (int i = 0; i < N; i++)


used[i] = 0;


CountRecur(result, used, 0);


}




void CountRecur(int result[M], int used[M], int i)






{


for (int k = 0; k < N; k++)






{


if (used[k])


continue ;




result[i] = k;


used[k] = 1;


if (i < M - 1)


CountRecur(result, used, i + 1);


else


Add(result);


used[k] = 0;


}


}




// Save the result


void Add(int sz[M])






{


memcpy(m_result[m_nIndex], sz, M * sizeof(int));


++m_nIndex;


}




// Count the number of subsets


// C(N, M) = N! / ((N - M)! * M!)


static int NumberOfResult()






{


if (N <= 0 || M <= 0 || M > N)


return 0;


int result = 1;


for (int i = 0; i < M; i++)


result *= N - i;


return result;


}




// Print them to the standard output device


void Print()






{


for (int i = 0; i < m_nCount; i++)






{


cout << setw(3) << setfill(' ') << i + 1 << ":";


for (int j = 0; j < M; j++)


cout << setw(3) << setfill(' ') << m_result[i][j] + 1;


cout << endl;


}


}




CPermutation()






{


// allocate memories for the result


m_nCount = NumberOfResult();


m_result = new int*[m_nCount];


for (int i = 0; i < m_nCount; i++)


m_result[i] = new int[M];


}


~CPermutation()






{


// deallocate memories for the result


for (int i = 0; i < m_nCount; i++)


delete[] m_result[i];


delete[] m_result;


}


};



问题2:从N个元素中选择M个元素进行组合,列出所有结果。
与排列不同的是,组合不需要对选择出来的M个元素进行排列,不妨假定每一组结果中的M个元素从小到大排列。


// combination.h : list all the subsets of a certian set


//




#pragma once




#include <iostream>


#include <iomanip>


using namespace std;




// List all the M-subsets of the N-set


template<int N, int M>


class CCombination






{


private:


int** m_result; // two-dimension array of m_nCount * M


int m_nCount; // how many results of M-length array




public:


CCombination()






{


// allocate memories for the result


m_nCount = NumberOfResult();


m_result = new int*[m_nCount];


for (int i = 0; i < m_nCount; i++)


m_result[i] = new int[M];


}


~CCombination()






{


// deallocate memories for the result


for (int i = 0; i < m_nCount; i++)


delete[] m_result[i];


delete[] m_result;


}




// process of counting


void Count()






{


int sz[M];


int nResultCount = 0;


CountRecur(sz, 0, 0, nResultCount);


}




// Print them to the standard output device


void Print()






{


using std::cout;


using std::setw;


using std::setfill;


using std::endl;




for (int i = 0; i < m_nCount; i++)






{


cout << setw(3) << setfill(' ') << i + 1 << ":";


for (int j = 0; j < M; j++)


cout << setw(3) << setfill(' ') << m_result[i][j] + 1;


cout << endl;


}


}




private:


// Count the number of subsets


// C(N, M) = N! / ((N - M)! * M!)


int NumberOfResult()






{


int result = 1;


for (int i = 0; i < M; i++)


result *= N - i;


for (int j = 1; j <= M; j++)


result /= j;


return result;


}




// Get the current value


// sz - array of the current result


// nIndex - index of sz, 0 <= nIndex < M


// nStartVal - the current minimum value, 0 <= nStartVal < N


// nResultCount - index of m_result


void CountRecur(int sz[M], int nIndex, int nStartVal, int& nResultCount)






{


if (nStartVal + M - nIndex > N)


return ;




for (int i = nStartVal; i < N; i++)






{


sz[nIndex] = i;


if (nIndex == M - 1)


Add(sz, nResultCount);


else


CountRecur(sz, nIndex + 1, i + 1, nResultCount);


}


}




// Save the result


void Add(int* sz, int& nIndex)






{


memcpy(m_result[nIndex], sz, M * sizeof(int));


++nIndex;


}


};



当然,如果将回溯法运用到工程中去,正确性只是必要条件之一,效率显得相当重要,高效的界定代码则是其中的关键。甚至可以考虑换一种方法来实现,当然回溯的思想应该都一样。下面一段代码实现N的阶乘(排列的一个特例),是我在工程中的一个应用。思路则是假设我们已经按照一种方式列出了所有结果
1, 2, 3
1, 3, 2
2, 1, 3
2, 3, 1
3, 1, 2
3, 2, 1
然后纵向的填写所有结果至结果数组中。


// pi : this algorith lists all the sequences of a certian array


//




#pragma once




#include "common.h"




// how many sequence for N cells


int NumberOfPI(int N)






{


if (N < 0)


return 0;




if (N == 0)


return 1;




int pi = 1;


for (int i = 1; i <= N; i++)


pi *= i;




return pi;


}




// print all the sequence to a certain array


// N - number of cells


// PI - array of cells


// selPI - index of array with initial value of 0 and maximum value of N - 1


// Index - result array


// selIndex - number of result array


void PrintPI(int N, int* PI, int selPI, int** Index, int selIndex)






{


if (N <= 0)


return ;




int num = NumberOfPI(N - selPI - 1);


for (int i = selPI; i < N; i++)






{


swap(PI[selPI], PI[i]);


PrintPI(N, PI, selPI + 1, Index, selIndex);


for (int j = 0; j < num; j++)


Index[selIndex++][selPI] = PI[selPI];


swap(PI[selPI], PI[i]);


}


}




int PrintPITest()






{


// Number


static const int N = 5;





// Result in all sequences


int PI
;


for (int l = 0; l < N; l++)


PI[l] = l + 1;




// Allocate the two_demension array


const int num = NumberOfPI(N);


int **Index;


Index = new int*[num];


for (int l = 0; l < num; l++)


Index[l] = new int
;




// Calculating.


PrintPI(N, PI, 0, Index, 0);




// Print it to cout


for (int i = 0; i < num; i++)






{


printf("%5d : ", i + 1);


for (int j = 0; j < N; j++)


printf("%2d ", Index[i][j]);


printf("/n");


}




// Check if there is any identical indexes.


for (int i = 0; i < num - 1; i++)


for (int j = i + 1; j < num; j++)






{


int k = 0;


for (; k < N; k++)


if (Index[i][k] != Index[j][k])


break;


if (k == N)


printf("Check ERROR!/n");


}




// Release the two_demension array


for (int l = 0; l < num; l++)


delete[] Index[l];


delete[] Index;




return 0;


}



事实上,如果你在工程应用中用到的只是可列举的排列组合结果,你应该考虑把结果直接编码到程序中去,这样最快了,只是扩展性不好罢了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: