How do I calculate the “median of five” in C#?

后端 未结 10 2038
遇见更好的自我
遇见更好的自我 2020-12-04 22:05

The median of five is sometimes used as an exercise in algorithm design and is known to be computable using only 6 comparisons.

What is the best way

相关标签:
10条回答
  • 2020-12-04 22:49

    Here's a bit of a variant on the other answers, which provides on average from 3.33% to 66.67% improvement over 6 comparisons, and fully partitions the 5 elements around their median as a bonus at no extra cost.

    It's possible to find the median of 5 distinct elements in an average of 5.8 comparisons (averaged over all permutations) by using median-of-3 and quickselect, using median-of-3 to select the pivot from a sample of 3 of the 5 elements. Median-of-3 partitions those three elements, which need not be recompared to the pivot when partitioning the remaining 2 elements. So far that's 4-5 comparisons to partition the 5 elements around one of the three middle elements (the median-of-3 cannot be either the minimum or maximum of 5). Up to 3 more comparisons may be necessary to partition the 5 elements around their median (which strictly speaking is more work than merely finding the median), for a total ranging from 4 to 7 comparisons, with (as mentioned) an average of 5.8 over all possible permutations of 5 distinct elements (fewer comparisons if the elements are not distinct). Note that this differs from the usual always-6-comparisons solutions, in that a few cases of distinct inputs may require as many as 7 comparisons, but on the other hand, most permutations of distinct inputs require no more than 6, and often fewer; moreover it is fairly easy to code to save comparisons for non-distinct inputs (only 2 comparisons are required if all inputs are equal; the code to save comparisons when inputs are not distinct using the usual 6-comparison method becomes rather convoluted (try it!), and without that it still takes 6 comparisons even when all inputs are equal).

    Order statistics other than the median can be found this way: the 2nd smallest or 2nd largest can be found in on average slightly more (5.81666... comparisons), and of course it's possible to find the minimum or maximum with only 4 comparisons.

    Based on that, and by request, here's a heavily commented function to return a pointer to the median of 5 elements, using a variadic comparison function. It's written in C, but it should work just as well in the quadrathorpe-y deviant or in sea ploose ploose. Note that this returns a pointer to the median-valued element only; it does not partition the elements (in fact it doesn't move any elements).

    /* a virtual swap, preserving both pointers for later use */
    #define VSWAP(ma,mb,mt) mt=ma,ma=mb,mb=mt
    /* a virtual swap only preserving the first pointer */
    #define VSWAP2(ma,mb,munused) ma=mb
    /* virtual rotation to the left; reverse the first 3 args for right rotation */
    #define ROTATE(ma,mb,mc,mt) (mt)=(ma),(ma)=(mb),(mb)=(mc),(mc)=(mt)
    
    
    /* median of 5 elements, no data movement, minimum (average 5.8) comparisons */
    /* This implementation minimizes the number of comparisons made (min. 2) when
       elements compare equal.
    */
    /* As no elements are moved, the elements are of course not partitioned around
       the element pointed to by the returned pointer (this differs from selection
       of the median via quickselect).
    */
    /* Results are biased toward the middle: pc, then pb or pd, last pa or pe. */
    /* The implementation is based on a simulation of quickselect using partition3
       to select a pivot from the middle three elements, with partitioning by
       skipping over the 3 partitioned elements.  For distinct inputs, it uses on
       average 5.8 comparisons (averaged over all permutations of 5 distinct
       elements); fewer for inputs with equal elements.  That's an improvement of
       about 3% over the usual 6-comparison implementation of median-of-5.
    */
    void *fmed5(void *pa, void *pb, void *pc, void *pd, void *pe,
        int(*compar)(const void *,const void *))
    {
        void *t;
        int c, d;
        /* First compare the 3 middle elements to determine their relative
           ordering; pb will point to the minimum, pc to the median, and pd to
           the maximum of the three.
        */
        /* Ternary median-of-3, biased toward middle element if possible, else
           first element.  Average 8/3 comparisons for 3 elements (distinct values)
           = 0.889 comparisons/element
        */
        c=compar(pb,pc); /* 1 */
        if (0<c) { /* *pb,*pc out-of-order: *pb>*pc */
            /* Before doing anything about pb,pc, compare *pc,*pd. */
            d=compar(pc,pd); /* 2a */
            if (0>d) { /* *pc<*pd: strictly in order */
                /* But *pb might be either larger than or smaller than (or equal
                   to) *pd, so they may (i.e. unless it's known from the earlier
                   comparison of original *pc and *pd that *pb is larger than
                   both) have to be compared,  Possibilities:
                   *pc<*pb<=*pd (virtual swap of pb,pc corrects relationship)
                   *pc<*pd<*pb (virtual rotation to the left corrects it)
                */
                c=compar(pb,pd); /* 3a (if needed) */
                if (0<c) { /* *pc < *pd < *pb */
                    ROTATE(pb,pc,pd,t);
                } else { /* *pc < *pb <= *pd */
                    VSWAP(pb,pc,t);
                }
            } else { /* *pd==*pc<*pb or reversed ordering: *pd<*pc<*pb */
                VSWAP(pb,pd,t); /* one (virtual) swap takes care of it */
            } /* *pc:*pd comparisons results if-else */
            /* Note that if pc,pd compare equal, pc remains as the chosen
               median (biased toward the middle element).
            */
        } else if (0==c) { /* *pb,*pc compare equal */
            /* Either pb or pc can be taken as the median; bias here is towards
               pc, which is already in the middle position. But pd might be
               the minimum of the three or the maximum (or it may also be equal
               to both pb and pc).
            */
            d=compar(pb,pd); /* 2b */
            if (0<d) { /* pb,pd are out-of-order */
                VSWAP(pb,pd,t);
            }
        } else { /* *pb,*pc in strict order: *pb < *pc; how about *pc,*pd? */
            d=compar(pc,pd); /* 2c */
            if (0<d) { /* *pc,*pd are strictly out-of-order: *pd < *pc */
                /* But *pb might be either larger than or smaller than (or equal
                   to) *pd, so they may (i.e. unless it's known from the earlier
                   comparison of original *pc and *pd that *pb is larger than
                   both) have to be compared,  Possibilities:
                   *pb<=*pd<*pc (virtual swap of pc,pd corrects relationship)
                   *pd<*pb<*pc (virtual rotation to the right corrects it)
                */
                c=compar(pb,pd); /* 3b (if needed) */
                if (0<c) { /* *pd < *pb < *pc */
                    ROTATE(pd,pc,pb,t);
                } else { /* *pc < *pb <= *pd */
                    VSWAP(pc,pd,t);
                }
            } /* *pc:*pd comparisons results if-else */
            /* Note that if pc,pd compare equal, pc remains as the chosen
               median (biased toward the middle element).
            */
        } /* *pb:*pc comparisons results if-else chain */
        /* Now pb points to the minimum of pb,pc,pd; pc to the median, and pd
           to the maximum.
        */
        /* Special case: if all three middle elements compare equal (0==c==d),
           any one can be returned as the median of 5, as it's impossible for
           either of the other two elements to be the median (unless of course
           one or both of them also compares equal to pb,pc,pd, in which case
           returning any of pb,pc,pd is still correct).  Nothing more needs to
           be done in that case.
        */
        if ((0!=c)||(0!=d)) { /* Not all three of pb,pc,pd compare equal */
            int e;
            /* Compare pa and pe to pc. */
            e=compar(pa,pc); /* 3c or 4a (iff needed) */
            /* If three (or more) of the four elements so far compared are
               equal, any of those equal-comparing elements can be chhosen as
               the median of 5.  If all five elements were arranged in order,
               one of the three equal-comparing elements would necessarily be
               in the middle (at most both of the remaining elements might be
               either larger or smaller than the equal elements).  So if pa
               compares equal to pc and pc also compared equal to pb or to pd,
               nothing more need be done; pc can be considered as the median of
               five.
            */
            if ((0!=e)||(0!=c)||(0!=d)) { /* no three elements compare equal */
                int f;
                f=compar(pe,pc); /* 4b or 5a (iff needed) */
                /* Check again for three equal comparisons to avoid doing any
                   unnecessary additional work.
                */
                if (
                    (0!=f) /* also not equal; still not three */
                  ||
                    ( /* 0==f && */
                     ((0!=c)&&(0!=d)) /* at most e and f; not three */
                   ||
                     ((0!=c)&&(0!=e)) /* at most d and f; not three */
                   ||
                     ((0!=d)&&(0!=e)) /* at most c and f; not three */
                    )
                ) {
                    /* Possibilites are that:
                         one of *pa,*pe is less than (or equal to) *pc and one
                           is greater than (or equal to) *pc; *pc is the median
                           of five.
                         *pa and *pe are both less than *pc; the median of five
                           is then the maximum of *pa,*pb,*pe (*pc and *pd are
                           at least as large as those three).  The ordering of
                           those 3 elements has not been established, and it
                           will take two additional comparisons to do so.
                         *pa and *pe are both greater than *pc; the median of
                           five is the minimum of *pa,*pd,*pe (neither *pb nor
                           *pc can be larger than any of those three).
                    */
                    if ((0<e)&&(0<f)) { /* *pa,*pe both > *pc; median of five is
                                           the minimum of *pa,*pe,*pd
                                        */
                        /* Bias towards *pd (originally closest of these three
                           to the middle.  Neither *pa nor *pe has yet been
                           compared to *pd.
                        */
                        e=compar(pa,pe); /* 5b or 6a (iff needed) */
                        if (0<e) { /* *pe is smaller */
                            f=compar(pd,pe); /* 6b or 7a (iff needed) */
                            if (0<f) { /* *pe is smaller */
                                VSWAP2(pc,pe,t);
                            } else { /* *pd is smaller or *pd==*pe */
                                VSWAP2(pc,pd,t);
                            }
                        } else { /* *pa==*pe or *pa is smaller */
                            f=compar(pd,pa); /* 6c or 7b (iff needed) */
                            if (0<f) { /* *pa is smaller */
                                VSWAP2(pc,pa,t);
                            } else { /* *pd is smaller or *pd==*pa */
                                VSWAP2(pc,pd,t);
                            }
                        }
                    } else if ((0>e)&&(0>f)) { /* *pa,*pe both < *pc; median of
                                           five is the maximum of *pa,*pb,*pe
                                        */
                        /* Bias towards *pb (originally closest of these three
                           to the middle.  Neither *pa nor *pe has yet been
                           compared to *pb.
                        */
                        e=compar(pa,pe); /* 5c or 6d (iff needed) */
                        if (0<e) { /* *pa is larger */
                            f=compar(pa,pb); /* 6e or 7c (iff needed) */
                            if (0<f) { /* *pa is larger */
                                VSWAP2(pc,pa,t);
                            } else { /* *pb is larger or *pa==*pb */
                                VSWAP2(pc,pb,t);
                            }
                        } else { /* *pe is larger or *pa==*pe */
                            f=compar(pe,pb); /* 6f or 7d (iff needed) */
                            if (0<f) { /* *pe is larger */
                                VSWAP2(pc,pe,t);
                            } else { /* *pb is larger or *pe==*pb */
                                VSWAP2(pc,pb,t);
                            }
                        } /* *pa:*pe results if-else */
                    } /* median of five: minimum or maximum of three if-else */
                } /* not three equal elements among five */
            } /* not three equal elements among four */
        } /* not three equal elements among three */
        return pc;
    }
    
    0 讨论(0)
  • 2020-12-04 22:59

    An interesting thread here:

    http://www.ocf.berkeley.edu/~wwu/cgi-bin/yabb/YaBB.cgi?board=riddles_cs;action=display;num=1061827085

    Quote from thread:

    1. Put the numbers in an array.

    2. Use three comparisons and shuffle around the numbers so that a[1] < a[2], a[4] < a[5], and a[1] < a[4].

    3. If a[3] > a[2], then the problem is fairly easy. If a[2] < a[4], the median value is the smaller of a[3] and a[4]. If not, the median value is the smaller of a[2] and a[5].

    4. So a[3] < a[2]. If a[3] > a[4], then the solution is the smaller of a[3] and a[5]. Otherwise, the solution is the smaller of a[2] and a[4].

    0 讨论(0)
  • 2020-12-04 23:01

    I found this post interesting and as an exercise I created this which ONLY does 6 comparisons and NOTHING else:

    static double MedianOfFive(double a, double b, double c, double d, double e)
    {
        return b < a ? d < c ? b < d ? a < e ? a < d ? e < d ? e : d
                                                     : c < a ? c : a
                                             : e < d ? a < d ? a : d
                                                     : c < e ? c : e
                                     : c < e ? b < c ? a < c ? a : c
                                                     : e < b ? e : b
                                             : b < e ? a < e ? a : e
                                                     : c < b ? c : b
                             : b < c ? a < e ? a < c ? e < c ? e : c
                                                     : d < a ? d : a
                                             : e < c ? a < c ? a : c
                                                     : d < e ? d : e
                                     : d < e ? b < d ? a < d ? a : d
                                                     : e < b ? e : b
                                             : b < e ? a < e ? a : e
                                                     : d < b ? d : b
                     : d < c ? a < d ? b < e ? b < d ? e < d ? e : d
                                                     : c < b ? c : b
                                             : e < d ? b < d ? b : d
                                                     : c < e ? c : e
                                     : c < e ? a < c ? b < c ? b : c
                                                     : e < a ? e : a
                                             : a < e ? b < e ? b : e
                                                     : c < a ? c : a
                             : a < c ? b < e ? b < c ? e < c ? e : c
                                                     : d < b ? d : b
                                             : e < c ? b < c ? b : c
                                                     : d < e ? d : e
                                     : d < e ? a < d ? b < d ? b : d
                                                     : e < a ? e : a
                                             : a < e ? b < e ? b : e
                                                     : d < a ? d : a;
    }
    
    0 讨论(0)
  • 2020-12-04 23:01

    For completeness, the question is a specific case of a sorting network, which Knuth (Art of Computer Programming, vol 3) covers in great detail. The classic paper by K.E. Batcher on the subject is brief and worth reading.

    0 讨论(0)
  • 2020-12-04 23:05

    Thanks. I know your posts are quite old, but it was useful for my issue.

    I needed a way to compute the median of 5 SSE/AVX registers (4 floats / 8 floats at once, or 2 doubles/4 doubles at once):

    • without any conditional jumps

    • only with min/max instructions

    If the min/max functions are programmed for scalar registers with conditional jumps, my code is not optimal in term of comparisons. But if the min/max functions are coded with corresponding CPU instructions, my code is very effective because no conditional jump is done by the CPU when executing.

        template<class V> 
        inline V median(const V &a, const V &b, const V &c)
        {
          return max(min(a,b),min(c,max(a,b))); 
        } 
    
        template<class V> 
        inline V median(const V &a, const V &b, const V &c, const V &d, const V &e)
        {
          V f=max(min(a,b),min(c,d)); // discards lowest from first 4
          V g=min(max(a,b),max(c,d)); // discards biggest from first 4
          return median(e,f,g);
        } 
    
    0 讨论(0)
  • 2020-12-04 23:06

    This is pretty ugly and could use some refactoring, but it explicitly walks through all the comparisons and swaps so you can see what's going on.

    public double medianOfFive(double a, double b, double c, double d, double e){
        double median;
        // sort a and b
        if(a > b) // comparison # 1
        {
            double temp = a;
            a = b;
            b = temp;
        }
    
        // sort c and d
        if(c > d)  // comparison # 2
        {
            double temp = c;
            c = d;
            d = temp;
        }
    
        // replace the lower of a and c with e
        // because the lowest of the first four cannot be the median
        if(a < c) // comparison # 3
        {
            a = e;
            // re-sort a and b
            if(a > b) // comparison # 4
            {
                double temp = a;
                a = b;
                b = temp;
            }
        }
        else
        {
            c = e;
            // re-sort c and d
            if(c > d)  // comparison # 4
            {
                double temp = c;
                c = d;
                d = temp;
            }
        }
    
        // eliminate a or c, because the lowest
        // of the remaining four can't be the median either
        if(a < c) // comparison #5
        {
             if(b < c) // comparison #6
             {
                  median = c;
             }
             else
             {
                  median = b;
             }
        }
        else
        {
             if(d < a) // comparison #6
             {
                  median = a;
             }
             else
             {
                  median = d;
             }
        }
        return median;
    }
    
    0 讨论(0)
提交回复
热议问题