【问题标题】:Fastest way of finding the middle value of a triple?找到三元组中间值的最快方法?
【发布时间】:2010-12-07 15:07:03
【问题描述】:

Given 是一个由三个数值组成的数组,我想知道这三个数值的中间值。

问题是,找到三者中间的最快方法是什么

我的方法是这种模式 - 因为有三个数字,所以有六个排列:

if (array[randomIndexA] >= array[randomIndexB] &&
    array[randomIndexB] >= array[randomIndexC])

如果有人可以帮助我找到一种更优雅更快的方法,那就太好了。

【问题讨论】:

  • 幸运的是,无论您比较整数还是浮点数,答案都是一样的 :-)
  • QuickSort 的中值三个枢轴选择?
  • 也可以是快速选择

标签: java algorithm conditional logic median


【解决方案1】:

这里有一个使用 min/max 且没有分支的答案 (https://stackoverflow.com/a/14676309/2233603)。实际上 4 min/max 操作足以找到中位数,不需要 xor's:

median = max(min(a,b), min(max(a,b),c));

不过,它不会为您提供中值的索引...

所有案例的细分:

a b c
1 2 3   max(min(1,2), min(max(1,2),3)) = max(1, min(2,3)) = max(1, 2) = 2
1 3 2   max(min(1,3), min(max(1,3),2)) = max(1, min(3,2)) = max(1, 2) = 2
2 1 3   max(min(2,1), min(max(2,1),3)) = max(1, min(2,3)) = max(1, 2) = 2
2 3 1   max(min(2,3), min(max(2,3),1)) = max(2, min(3,1)) = max(2, 1) = 2
3 1 2   max(min(3,1), min(max(3,1),2)) = max(1, min(3,2)) = max(1, 2) = 2
3 2 1   max(min(3,2), min(max(3,2),1)) = max(2, min(3,1)) = max(2, 1) = 2

【讨论】:

  • 这段代码令人印象深刻,只用了 4 min/max 来实现它。
  • it works 即使某些值相等
  • 感谢代码!终于找到了一个中位数为三的优雅代码!
  • 洞察这个答案:当你有一个函数clamp(x,L,H) = max(L,min(H,x))时,3的中位数是clamp(c, min(a,b), max(a,b))
  • 这是一个很棒的实现,谢谢!对向量有用
【解决方案2】:

如果硬件可以在没有分支的情况下回答最小和最大查询(今天的大多数 CPU 都可以做到这一点),那么就可以在没有分支的情况下回答查询。

运算符 ^ 表示按位异或。

Input: triple (a,b,c)
1. mx=max(max(a,b),c)
2. mn=min(min(a,b),c)
3. md=a^b^c^mx^mn
4. return md

这是正确的,因为:

  • xor 是可交换的和关联的
  • 等位异或产生零
  • xor 为零不会改变位

应该为 int/float 选择适当的 min/max 函数。 如果仅存在正浮点数,则可以直接在浮点表示上使用整数 min/max(这可能是可取的,因为整数运算通常更快)。

在硬件不支持最小/最大的不太可能的情况下,可以执行以下操作:

max(a,b)=(a+b+|a-b|)/2
min(a,b)=(a+b-|a-b|)/2

但是,使用浮点运算时这是不正确的,因为需要精确的最小值/最大值,而不是接近它的值。幸运的是,硬件支持浮点最小值/最大值已有很长时间了(在 x86 上,从 Pentium III 及更高版本)。

【讨论】:

  • b+|a 是什么意思? +| 都是二元运算符。
  • 这只是使用绝对值对 min 和 max 函数的扩展。 |a-b|表示 a-b 的绝对值。无论哪种方式,我都推荐 Gyorgy (stackoverflow.com/a/19045659/2037811) 下面给出的答案,它比我的更简洁。
  • min = (a < b) ? (a < c) ? a : c : (b < c) ? b : c;max = (a > b) ? (a > c) ? a : c : (b > c) ? b : c;
  • @Max 我发现您的解决方案比 Gyorgy 的解决方案更容易理解。但最令人惊讶的是,如果我使用 gcc 7.2 -O3 编译这些解决方案,您的解决方案会快两倍。使用 clang 4.0 Gyorgy 的解决方案比您的解决方案略快,并且它们都比最好的 gcc 快 15%。
【解决方案3】:

如果您正在寻找最有效的解决方案,我想它是这样的:

if (array[randomIndexA] > array[randomIndexB]) {
  if (array[randomIndexB] > array[randomIndexC]) {
    return "b is the middle value";
  } else if (array[randomIndexA] > array[randomIndexC]) {
    return "c is the middle value";
  } else {
    return "a is the middle value";
  }
} else {
  if (array[randomIndexA] > array[randomIndexC]) {
    return "a is the middle value";
  } else if (array[randomIndexB] > array[randomIndexC]) {
    return "c is the middle value";
  } else {
    return "b is the middle value";
  }
}

这种方法需要至少两次,最多三个比较。它故意忽略了两个值相等的可能性(就像您的问题一样):如果这很重要,则可以扩展该方法以检查这一点。

【讨论】:

  • 这有点难看,我认为 OP 正在寻找一个优雅的解决方案。诀窍是很多人将更少的字符误认为更优雅,而实际上,更直接的(这个答案)更容易被编译器/虚拟机优化。
  • 即使这段代码是18行;这是有效的。将它放在一个函数中,并在需要时简单地调用它。
【解决方案4】:

这最多可以通过两次比较来完成。

int median(int a, int b, int c) {
    if ( (a - b) * (c - a) >= 0 ) // a >= b and a <= c OR a <= b and a >= c
        return a;
    else if ( (b - a) * (c - b) >= 0 ) // b >= a and b <= c OR b <= a and b >= c
        return b;
    else
        return c;
}

【讨论】:

  • 您尝试过中位数(INT_MIN,INT_MAX,0)吗?我在二补机上得到 INT_MAX...
  • 是的,这很容易发生整数溢出。我不建议在生产中使用它,因为它是这样写的。
  • 在第二个条件中使用((long)b - c) 允许重用((long)a - b)
【解决方案5】:

还有一个想法。有三个号码{a,b,c}。那么:

middle = (a + b + c) - min(a,b,c) - max(a,b,c);

当然,我们必须记住数字限制...

【讨论】:

  • 不明白。 Java 没有采用 3 个参数的 min()max()
  • 这只是一个idea如何解决问题,而不是确切的解决方案
  • @Celeritas min(a,b,c) = min(a,min(b,c))
  • 对于带有 3 个参数的 min/max,您需要再次进行 2 或 3 次比较,因此在这样的解决方案中没有真正的性能
【解决方案6】:

以下是仅使用条件句的表达方式:

int a, b, c = ...
int middle = (a <= b) 
    ? ((b <= c) ? b : ((a < c) ? c : a)) 
    : ((a <= c) ? a : ((b < c) ? c : b));

编辑:

  1. @Pagas 发现的上述错误已得到修复。
  2. @Pagas 还指出,如果只使用条件,则不能使用少于 5 个条件来执行此操作,但您可以使用临时变量或值交换来减少这种情况。
  3. 我要补充一点,很难预测纯条件或赋值解决方案是否会更快。这可能取决于 JIT 有多好,但我认为条件版本对优化器来说更容易分析。

【讨论】:

  • 嘿...您的第一个答案与使用最小值和最大值完全不同。为什么要改变它?我认为这是一个很好的方法
  • @reinier ...这不是我的答案。
  • 斯蒂芬:嗯?是从其他人那里删除的答案吗?啊,好吧...也许它没有用,他们删除了它或其他东西
  • @reinier:是“Stephan202”删除了他的答案。
  • 你不能避免至少有 5 个条件,除非你做值交换或递归之类的事情。这是因为相应的决策树有 6 个叶子,这意味着 5 个内部节点,因此整个代码中有 5 个决策点,尽管一次只有两个或三个处于活动状态,即答案叶子路径中的那些。但也许代码的大小,或者至少条件的数量,可以通过使用交换或其他技术来减少!
【解决方案7】:

我没有看到实现交换的解决方案:

int middle(int a, int b, int c) {
    // effectively sort the values a, b & c
    // putting smallest in a, median in b, largest in c

    int t;

    if (a > b) {
        // swap a & b
        t = a;
        a = b;
        b = t;
    }

    if (b > c) {
        // swap b & c
        t = b;
        b = c;
        c = t;

        if (a > b) {
            // swap a & b
            t = a;
            a = b;
            b = t;
        }
    }

    // b always contains the median value
    return b;
}

【讨论】:

  • 不明白为什么这个解决方案不在上面,因为它只有 2 或 3 个比较并且易于理解。
【解决方案8】:

撞了一个旧线程,但它仍然是最短的解决方案,没有人提到它。

解决方案:

int median2(int a, int b, int c) {
    return (a > b) ^ (a > c) ? a : (a > b) ^ (b > c) ? c : b;
}

测试:

(测试涵盖所有可能的组合,全部打印 6)

public static void main(String[] args) {

    System.out.println(median(3, 6, 9));
    System.out.println(median(3, 9, 6));
    System.out.println(median(6, 3, 9));
    System.out.println(median(6, 9, 3));
    System.out.println(median(9, 3, 6));
    System.out.println(median(9, 6, 3));
    System.out.println(median(6, 6, 3));
    System.out.println(median(6, 6, 9));
    System.out.println(median(6, 3, 6));
    System.out.println(median(6, 9, 6));
    System.out.println(median(3, 6, 6));
    System.out.println(median(9, 6, 6));
    System.out.println(median(6, 6, 6));

}

说明1

(a &gt; b) ^ (a &gt; c) 如果c &gt; a &gt; bc &lt; a &lt; b 为假,则返回a

否则(a &gt; b) ^ (b &gt; c) false 如果a &gt; b &gt; ca &lt; b &lt; c - 返回b;

否则返回 c;

说明2

假设p = a &gt; b; q = b &gt; c; s = a &gt; c;

让我们建立一个Karnaugh map

   | 00  01  11  10 (p, q)
---+----------------------
 0 |  b   c   *   a
 1 |  *   a   b   c
(s)|

*表示组合是不可能的(如a &gt; b; b &gt; c; a &lt; c

注意右边部分是镜像的左边部分,可以通过引入t = p ^ q; u = s ^ p来简化地图

   |  0   1 (t)
---+---------
 0 |  b   c  
 1 |  *   a  
(u)|

所以函数可以写成

private static int median(int a, int b, int c) {
    boolean t = (a > b) ^ (b > c);
    boolean u = (a > b) ^ (a > c);
    if (u)
        return a;
    else if (t)
        return c;
    else
        return b;
}

内联变量并用 ? 替换 ifs:给出答案

int median2(int a, int b, int c) {
    return (a > b) ^ (a > c) ? a : (a > b) ^ (b > c) ? c : b;
}

即使输入上的某些输入相等,该解决方案也能正常工作,这可能并不明显,但很合乎逻辑。

【讨论】:

    【解决方案9】:

    如果您必须从满足某些标准的 X 值中找出一个,您至少必须将该值与其他 X-1 个值进行比较。对于三个值,这意味着至少进行两次比较。由于这是“找到不是最小也不是最大的值”,因此您只需进行两次比较即可。

    然后您应该专注于编写代码,这样您就可以非常清楚地看到发生了什么并保持简单。这意味着嵌套的 if。这将允许 JVM 在运行时尽可能地优化这种比较。

    请参阅 Tim (Fastest way of finding the middle value of a triple?) 提供的解决方案以查看此示例。许多代码行不一定比嵌套问号冒号的代码大。

    【讨论】:

      【解决方案10】:

      你不妨用最直接的方式写这个。正如你所说,只有六种可能性。没有任何合理的方法会更快或更慢,所以只需要一些易于阅读的东西。

      为了简洁起见,我会使用 min() 和 max(),但我认为三个嵌套的 if/then 也一样好。

      【讨论】:

        【解决方案11】:

        median = (a+b+c) - Math.min(Math.min(a,b),c) - Math.max(Math.max(a,b),c)

        这是基本的,我不知道它的工作效率如何,但这些函数毕竟使用 if 条件。如果您愿意,可以将此语句转换为 if-else 语句,但这需要时间。为什么这么懒?

        【讨论】:

          【解决方案12】:

          最简单的方法是通过排序。 例如考虑这段代码:

          import java.util.Arrays;
          
          
          int[] x = {3,9,2};
          Arrays.sort(x); //this will sort the array in ascending order 
          
          //so now array x will be x = {2,3,9};
          //now our middle value is in the middle of the array.just get the value of index 1
          //Which is the middle index of the array.
          
          int middleValue = x[x.length/2]; // 3/2 = will be 1
          

          就是这样。就这么简单。

          这样你就不需要考虑数组的大小了。所以如果你有47个不同的值那么你也可以使用这个代码来找到中间值。

          【讨论】:

            【解决方案13】:

            基于 Gyorgy 的出色回答,您可以通过将 min/max 替换为条件移动来获得没有分支的中位数索引:

            int i = (array[A] >= array[B]) ? A : B;
            int j = (array[A] <= array[B]) ? A : B;
            int k = (array[i] <= array[C]) ? i : C;
            int median_idx = (array[j] >= array[k]) ? j : k;
            

            javac 应该为这些三元赋值中的每一个生成一个 ConditionalNode,它在汇编中转换为 cmp/cmov 对。另请注意,选择比较是为了在相等的情况下返回按字母顺序排列的第一个索引。

            【讨论】:

            • 这是一些严重搞砸的代码,当然不是有效的 Java。 (array[A] &lt; array[B]) * 4 是什么?第一部分返回一个布尔值,因为 &gt; 但 4 是一个整数,运算符 * 不适用于布尔值和整数。看来您有一个有趣的想法,我想听听您解释它,但没有任何进一步的答案,这个答案质量太低了,如果没有进行任何编辑,我会标记它。
            • 我的错,这是一个笨拙的 C 习惯。我之前的解决方案涉及使用((a-b) &gt;&gt;&gt; 31) (graphics.stanford.edu/~seander/bithacks.html#CopyIntegerSign) 将布尔表达式(a&lt;b) 计算为整数,然后从(a&lt;b)(a&lt;c)(b&lt;c) 的三个比较中创建一个三位数字,并使用该数字索引一个 String[8] 数组。但那是在考虑条件移动之前!
            【解决方案14】:

            这个可以用:

            template<typename T> T median3_1_gt_2(const T& t1, const T& t2, const T& t3) {
                if (t3>t1) {
                    return t1;
                } else {
                    return std::max(t2, t3);
                }
            }
            template<typename T> T median3(const T& t1, const T& t2, const T& t3) {
                if (t1>t2) {
                    return median3_1_gt_2(t1, t2, t3);
                } else {
                    return median3_1_gt_2(t2, t1, t3);
                }
            }
            

            https://github.com/itroot/firing-ground/blob/864e26cdfced8394f8941c8c9d97043da8f998b4/source/median3/main.cpp

            【讨论】:

              【解决方案15】:
                  if(array[aIndex] > array[bIndex]) {
                      if(array[bIndex] > array[cIndex]) return bIndex;
                      if(array[aIndex] > array[cIndex]) return cIndex;
                      return aIndex;
                  } else {
                      if(array[bIndex] < array[cIndex]) return bIndex;
                      if(array[aIndex] < array[cIndex]) return cIndex;
                      return aIndex;
                  }
              

              【讨论】:

                【解决方案16】:
                largest=(a>b)&&(a>c)?a:(b>c?b:c);
                smallest=(a<b)&&(a<c)?a:(b<c?b:c);
                median=a+b+c-largest-smallest;
                

                【讨论】:

                • 你能解释一下你的答案吗?
                • 我不知道为什么,但我认为应该找到 3 个数字中最大、中位数和最小的一个。但它可能是答案(也许不是最好的)。但只有一个变量(更好的记忆)median=a+b+c-(a&gt;b)&amp;&amp;(a&gt;c)?a:(b&gt;c?b:c)-(a&lt;b)&amp;&amp;(a&lt;c)?a:(b&lt;c?b:c);我认为下一个变体更好,但更难阅读(甚至使用更多的括号)median= (a&gt;=b)&amp;&amp;(a&gt;=c)?(b&gt;c?b:c):(((a/b)*b+(a/c)*c)&gt;a?((a/b)*b+(a/c)*c):a);这个变体仅适用于整数(如果a a/b==0)跨度>
                【解决方案17】:

                方法一

                int a,b,c,result;
                printf("enter three number");
                scanf("%d%d%d",&a,&b,&c);
                result=a>b?(c>a?a:(b>c?b:c)):(c>b?b:(a>c?a:c));
                printf("middle %d",result);
                

                方法二

                int a=10,b=11,c=12;
                //Checking for a is middle number or not
                if( b>a && a>c || c>a && a>b )
                {
                    printf("a is middle number");
                }
                
                //Checking for b is middle number or not
                if( a>b && b>c || c>b && b>a )
                {
                    printf("b is middle number");
                }
                
                //Checking for c is middle number or not
                if( a>c && c>b || b>c && c>a )
                {
                    printf("c is middle number");
                }
                

                方法三

                if(a>b)
                {
                    if(b>c)
                    {
                        printf("b is middle one");
                    }
                    else if(c>a)
                    {
                        printf("a is middle one");
                    }
                    else
                    {
                        printf("c is middle one");
                    }
                }
                else
                {
                    if(b<c)
                    {
                        printf("b is middle one");
                    }
                    else if(c<a)
                    {
                        printf("a is middle one");
                    }
                    else
                    {
                        printf("c is middle one");
                    }
                }
                

                我得到了finding the middle value of a triple的适当答案

                【讨论】:

                  【解决方案18】:
                  // Compute median of three values, no branches
                  
                  int median3(int V[3])
                  {
                    unsigned int A,B,C;
                    
                    A=(V[0] < V[1]);
                    B=(V[1] < V[2]);
                    C=(V[0] < V[2]);
                  
                    return V[(B^C)<<1 | (A^B^1)];
                    
                  }
                  

                  【讨论】:

                  • 虽然此代码可以解决 OP 的问题,但最好包含关于您的代码如何解决 OP 问题的说明。这样,未来的访问者可以从您的帖子中学习,并将其应用到他们自己的代码中。 SO 不是编码服务,而是知识资源。此外,高质量、完整的答案更有可能获得支持。这些功能,以及所有帖子都是独立的要求,是 SO 作为一个平台的一些优势,使其与论坛区分开来。您可以编辑以添加其他信息和/或使用源文档补充您的解释。
                  【解决方案19】:

                  在ary中使用idxA到idxC,

                  int ab = ary[idxA] < ary[idxB] ? idxA : idxB;
                  int bc = ary[idxB] < ary[idxC] ? idxB : idxC;
                  int ac = ary[idxA] < ary[idxC] ? idxA : idxC;
                  
                  int idxMid = ab == bc ? ac : ab == ac ? bc : ab;
                  

                  indexMiddle 指向中间值。

                  说明:从3个最小值中,2个是整体最小值,其他值必须是中间值。因为我们检查相等性,所以我们可以比较最后一行的索引,而不必比较数组值。

                  【讨论】:

                  • 这给出了 minimum 值,而不是 middle 值。
                  • 大声笑,你试过了吗?第一行将 indexAB 设置为 A 和 B 的最大值,第二行将 indexMiddle 设置为该最大值和 C 的最小值,为您提供中间值。我猜你错过了第一行的“index_B_ : index_A_”部分?
                  • 除了如果C是最小值,这将产生C而不是中间值。
                  • 对不起,不,我没试过,你说得对,我看错了。我很抱歉。但是,关键是您不能仅通过两次比较就做到这一点,如上面的 jk 所示。
                  • 糟糕,你是对的。我用我认为现在正确的解决方案替换了它:-)
                  【解决方案20】:

                  你可以使用数组,像这样:

                  private static long median(Integer i1, Integer i2, Integer i3) {
                  
                      List<Integer> list = Arrays.asList(
                              i1 == null ? 0 : i1,
                              i2 == null ? 0 : i2,
                              i3 == null ? 0 : i3);
                  
                      Collections.sort(list);
                      return list.get(1);
                  }
                  

                  【讨论】:

                    【解决方案21】:

                    这是 Python 中的答案,但同样的逻辑也适用于 Java 程序。

                    def middleOfThree(a,b,c):
                        middle = a
                        if (a < b and b < c) or (c < b and b < a):
                            middle = b 
                        elif (a < c and c < b) or (b < c and c < a):
                            middle = c    
                        print 'Middle of a=%d, b=%d, c=%d is %d' % (a,b,c,middle)
                    
                    middleOfThree(1,2,3)
                    middleOfThree(1,3,2)
                    middleOfThree(2,1,3)
                    middleOfThree(2,3,1)
                    middleOfThree(3,2,1)
                    middleOfThree(3,1,2)
                    

                    【讨论】:

                      【解决方案22】:

                      整数的 100% 无分支版本:

                      int mid(const int a, const int b, const int c) {
                          const int d0 = b - a;
                          const int m = (d0 >> 31);
                          const int min_ab = a + (d0 & m);
                          const int max_ab = a + (d0 & ~m);
                          const int d1 = c - max_ab;
                          const int min_max_ab_c = max_ab + (d1 & (d1 >> 31));
                          const int d2 = min_ab - min_max_ab_c;
                          return min_ab - (d2 & (d2 >> 31));
                      }
                      

                      使用无分支最小/最大函数构造:

                      int min(const int a, const int b) { const int d = b - a; return a + (d & (d >> 31)); }
                      int max(const int a, const int b) { const int d = a - b; return a - (d & (d >> 31)); }
                      

                      它可能看起来不漂亮,但机器代码在某些架构上可能会更有效。特别是那些没有最小/最大指令的。但是我没有做任何基准来确认它。

                      【讨论】:

                        【解决方案23】:

                        或者一个用于在包含中间值的数组中查找索引的单行:

                         int middleIndex = (a[0]<a[1]) ? ((a[0]<a[2) ? a[2] : a[0]) : ((a[1]<a[2) ? a[2] : a[1]);
                        

                        【讨论】:

                        • 首先,这给出了一个值,而不是一个索引。其次,对于a[0] &lt; a[1] &lt; a[2],它给出a[2] 作为答案,这是不正确的。
                        【解决方案24】:

                        其中很多似乎都在使用相当复杂的 if 语句。 我找到了一个使用数学库的非常简单的解决方法。

                        Math.max(Math.min(array[start], array[mid]), Math.min(array[start], array[mid], array[end]))
                        

                        效果很好。

                        【讨论】:

                        • 考虑数组 (1, 2, 3)。这将产生输出 1。这不是中间值。
                        【解决方案25】:

                        用三元运算符一行就可以解决

                        int middle(int A, int B, int C) {
                              return (A>B&&A>C)?B>C?B:C:(B>C&&B>A)?A>C?A:C:B;
                        }
                        

                        【讨论】:

                        • 您好,欢迎来到 StackOverflow。请在发布问题的答案时检查正确的代码格式,下次不要全部大写。谢谢。
                        • 在执行中,这可能需要进行五次顺序比较:Fastest way of finding the middle value of a triple 是怎么回事?
                        猜你喜欢
                        • 1970-01-01
                        • 2021-11-26
                        • 2019-07-07
                        • 1970-01-01
                        • 2011-12-09
                        • 1970-01-01
                        • 1970-01-01
                        • 2016-03-07
                        • 1970-01-01
                        相关资源
                        最近更新 更多