排列組合 c++實現

   首先說排列,可以根據遞歸定義,從n個元素裏面選m個進行排列,第一步先從n個元素裏面選任意一個元素,然後再在剩下的n-1個元素裏面選m-1個元素進行排列。這個就是遞歸定義。因爲是任意一個元素,因此必須進行一個從第0個元素到n-1個元素的循環。

比如從集合[1,2,3]中取2個數進行排列

(1)第一個循環,取出元素1作爲集合的第一個排列數;然後再利用遞歸從剩下的元素[2,3]中取1個元素進行排列,這個得到的集合就是[2]和[3];最後再將第一個排列數1加到第二步遞歸得到的所有集合中,即得到第一個循環找出的兩個集合[1,2]和[1,3]

(2)第二個循環,取出元素2作爲集合的第一個排列數;然後再利用遞歸從剩下的元素[1,3]中取1個元素進行排列,這個得到的集合就是[1]和[3];最後再將第一個排列數2加到第二步遞歸得到的所有集合中,即得到第二個循環找出的兩個集合[2,1]和[2,3]

(3)依次類推,當所有的循環完成後,把每一趟循環得到的結果集都收集起來,就是這個3取2排列的所有結果。

   然後說組合。從n個元素裏面取m個進行組合,安照數據公式的理解,組合比排列要複雜一些,其實排列數已經包含了組合數,只是組合數不需要包含重複的集合,比如集合[1,2,3]和集合[3,2,1]就是不同的排列,但是是同一種組合。在計算組合數的時候,需要注意不要有重複的組合。組合也是利用遞歸來求解。不同的組合,要點就是其中某個集合包含某個元素,其他元素不包含這個元素,就構成不同的集合。也是從n個元素從取任意一個元素,表示爲Ni,表示m個元素的i組合中,必定含有元素Ni。然後再對剩下的數求m-1個元素的組合,此時要注意,後續的遞歸中,原始數據集中必不包含Ni這個元素。這也需要一個循環。比如從集合[1,2,3]從取2個元素的組合:

(1)第一趟循環,取元素1爲第一個組合數,然後對集合[2,3]求取1個元素的組合,這兩個結果爲[2]和[3];然後再加入第一個組合數1,則得到組合[2,1]和[3,1]

(2)第二趟循環,取元素2爲第一個組合數,然後對集合[3]求取1個元素的組合(此時初始數據已經不能包含1了,因爲在第一趟循環的時候,含有元素1的所有組合已經存在了),結果只有[3];然後加入第一個組合數2,則得到組合[3,2]

(3)如果此時剩下的元素不足m個,則不需要進行無謂的循環了,此時已經找出所有的組合

代碼如下:

#include <iostream>
#include <string>
#include <vector>
using namespace std;

//從集合中取m個元素的所有組合
vector<vector<int>> Combine(vector<int> &vt, int m)
{
	vector<vector<int> > ret;
	if (vt.size() < m)
	{
		return ret;
	}

	//如果是集合大小vt.size() 和 m相同,也可以確定結果,相當於在有m個元素的集合裏面取m個元素的組合,只有一種
	//因此不需要往後做多餘的遞歸調用
	if (vt.size() == m)
	{
		ret.push_back(vt);
		return ret;
	}

	//遞歸出口
	if (1 == m)
	{
		for (auto &item : vt)
		{
			vector<int> vtTemp;
			vtTemp.push_back(item);
			ret.push_back(vtTemp);
		}
		return ret;
	}
	int i = 1;
	for (auto it = vt.begin(); it != vt.end() && (vt.size() - i) >= (m - 1); it++, i++)
	{
		//單趟循環,得到的集合中,取出的m個元素中必含有當前元素it,下一個循環得到的集合中,必不含有前一個循環必定會包含的元素
		//這就做到每次循環都不會有重複的集合
		//依次對剩下的元素進行取m-1個元素的遞歸調用
		//但是剩下的元素要滿足元素個數大於等於m-1這個條件,避免無謂的遞歸調用
		if (it + 1 != vt.end())
		{
			vector<int> vtBack(it + 1, vt.end());
			vector<vector<int>> vtCombi1 = Combine(vtBack, m - 1);
			//加入當前元素
			for (auto &item : vtCombi1)
			{
				item.push_back(*it);
			}
			ret.insert(ret.end(), vtCombi1.begin(), vtCombi1.end());
		}
		
	}

	return ret;
}

//從集合中取m個元素進行全排,遞歸實現
vector<vector<int> > Arrange(vector<int> &vt, int m)
{
	vector<vector<int> > ret;
	if (vt.size() < m)
	{
		return ret;
	}

	//遞歸出口,當遞歸到m=1時,就可以確定所有的集合
	if (1 == m)
	{
		for (auto &item : vt)
		{
			vector<int> vtTemp;
			vtTemp.push_back(item);
			ret.push_back(vtTemp);
		}
		return ret;
	}

	for (auto it = vt.begin(); it != vt.end(); it++)
	{
		//取集合中的一個元素,然後讓剩下的元素取m-1個元素進行全排,遞歸
		vector<int> vtTemp;
		//取當前元素的前面部分元素
		if (it != vt.begin())
		{
			vtTemp.insert(vtTemp.end(), vt.begin(), it);
		}
		//取當前元素的後面部分
		if (it + 1 != vt.end())
		{
			vtTemp.insert(vtTemp.end(), it + 1, vt.end());
		}
		vector<vector<int> > vtArrage = Arrange(vtTemp, m - 1);//遞歸,分解問題
		//把當前元素加入集合,並把集合加入結果集
		for (auto &vtSet : vtArrage)
		{
			auto itInsert = ret.insert(ret.end(), vtSet);
			itInsert->push_back(*it);//加入當前元素
		}
	}

	return ret;
}

int main(int argc, char** argv)
{
	vector<int> vt = { 1, 2, 3 ,4, 5};
	vector<vector<int>> ret = Combine(vt, 2);
	//vector<vector<int>> ret = Arrange(vt, 2);

	for (auto &vtInt : ret)
	{
		for (auto &item : vtInt)
		{
			cout << item << "";
		}
		cout << endl;
	}

	cout << "size:" << ret.size() << endl;
	return 0;
}

 

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