莫泽-德布鲁因数列

data structurec++programming

问题描述包括打印莫泽-德布鲁因数列的前 N ​​项,其中 N 由用户输入给出。

莫泽-德布鲁因数列是一个由整数组成的序列,这些整数都是 4 的不同幂之和,即 1、4、16、64 等等。

该序列的前几个数字包括 0、1、4、5、16、17、20、21、64……

该序列始终以零开头,后跟 4 的不同幂之和,例如 $\mathrm{4^{0}}$,即 $\mathrm{4^{1}\:即 4,}$,然后是$\mathrm{4^{0}\:and\:4^{1}\:即\:5}$ 等等。

在这个问题中,我们将给定任意正整数 N,我们的任务是打印最多 N 项的 Moser−de Bruijn 序列。

让我们通过下面给出的例子来理解这个问题。

输入

N=6

输出

0 1 4 5 16 17

解释- 给定的输入是 6,这意味着我们需要打印 Moser−de Bruijn 序列的前 6 项,这是我们所需的输出。

输入

N=12

输出

0  1  4  5  16  17  20  21  64  65  68  69

解释- Moser-de Bruijn 序列的前 12 个项是我们所需的输出。我们可以通过将不同的 4 的幂相加来计算序列的第 N 项。

让我们理解根据输入中的 N 值打印 Moser-de Bruijn 序列前 N 项的算法。

算法

通过观察 Moser-de Bruijn 序列中的数字,我们可以看到序列中的数字遵循它们之间的数学关系。

序列中的前几个数字是 0、1、4、5、16、17、20、21、64、65、68、69、80、81、84……

序列中的数字仅包含不同的 4 的幂和不同的 4 的幂之和。

如果我们从 0 开始考虑序列中数字的位置,我们可以观察到M(0)=0 且 M(1)=1。

并且,每个 N 为偶数的第 N 项都可以表示为:

$$\mathrm{M(N)=4*M(N/2)}$$

类似地,每个 N 为奇数的第 N 项都可以表示为:

$$\mathrm{M(N)=4*M(N/2)+1}$$

让我们尝试利用上述关系找出 Moser−de Bruijn 序列的几个数字。

$$\mathrm{M(0)=0\:\:\:M(1)=1}$$

由于在这种情况下 N 为偶数,因此 M(2) 将等于 4*M(N/2)。因此 M(2)=4*1=4。

由于本例中 N 为偶数,M(3) 等于 4*M(N/2)+1。因此,M(3)=5。

类似地,

$$\mathrm{M(4)=4*M(4/2)=4*4=16}$$

$$\mathrm{M(5)=4*M(5/2)+1=4*4+1=17}$$

我们可以使用此关系计算出数列中直到 N 的所有项。我们将利用上述数列中数字之间的关系,输出 Moser−de Bruijn 数列的前 N ​​项。

方法 −1(使用递归)

我们将使用递归,利用算法中讨论的公式,找到数列的第 i 项。在我们的方法中,实现递归以打印 Moser-de Bruijn 序列的前 N ​​项的步骤如下:

  • 为了找到序列的第 i 个数字,我们将创建一个递归函数。

  • 在函数中,如果 i=0,则返回 0;如果 i=1,则返回 1。对于 i 的所有其他值,我们将检查 i 是偶数还是奇数。如果 i 为偶数,则返回 4*rec(i/2);如果 i 为奇数,则返回 4*rec(i/2)+1。

  • 我们可以通过使用递归计算第 i/2 项的值来获得第 i 项的值,从而解决其中的子问题。

  • 使用 for 循环从 i=0 迭代到 i>N,以打印 Moser−de Bruijn 序列的前 N ​​项。

  • 每次迭代时,我们将调用递归函数来获取序列中第 i 项的值并继续打印它们。

示例

//C++ 代码打印 Moser-de Bruijn 数列的 N 项
#include <bits/stdc++.h>

using namespace std;

//递归函数获取数列的第 N 项
int rec(int N){
    
    if(N==0){ //as M(0)=0
        return 0;
    }
    
    else if(N==1){  //as M(1)=1
        return 1;
    }
    
    else if(N&1){ //对于 N 为奇数的情况
        return 4*rec(N/2)+1;
    }
    
    else{  //对于 N 为偶数的情况
        return 4*rec(N/2);
    }
}

//打印序列的前 N ​​个项
void print_sequence(int N){
	//使用 rec() 函数打印至 N 的每个项
    for(int i=0;i<N;i++){
        cout<<rec(i)<<"  ";
    }
}

int main()
{
    int N; //输入值
    N=22;
    
    print_sequence(N); //调用函数
    
    return 0;
}

输出

0  1  4  5  16  17  20  21  64  65  68  69  80  81  84  85  256  257  260  261  272  273

方法 2(动态规划)

众所周知,动态规划是针对使用递归解决问题的优化方案。因此,为了优化上述方法,我们将使用动态规划的概念来打印 Moser-de Bruijn 序列的前 N ​​项。

在这里,我们将第 i 项的值存储在一个 N 数组中,这样我们就不需要计算序列中第 i 个重复子问题的值了。

使用动态规划方法打印 Moser-de Bruijn 序列的前 N ​​项的步骤如下:

  • 我们将创建一个大小为 N 的向量来存储序列的前 N ​​项。

  • 为了将数字存储在数组中,我们将创建一个函数,使用算法部分中讨论的公式来查找序列的第 i 个数字。

  • 当向量更新到序列的 N 项时,我们将通过在 for 循环中从 i=0 迭代到 i<N (即 Moser-de Bruijn 序列的前 N ​​项)来打印向量中存储的数字。

示例

//C++ 代码用于打印 Moser-de Bruijn 数列的前 N ​​项

#include 

using namespace std;

//函数用于使用 dp 更新向量
void calculate_numbers(vector<int>& dp, int N){
    
    dp[0]=0; //as M(0)=0
    dp[1]=1; //M(1)=1
    
    for(int i=2;i<N;i++){
        if((i&1) == 0){ //如果 i 是偶数
            dp[i]=4*dp[i/2];
        }
        else{ //如果 i 是奇数
            dp[i]=4*dp[i/2]+1;
        }
    }
}

//打印 Moser-de Bruijn 序列的前 N ​​项的函数
void print_sequence(vector<int>& dp, int N){
    
    for(int i=0;i<N;i++){
        cout<<dp[i]<<"  ";
    }
}

int main()
{
    int N; //用于输入
    N=35;
    
    vector<int> dp(N,0); //用于存储序列的前N个元素
    
    //调用函数更新数组至N
    calculate_numbers(dp,N);
    
    //用于打印序列
    print_sequence(dp,N);
    
    return 0;
}

输出

0  1  4  5  16  17  20  21  64  65  68  69  80  81  84  85  256  257  260  261  272  273  276  277  320  321  324  325  336  337  340  341  1024  1025  1028

时间复杂度:O(N) 因为我们在 for 循环中迭代 N 次来更新序列的 N 项。

空间复杂度:O(N) 因为我们使用了一个大小为 N 的向量来存储序列的数字。

结论

本文讨论了莫泽-德布鲁因序列的概念。我们讨论了利用数字之间的数学关系来查找序列中任意第 N 项的算法,并在 C++ 中结合递归和动态规划实现了该算法,以便帮助您更好地理解。

希望本文能帮助您理清关于该主题的所有概念。


相关文章