全排列递归的理解

把array[] = {1,2,3} 全排列。这个递归式到处都能见到。但是理解起来,我个人觉得很绕,因为不能只简单想A33。由于目前对于gdb还是不熟。以下附上print的方式帮助自己理解。

#include<stdio.h>

void perm(int *data, int n, int curr) {
	int i,m;
	if (curr == n-1){
		for (m=0; m<n;++m)
		printf("%d", data[m]);
		printf("\n");
	}
	else {
		for (i=curr; i<n; ++i) {
			int t;
			 t=data[curr],data[curr]=data[i],data[i]=t;
			 printf("line1: curr = %d, i = %d\n", curr,i);                          //watch point 1
			 perm(data,n,curr+1);
			 printf("line2--after perm(curr+1): curr = %d, i = %d\n", curr, i);     //watch point 2
			 t=data[curr],data[curr]=data[i],data[i]=t;
		}
	}
}

int main() {

	int z;
        int array[] = {1,2,3};
	perm(array,sizeof(array)/sizeof(array[0]),0);
        for (z=0;z<3;z++) {
		printf("%d",array[z]);
	}
	printf("\n");
	return 0;
}


以上的两个watch point,可以很好的看出程序运行的路线。运行结果如下,



接下来我用自己的描述方式走一遍从print 123到print 123,帮助以后的我回头看这篇文章带来一些直观的提醒。


首先程序从 perm({1,2,3}, 3,0)出发,一开始curr=i=0。t=data[0], data[curr]=data[i]=1, data[0]=t。这里的data[0] = 1。

接着,程序遇到了perm({1,2,3},3,0+1),因为curr = 1, 1<3-1,所以进入line 1 部分,这个时候,curr = i =  1。t=data[1], data[curr] = data[i]=2, data[1]=t。这里data[1]=2。

接着,程序遇到了perm({1,2,3},3,1+1),因为curr = 2,进入if部分,print 123。

*

程序出来后进入line 2部分,curr = 1,i = 1。data[]间没有换位。

因为i<3,i增成了2,进入line 1部分。curr = 1, i = 2。data[1]与data[2]换位了。此时数组变成了 {1,3,2}。程序去到了({1,3,2}, 3, 1+1),于是又print出了 1,3,2。

*

出来后程序来到了line 2,此时 curr = 1, i = 2。所以 data[1]与data[2]换位,此时数组成了{1,2,3}。i于是又增成了3。for 的条件此时不满足了。我把这叫奔溃。

程序“奔溃后”它要开始倒退了。此时会发现最初的那个perm({1,2,3}, 3,0+1)后,咱还没往下走过呢。程序这时候就是退到了那里。再次进入line 2部分。

回到了最上面那个红字的状态curr=0, i=0。line2下面那个转换无变化。这时候因为i=0,我们继续走循环,i被加了1,即 i = 1。而curr 仍为0 (i = 1, curr = 0)。数组{1,2,3}中的1与2换位。

数组成为了{2,1,3},然后接着走去perm({2,1,3},3,0+1),数组又来到 line 1部分,curr=1, i=1。没有换位发生数组再去perm({2,1,3},3,1+1),print 出了213。

*

完事后,程序去到了line2部分。到了上面的第二处红字 curr=1, i=1。line2部分没有发生转换。i又增加了1,成为 i = 2。这时候程序去到了line1部分并发生了转换,是data[1]与data[2]间的转换,成为数组{2,3,1}。程序走到了perm({2,3,1},3,1+1),打印出了231。

*

接着程序又来到了line 2 部分。curr = 1, i = 2。先换位成为数组{2,1,3}。接下来程序再奔溃。

程序开始往后退,退到 curr = 0, i = 1那会儿 (记得最初那个curr = 0, i = 0么,)。然后数组换位成{1,2,3}。接下来去line1部分,i 增1。此时状态curr = 0, i = 2。数组成了{3,2,1}。然后又去到了perm({3,2,1},3,0+1}。到了line1部分。curr=i=1。所以没动静。又去到perm({3,2,1},3,1+1}。数组{3,2,1}被打印出了。

*

此时,curr=1, i = 1。去到line2 那里,所以没动静。再循环去line1, i = 2而curr=1。于是数组换位成{3,1,2}。去到了perm({3,1,2},3,1+1)。然后打印出来了 312。

*

此时,以下都发生在line2部分: 【curr=1, i = 2。数组换位成{3,2,1}。i 是2了,所以程序又奔溃啦。回到了第二个最初,既 curr = 0, i = 0与curr = 0, i = 1之后。这里就是curr = 0, curr = 2, 然后0,2换位,数组成了{1,2,3}】。

程序再奔溃。curr = 0, i = 2之后,程序不满足for语句了。也自然不会去上面的那个perm(...curr+1)的部分。 它跳出了整个的perm function。来到了main。print了123。

***

图片中的三种颜色的框就是三次最大的"退回"。颜色一一对应。递归走人脑,真是好考记忆力啊。

***

哭了...终于写完了。为啥我脑海里蹦出了电影蝴蝶效应,别说那电影是说蝴蝶效应的。这男主人公奔溃后老是回过去改历史,虽然改历史的内容是任意他说了算的,不像递归。但是奔溃后老是回到前点,还真是递归味很浓啊。电影应该改名叫 Recursive Butterfly Effect

***

如果真的有人看我这篇文章,如果你有更好的方式理解递归,希望不吝赐教!低手在这里感激不尽!


后记:今天随意看回这篇文章,发现自己之前写的东西真是挺搞笑的,哈哈。由衷觉得学习的历程很奇妙。虽然写的内容通篇连入栈出栈这样的关键字都看不到,但是道理还是一样的,而且这样写也许反而适合用来底层学习用。(2015年02月23日)



### C++ 中使用递归回溯实现全排列算法 #### 示例代码解释 为了更好地理解如何通过递归来生成所有可能的排列组合,在此提供一段基于C++语言编写的程序,用于展示如何利用递归加回溯的方式求解给定集合的所有排列情况。 ```cpp #include <iostream> #include <vector> using namespace std; void permute(vector<int>& nums, vector<bool> &used, vector<int> &current, vector<vector<int>> &result) { if (current.size() == nums.size()) { // 当前路径长度等于输入列表大小时说明找到了一组完整的排列 result.push_back(current); // 将当前找到的一组排列加入最终的结果集中 return; } for (int i = 0; i < nums.size(); ++i) { // 遍历每一个可选元素 if (!used[i]) { // 如果该位置上的元素还未被选用,则可以考虑将其作为下一个候选者 current.push_back(nums[i]); // 把这个新选定的元素添加到当前构建的部分排列后面 used[i] = true; // 更新状态表示该元素已被占用 permute(nums, used, current, result); // 继续处理剩余部分直至完成整个排列过程 current.pop_back(); // 移除最后一个元素以便尝试其他可能性 used[i] = false; // 恢复原来的状态允许后续操作重新选取该元素 } } } vector<vector<int>> generatePermutations(const vector<int>& nums) { vector<vector<int>> all_perms; vector<int> current_permutation; vector<bool> elements_used(nums.size(), false); permute(nums, elements_used, current_permutation, all_perms); return all_perms; } ``` 上述代码实现了对任意整型向量`nums`内元素进行全排列的功能。这里采用了两个辅助结构体:一个是布尔类型的向量`elements_used[]`用来记录哪些索引处的数据已经被加入了正在形成的某个特定顺序之中;另一个则是动态增长/缩减的临时存储容器`current_permutation[]`负责累积构成中的单次排列方案[^1]。 当函数检测到所积累起来的那一串数字正好匹配上了原始数据集的数量级(`if (current.size() == nums.size())`),就表明此时已经成功构造出了一个新的合法排列形式,并立即将其追加至全局变量`all_perms[][]`当中去保存下来待会儿返回给调用方[^2]。 而每当遇到尚未参与过任何一轮分配流程里的成员节点时(即满足条件`!used[i]`),就会开启新一轮迭代分支探索之旅——先把这个新鲜出炉的选择项安插进来形成新的局部进展状况,紧接着再深入挖掘下一步能做些什么变化;一旦这条路走到尽头或者发现死胡同了,那么就要及时撤退并清理现场,也就是把刚才做的那个决定撤销掉让位给别人试试看有没有更好的出路[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

取啥都被占用

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值