C++实现组合算法

组合算法(二进制辅助法)

1.什么是数学中的组合

组合和排列不同,组合不需要考虑选择的元素的顺序,而排列需要。

  • 组合:从n个数中选择k个数组成一个组合,看有多少种不同的组合,每个数选择的先后顺序不受限制
    C n k = A n k k ! = n ! k ! ( n ! − k ! ) C^k_n = \frac{A_n^k}{k!} = \frac{n!}{k!(n!-k!)} Cnk=k!Ank=k!(n!k!)n!

  • 排列:从n个数中选择k个数排成一个队列,看有多少种不同的排列,每个数选择的先后顺序不同则所组成的排列不同。

A n k = n ( n − 1 ) ( n − 2 ) . . . ( n − k + 1 ) = n ! ( n − k ) ! A^k_n=n(n-1)(n-2)...(n-k+1) = \frac{n!}{(n-k)!} Ank=n(n1)(n2)...(nk+1)=(nk)!n!

以1~5五个数中选三个数为例,总共可以组成的组合有:

1,2,3

1,2,4

1,3,4

2,3,4

1,2,5

1,3,5

2,3,5

1,4,5

2,4,5

3,4,5

2.计算机中如何实现高效的组合算法

这里我分递归跟非递归两种做法!

①递归做法

组合的方式我们可以用实际上的问题来理解

比如一个箱子里面有五个球,你每次从里面随机拿一个球,总共拿三次。

你每拿一次箱子里面的五个球都会有选 与 不选两种可能。

当你拿了一个之后箱子里面就剩下四个球。

这个四个球又会同样有选与不选两种可能。这就会形成一个递归

直至三个球全部选完就可以看我们选了哪三个球了。

当我们的将所有的可能枚举出来的时候所有的组合可能也就出来了。

代码:

#include<iostream>
using namespace std;
int n, k;

/*
 *递归函数。
 *参数u:枚举了多少位数
 *参数sum:选择了多少
 *参数state:用二进制的1和0两种状态表示五个数的选与不选的状态
 *k:要选择的数, 5选3 中的3
 *n:在多少里面选, 5选3中的5
*/
void dfs(int u, int sum, int state)
{
    /* n - u :表示还未枚举的数
     * sum:表示已经选择的数
     * 整体表示,已选择的数加上最后可选择的数如果还不够总共需要选择的数的话就可以返回了
    */
    if(sum + (n - u) < k) return;
    
    //当选择的数够3个了的时候
    if (sum == k){
        for ( int i = 0; i < n; i++ ) {
            if ( state>>i & 1 ){
                cout<<i+1<<' ';
            }
        }
        cout<<endl;
        return;
    }
    
    //当所有数都枚举完了的时候
    if ( u == n ) return;
    
    //表示这个数被选的情况
	dfs(u+1,sum+1,state | 1<<u);
    //表示这个数没有被选的情况
    dfs(u+1,sum,state);
}

int main(){
    cin>>n>>k;
    
    //将所有的数都遍历一遍,选与被选都考虑了一遍!
    dfs(0,0,0);
    return 0;
}

运行结果:

1571118941579

②非递归做法

参考链接

非递归的方式我感觉非常的巧妙而且有意思,而且相对递归会比较容易理解很多,毕竟递归还是比较难的。

思路

我们还是以五个数里面选择数三个数为例。

由上面递归的做法可以知道,每个数都有选与不选两种可能。

可以假设一个容量为五的数组里面,我们将选的赋值为1,将没选的赋值为0。

我们可以转化成用二进制的形式,那每一种组合就相当于一个数值,因为如果在0~31个范围里面只选取三个数赋值为1的话,在不同的位置赋值对于一个二进制数来说都是不同的数值。我们换算成十进制从左往右算。

1,2,3(1,1,1,0,0)—— 7

1,2,4(1,1,0,1,0)—— 11

1,3,4(1,0,1,1,0)—— 13

2,3,4(0,1,1,1,0)—— 14

1,2,5(1,1,0,0,1)—— 19

1,3,5(1,0,1,0,1)—— 21

2,3,5(0,1,1,0,1)—— 22

1,4,5(1,0,0,1,1)—— 25

2,4,5(0,1,0,1,1)—— 26

3,4,5(0,0,1,1,1)—— 28

我们可以看出如果用二进制的形式来表示选与不选,通过位移1的位置我们可以得到不同的数值,也就是不同的组合,而且如果按照一定的规则位移的话你可以发现他是刚好从小到大的排序的。

这边我们要注意的是我们是从左往右计算,位移的。

所以是右大左小,每最右边的那个1往右位移一位的时候就相当于进一了,不管后面的1怎么放都会比前面的那个组合大,所以后面的1就要从最左边也就是最小的那边开始枚举。

代码实现

  • n选k,初始化n的个数,将他们都赋值为0,然后将前面k个数赋值为1,表示最小的二进制数。
  • 如果找到下一个比较大的数呢,那就是位移,每次都是只位移一位,所以可以将右边的0赋值为1,将原本的1改为0,就是遇到10的都全部改为01,那么这个数就变大了。
  • 每次位移的时候都要将位移的这个1后面的全部1都移到最左边,这样才是最小的数。

代码如下:

#include <iostream>
using namespace std;

#define NUM 5
int M[NUM];

/*输出*/ 
void output(int m[])
{
	for(int i = 0 ;i < NUM; i++){
		if(M[i]){
			cout<<m[i];
		}
//cout<<M[i];
	}
	cout<<endl;
}


/*将前面的数从新排序,将1都移至最左端*/
void paixu(int k)
{
	for(int i = k;i >= 0; i--){
		for(int j = i-1; j>=0; j--){
			if(M[i]>M[j]){
				int tmp = M[i];
				M[i] = M[j];
				M[j] = tmp;
			}
		}	
	}
}

/*判断是否结束*/
int jdgover(int n)
{
	for(int i = NUM-n;i < NUM ; i++){
		if(M[i] == 0)
		return 0;
	}
	return 1;
 } 

/*主函数*/ 
int main() 
{
	int m[5] = {1,2,3,4,5};
	int i,n;
	cout<<"请输入你要从五个数中选几个数进行组合:"; 
	cin>>n;
	for(int k = 0;k<n;k++){
		M[k] = 1;
	} 
output(m);
	
	/*遍历,直到1从左边全部移到右边*/ 
	for (i = 0;i <NUM ;i++){
		if(M[i] == 1 && M[i+1] == 0){
				M[i] = 0;
				M[i+1] = 1;
				paixu(i);	
				output(m);
				i = -1;
			}	
			if(jdgover(n))
			break;	
		}		
}

运行结果

1571122378356

  • 5
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
### 回答1: 好的,FFT(快速傅里叶变换)是一种用来计算离散傅里叶变换(DFT)的算法,可以更快地计算出DFT的结果。FFT算法是基于分治思想,将一个序列分成两个子序列并分别对其进行DFT,然后再将这两个子序列的DFT合并起来。 下面是一个使用递归的C语言实现的FFT算法的代码示例(需要复数运算的支持): ``` #include <math.h> #include <complex.h> #define PI 3.14159265358979323846 // 递归实现的FFT算法 void FFT(complex double *x, int N, int inv) { if (N == 1) return; complex double wn, w, temp; complex double *even = (complex double*)malloc((N / 2) * sizeof(complex double)); complex double *odd = (complex double*)malloc((N / 2) * sizeof(complex double)); // 将x序列拆分成偶序列和奇序列 for (int i = 0; i < N; i++) { if (i % 2 == 0) even[i / 2] = x[i]; else odd[i / 2] = x[i]; } // 分别对偶序列和奇序列进行DFT FFT(even, N / 2, inv); FFT(odd, N / 2, inv); // 合并偶序列和奇序列的DFT for (int k = 0; k < N / 2; k++) { wn = cexp(-2 * PI * I * k / N * inv); w = 1; temp = w * odd[k]; x[k] = even[k] + temp; x[k + N / 2] = even[k] - temp; w = w * wn; } free(even); free(odd); } ``` 在上面的代码中,`x` 是要进行FFT计算的序列,`N` 是序列 ### 回答2: FFT(快速傅里叶变换)算法在信号处理和图像处理等领域应用广泛,可以高效地计算离散傅里叶变换(DFT)。下面我将简要介绍如何实现FFT算法。 FFT 算法的核心思想是将 DFT 的计算分解成更小规模的子问题,通过递归地将子问题继续分解,最终得到 DFT 的结果。具体实现步骤如下: 1. 首先判断输入的序列长度是否为 2 的幂次,如果不是,则需要将序列长度扩展为 2 的幂次。一种常用的方法是在输入序列的末尾添加足够数量的零值。 2. 将输入序列划分为两个子序列,分别对它们进行 DFT 的计算。可以使用递归方式来实现这一步骤。 3. 计算每个子序列的 DFT,并得到两个子序列的 DFT 结果。 4. 将两个子序列的 DFT 结果合并为最终的 DFT 结果。合并的过程可以利用旋转因子来实现,旋转因子的计算需要使用复数的运算。 5. 重复以上步骤,直到得到整个序列的 DFT 结果。 实现 FFT 算法的关键在于递归步骤的实现和旋转因子的计算。递归步骤需要将输入序列划分为更小规模的子序列进行计算,直到序列长度为 1。在计算过程中,需要注意有限域理论和复数运算的相关知识。 当得到序列的 DFT 结果后,可以通过 DFT 结果在频域上进行进一步的分析和处理。例如,可以筛选某些频率成分,以提取信号的特征。 总之,实现 FFT 算法需要具备一定的数学和编程知识,并且对离散傅里叶变换的原理有一定的了解。通过递归划分和合并子问题的方式,可以高效地计算出离散傅里叶变换的结果。 ### 回答3: FFT(快速傅里叶变换)是一种用于将离散信号从时域转换到频域的算法。其实现过程如下: 首先,将输入信号分为偶数和奇数下标的两个序列。偶数下标序列包含原始信号中的偶数下标数据,奇数下标序列包含原始信号中的奇数下标数据。 接下来,对这两个序列分别进行FFT,得到它们的频域表示。可以使用递归调用FFT算法进行处理。递归的基本情况是序列长度为1,此时直接返回该序列作为频域表示。 然后,将得到的频域表示进行组合。将偶数下标序列的频域表示与奇数下标序列的频域表示结合起来,得到最终的频域表示。组合的方法是利用旋转因子进行计算,将奇数下标序列进行旋转后与偶数下标序列相加。 最后,对得到的频域表示进行逆FFT(IFFT)操作,将其转换回时域表示。IFFT的实现与FFT类似,只是旋转因子的位置有所不同。 总结来说,FFT算法是一个基于分治思想的快速算法,通过将输入信号分解成不同频率的子信号,再通过频域表示进行计算,最后将结果合并得到输出。其时间复杂度为O(nlogn),比传统的傅里叶变换算法更高效。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值