【问题标题】:Linear solution for 2^x *3^y*5^z [closed]2^x *3^y*5^z 的线性解
【发布时间】:2016-10-24 20:08:43
【问题描述】:

我需要以升序生成 1,2,3,4,5,6,8,9,10,12,15,16 等(给定的 n 个数字)(仅包含 2,3 和 5 的幂)线性时间排序(O(n))。

我有以下代码(我不太确定它的复杂性,也希望得到关于其复杂性的答案)

bool s_a (int a)
{
    while (a % 2 == 0)
        a = a / 2;
    while (a % 3 == 0)
        a = a / 3;
    while (a % 5 == 0)
        a = a / 5;
    if (a == 1)
        return true;
    else
        return false;
}
void s_b (int n)
{
    unsigned int k = 1,nc = 1;
    while ( k <= n)
    {
        if(s_a(nc) == true)
        {
            cout << nc << " ";
            k++;
        }
        nc++;
    }
}

【问题讨论】:

  • 您的问题是什么?如果您的问题是“此代码的 O 表示法是什么”,那么您不在正确的网站上
  • 什么是nx + y + z?数量多少? max(2^x*3^y*5^z)?
  • 看起来像家庭作业。
  • @FirstStep 我想知道如何在 O(n) 中按顺序生成序列,其中 n 是您生成的数字数量
  • 只需使用筛子,扫描结果。

标签: c++


【解决方案1】:

由于您要询问给定代码的复杂性,

bool s_a (int a)
{
    while (a % 2 == 0)
        a = a / 2;
    while (a % 3 == 0)
        a = a / 3;
    while (a % 5 == 0)
        a = a / 5;
    if (a == 1)
        return true;
    else
        return false;
}
void s_b (int n)
{
    unsigned int k = 1,nc = 1;
    while ( k <= n)
    {
        if(s_a(nc) == true)
        {
            cout << nc << " ";
            k++;
        }
        nc++;
    }
}

也希望得到有关其复杂性的答案

……显然,这需要一个比“使用筛子”更深入的答案。

代码计算 2a×3b×5c,其中abc是非负整数。

这些数字在计算机科学中称为 汉明数,更普遍地称为 Regular numbers


顺便说一下,上面的代码可以通过

  • 使用自描述名称,

  • 直接在return 语句中使用布尔表达式,而不是if-else 构造,并且只说if(表达式) 而不是@ 987654333@表达式==true),和

  • 不在基本计算函数中执行 i/o(考虑:这样的函数不能在 GUI 中使用),而是返回结果。

然后是模数格式和基本语法——我在这里使用我的偏好——代码看起来像这样:

auto is_hamming_number( int a )
    -> bool
{
    while( a % 2 == 0 ) { a = a / 2; }
    while( a % 3 == 0 ) { a = a / 3; }
    while( a % 5 == 0 ) { a = a / 5; }
    return (a == 1);
}

auto hamming_numbers( int const n )
    -> vector<int>
{
    vector<int> result;
    for( int i = 1; n != n_items( result ); ++i )
    {
        if( is_hamming_number( i ) )
        {
            result.push_back( i );
        }
    }
    return result;
}

… 其中n_items 定义为例如

template< class Container >
auto n_items( Container const& c )
    -> ptrdiff_t
{ return c.size(); }

...为了避免有符号/无符号比较的问题,以及相关的编译警告。


在此答案的第一个简短版本中,我发布了该答案以在问题可能关闭之前获得可编辑的答案,我写道

对复杂性的简短回答是,上面的代码扫描了所有直到并包括n的数字,显然包括nk 其中 k 相当小,例如k = 1.36,并且对于每个数字,通过删除 2、3 和 5 的所有因子来检查它,我认为平均而言这是一个 linear 对数过程,因此(在这些假设)它的复杂度是 O(n1.36logn)。网上肯定有数学分析,但我没找到。

我现在有了更好的数据,通过编写一个 O(n log n) 时间序列生成器:

n 最大火腿 1 1 2 2 4 4 8 9 16 25 32 90 64 450 128 3375 256 43200 512 1049760 1024 60466176

这表明nth汉明数比n的平方增长得更快。直觉上,我期望指数行为。但我不是数学家,简单的谷歌搜索并没有引导我对行为进行任何描述或数学分析。

简单代码的复杂性必须至少与 nth 汉明数的行为一样高,因为它会检查所有数字。然后乘以从每个数字中删除 2、3 和 5 个因子所需的时间,我认为这与数字的大小成对数。所以我们至少在谈论 O(n2×log(n)),但我不确定它有多糟糕.


研究(谷歌搜索)nth 汉明数的行为,我发现了这个问题的部分重复,即 (Finding Hamming Numbers - not code or distance)。 M Oehm's answer there 中的第二个代码示例几乎正是您所要求的,以线性复杂度要求为模。但是,就像你clarified in a comment on this answer

它并不完全符合我的要求,因为它显示了小于 n 的汉明数,而不是前 n 个汉明数。代码还用到了一些我还没学过的东西,比如筛子,所以有点难理解

下面的代码没有使用筛子,而是使用了 Oehm 示例第二个的主要思想,即直接生成所有小于某个限制的 (2,3,5) 因子组合的乘积,然后对它们进行排序。

为了生成至少 n 个这样的产品,下面的代码对 nth 汉明数进行了猜测。如果该猜测结果太低——因为生成的汉明数少于 n 个——则猜测加倍并重新生成汉明数。加倍意味着最后一代的复杂性压倒了任何前几代的复杂性,因此可以忽略它们。复杂性。

#include <algorithm>        // std::sort
#include <assert.h>         // assert
#include <iostream>
#include <limits.h>         // INT_MAX
#include <math.h>           // pow
#include <stddef.h>         // ptrdiff_t
#include <unordered_set>    // std::unordered_set
#include <vector>           // std::vector
using namespace std;

using Size = ptrdiff_t;     // Signed integer corresponding to size_t.

template< class Container >
auto n_items( Container const& c )
    -> Size
{ return c.size(); }

class Hamming_sequence
{
private:
    int             n_;
    vector<int>     numbers_;

public:
    auto computed_numbers() const
        -> vector<int> const&
    { return numbers_; }

    auto size() const -> Size   { return n_; }
    auto begin() const          { return numbers_.begin(); }
    auto end() const            { return begin() + size(); }

    auto operator[]( Size const i ) const
        -> int
    { return numbers_[i]; }

    Hamming_sequence( int const n )
        : n_( n )
    {
        double const fp_max_number = pow( n, 2.5 );     // Reasonable
        assert( fp_max_number <= INT_MAX );

        int max_number  = static_cast<int>( fp_max_number );
        while( n_items( numbers_ ) < n )
        {
            numbers_.clear();
            clog << ": trying with max number " << max_number << endl;
            for( int two = 1; two <= max_number/2; two *= 2 )
            {
                for( int three = 1; three <= max_number/two/3; three *= 3 )
                {
                    for( int five = 1; five <= max_number/two/three/5; five *= 5 )
                    {
                        int const number = two*three*five;
                        numbers_.push_back( number );
                    }
                }
            }
            assert( max_number <= INT_MAX/2 );
            max_number *= 2;
        }
        sort( numbers_.begin(), numbers_.end() );       // O( n*log(n) )
    }
};

#include <iomanip>      // setw
auto main() -> int
{
    for( int const number : Hamming_sequence{ 720 } )
    {
        cout << number << " ";
    }
    cout << "\n";

    cout << "\n";
    Hamming_sequence const h{ 1024 };
    for( int i = 1; i <= n_items( h ); i *= 2 )
    {
        cout << setw( 5 ) << i << setw( 15 ) << h[i-1] << endl;
    }
}

asserts 不是关于我认为在执行中的那些点是正确的断言,而是一种在数字太大而无法处理时使程序崩溃的设备。所以这不是“生产代码”。对于更健壮的代码替换为例如抛出异常。

关于复杂度,关于最大汉明数的最终猜测必然在真实值的 2 倍以内,因为不会太小n。并且由于很明显将汉明数的值加倍(参见上面的行为表)不会使序列长度加倍,因此计算出的汉明数的个数在 2n 以内。添加排序后,汉明数的数量为 O(n log n)。


Wikipedia article 描述了 Edsger W. Dijkstra 的线性时间算法,简单来说就是合并 2×H、3×H 和 5×H 三个数列,其中 H 是基本的汉明数列。这是因为每个汉明数 h 至少具有 2×h、3×h 和 5×h 的形式之一,其中 h 是一些较小的汉明数。这样就可以向前推进了:

#include <algorithm>        // std::min_element
#include <array>            // std::array
#include <assert.h>         // assert
#include <iostream>
#include <limits.h>         // INT_MAX
#include <math.h>           // pow
#include <stddef.h>         // ptrdiff_t
#include <unordered_set>    // std::unordered_set
#include <vector>           // std::vector
using namespace std;

using Size = ptrdiff_t;     // Signed integer corresponding to size_t.

template< class Container >
auto n_items( Container const& c )
    -> Size
{ return c.size(); }

class Hamming_sequence
{
private:
    int             n_;
    vector<int>     numbers_;

    template< int factor >
    struct H_
    {
        vector<int>&    h_;
        int             i_  = 0;

        auto operator*()
            -> int
        {
            assert( h_[i_] <= INT_MAX/factor );
            return factor*h_[i_];
        }

        void operator++() { ++i_; }

        H_( vector<int>& numbers ): h_( numbers ) {}
    };

public:
    auto computed_numbers() const
        -> vector<int> const&
    { return numbers_; }

    auto size() const -> Size   { return n_; }
    auto begin() const          { return numbers_.begin(); }
    auto end() const            { return begin() + size(); }

    auto operator[]( Size const i ) const
        -> int
    { return numbers_[i]; }

    Hamming_sequence( int const n )
        : n_( n )
    {
        H_<2>   h2{ numbers_ };
        H_<3>   h3{ numbers_ };
        H_<5>   h5{ numbers_ };

        numbers_ = {1};
        array<int, 3>   h = {*h2, *h3, *h5};
        int previous = 1;
        while( n_items( numbers_ ) < n_ )
        {
            int const i = min_element( h.begin(), h.end() ) - h.begin();
            if( h[i] != previous )
            {
                numbers_.push_back( h[i] );
                previous = h[i];
            }
            switch( i )
            {
                case 0: ++h2; h[0] = *h2; break;
                case 1: ++h3; h[1] = *h3; break;
                case 2: ++h5; h[2] = *h5; break;
            }
        }
    }
};

#include <iomanip>      // setw
auto main() -> int
{
    for( int const number : Hamming_sequence{ 720 } )
    {
        cout << number << " ";
    }
    cout << "\n";

    cout << "\n";
    Hamming_sequence const h{ 1024 };
    for( int i = 1; i <= n_items( h ); i *= 2 )
    {
        cout << setw( 5 ) << i << setw( 15 ) << h[i-1] << endl;
    }

    cout << "\n";
    cout << "The 1500'th Hamming number is " << Hamming_sequence( 1'500 )[1'499] << "." << endl;
}

输出:

1 2 3 4 5 6 8 9 10 12 15 16 18 20 24 25 27 30 32 36 40 45 48 50 54 60 64 72 75 80 81 90 96 100 108 120 125 128 135 144 150 160 162 180 192 200 216 225 240 243 250 256 270 288 300 320 324 320 480 486 525 640 576 600 625 640 650 486 720 729 750 768 800 810 864 900 960 972 1000 1024 1080 112012141414401440141440144014141412141914191414145012144014401414141415/1214 1600 1620 1728 1800 1875 1920 1944 2000 2025 2048 2160 2187 2250 2304 2400 2430 2500 2560 2592 2700 2880 2916 3000 3072 3125 3200 3240 3375 3456 3600 3645 3750 3840 3888 4000 4050 4096 4320 4374 4500 4608 4800 4860 5000 5120 5184 5400 5625 5760 5832 6000 6075 6144 6250 6400 6480 6561 6750 6912 7200 7290 7500 7680 7776 8000 8100 8192 8640 8748 9000 9216 9375 9600 9720 10000 10125 10240 10368 10800 10935 11250 11520 11664 12000 12150 12288 12500 12800 12960 13122 13500 13824 14400 14580 15000 15360 15552 15625 16000 16200 16384 16875 17280 17496 18000 18225 18432 18750 19200 19440 19683 20000 20250 20480 20736 21600 2187 0 22500 23040 23328 24000 24300 24576 25000 25600 25920 26244 27000 27648 28125 28800 29160 30000 30375 30720 31104 31250 32000 32400 32768 32805 33750 34560 34992 36000 36450 36864 37500 38400 38880 39366 40000 40500 40960 41472 43200 43740 45000 46080 46656 46875 48000 48600 49152 50000 50625 51200 51840 52488 54000 54675 55296 56250 57600 58320 59049 60000 60750 61440 62208 62500 64000 64800 65536 65610 67500 69120 69984 72000 72900 73728 75000 76800 77760 78125 78732 80000 81000 81920 82944 84375 86400 87480 90000 91125 92160 93312 93750 96000 97200 98304 98415 100000 101250 102400 103680 104976 108000 109350 110592 112500 115200 116640 118098 120000 121500 122880 124416 125000 128000 129600 131072 131220 135000 138240 139968 140625 144000 145800 147456 150000 151875 153600 155520 156250 157464 160000 162000 163840 164025 165888 168750 172800 174960 177147 180000 182250 184320 186624 187500 192000 194400 196608 196830 200000 202500 204800 207360 209952 216000 218700 221184 225000 2 30400 233280 234375 236196 240000 243000 245760 248832 250000 253125 256000 259200 262144 262440 270000 273375 276480 279936 281250 288000 291600 294912 295245 300000 303750 307200 311040 312500 314928 320000 324000 327680 328050 331776 337500 345600 349920 354294 360000 364500 368640 373248 375000 384000 388800 390625 393216 393660 400000 405000 409600 414720 419904 421875 432000 437400 442368 450000 455625 460800 466560 468750 472392 480000 486000 491520 492075 497664 500000 506250 512000 518400 524288 524880 531441 540000 546750 552960 559872 562500 576000 583200 589824 590490 600000 607500 614400 622080 625000 629856 640000 648000 655360 656100 663552 675000 691200 699840 703125 708588 720000 729000 737280 746496 750000 759375 768000 777600 781250 786432 787320 800000 810000 819200 820125 829440 839808 843750 864000 874800 884736 885735 900000 911250 921600 933120 937500 944784 960000 972000 983040 984150 995328百万1012500 1024000 1036800 1048576 1049760 1062882 1080000 109350 0 1105920 1119744 1125000 1152000 1166400 1171875 1179648 1180980 1200000 1215000 1228800 1244160 1250000 1259712 1265625 1280000 1296000 1310720 1312200 1327104 1350000 1366875 1382400 1399680 1406250 1417176 1440000 1458000 1474560 1476225 1492992 1500000 1518750 1536000 1555200 1562500 1572864 1574640 1594323 1600000 1620000 1638400 1640250 1658880 1679616 1687500 1728000 1749600 1769472 1771470 1800000 1822500 1843200 1866240 1875000 1889568 1920000 1944000 1953125 1966080 1968300 1990656 2000000 2025000 2048000 2073600 2097152 2099520 2109375 2125764 2160000 2187000 2211840 2239488 2250000 2278125 2304000 2332800 2343750 2359296 2361960 2400000 2430000 2457600 2460375 2488320 2500000 2519424 2531250 2560000 2592000 2621440 2624400 2654208 2657205 2700000 2733750 2764800 2799360 2812500 2834352 2880000 2916000 2949120 2952450 2985984 3000000 3037500 3072000 3110400 3125000 3145728 3149280 3188646 3240000 320万3276800 3280500 3317760 3359232 3375000 3456000 3499200 3515625 353894 4 3542940 3600000 3645000 3686400 3732480 3750000 3779136 3796875 3840000 3888000 3906250 3932160 3936600 3981312 4000000 4050000 4096000 4100625 4147200 4194304 4199040 4218750 4251528 4320000 4374000 4423680 4428675 4478976 4500000 4556250 4608000 4665600 4687500 4718592 4723920 4782969 4800000 4860000 4915200 4920750 4976640 5000000 5038848 5062500 5120000 5184000 5242880 5248800 5308416 5314410 5400000 5467500 5529600 5598720 5625000 5668704 5760000 5832000 5859375 5898240 5904900 5971968 6000000 6075000 6144000 6220800 6250000 6291456 6298560 6328125 6377292 6400000 6480000 6553600 6561000 6635520 6718464 6750000 6834375 6912000 1 1 2 2 4 4 8 9 16 25 32 90 64 450 128 3375 256 43200 512 1049760 1024 60466176 第 1500 个汉明数是 859963392。

【讨论】:

  • 非常感谢您的回答!正如我在其他cmets中所说,这不是真正的家庭作业,我正在准备参加大学的考试,这是在2010年给出的,但你说得对,我需要外行的解释。在您的回答中,我可以帮助但注意到您有两个小错误:1。该代码扫描了 n 个以上的数字,它会扫描直到找到 n 个 GOOD/fit 数字(例如,它扫描 7 但将其关闭)并且删除因子不是一个线性过程。例如 2 ^100 将在该算法中执行 100 步,而不是 2 ^100。你能不能简单解释一下什么是筛子?
  • 从我在谷歌上找到的(都是 Eratosthenes 的筛子)筛子是算法本身,而不是像双端队列或队列这样的结构。我问是因为英语不是我的母语,所以我不知道英语中的所有术语
  • 哦,我需要修改这个简短的答案,因为“可以很短”是错误的。我现在已经喝了半杯咖啡 + 编码,这种说法是错误的。对不起。 :)
  • 没问题 :) 这个问题被否决了,所以你是我最后一次理解。我想要一些使用数组的东西,因为我只使用了一点点动态内存、队列等,所以我不掌握它们。
  • @user6500701: 搜索序列的指数极限函数(分配适当大小的集合或数组)我发现了一个可能的 duplicate 有一个很好的答案,包括代码( “spread”函数是使用筛子的一个例子,std::set 是筛子):(stackoverflow.com/questions/29286845/…)。这回答了你的问题了吗?如果是这样,我会将其作为重复项关闭。
猜你喜欢
  • 1970-01-01
  • 2019-01-07
  • 1970-01-01
  • 2011-11-05
  • 1970-01-01
  • 1970-01-01
  • 2013-02-26
  • 1970-01-01
相关资源
最近更新 更多