浅析Hadoop下的基础排序算法(QuickSort)

一     前言:

1.        语术:

  • QS: QuickSort。
  • IS: InsertSort
  • HS:  HeapSort
  • Hadoop 版本: 基于Version 2.7.1代码分析

2.  QS排序思想回顾:两个索引 ij 分别从左右两端进行扫描,并让索引i扫描到大于等于分割基数为止,索引j扫描到小于等于分割基数为止,然后交换两个元素,重复这个过程直到两个索引相遇; 

3. 目的

   分析Haoop 里的 QuickSort 与你认识的 QuickSort 有哪些区别?是否有优化?

二     内容:

public final class QuickSort implements IndexedSorter {

  private static final IndexedSorter alt = new HeapSort();

  public QuickSort() { }

  private static void fix(IndexedSortable s, int p, int r) {
    if (s.compare(p, r) > 0) {
      s.swap(p, r);
    }
  }

  /**
   * Deepest recursion before giving up and doing a heapsort.
   * Returns 2 * ceil(log(n)).
   */
  protected static int getMaxDepth(int x) {
    if (x <= 0)
      throw new IllegalArgumentException("Undefined for " + x);
    return (32 - Integer.numberOfLeadingZeros(x - 1)) << 2;
  }

  /**
   * Sort the given range of items using quick sort.
   * {@inheritDoc} If the recursion depth falls below {@link #getMaxDepth},
   * then switch to {@link HeapSort}.
   */
  @Override
  public void sort(IndexedSortable s, int p, int r) {
    sort(s, p, r, null);
  }

  @Override
  public void sort(final IndexedSortable s, int p, int r,
      final Progressable rep) {
    sortInternal(s, p, r, rep, getMaxDepth(r - p));
  }

  private static void sortInternal(final IndexedSortable s, int p, int r,
      final Progressable rep, int depth) {
    if (null != rep) {
      rep.progress();
    }
    while (true) {
    if (r-p < 13) {
      for (int i = p; i < r; ++i) {
        for (int j = i; j > p && s.compare(j-1, j) > 0; --j) {
          s.swap(j, j-1);
        }
      }
      return;
    }
    if (--depth < 0) {
      // give up
      alt.sort(s, p, r, rep);
      return;
    }

    // select, move pivot into first position
    fix(s, (p+r) >>> 1, p);
    fix(s, (p+r) >>> 1, r - 1);
    fix(s, p, r-1);

    // Divide
    int i = p;
    int j = r;
    int ll = p;
    int rr = r;
    int cr;
    while(true) {
      while (++i < j) {
        if ((cr = s.compare(i, p)) > 0) break;
        if (0 == cr && ++ll != i) {
          s.swap(ll, i);
        }
      }
      while (--j > i) {
        if ((cr = s.compare(p, j)) > 0) break;
        if (0 == cr && --rr != j) {
          s.swap(rr, j);
        }
      }
      if (i < j) s.swap(i, j);
      else break;
    }
    j = i;
    // swap pivot- and all eq values- into position
    while (ll >= p) {
      s.swap(ll--, --i);
    }
    while (rr < r) {
      s.swap(rr++, j++);
    }

    // Conquer
    // Recurse on smaller interval first to keep stack shallow
    assert i != j;
    if (i - p < r - j) {
      sortInternal(s, p, i, rep, depth);
      p = j;
    } else {
      sortInternal(s, j, r, rep, depth);
      r = i;
    }
    }
  }

}

QS算法分析:

 

1.      当排序的sub数组长度少于13时,使用IS排序方式。目的是应该是为了减少迭代调用。相关代码如下:

    if (r-p < 13) {

      for (int i = p; i < r; ++i) {

        for (int j = i; j > p && s.compare(j-1, j)> 0; --j) {

          s.swap(j, j-1);

        }

      }

      return;

}

高亮的代码含有个小技巧,为什么写成下面这样不好(显而易见)?

for (int j = i; j > p ; --j){

if (s.compare(j-1, j) > 0) s.swap(j, j-1);

}

 

2.      当迭代深度depth使用完后,开始用HS排序方式来接手后面的活。//例如,100 000条数时,按getMaxDepth (100 000)返回depth = 68,最多可迭代68次. 如果没有getMaxDepth这个控制,QS标准代码最坏情况时(比如是一个已排好序的数组)depth=n-1=99999,需要迭代99999次。

相关代码如下:

Ø  private static final IndexedSorter alt = new HeapSort();

Ø  protected static int getMaxDepth(int x) {

    if (x <= 0)

      throw newIllegalArgumentException("Undefined for " + x);

    return (32 - Integer.numberOfLeadingZeros(x- 1)) << 2;

  }

Ø  private static void sortInternal(final IndexedSortable s, int p, intr, final Progressable rep, int depth) {

    if (--depth < 0) {

      // give up

      alt.sort(s, p, r, rep);

      return;

}

}

3.      每次sub数组QS前排序都先调整pivot, 就是QS时小于pivot的放前面,大于pivot放后面。调整pivot就是比较数据下标最前的,中间的和最后的,把三个中的最小值放中间,三个中的最大值放最后,三个中的中间值放最开始(也就是pivot的值)

相关代码如下:

Ø  private static void fix(IndexedSortable s, int p, int r) {

    if (s.compare(p, r) > 0) {

      s.swap(p, r);

    }

  }

Ø  private static void sortInternal(final IndexedSortable s, int p, intr, final Progressable rep, int depth) {

   // select, move pivot intofirst position

   fix(s, (p+r) >>>1, p);

   fix(s, (p+r) >>>1, r - 1);

fix(s, p, r-1);

      }

4.      分(divide)而治(conquer)之,这个采取了3 way QS的策略(QS的优化算法,感兴趣的可以参考这个文档QuicksortIsOptimal.pdf)。3 way QS的相关代码如下:

Ø  需要使用的变量:

int ll =p;

int rr = r;

Ø  如果找到与pivot相等的值时,先交换到两端用ll和rr变量标识。

    if (0 == cr && ++ll != i){

          s.swap(ll, i);

        }

       

   if (0 == cr && --rr != j) {

          s.swap(rr, j);

        }

Ø  放到pivot中间段位置,  注意ll >= p的>=条件符,把最第一个pivot的值也换到中间段位置。

j = i;

   // swap pivot- and all eq values- into position

   while (ll >= p) {

     s.swap(ll--, --i);

    }

   while (rr < r) {

     s.swap(rr++, j++);

    }

5.      为了保持浅栈,先排序小sub数组

// Recurse onsmaller interval first to keep stack shallow

assert i != j;

   if (i - p < r - j) {

     sortInternal(s, p, i, rep, depth);

     p = j;

   } else {

     sortInternal(s, j, r, rep, depth);

     r = i;

   }

 

三、思考

 

1. 为什么QS在实践中是最快的排序算法? 推荐链接分析: https://cs.stackexchange.com/questions/3/why-is-quicksort-better-than-other-sorting-algorithms-in-practice

你可能感兴趣的:(Hadoop)