【问题标题】:Find sequential smallest sums of the multiples of three different numbers, javascript查找三个不同数字的倍数的连续最小总和,javascript
【发布时间】:2017-05-30 20:11:29
【问题描述】:

this post 上,我找到了一种算法来确定 RGB 颜色的亮度:

亮度(某些色彩空间的标准):(0.2126*R + 0.7152*G + 0.0722*B)

我想使用这个等式,从 rgb(0,0,0) 开始,按照从最低到最高亮度的顺序生成所有 RGB 颜色,然后将它们绘制到 4096x4096 画布上。

我的问题是,对于 1670 万种不同的组合,我无法全部生成它们然后对它们进行排序,而不会导致浏览器崩溃或花费数天时间来完成渲染。所以我想找到一种方法来找到每个数字的倍数,这些数字总和是下一个最小的数字。

例如,从0,0,0 的 rgb 开始,亮度将为 0 (0.2126*0 + 0.7152*0 + 0.0722*0 = 0),下一个最小发光 rgb 值将是0,0,1,因为0.2126*0 + 0.7152*0 + 0.0722*1 = .0722,并且没有一组倍数可以求和为一个较小的数。

前 19 个连续的亮度值如下(我可能错过了一两个,因为我手动计算了它们,但希望它有助于说明这一点):

RGB       =>     Luminence

0,0,0     =>     0
0,0,1     =>     .0722
0,0,2     =>     .1444
1,0,0     =>     .2126
0,0,3     =>     .2166
1,0,1     =>     .2848
0,0,4     =>     .2888
1,0,2     =>     .357
0,0,5     =>     .361
2,0,0     =>     .4252
1,0,3     =>     .4292
0,0,6     =>     .4332
2,0,1     =>     .4974
1,0,4     =>     .5014
0,0,7     =>     .5054
2,0,2     =>     .5696
1,0,5     =>     .5736
0,0,8     =>     .5776
3,0,0     =>     .6378

我似乎找不到任何模式,所以我希望也许有一个方程式或编码技巧可以让我找到三个数字的倍数的最小和,高于前一个总和,无需暴力破解并检查每个可能的值。

编辑:我做了一些额外的研究,看起来解决方案可能在于使用线性丢番图方程。如果我取每个小数并乘以 1000,得到2126, 7152, & 722。然后逐一计数到2,550,000 (2126*255 + 7152*255 + 722*255),我可以检查每个数字是否是方程2126r + 7152g + 722b = n 的解,其中 n 是当前计数到的数字,r, g, & b 是未知数。如果我能做到这一点,我可以计算出下一个连续亮度值处所有可能的 rgb 值,甚至不必将重复亮度值的任何值加倍,我只需要进行 255 万次计算而不是 16.77+ 百万次(每种颜色一个)。如果有人知道如何编写这个方程式,或者如果有人有更好的解决方案,我将非常感激。谢谢!

【问题讨论】:

    标签: javascript algorithm rgb luminance diophantine


    【解决方案1】:

    这里有一个算法(忘记了它的名字)你的问题: 该算法可以列出按某种顺序排序的所有颜色元组 {R,G,B}。在您的情况下,它是亮度上升:color1 f(color1)

    • 初始化:arr = [{r:0, g:0, b:0}](最小颜色)
    • 重复:
      • 为每个 i 选择 min(iR):a[iR] = {rR arr[i]。 (选择 arr 中的第一个颜色,如果我们将 1 添加到它的 r 分量,我们会得到一个新颜色,它大于当前 arr 中的所有颜色)
      • iG 和 iB 类似 => 也得到 cG = {rG, gG + 1, bG} 和 cB = {rB, gB, bB + 1}
      • 在cR、cG和cB中选择最小的颜色c
      • 将 c 附加到数组 arr

    当找不到这样的 iR、iG 或 iB 时,算法停止。

    注意事项:

    • arr 总是按排序(升序)顺序,因为每次向 arr 追加新颜色时,它总是大于当前 arr 中的每个元素强>。
    • 因为arr是升序排列,所以我们只需要比较cR/cG/cB和arr的最后一个元素,看看它是否大于arr
    • iR、iG 和 iB 在整个算法中增加
    • 复杂度为 O(N),其中 N 的颜色数 (2^24) ~ 16M。使用基于堆的算法,复杂度约为 O(NlogN)。

    这是我的实现(在 nodejs 6 中测试)

    //  use integer to avoid floating point inaccuracy
    const lumixOf = {r: 2126, g: 7152, b: 722};
    
    const maxValue = 256;
    const components = ['r', 'g', 'b'];
    
    class Color {
      constructor(r, g, b, lum) { 
        this.r = r;
        this.g = g;
        this.b = b;
        this.lum = lum;
      }
    
      add(component) { 
        const ans = new Color(this.r, this.g, this.b, this.lum);
        if (++ans[component] >= maxValue) return null; // exceed 255
        ans.lum += lumixOf[component];
        return ans;
      }
    
      greater(color2) {
        // return this.lum > color2.lum;
        if (this.lum !== color2.lum) return this.lum > color2.lum;
        if (this.r !== color2.r) return this.r > color2.r;
        if (this.g !== color2.g) return this.g > color2.g;
        return this.b > color2.b;        
      }
    }
    
    let a = [new Color(0, 0, 0, 0)];  //  R, G, B, lumix
    let index = {r: 0, g: 0, b: 0};
    
    console.log('#0:', a[0]);
    // Test: print the first 100 colors
    for (let count = 1; count < 100; ++count) {
      let nextColor = null;
      const len = a.length;
      const currentColor = a[len - 1];
      components.forEach(component => {
        let cIndex = index[component];
        for (; cIndex < len; ++cIndex) {
          const newColor = a[cIndex].add(component);
          if (!newColor || !newColor.greater(currentColor)) continue;
    
          //  find the minimum next color
          if (nextColor == null || nextColor.greater(newColor)) {
            nextColor = newColor;
          }
          break;
        }
        index[component] = cIndex;
      });
    
      if (!nextColor) break;  //  done. No more color
      a.push(nextColor);
      console.log('#' + count + ':', nextColor);
    }
    console.log(a.length);
    

    这个实现列出了所有 2^24 = 16777216 种颜色(一旦你删除了主循环中的 count &lt; 100 条件,但你不想打印出这么多行)。如果某些颜色具有相同的亮度值,则将它们按其 R 值排序,然后是 G 值,然后是 B 值。如果每个亮度值只需要一种颜色,请取消注释 greater() 函数中的第一行 - 然后您将获得 1207615 种具有不同亮度的颜色

    【讨论】:

    • 您好,很抱歉我花了一段时间才回复。我试过你的解决方案,效果很好。但是,我一生都无法理解您的方法。我将把它设置为接受的答案,但如果你有时间为我把它变笨,那么我将非常感激。如果你不能,那不是问题。我主要是一名设计师,开发实际上只是一个副业,因此我更像是一个视觉学习者。因此,如果您碰巧能够找到与您的方法相关的任何图像或视频,那么这可能是完美的。再次,非常感谢!
    • 因为我的答案已经很长了,所以我要添加另一个答案来澄清算法。
    【解决方案2】:

    您可以利用的一个事实是,序列中的每个三元组的 R、G 或 B 值仅比已输出的三元组大一个。

    因此,您可以维护一个BinaryHeap(按亮度排序),其中包含在 R、G 或 B 中比已输出的三元组大 1 的所有三元组,并在循环中执行此操作:

    • 从堆中移除最小元素(r, g, b)
    • 输出
    • 将 (r+1, g, b)、(r, g+1, b) 和 (r, g, b+1) 添加到堆中,但前提是它们是有效的三元组(所有值小于或等于 255),并且仅当它们尚未在堆中时。如果可以从(在允许的范围内 r、g 或 b 中少 1 个)生成的替代三元组具有比 (r, g, b) 更高的亮度,则三元组将不会已经在堆中。

    例如,如果 (r+1, g-1, b) 的亮度高于 (r, g, b) 或 (r+1, g-1, b) 无效。由于基于 r, g, b 计算亮度的因子是固定的,因此 (r+1, g-1, b) 将始终具有较低的亮度,并且您应该只添加 (r+1, g, b) 如果 ( r+1, g-1, b) 无效,即g为0时。

    在伪代码中,规则如下:

    function addTriplets(r, g, b)
    {
        if(g < 255)
           pushTripletToHeap(r, g + 1, b);
        if((g == 0) && (r < 255))
           pushTripletToHeap(r + 1, g, b);
        if((g == 0) && (r == 0) && (b < 255))
           pushTripletToHeap(r, g, b + 1);
    }
    

    在开始循环之前将 (0, 0, 0) 三元组压入堆中,并在堆为空时停止循环。

    【讨论】:

    • 感谢您的回答,我没有受过正式的计算机科学教育,因此直到现在我才听说过二进制堆。我不得不说这实际上是一个有趣的概念,并且是一种非常酷的应用方式。但我确实有一个问题,如果你看过我的问题的编辑,我会讨论使用线性丢番图方程的可能性。考虑到这一点,我做了一些研究,发现任何不能被三个系数的最大公约数整除的数字都不能成为方程的解。 (续)
    • GCD 恰好为 2,这意味着只有 0 到 255 万 (2126*255 + 7152*255 + 722*255) 之间的 127.5 万个数字是可能的解决方案,而计数的数字是最终的亮度。话虽如此,有 16,777,216 个不同的 rgb 值,但只有 1,275,000 个可能的亮度值,这意​​味着每个可能的数字平均会有 13 个重叠的亮度值。如果亮度值相同,我想按红色的数量排序,然后是绿色,然后是蓝色。 (续)
    • 所以我的问题是,知道会有很多重叠值,并且知道在这种情况下我想按 rgb 顺序排序,使用二进制堆仍然是最有效的方法,或者它甚至是最实用的吗?再次,我非常感谢您的回答,它非常聪明,但是您可以提供任何更多的见解将不胜感激!谢谢!
    • 使用线性丢番图方程绝对是解决这个问题的一种聪明而有效的方法。它肯定会使用更少的内存,尽管我不确定它是否会比使用堆更快。唯一的问题是易于实现:编写丢番图方程求解器是否比使用二叉堆更复杂。
    【解决方案3】:

    对不起,我不得不说你在做一些浪费的工作。

    RGB 的 8 位量化将在 256 个亮度级别(包括黑白)下产生 1670 万种颜色但是您没有足够的像素将它们全部显示在 4K 显示器上,而 4K 显示器的像素为 3840 x 2160 = 8294400 4K 电视标准或类似 4096 x 2160 = 4K 电影标准的 8847360。除了 1 像素颜色样本对眼睛来说意味着什么,尤其是在 4K 显示器上..?

    我建议您使用 7 位量化而不是 8 位。这将为您提供 2^21 => 2097152 个颜色样本,它们将在高清监视器/电视上映射为单个像素,在 4K 监视器/电视上映射为 2x2 像素。漂亮。

    代码如下;

    "use strict";
    var allColors = Array(Math.pow(2,21)),           // All 2^21 colors
             cgbl = Array(128).fill().map(e => []);  // Colors gropuped by luminance
    for (var i = 0, len = allColors.length; i < len; i++) allColors[i] = [i>>14, (i&16256)>>7, i&127];
    allColors.reduce((g,c) => (g[Math.round(c[0]*0.2126 + c[1]*0.7152 + c[2]*0.0722)].push(c),g), cgbl);
    cgbl.forEach((y,i) => console.log(y.length,"Colors at luminance level:",i));

    但请记住,您的 RGB 值现在处于 7 位量化状态。由于我们已经将它们分组为 128 个亮度级别,我还建议您将亮度组(子数组)中的每个 RGB 值映射回 8 位,方法是在显示它们之前将它们的值左移 1 位 (r &lt;&lt; 1; g &lt;&lt; 1; b &lt;&lt; 1;)。通过使用 .map() 函子,这是一项微不足道的工作。

    【讨论】:

    • 感谢您的意见,但这并不意味着任何公开。我试图为个人研究项目完成此任务,因此我必须拥有全部 1670 万种颜色。同样,您认为只有 256 个亮度级别是不正确的。哎呀,简单地向单个颜色通道添加 1 直到它们全部充满将产生至少 768 个独特的亮度级别,但这只是假设每个通道的权重相同。就像我说的,感谢您抽出宝贵时间留下您的意见,但这个答案既不准确又居高临下。
    【解决方案4】:

    因为我的原始答案已经很长了,所以我做出这个答案是为了澄清算法,正如 OP 所要求的那样

    让我们考虑一个类似的问题(但更容易推理):

    • 让 A 是一组数字,按升序排列,除了 2、3 和 5 之外没有其他素因数 (Ai = 2^x * 3^y * 5^z)
    • 求A的第n个数

    当然 A1 = 1 = 2^0 * 3^0 * 5^0

    假设在某个步骤,我们已经计算了 A1..An,我们需要找到 A[n+1]

    • 如果 A[n+1] 可以被 2 整除,则 A[n+1] = A[i2]*2 且 1
    • 如果 A[n+1] 可以被 3 整除,则 A[n+1] = A[i3]*3 且 1
    • 如果 A[n+1] 可以被 5 整除,则 A[n+1] = A[i5]*5 且 1

    (显然 A[n+1] 可以被其中至少一个整除)

    证明:A[n+1] = 2^x * 3^y * 5^z。如果 A[n+1] 可以被 2 整除,则 x > 0,所以 B = A[n+1]/2 = 2^(x-1) * 3^y * 5^z 必须在 A 中。因为 B

    所以要找到 A[n+1],我们可以:

    • 求 A[i2]*2 > A[n] 的最小 i2
    • i3 和 i5 类似
    • ==> A[n+1] = min(A[i2]*2, A[i3]*3, A[i5]*5)

    让 A1 = 1,运行这些步骤 (n - 1) 次,我们找到 A 的第 n 个数

    现在,如果在每次迭代中找到 A[n+1],我们使用从 1 到 n 的 3 个 for 循环来计算 i2、i3 和 i5,时间复杂度将为 O(N^2)。但是您可以看到每次迭代的 i2、i3 和 i5 永远不会减少(分别小于前一次迭代的值)。所以我们可以保存这些 i2、i3 和 i5 值,并且在每次迭代中我们只需要:

    while (A[i2]*2 <= A[n]) ++i2;
    while (A[i3]*3 <= A[n]) ++i3;
    while (A[i5]*5 <= A[n]) ++i5;
    

    现在时间复杂度变成 O(N):虽然while 循环仍然嵌套在主循环for 1-&gt;n 中,但只有 4 个变量从 1 -> n 递增,可以认为是 4 个独立循环。您可以使用时钟验证 O(N) 属性并测量不同 N s 的运行时间

    将此算法应用于您的问题:

    • 2、3 和 5 变为 R、G 和 B
    • 整数比较变成你定义的颜色比较函数
    • 如果您需要列出所有 2^24 种颜色,请定义比较函数,以便没有 2 种不同的颜色“相等”(如果 C1 和 C2 是 2 种不同的颜色,则 C1

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-03-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-07-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多