题目
定义一个数组打印他的全排列,例如定义:
#define N 3
int a[N] = { 1, 2, 3,};
则运行结果是:
$ ./a.out
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
该问题的思路:
- 把第一个数排到最前面来,准备打印1XX,再对后面两个数2和3做全排序
- 把第二个数排到最前面来,准备打印2XX,再对后面两个数1和3做全排序
- 把第三个数排到最前面来,准备打印3XX,再对后面两个数1和2做全排序
问题1
该过程是递归的,对整个序列做全排序可以归结为对其子序列做全排序的问题,Base Case 的问题需要自己处理。
问题2
扩展该问题,从M个数据中取N个中做全排序
问题3
这次做组合,从M个数据中取N个做组合
解题
1 全排序
基础问题分析
根据需求先定义一个数组a, 以及交换函数
#define N 3
int a[N] = {1,2,3};
void swap(int i, int index) {
int tmp;
tmp = a[index];
a[index] = a[i];
a[i] = tmp;
}
递归等价于循环,所以必须有个base case,也就是说递归必须有个输入作为判断条件.将当前正在经行交换的序号 n 作为输入,如果没到最后一个,就挨个交换,到了最后一个就直接打印数组,返回即可
void print_all_sort(int index) {
if (index == N - 1) {
print_array();
} else {
for (int i = index; i < N; i++) {
swap(i, index);
print_all_sort(index + 1);
}
}
}
测试结果
1 2 3
1 3 2
3 1 2
3 2 1
1 2 3
1 3 2
明显和之前想的并不一样,例如
- 1 & 2 交换后变成了 2,1,3
- 下一次循环需要第一和第三交换,交换后变成了 3,1,2
所以无法打印出2开头的序列
调试
该问题的原因:
所有的交换是从原始 {1,2,3} 开始,中间的交换过程对最终产生了影响,所有要消除这个问题,即在打印后要将数据复原,
很容易就想到了重置法:
打印后重置
void print_all_sort(int index) {
if (index == N - 1) {
print_array();
reset();
} else {
for (int i = index; i < N; i++) {
swap(i, index);
print_all_sort(index + 1);
}
}
}
void reset(void) {
a[1] = 1;
a[2] = 2;
a[3] = 3;
}
结果:
1 2 3
1 3 2
2 1 3
1 3 2
3 2 1
1 3 2
明显还是有问题
很明显依旧有影响,目标是打印后接触者一层的交换,保留更上的递归层不变,所以采用递归交换后反向交换
void print_all_sort(int index) {
if (index == N - 1) {
print_array();
} else {
for (int i = index; i < N; i++) {
swap(i, index);
print_all_sort(index + 1);
swap(i, index);
}
}
}
结果
1 2 3
1 3 2
2 1 3
2 3 1
3 2 1
3 1 2
调试完成
N中选M个全排序
高中数学的玩意
A
N
=
N
!
A_{N} = N!
AN=N!
A
N
M
=
N
!
(
N
−
M
)
!
A_{N}^{M} = \frac{N!}{(N-M)!}
ANM=(N−M)!N!
根据上一问,很快就可以调整出来
void print_all_sort(int index) {
if (index == M - 1) {
print_array();
} else {
for (int i = index; i < N; i++) {
swap(i, index);
print_all_sort(index + 1);
swap(i, index);
}
}
}
为了更加普适与方便调整,将M设置位可变参数,并从外部输入
void print_select_sort(int index, int maxnum) {
if (index == maxnum) {
print_array(maxnum);
} else {
for (int i = index; i < N; i++) {
swap(i, index);
print_select_sort(index + 1, maxnum);
swap(i, index);
}
}
}
最终全部代码与输出
#include <stdio.h>
#define N 4
int a[N];
void print_select_sort(int, int);
void print_array(int);
void swap(int, int);
int main() {
for (int i = 0; i < N; i++)
a[i] = i + 1;
print_select_sort(0, 2);
}
void print_select_sort(int index, int maxnum) {
if (index == maxnum) {
print_array(maxnum);
} else {
for (int i = index; i < N; i++) {
swap(i, index);
print_select_sort(index + 1, maxnum);
swap(i, index);
}
}
}
void print_array(int maxnum) {
for (int i = 0; i < maxnum; i++)
printf(" %d ", a[i]);
printf("\n");
}
void swap(int i, int index) {
int temp;
temp = a[index];
a[index] = a[i];
a[i] = temp;
}
./test_8_3_2_2.ex
1 2
1 3
1 4
2 1
2 3
2 4
3 2
3 1
3 4
4 2
4 3
4 1
N 中选择 M 个组合
高中知识
C
N
M
=
N
!
M
!
⋅
(
N
−
M
)
!
C_{N}^{M} = \frac{N!}{M! \cdot (N-M)!}
CNM=M!⋅(N−M)!N!
这个题目咋一看,确实写不来,但是没关系吗,可以换个简单思路再去试一试、理论上递归与循环是等价的,用循环的方式先试一试
有5个数,挑选3个做组合,
123 | 124 | 125 | 134 | 135 | 145 |
234 | 235 | 245 | |||
345 |
理论上即输出序列 b 先确定第一个,再确定第二个,而第二个的位置必须在第一个之后,再确定第三个,而第三个也必须在第二关后
大致的代码
#define N 5
#define M 3
int a[N] = {1,2,3,4,5};
int b[M];
int getb(void) {
for (int i= 0; i <3; i++) {
b[0] = a[i];
for (int j = i + 1; j < 4; j++) {
b[1] = a[j];
for (int k = j + 1; k <5; k++) {
b[2] = a[k];
print_b();
}
}
}
}
更进一部抽象
#define N 5
#define M 3
int a[N] = {1,2,3,4,5};
int b[M];
int getb(void) {
int index = 0
for (int i= 0; i <= N - M + index; i++) {
b[index] = a[i];
index++ ;
for (int j = i + 1; j <= N - M + index; j++) {
b[index] = a[j];
index++ ;
for (int k = j + 1; k <= N - M + index; k++) {
b[index] = a[k];
print_b();
}
}
}
}
测试一下
123
123
123
···
结果打印了1000行,问题出在了index++ 这个环节,每次循环都会自增,导致了bug,再修改一下
void print_all_comb(void) {
for (int i = 0; i <= N - M + 0; i++) {
arr[0] = a[i];
for (int j = i + 1; j <= N - M + 1; j++) {
arr[1] = a[j];
for (int k = j + 1; k <= N - M + 2; k++) {
arr[2] = a[k];
print_array(3);
}
}
}
}
1 2 3
1 2 4
1 2 5
1 3 4
1 3 5
1 4 5
2 3 4
2 3 5
2 4 5
3 4 5
结果正确,我们希望的是每进入一层for循环 index += 1,这样的话可以用递归实现
void print_all_comb(int index, int pre) {
if (index == M) {
print_b();
} else {
for (int i = pre + 1; i <= N - M + index; i++) {
b[index] = a[i];
print_all_comb(++index, i);
}
}
}
测试一下
#include <stdio.h>
#define N 5
#define M 3
int a[N] = {
1, 2, 3, 4, 5,
};
int arr[M];
void print_all_comb(int, int);
void print_array(int);
int main() { print_all_comb(0, -1); }
void print_all_comb(int index, int pre) {
if (index == M) {
print_array(3);
} else {
for (int i = pre + 1; i <= N - M + index; i++) {
arr[index] = a[i];
print_all_comb(index + 1, i);
}
}
}
void print_array(int maxnum) {
for (int i = 0; i < maxnum; i++)
printf("%d ", arr[i]);
printf("\n");
}
结果
./test_8_3_2_3.ex
1 2 3
1 2 4
1 2 5
1 3 4
1 3 5
1 4 5
2 3 4
2 3 5
2 4 5
3 4 5
为了方便函数调用,最后再搞一个外层包装,避免-1,0这类固定值参数的输入
void print_all_comb(int index, int pre, int m) {
if (index == m) {
print_array(m);
} else {
for (int i = pre + 1; i <= N - m + index; i++) {
arr[index] = a[i];
print_all_comb(index + 1, i, m);
}
}
}
void print_comb(int m) {
int index = 0, pre = -1;
print_all_comb(index, pre, m);
}