9

As of right now, my functioin finds the median of 3 numbers and sorts them, but it always makes three comparisons. I'm thinking I can use a nested if statement somewhere so that sometimes my function will only make two comparisons.

int median_of_3(int list[], int p, int r)
{
    int median = (p + r) / 2;

    if(list[p] > list[r])
        exchange(list, p, r);
    if(list[p] > list[median])
        exchange(list, p, median);
    if(list[r] > list[median])
        exchange(list, r, median);

    comparisons+=3;                // 3 comparisons for each call to median_of_3

    return list[r];
}

I'm not sure I see where I can make that nested if statement.

Zack
  • 713
  • 2
  • 8
  • 21
  • If you make a nested if, that won't optimize your algorithm either. –  Oct 17 '12 at 15:24
  • Any particular reason why you're rearranging the list? – Mark Ransom Oct 17 '12 at 15:34
  • Yes, I'm using it for Quicksort. I could have sworn I can have a minimum of two comparisons - just not seeing it :/ – Zack Oct 17 '12 at 15:36
  • 1
    @user1493692: You can see that two comparisons might not be enough because if you compare any two pairs and the common item is bigger (or smaller) in both then you can't derive the relationship between the other two so you need a third comparison. If the common item is bigger than one and smaller than the other then you can use that information to know the relationship between the other two. – Chris Oct 17 '12 at 15:45

7 Answers7

30

If you only need the median value, here's a branch-less solution based on min/max operators:

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

Intel CPU's have SSE min/max vector instructions, so depending on your or your compiler's ability to vectorize, this can run extremely fast.

Gyorgy Szekely
  • 2,654
  • 3
  • 23
  • 32
  • 1
    my mind cant comprehend how this magic is happening. i get max returns maximum of 2, and min minimum of two. And it works for all six possibilities of a,b,c given none are equal. – Muhammad Umer Sep 25 '14 at 19:20
  • 2
    It's actually bubble sort for 3 items. The order of comparisons done by BS for a given array size is always the same, independent of data. The basic operation of BS is compare followed by a swap if the order is wrong. This operation can be defined as: a' = min(a,b); b'=max(a,b). Since the algorithm is value independent and the basic step is value independent too, the sorting can be described by a static branchless algorithm. The above line only returns the middle item, but it could be constructed for the whole input size. Google 'sorting networks' for a formal definition. – Gyorgy Szekely Aug 20 '15 at 12:52
  • Compilers on godbolt generate [branches for the code in the answer (with `-Ofast` option)](https://godbolt.org/g/JnwgCg). I've tried passing `-msse`, `-march=core2`, etc with no effect. – jfs Sep 05 '17 at 09:57
  • 4
    The linked code is scalar, there's nothing to vectorize on it (and AFAIK there are no scalar min/max instructions). [This](https://godbolt.org/g/gnDLTr) gets vectorized and unrolled properly. – Gyorgy Szekely Sep 05 '17 at 11:38
  • 1
    The explanation that works for me is a two-step algorithm. First sort two of the values: `L=min(a,b)` and `H=max(a,b)`, then clamp the third value between them: `clamp(c,L,H) = max(L,min(c,H))`. – Řrřola May 22 '19 at 20:45
2

If we allow extra operations, we could use at most 2 comparisons to find the median. The trick is to use exclusive or to find the relationship among three numbers.

void median3(int A[], int p, int r)
{
    int m = (p+r)/2;
    /* let a, b, c be the numbers to be compared */
    int a = A[p], b = A[m], c = A[r];
    int e = a-b;
    int f = a-c;

    if ((e^f) < 0) {
        med_comparisons += 1;
        /* a is the median with 1 comparison */
        A[m] = a;
        /* b < a < c ? */
        if (b < c) /* b < a < c */ { A[p] = b, A[r] = c; }
        else       /* c < a < b */ { A[p] = c, A[r] = b; }
        comparisons += 2;
    } else {
        med_comparisons += 2;
        int g = b-c;
        if ((e^g) < 0) {
            /* c is the median with 2 comparisons */ 
            A[m] = c;
            /* a < c < b ? */
            if (a < b) /* a < c < b */ { A[p] = a, A[r] = b; }
            else       /* b < c < a */ { A[p] = b, A[r] = a; }
        } else {
            /* b is the median with 2 comparisons */
            A[m] = b;
            /* c < b < a ? */
            if (a > c) /* c < b < a */ { A[p] = c; A[r] = a; }
            else       /* a < b < c */ { /* do nothing */    }
        }
        comparisons += 3;
    }
}

The first exclusive or (e^f) is to find out the difference of the sign bit between (a-b) and (a-c).
If they have different sign bit, then a is the median. Otherwise, a is either the minimum or the maximum. In that case, we need the second exclusive or (e^g).

Again, we are going to find out the difference of the sign bit between (a-b) and (b-c). If they have different sign bit, one case is that a > b && b < c. In this case, we also get a > c because a is the maximum in this case. So we have a > c > b. The other case is a < b && b > c && a < c. So we have a < c < b; In both cases, c is the median.

If (a-b) and (b-c) have the same sign bit, then b is the median using similar arguments as above. Experiments shows that a random input will need 1.667 comparisons to find out the median and one extra comparison to get the order.

Aayush
  • 1,244
  • 5
  • 19
  • 48
t.k
  • 21
  • 1
1

To sort 3 items, you need exactly 3 comparisons.

To find the middle one by chance, you need 2.

To find the middle one exactly, you need on average 2+2/3 ~= 2.67 (with uniformly distributed random data)

if (a<b) {
   // partial order = a,b
   if (b<c) {  } // 2 comparisons: order is a,b,c
      else { // order is a,c,b or c,a,b
          if (a<c) { } // order is a,c,b -- 3 comparisons
          else { }     // order is c,a,b -- 3 comparisons
      }
} else {
   // partial order = b,a  
   if (c<b) {  } // 2 comparisons: order is c,b,a
   else {  // order is b,c,a or b,a,c
      if (c>a) { } // order is b,a,c -- 3 comparisons
      else { }   // order is b,c,a -- 3 comparisons
   }
}

As an additional side note: some languages (Fortran, IIRC), as well as some ISAs (VAX, again IIRC) support comparisons, where the next PC address is selected from three choices: LT,EQ,GT. With small enough alphabet this chance reduces slightly the number of needed comparisons.

Also this has probably no practical use, taken, that penalties from wrong branch predictions because of overly complex nested structures can be much larger than gain from a saved comparison.

Aki Suihkonen
  • 19,144
  • 1
  • 36
  • 57
1
int m = (p + r) / 2;
if (list[p] < list[m])
    if (list[p] >= list[r])
        return list[p];
    else if (list[m] < list[r])
        return list[m];
else
    if (list[p] < list[r])
        return list[p];
return list[r];
Josh Greifer
  • 3,151
  • 24
  • 25
1

More like this

#define MEDIAN(a,b,c) ( (a > b) ? max(b, min(a,c)) :
                                  min(b, max(a,c)) )
Sriram Murali
  • 5,804
  • 2
  • 26
  • 32
0

Python

#!/usr/bin/env python3

def bigger(a,b):
    if a > b:
       return a
    else:
    return b

def biggest(a,b,c):
    return bigger(a,bigger(b,c))

def median(a,b,c):
    big = biggest(a,b,c)
    if big == a:
       return bigger(b,c)
    if big == b:
       return bigger(a,c)
    else:
       return bigger(a,b)

to print the Median

print(median(20,18,19)) # => 19
Mazen Alhrazi
  • 336
  • 1
  • 3
  • 10
0

Using only two comparisons:

double median_of_three(double left, double middle, double right) 
{
    double med = middle;
    if (left  < med) med = left;
    if (right > med) med = right;
    return med;
}