什么是最快的分解算法?

时间:2010-02-15 16:02:44

标签: algorithm math language-agnostic maple factorization

我写了一个试图找到Amicable Pairs的程序。这需要找到数字的适当除数的总和。

以下是我当前的sumOfDivisors()方法:

int sumOfDivisors(int n)
{  
    int sum = 1;
    int bound = (int) sqrt(n);
    for(int i = 2; i <= 1 + bound; i++)
    {
        if (n % i == 0)
            sum = sum + i + n / i;
    } 
    return sum;
}

所以我需要进行大量的因子分解,这开始成为我应用程序的真正瓶颈。我在MAPLE中输入了一个巨大的数字,它将它快速地考虑在内。

什么是更快的分解算法之一?

9 个答案:

答案 0 :(得分:84)

直接从我对this other question的答案中拉出来。

该方法可行,但速度很慢。 “你的数字有多大?”确定要使用的方法:

答案 1 :(得分:21)

标题中的问题(以及最后一行)似乎与问题的实际主体没什么关系。如果你试图找到友方对,或计算许多数的除数之和,那么单独分解每个数字(即使用最快的算法)绝对是一种低效的方法。

sum-of-divisors functionσ(n) = (sum of divisors of n)multiplicative function:对于相对素数m和n,我们有σ(mn) = σ(m)σ(n),所以

σ(P <子> 1 ķ<子> 1 ... P <子> - [R ķ<子> - [R )= [(p 1 k 1 +1 -1)/(p 1 -1)] ... [(p <子> - [R ķ<子> - [R 1 -1)/(p <子> - [R - 1)]。

因此,您可以使用任何简单的筛子(例如Sieve of Eratosthenes的增强版本)来查找最多n的素数,并且在此过程中,分解所有数字最多为n。 (例如,当你进行筛选时,存储每个n的最小素数因子。然后你可以通过迭代来对任何数字n进行因式分解。)这比(整体)更快多次使用任何单独的分解算法。

BTW:已经存在几个已知的友好对列表(参见例如hereMathWorld处的链接) - 所以你试图扩展记录,还是只是为了好玩?

答案 2 :(得分:15)

Shor的算法: http://en.wikipedia.org/wiki/Shor%27s_algorithm

当然,你需要一台量子计算机:D

答案 3 :(得分:12)

我建议从Maple使用的相同算法开始, Quadratic Sieve

  1. 选择奇数 n 进行分解,
  2. 选择一个自然数 k
  3. 搜索所有 p &lt; = k ,以便 k ^ 2 (n mod p)不一致获得因子基数 B = p1,p2,...,pt
  4. r 开始&gt; floor(n)至少搜索 t + 1 值,这样 y ^ 2 = r ^ 2 - n 都只有B中的因子,
  5. 对于每个 y1 y2 ,..., y(t + 1)刚刚计算出你生成一个向量 v (yi)=(e1,e2,...,et)其中 ei 是通过将模数2减去 yi中的指数 pi 来计算的
  6. 使用Gaussian Elimination查找一些添加在一起的向量提供空向量
  7. x 设置为与上一步中找到的 yi 相关的 ri 的产品,并将 y 设置为p1 ^ a * p2 ^ b * p3 ^ c * .. * pt ^ z其中指数是在 yi 的分解中找到的指数的一半
  8. 计算 d = mcd(x-y,n),如果 1&lt; d&lt; n 然后 d n 的重要因素,否则从步骤2开始选择更大的k。
  9. 这些算法的问题在于它们在数值计算中真正意味着很多理论。

答案 4 :(得分:6)

这是Maple中整数因子分解的论文。

“从一些非常简单的指令开始 - ”使整数分解更快 在Maple中 - 我们已经实现了Quadratic Sieve因子分解算法 Maple和C的结合......“

http://www.cecm.sfu.ca/~pborwein/MITACS/papers/percival.pdf

答案 5 :(得分:4)

取决于你的数字有多大。如果您正在搜索友好对,那么您需要进行大量的因子分配,因此关键可能不是尽可能快地考虑因素,而是要在不同的呼叫之间共享尽可能多的工作。为了加快试验分工,您可以查看备忘录,和/或预先计算素数,直到您关注的最大数字的平方根。获得素数因子化更快,然后计算所有因子的总和,而不是每个数字一直循环到sqrt(n)。

如果你正在寻找非常友好的对,比如大于2 ^ 64,那么在少数机器上,无论你的因子分解有多快,你都不能通过分解每一个数字来做到这一点。您用来寻找候选人的捷径可能会帮助您考虑因素。

答案 6 :(得分:4)

针对1GB内存的更多2015 C ++版本2 27 查找表实现:

#include <iostream.h> // cerr, cout, and NULL
#include <string.h>   // memcpy()
#define uint unsigned __int32
uint *factors;
const uint MAX_F=134217728; // 2^27

void buildFactors(){
   factors=new (nothrow) uint [(MAX_F+1)*2]; // 4 * 2 * 2^27 = 2^30 = 1GB
   if(factors==NULL)return; // not able to allocate enough free memory
   int i;
   for(i=0;i<(MAX_F+1)*2;i++)factors[i]=0;

   //Sieve of Eratosthenese
   factors[1*2]=1;
   factors[1*2+1]=1;
   for(i=2;i*i<=MAX_F;i++){
      for(;factors[i*2] && i*i<=MAX_F;i++);
      factors[i*2]=1;
      factors[i*2+1]=i;
      for(int j=2;i*j<=MAX_F;j++){
         factors[i*j*2]=i;
         factors[i*j*2+1]=j;
      }
   }
   for(;i<=MAX_F;i++){
      for(;i<=MAX_F && factors[i*2];i++);
      if(i>MAX_F)return;
      factors[i*2]=1;
      factors[i*2+1]=i;
   }
}

uint * factor(uint x, int &factorCount){
   if(x > MAX_F){factorCount=-1;return NULL;}
   uint tmp[70], at=x; int i=0;
   while(factors[at*2]>1){
      tmp[i++]=factors[at*2];
      cout<<"at:"<<at<<" tmp:"<<tmp[i-1]<<endl;
      at=factors[at*2+1];
   }
   if(i==0){
      cout<<"at:"<<x<<" tmp:1"<<endl;
      tmp[i++]=1;
      tmp[i++]=x;
   }else{
      cout<<"at:"<<at<<" tmp:1"<<endl;
      tmp[i++]=at;
   }
   factorCount=i;
   uint *ret=new (nothrow) uint [factorCount];
   if(ret!=NULL)
      memcpy(ret, tmp, sizeof(uint)*factorCount);
   return ret;
}

void main(){
   cout<<"Loading factors lookup table"<<endl;
   buildFactors(); if(factors==NULL){cerr<<"Need 1GB block of free memory"<<endl;return;}
   int size;
   uint x=30030;
   cout<<"\nFactoring: "<<x<<endl;
   uint *f=factor(x,size);
   if(size<0){cerr<<x<<" is too big to factor. Choose a number between 1 and "<<MAX_F<<endl;return;}
   else if(f==NULL){cerr<<"ran out of memory trying to factor "<<x<<endl;return;}

   cout<<"\nThe factors of: "<<x<<" {"<<f[0];
   for(int i=1;i<size;i++)
      cout<<", "<<f[i];
   cout<<"}"<<endl;
   delete [] f;

   x=30637;
   cout<<"\nFactoring: "<<x<<endl;
   f=factor(x,size);
   cout<<"\nThe factors of: "<<x<<" {"<<f[0];
   for(int i=1;i<size;i++)
      cout<<", "<<f[i];
   cout<<"}"<<endl;
   delete [] f;
   delete [] factors;
}

更新:或者在过去2 28

之后再牺牲一些简单性
#include <iostream.h> // cerr, cout, and NULL
#include <string.h>   // memcpy(), memset()

//#define dbg(A) A
#ifndef dbg
#define dbg(A)
#endif

#define uint   unsigned __int32
#define uint8  unsigned __int8
#define uint16 unsigned __int16

uint * factors;
uint8  *factors08;
uint16 *factors16;
uint   *factors32;

const uint LIMIT_16   = 514; // First 16-bit factor, 514 = 2*257
const uint LIMIT_32   = 131074;// First 32-bit factor, 131074 = 2*65537
const uint MAX_FACTOR = 268501119;
//const uint64 LIMIT_64 = 8,589,934,594; // First 64-bit factor, 2^33+1

const uint TABLE_SIZE = 268435456; // 2^28 => 4 * 2^28 = 2^30 = 1GB 32-bit table
const uint o08=1, o16=257 ,o32=65665; //o64=4294934465
// TableSize = 2^37 => 8 * 2^37 = 2^40 1TB 64-bit table
//   => MaxFactor = 141,733,953,600

/* Layout of factors[] array
*  Indicies(32-bit)              i                 Value Size  AFactorOf(i)
*  ----------------           ------               ----------  ----------------
*  factors[0..128]            [1..513]             8-bit       factors08[i-o08]
*  factors[129..65408]        [514..131073]        16-bit      factors16[i-o16]
*  factors[65409..268435455]  [131074..268501119]  32-bit      factors32[i-o32]
*
* Note: stopping at i*i causes AFactorOf(i) to not always be LargestFactor(i)
*/
void buildFactors(){
dbg(cout<<"Allocating RAM"<<endl;)
   factors=new (nothrow) uint [TABLE_SIZE]; // 4 * 2^28 = 2^30 = 1GB
   if(factors==NULL)return; // not able to allocate enough free memory
   uint i,j;
   factors08 = (uint8 *)factors;
   factors16 = (uint16 *)factors;
   factors32 = factors;
dbg(cout<<"Zeroing RAM"<<endl;)
   memset(factors,0,sizeof(uint)*TABLE_SIZE);
   //for(i=0;i<TABLE_SIZE;i++)factors[i]=0;

//Sieve of Eratosthenese
     //8-bit values
dbg(cout<<"Setting: 8-Bit Values"<<endl;)
   factors08[1-o08]=1;
   for(i=2;i*i<LIMIT_16;i++){
      for(;factors08[i-o08] && i*i<LIMIT_16;i++);
dbg(cout<<"Filtering: "<<i<<endl;)
      factors08[i-o08]=1;
      for(j=2;i*j<LIMIT_16;j++)factors08[i*j-o08]=i;
      for(;i*j<LIMIT_32;j++)factors16[i*j-o16]=i;
      for(;i*j<=MAX_FACTOR;j++)factors32[i*j-o32]=i;
   }
   for(;i<LIMIT_16;i++){
      for(;i<LIMIT_16 && factors08[i-o08];i++);
dbg(cout<<"Filtering: "<<i<<endl;)
      if(i<LIMIT_16){
         factors08[i-o08]=1;
         j=LIMIT_16/i+(LIMIT_16%i>0);
         for(;i*j<LIMIT_32;j++)factors16[i*j-o16]=i;
         for(;i*j<=MAX_FACTOR;j++)factors32[i*j-o32]=i;
      }
   }i--;

dbg(cout<<"Setting: 16-Bit Values"<<endl;)
     //16-bit values
   for(;i*i<LIMIT_32;i++){
      for(;factors16[i-o16] && i*i<LIMIT_32;i++);
      factors16[i-o16]=1;
      for(j=2;i*j<LIMIT_32;j++)factors16[i*j-o16]=i;
      for(;i*j<=MAX_FACTOR;j++)factors32[i*j-o32]=i;
   }
   for(;i<LIMIT_32;i++){
      for(;i<LIMIT_32 && factors16[i-o16];i++);
      if(i<LIMIT_32){
         factors16[i-o16]=1;
         j=LIMIT_32/i+(LIMIT_32%i>0);
         for(;i*j<=MAX_FACTOR;j++)factors32[i*j-o32]=i;
      }
   }i--;

dbg(cout<<"Setting: 32-Bit Values"<<endl;)
     //32-bit values
   for(;i*i<=MAX_FACTOR;i++){
      for(;factors32[i-o32] && i*i<=MAX_FACTOR;i++);
      factors32[i-o32]=1;
      for(j=2;i*j<=MAX_FACTOR;j++)factors32[i*j-o32]=i;
   }
   for(;i<=MAX_FACTOR;i++){
      for(;i<=MAX_FACTOR && factors32[i-o32];i++);
      if(i>MAX_FACTOR)return;
      factors32[i-o32]=1;
   }
}

uint * factor(uint x, int &factorCount){
   if(x > MAX_FACTOR){factorCount=-1;return NULL;}
   uint tmp[70], at=x; int i=0;
   while(at>=LIMIT_32 && factors32[at-o32]>1){
      tmp[i++]=factors32[at-o32];
dbg(cout<<"at32:"<<at<<" tmp:"<<tmp[i-1]<<endl;)
      at/=tmp[i-1];
   }
   if(at<LIMIT_32){
      while(at>=LIMIT_16 && factors16[at-o16]>1){
         tmp[i++]=factors16[at-o16];
dbg(cout<<"at16:"<<at<<" tmp:"<<tmp[i-1]<<endl;)
         at/=tmp[i-1];
      }
      if(at<LIMIT_16){
         while(factors08[at-o08]>1){
            tmp[i++]=factors08[at-o08];
dbg(cout<<"at08:"<<at<<" tmp:"<<tmp[i-1]<<endl;)
            at/=tmp[i-1];
         }
      }
   }
   if(i==0){
dbg(cout<<"at:"<<x<<" tmp:1"<<endl;)
      tmp[i++]=1;
      tmp[i++]=x;
   }else{
dbg(cout<<"at:"<<at<<" tmp:1"<<endl;)
      tmp[i++]=at;
   }
   factorCount=i;
   uint *ret=new (nothrow) uint [factorCount];
   if(ret!=NULL)
      memcpy(ret, tmp, sizeof(uint)*factorCount);
   return ret;
}
uint AFactorOf(uint x){
   if(x > MAX_FACTOR)return -1;
   if(x < LIMIT_16) return factors08[x-o08];
   if(x < LIMIT_32) return factors16[x-o16];
                    return factors32[x-o32];
}

void main(){
   cout<<"Loading factors lookup table"<<endl;
   buildFactors(); if(factors==NULL){cerr<<"Need 1GB block of free memory"<<endl;return;}
   int size;
   uint x=13855127;//25255230;//30030;
   cout<<"\nFactoring: "<<x<<endl;
   uint *f=factor(x,size);
   if(size<0){cerr<<x<<" is too big to factor. Choose a number between 1 and "<<MAX_FACTOR<<endl;return;}
   else if(f==NULL){cerr<<"ran out of memory trying to factor "<<x<<endl;return;}

   cout<<"\nThe factors of: "<<x<<" {"<<f[0];
   for(int i=1;i<size;i++)
      cout<<", "<<f[i];
   cout<<"}"<<endl;
   delete [] f;

   x=30637;
   cout<<"\nFactoring: "<<x<<endl;
   f=factor(x,size);
   cout<<"\nThe factors of: "<<x<<" {"<<f[0];
   for(int i=1;i<size;i++)
      cout<<", "<<f[i];
   cout<<"}"<<endl;
   delete [] f;
   delete [] factors;
}

答案 7 :(得分:0)

从2020年开始,这是一个重要的开放数学问题

另一些​​人则从实用的角度回答了问题,对于在实践中遇到的问题大小,这些算法很可能接近最优值。

但是,我还要强调一点,更普遍的数学问题(在asymptotic computation complexity中,即随着位数趋于无穷大)是完全没有解决的。

没有人能证明最快的算法中最短的最佳时间是什么。

这显示在Wikipedia页面上: https://en.wikipedia.org/wiki/Integer_factorization该算法还在Wiki的“计算机科学中未解决的问题列表”页面上显示:https://en.wikipedia.org/wiki/List_of_unsolved_problems_in_computer_science

我们所知道的是,我们目前拥有的最好的就是general number sieve。直到2018年,我们didn't even have a non-heuristic proof for its complexity

截至2020年,我们甚至还没有证明问题是否为NP-complete(尽管显然是NP,因为您要做的是验证解决方案就是将数字相乘)!尽管人们普遍认为它是NP完全的。我们可以在寻找算法方面做得那么好,可以吗?

答案 8 :(得分:-1)

当然还有 Hal Mahutan 教授的 HAL 算法(2021 年 2 月),它处于因子分解研究的边缘。

求解公钥的两个大素数如下...

可以在正 X 轴和 Y 轴上绘制任何 AxB = 公钥,该轴形成一条连续曲线,其中所有非整数因子都可以求解公钥。当然,这没有用,目前只是一个观察。

Hal 的见解是:如果我们坚持认为我们只对 A 是整数的点感兴趣,尤其是当 A 是整数时 B 出现的点。

当数学家们为 B 的其余部分的明显随机性或至少缺乏可预测性而苦苦挣扎时,以前使用这种方法的尝试失败了。

Hal 的意思是,只要 a/b 的比率相同,任何公钥的可预测性都是通用的。基本上,当一系列不同的公钥用于分析时,如果它们在处理过程中共享相同的点,其中 a/b 是恒定的,即它们共享相同的切线,则可以对所有公钥进行相同的处理。

看看我画的这张草图,试图解释马胡坦教授在这里发生了什么。

Hal's Insight

所以,这就是哈尔的天才之处。 Hal 利用强大的超级计算机生成一系列哈希表(在图表中,Q、R、S & T)。您可以在左侧的 3 A x B = Key 曲线中看到的是,它们都共享切线 T 和 S(此处唯一突出显示的那些),但该图显示的是给定任何公钥,在切线相同的曲线,那么您可以共享主持该区域的哈希表。

只是一个技术说明,显然在 AxB=Key 的曲线中,随着 AxB 的值的变化,事物正在不断变化,因此理论上,映射到哈希表的共享切线将过时,但是有趣的是,使用非常大的密钥(具有讽刺意味的是,这使它们更容易破解,因为它们共享更长的运行时间,而哈希表是有用的。)。所以这是个好消息,因为随着分解和计算的进步加速,密钥大小预计会变得更大。实际发生的情况是,哈希表的可预测性将在字面上“失去焦点”,因为它们所应用的切线开始出现分歧。幸运的是,这不是问题,因为您会跳转到下一个适当映射到新切线的哈希表。

明确地说,所有生成的公钥总是使用相同的哈希表集,因此这是一种一次性投资,可以在线存储,字面上数百万兆字节的查找数据,因为所有密钥遵循相同的切线比。

那么,哈希表如何加速质数的查找。当公钥除以 B 时,哈希表将用剩余的部分进行索引。基本上,Hal 说对于所有的键,可以查找 A x B 的任何比率。共享相同切线的不同曲线之间的唯一区别是它们需要由“控制​​曲线”确定的不同偏移。 “控制曲线”是您为其生成适当因子的任意曲线。假设对于“控制曲线”,Key 为 15,映射的切线是 B = 5 时,因此 A 为 3,余数为零。在另一个具有相同切线的映射中,假设密钥现在是 16。我们需要找到相同的切线,它位于 B 的 5.33 和 A 的 3.2。所以 A 的余数是 0.2,所以公钥 16 可以使用与 15 相同的查找表,前提是它适当地偏移了 0.2。

那么哈希表中有什么?由偏移量索引,该值始终返回沿 AxB 曲线的距离,您找不到其他 B 的整数。Hal 所说的是向前跳跃而不检查这些数字是否为因数是安全的。基本上就是这样。哈尔在曲线上打了一些不需要检查的洞,这只会加快整个游戏的速度。

谢谢马胡坦教授!


对于那些仍在关注的人,以下是我们的一些工作笔记:


快速分解攻击算法的要点

  • 所有公钥都可以沿曲线 A x B = 'Key' 表示
  • 这是一个映射所有实数的观察(这是非整数的正确术语吗?)所有实数相乘以等于键......到目前为止没有用
  • 我们只对 A 是整数且 B 都是整数的点感兴趣。
  • 我们可以遍历整条线,其中 A 是完整的。这是一半,但有问题。首先,我们不知道 B 在哪里是完整的,而且计算所有点需要太多的处理能力。
  • 我们感兴趣的是预测 B 在哪里也是整数,所以我们希望有一种机制能够沿着曲线“跳跃”,在那里我们知道 B 肯定仍然是一个实数(非整数)。如果我们可以进行足够大的跳跃,那么我们就会减少所需的处理。

现在按照算法的策略预测B

  • 另一个观察结果是,对于足够大的“键”值,当我们以整数增量逐步改变 A 的值时,我们观察到 A/B 的比率或切线角将大部分保持不变一样。

  • 这个观察的一个重要方面是,随着密钥大小的增加,每次迭代的切线保持更加恒定。从根本上说,这意味着任何使用此属性的算法都将随着 Key 大小的增加而变得更加有效,这与传统方法相反,在传统方法中,增加 Key 大小会使猜测因子的难度呈指数级增长。这是非常重要的一点...(请尼克详细说明)

  • 算法本身如下

  • 在云上购买足够的存储和处理能力
  • 将问题分解为可以在不同进程上并行运行的部分。为此,我们逐步遍历 A 的不同值,并将搜索分配给云中的不同处理器。
  • 对于正在检查的 A 的任何值,使用通用查找表来预测沿曲线的安全距离,我们可以移动该距离而无需计算 B 是否为整数
  • 仅检查曲线上查找表显示其为整数的概率足以保证检查的那些位置。

这里的重要概念是,在查找变得不准确(并且失去焦点)之前,对于 A/B(切线)的比率足够接近的任何“键”都可以共享查找表。< /p>

(还要注意的是,随着密钥大小的变化,您要么需要一组新表,要么应该对现有比率表进行适当的映射,以便重用它们。)

Nick 另一个非常重要的点是所有 Key 都可以共享相同的一组查找表。

从根本上说,查找表映射了 Key / A 的任何计算的余数。我们对余数感兴趣,因为当余数 = 0 时,我们已经完成了,因为 A 已经是一个整数。

我建议我们有足够的哈希表来确保我们可以提前扫描而不必计算实际的余数。假设我们从 1 万亿开始,但这显然可以根据我们拥有的计算能力而改变。

任何适当接近的 A/b 比率的哈希表如下。该表使用适当的余数进行索引(键控),并且任何给定余数处的值是可以沿着 A * B 曲线遍历而余数不接触零的“安全”距离。

您可以想象余数在 0 和 1 之间振荡(伪随机)。

算法沿曲线选择一个数字 A,然后查找安全距离并跳转到下一个哈希表,或者至少算法会进行这些因素检查,直到下一个哈希表可用。如果有足够多的哈希表,我认为我们几乎可以避免大部分检查。

关于查找表的说明。

对于任何键,您都可以生成一个针对 A/B 比率适当弯曲的表格 表的重用势在必行。 建议的方法 从 N 的平方根(适当的键)生成 A/B 的一系列控制曲线,并通过平方在中间完成。 假设每个键都比前一个键大 0.0001% 让我们也将表格的大小设为 A/B 比率的 1% 在计算互质因子时,选择与键匹配的最接近的查找表。 选择进入哈希表的入口点。 这意味着记住表中入口点与实际入口点的偏移量。 查找表应为入口点提供一系列点,相应的互质入口可能非常接近于零,需要手动检查。 对于系列中的每个点,使用适当的数学公式计算实际偏移量。 (这将是一个积分计算,我们需要让数学家看一下) 为什么?因为我们的控制表是在 A/B 是 Key 的平方根时计算的。当我们沿着曲线移动时,我们需要适当地间隔开。 作为奖励,数学家能否将 Key 空间折叠成 A/B 曲线上的一个点。如果是这样,我们只需要生成一个表。

关键概念


数字 A x B = Key 绘制了以下内容:

X 轴映射 A 和 Y 轴映射 B。

曲线与 A 轴和 B 轴的接近程度取决于公钥的大小(其中 A x B = 密钥)。 基本上,随着 Key 变大,曲线将向右移动。

现在我想让你消化或有疑问的想法是

  • 给定曲线上的任何点,都存在切线(A/B 的比率)
  • 我们对 B 的值感兴趣,因为 A 以整数递增并且本身就是一个整数。特别是,当 Key / A 是余数为零时,我们真的只对 B 的余数感兴趣。我们将找到这个公钥的因素。具体来说,这将是我们尝试的 A 的最后一个值(也始终是整数),以及余数为零的值 B(因此也是整数)。
  • 基本算法非常简单。 -1- 在曲线上选择一个 A 是整数的点 -2- 找出 B 的余数,其中 Key/A 是 B -3- 检查 B 的余数是否为零,(如果为零,则完成。) -4- 返回步骤 1,(接下来您将选择 A 的下一个整数)

这会奏效,但是太慢了。我们对 HAL 算法所做的是改进上述基本算法,以便我们可以在知道余数不会太接近于零的情况下跳转曲线块。

我们跳得越多,算法就越高效。

在我们进入改进的 HAL 算法之前,让我们回顾一个关键概念。

  • 对于非常大的 Key 值(记住 A x B = Key),A/B 的比率几乎是恒定的,RSA 密钥是 4096 的 2 次方,这很大。

  • 假设我们已经创建了一组已预加载到云中的表,这些表针对特定(大致)大小的键进行了优化。

    • 假设我们有 100 万个表仅用于这种特别窄的键大小范围
    • 每个表映射的切线或 A/B 比率略有不同,但请记住,所有这些表只能用于适当的密钥大小
    • 这些表格沿曲线均匀分布,因此对于我选择的任何点,都有一个表格可以告诉我在出现余数为零的可能性出现之前我可以安全地跳跃多少个 A 的整数对于 B in Key/A = B。请记住,我们不想错过 B 为零或 HAL 不起作用的点。
    • 这些表使用当前的剩余部分进行索引。 (程序员或开发人员注意到这些查找表可以用 Hashtable 实现,比如在 C# 或 Java 中)假设我们必须检查 A 沿曲线移动的所有点,每次产生余数 B。只要 B 是足够接近任何索引,然后您可以使用该表计算出当 B 的余数为 0 时,您可以安全跳转多少个整数而不会遗漏。
  • 这件作品是一个关键概念。

    • 每组使用一些合理偏移进行索引的查找表只真正适用于特定的键大小。
    • 随着一系列表的键的增长或缩小,查找的结果会“失去焦点”或变得更加不准确。
    • 因此,随着 Key 大小的增长,我们还需要创建一系列新表。也许我们需要在密钥每增长 0.001% 时创建表。