【问题标题】:Golomb sequence without using array不使用数组的哥伦布序列
【发布时间】:2022-01-19 13:43:12
【问题描述】:

大家好,我正在寻找一个程序来查找第 n 个 Golomb 序列而不使用数组!!!!

** 我知道下面这个程序,但是它太慢了......

#include <bits/stdc++.h>
using namespace std;
int findGolomb(int);
int main()
{
  int n;
  cin >> n;
  cout << findGolomb(n);
  return 0;
}
int findGolomb(int n)
{
  if (n == 1)
    return 1;
  else
    return 1 + findGolomb(n - findGolomb(findGolomb(n - 1)));
}

【问题讨论】:

  • 1) 为什么不想使用数组? 2)如果您不想使用数组,这(可能)是最好的解决方案。您有O(1) 空间,但时间复杂度肯定更大。
  • 另一件事。 #include &lt;bits/stdc++.h&gt;using namespace std; 都是不好的做法。
  • 任何实现都是一种权衡,通常是在所需的内存和计算时间之间。如果您不想存储任何内容,则必须重新计算每个结果。 (可能)没有解决方案不占用内存,但也与需要存储先前结果的版本一样快......
  • 你好,只是一个新手。感谢您的提示和信息。我只是想知道解决这个问题的另一种方法,以及递归函数的更多练习......我相信它还有另一种解决方案,但我找不到!
  • 我更新了我的评论以包含“最佳”解决方案的详细信息,该解决方案的速度与使用大小为 n 的数组差不多,但它存储的值比 n 少得多。您只需要大约 30,000 个存储值即可计算 g(n) 到大约 n == 2000000。您可以扩展此逻辑以处理任何不会溢出的n 值。通过找出生成的参数到g 中的值必须是什么,您可能能够使用很少的值。例如,在g(n - 1) 中,你知道你总是需要存储n - 1 来计算g(n)

标签: c++


【解决方案1】:

这取决于您要计算的值有多大。对于n &lt;= 50000,以下工作:

#include <cmath>
/*
 */
 round(1.201*pow(n, 0.618));

事实证明,由于这个序列的性质,您几乎需要其中的每一个条目来计算 g[n]。我编写了一个解决方案,使用map 保存过去的计算,清除不需要的值。对于n == 500000,映射仍然有大约496000 条目,并且由于映射有两个值,而数组应该有一个值,因此您最终会使用大约两倍的内存。

#include <iostream>
#include <map>

using namespace std;

class Golomb_Generator {
public:
    int next() {
        if (n == 1)
            return cache[n++] = 1;
        
        int firstTerm = n - 1;
        int secondTerm = cache[firstTerm];
        int thirdTerm = n - cache[secondTerm];
        
        if (n != 3) {
            auto itr = cache.upper_bound(secondTerm - 1);
            cache.erase(begin(cache), itr);
        }

        return cache[n++] = 1 + cache[thirdTerm];
    }
    
    void printCacheSize() {
        cout << cache.size() << endl;
    }
    
private:
    int n = 1;
    map<int, int> cache;
};
 
void printGolomb(long long n)
{
    Golomb_Generator g{};
    
    for (int i = 0; i < n - 1; ++i)
        g.next();
        
    cout << g.next() << endl;
    g.printCacheSize();
}
 
int main()
{
    int n = 500000;
    printGolomb(n);
    return EXIT_SUCCESS;
}

你可以猜到很多。 n - g(g(n - 1)) 使用 g(n-1) 作为 g 的参数,它总是比 n 小得多。同时,递归也使用n - 1作为参数,与n接近。你不能删除那么多条目。

在没有 O(n) 内存的情况下,你可以做的最好的事情是递归结合对更小的n 准确的近似值,但它仍然会很快变慢。此外,随着递归调用的叠加,您可能会使用比适当大小的数组更多的内存。

不过,您也许可以做得更好。序列增长非常缓慢。将这一事实应用于g(n - g(g(n - 1))),您可以说服自己,这种关系主要需要更接近1 的存储值和更接近n 的存储值——nearN(n - near1(nearN(n - 1)))。您可以在两者之间有大量不需要存储的数据,因为它们将用于g(n) 的计算,比您关心的要大得多的n。下面是维护g 的第一个10000 值和g 的最后一个20000 值的示例。它至少对n &lt;= 2000000 有效,并且在n &gt;= 2500000 肯定会停止工作。对于n == 2000000,计算大约需要 5 到 10 秒。

#include <iostream>
#include <unordered_map>
#include <cmath>
#include <map>
#include <vector>

using namespace std;

class Golomb_Generator {
public:
    int next() {
        return g(n++);
    }
    
private:
    int n = 1;
    map<int, int> higherValues{};
    vector<int> lowerValues{1, 1};
    
    int g(int n) {
        if(n == 1)
            return 1;
        
        if (n <= 10000) {
           lowerValues.push_back(1 + lowerValues[n - lowerValues[lowerValues[n - 1]]]);
           return higherValues[n] = lowerValues[n];
        }
        
        removeOldestResults();
            
        return higherValues[n] = 1 + higherValues[n - lowerValues[higherValues[n - 1]]];
    }
    
    void removeOldestResults() {
        while(higherValues.size() >= 20000)
            higherValues.erase(higherValues.begin());
    }
};
 
void printGolomb(int n)
{
    Golomb_Generator g{};
    
    for (int i = 0; i < n - 1; ++i)
        g.next();
        
    cout << g.next() << endl;
}
 
int main()
{
    int n = 2000000;
    printGolomb(n);
    return EXIT_SUCCESS;
}

【讨论】:

    【解决方案2】:

    关于运行时有一些选择和注意事项。

    1. 将复杂性转移到数学上

    算法实际上只是计算机语言中的数学。该算法可以通过数学替换来改进。您可以查看有关此算法的研究,并可能找到更好的算法替代品。

    1. 将复杂性转移到编译器。

    当使用编译时已知的特定数字调用findGolomb(12) 时,我们可以使用 constexpr,将计算时间移至编译器。

    constexpr int findGolomb(int);
    
    1. 将复杂性转移到内存中

    尽管问题要求不使用数组,但这是一个相当大的限制。在不使用任何额外内存空间的情况下,该算法别无选择,只能使用运行时,例如,已知已计算的 findGolomb(..) 值。

    内存限制还可能包括编译程序的大小(通过额外的代码行)。

    1. 将复杂性转移到运行时

    当不使用数学、编译器或内存来增强算法时,别无选择,只能将复杂性转移到运行时。

    总而言之,如果没有上述四个选项,就没有任何选项可以改进运行时。移除编译器和内存优化,并认为当前运行时已经优化,您只剩下数学和研究。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-03-18
      • 2015-07-03
      • 1970-01-01
      • 2017-02-07
      相关资源
      最近更新 更多