找到1到10,000,000之间不丑的数字

时间:2015-04-14 16:02:50

标签: c++

当我试图找出不丑的数字时,我遇到了一些问题。     丑陋的数字是唯一的素数因子是2,3或5的数字。那么数字不是很难看?     我试着找出1到100,000,000之间不难看的数字。我的程序可以解决问题,但似乎有点慢。我怎么能让它更快?     这是代码:

#include <iostream>
#include <queue>
using namespace std;
typedef pair<unsigned long,int> node_type;
main()
{
    //generates 1500 ugly numbers into result[];
    unsigned long result[1500];
    priority_queue<node_type,vector<node_type>,greater<node_type> > Q;
    Q.push(node_type(1,2));
    for(int i=0;i<1500;i++)
    {
        node_type node = Q.top();
        Q.pop();
    switch(node.second)
    {
        case 2:Q.push(make_pair(node.first*2,2));
        case 3:Q.push(make_pair(node.first*3,3));
        case 5:Q.push(make_pair(node.first*5,5)); 
    }
    result[i]=node.first;
}
/*****************************************************
//Here is the approach I used:
//The initial value of the integer k is 1;
//and will increase by 1 every time 
//k will be checked if it's a ugly number,if not increase by 1,keep doing
//this till k is not a ugly number,then count how much ugly number(we may 
//call it n) is less the the
//current value of k.The result of (k-n) is the order number of this (k) not ugly
//for example:the 1st not ugly number is 7.
// there are 6 ugly number less than 7,which are 1 2 3 4 5 6,
// k=1-->k==2-->.....k=7, k-n=7-6=1,so 7 is the 1st not ugly number
***************************************************************/
int T;  // The amount of cases
cin>>T;
while(T--)
{
    int n;
    int k=0,i=0,count=0;
    cin>>n;

    while(n)
    {
        if((k+1)==result[i]) {k++;i++;count++;}
        else 
        {
            k++;
            if(k-count==n) {cout<<k<<endl;break;}
        }
    }
}}

最大的问题是它看起来不够快!你能告诉我如何让它更快吗?还是有其他方法可以解决这个问题?

3 个答案:

答案 0 :(得分:5)

好的,我会咬人。根据这个定义,测试一个数字是否丑陋,在计算上实际上相当简单。

蛮力测试1亿个数字
#include <iostream>

bool is_ugly(unsigned n) {
  while(n % 5 == 0) n /= 5;
  while(n % 3 == 0) n /= 3;
  while(n % 2 == 0) n /= 2;

  return n == 1;
}

int main() {
  unsigned counter = 0;
  for(unsigned i = 1; i <= 100000000; ++i) {
    if(!is_ugly(i)) {
      ++counter;
    }
  }

  std::cout << counter << std::endl;
}
在我的基准 1 中,

只需要半秒钟,这是非常可行的。当然,打印它们需要花费更长的时间,因为有99998895个非丑陋数字低于100000000并且您的终端必须全部渲染它们。即使重定向到/dev/null(将渲染从等式中取出),打印也需要6秒(比检查长10倍),使用libstdc ++和gcc 4.9和-O2。如果您要生成所有非丑陋的数字,这不是那么容易被击败,因为瓶颈不是你可以摆脱的东西。

另一方面,如果你的目标是生成低于阈值的所有丑陋数字(或计算非丑陋数字,这与计算丑陋数字并从阈值中减去数字相同)测试所有非丑陋的数字和丑陋的数字远非最佳。更好的方法是生成丑陋的数字,因为它们很少。使用递归最容易做到这一点:

#include <iostream>
#include <set>      // could also use an unordered_set, except that it turns
                    // out to be a pessimization

void generate_uglies(unsigned n, std::set<unsigned> &num, unsigned threshold) {
  // Abort recursion if we break the upper limit or find a number
  // that was already tested
  if(n <= threshold && num.find(n) == num.end()) {
    // Remember this ugly number
    num.insert(n);

    // Since n is an ugly number, these three are also ugly numbers.
    generate_uglies(n * 2, num, threshold);
    generate_uglies(n * 3, num, threshold);
    generate_uglies(n * 5, num, threshold);
  }
}

int main() {
  std::set<unsigned> num;

  generate_uglies(1, num, 100000000);

  std::cout << num.size() << std::endl;
}

这报告回来了,好吧......

$ time ./a.out 
1105

real    0m0.001s
user    0m0.000s
sys     0m0.000s

你可以使用这个,希望num.find(n) == num.end()is_ugly(n)(使用之前的is_ugly函数)更快地测试非丑陋,但在我的基准测试中差异是可以忽略不计,使用std::unordered_set实际上较慢的因子为2-3。

附录

std::vector<bool>

稍后使用// num is to have the wanted size in advance and be full of false void generate_uglies(unsigned n, std::vector<bool> &num) { if(n < num.size() && !num[n]) { num[n] = true; generate_uglies(n * 2, num); generate_uglies(n * 3, num); generate_uglies(n * 5, num); } } 测试非丑陋的数字。 !num[i]测试比!num[i]函数快得多(对于低于1亿的值,平均值为〜5) 1 。如果您打算将它们打印出来并不重要,原因如上所述,但在不同情况下它可能会产生明显的差异。请注意,此表需要12.5 MB RAM。

1 您的里程因您的机器不是我的而有所不同。我使用的是一个1.5岁的i7。

答案 1 :(得分:1)

从您的代码中了解,我知道您真正想要的是快速找到 第n个非丑陋的数字。

你的算法找到第n个非丑陋的数字是O(N),你可以发现它们使用二进制搜索,它是O(log(N))。

有T个案例,如果T非常大,我的方法可以节省大量时间。

这是我的代码,改变你的代码。

#include <iostream>
#include <queue>
using namespace std;
typedef pair<unsigned long,int> node_type;
int main()
{
    //generates 1500 ugly numbers into result[];
    unsigned long result[1500];
    priority_queue<node_type,vector<node_type>,greater<node_type> > Q;
    Q.push(node_type(1,2));
    for(int i=0;i<1500;i++)
    {
        node_type node = Q.top();
        Q.pop();
        switch(node.second)
        {
            case 2:Q.push(make_pair(node.first*2,2));
            case 3:Q.push(make_pair(node.first*3,3));
            case 5:Q.push(make_pair(node.first*5,5));
        }
        result[i]=node.first;
    }

    int T;  // The amount of cases
    cin>>T;
    //b_search_data[i] mean the count of non ugly number blow or equal i, i start from 1
    unsigned long b_search_data[1501];

    for (int i=0; i<1500; i++)
    {
        b_search_data[i] = result[i] - i - 1;
    }
    vector<int> search_data(b_search_data, b_search_data+1500);
    while(T--)
    {
        int n;
        int k=0,i=0,count=0;
        cin>>n;
        // use binary search here to find the n-th non ugly number instead your approach
        int position = upper_bound(search_data.begin(), search_data.end(), n-1) - search_data.begin();
        // position means
        cout<<position + n<<endl;
    }
}

答案 2 :(得分:0)

使用递归函数可以生成丑陋的数字。

实际上,如果N丑陋,那么2*N3*N5*N也很难看。

事实上,2*N3*N5*N是丑陋的数字大于N ,因此您可以修复最大值,让我们说MAX并且通过添加N2*N3*N(这是递归部分)来查看比5*N更难看的数字。

要停止重复搜索,我们可以将所有找到的数字存储在一组uglies中,所以当我们有{6}作为3*2的产品时,我们不会添加它(和研究当我们找到2*3(也等于6 ---我们称之为碰撞)时,所有丑陋的后代

程序最终到达:

#include <cstdio>
#include <set>

using namespace std;

const unsigned N = 10000000;

set<unsigned> uglys;
unsigned n_collis = 0;


void srch_ugly(unsigned n)
{
    if (uglys.find(n) != uglys.end()) { /* found in set */
        ++n_collis;
    } else {
        uglys.insert(n);
        if (2*n < N) srch_ugly(2*n);
        if (3*n < N) srch_ugly(3*n);
        if (5*n < N) srch_ugly(5*n);
    } /* if */
} /* srch_ugly(unsigned) */

int main()
{
    unsigned col = 0; /* printing column */

    srch_ugly(1);

    /* print results */    
    printf("%lu uglys.  %u collissions.\n", uglys.size(), n_collis);
    for (set<unsigned>::iterator i = uglys.begin();
            i != uglys.end();
            ++i)
    {
        if (col >= 72) {
            puts("");
            col = 0;
        } /* if */
        col += printf("%s%i", col ? " " : "", *i);
    } /* for */
    if (col) puts("");
} /* main */

注意

很抱歉使用了printf stdio函数,但我希望剪切72个字符的行,printf函数返回打印的字符数(我实际上并不知道如何从ostream类中获取实际打印字符数,所以我改为使用它们。