Как отсортировать отсортированный массив в максимально сжатые сроки? (Ява)

У меня есть массив значений, который почти, но не совсем отсортирован, с несколькими смещенными значениями (скажем, 50 в 100000). Как отсортировать его наиболее эффективно? (производительность здесь абсолютно важна и должна быть быстрее, чем O (N)).

Я знаю о smoothsort, но я не могу найти реализацию Java. Кто-нибудь знает, уже ли он реализован? Или что я могу использовать для этой задачи вместо smoothsort?

Ответы

Ответ 1

Фактически, Википедия содержит реализацию Java smoothsort. Вы можете найти его здесь:

http://en.wikipedia.org/wiki/Smoothsort.

Ответ 2

Коктейль Сортировать

Если вам нужен простой алгоритм, который легко реализовать, вы можете сделать сортировку коктейлей. Он будет работать достаточно хорошо на почти отсортированном входе.

Ответ 3

Как отметил Botz3000, вы не можете выполнять такую ​​операцию быстрее, чем O (N). Самым основным элементом любого алгоритма было бы найти те записи в массиве, которые вышли из строя. Для этого требуется O (N), даже до того, как вы выясните, что с ними делать.

Если действительно количество элементов "вне порядка" на порядки ниже общего количества элементов, вы можете использовать следующий алгоритм (предполагая связанный список):

  • Найдите все элементы вне порядка и извлеките из исходного списка в отдельный список, O (N)
  • В результате появляются два списка: отсортированный список и короткий извлеченный список
  • Для каждого из извлеченных элементов вставьте их в отсортированный список. Это будет O (log (N)) для каждого, то есть O (Xlog (N)), где X - количество извлеченных элементов. Если X очень мало относительно N, вы получаете в итоге O (N).

Ответ 4

Для этого существует много хороших алгоритмов.

Smoothsort - мой личный фаворит... Я действительно работал со всеми математическими здесь, если вам интересно, почему он работает так хорошо.

Довольно хороший алгоритм для уже отсортированных данных - это естественный mergesort, который является восходящей версией mergesort, которая работает, обрабатывая входные данные как последовательность отсортированных поддиапазонов, а затем делает несколько проходов по диапазону, объединяющему соседние отсортированные диапазоны. Он работает в O (n) времени, если данные уже отсортированы (потому что он может обнаружить только один отсортированный диапазон) и O (n lg n) в худшем случае. Этот алгоритм работает достаточно хорошо, если данные "отсортированы по блокам"; то есть он состоит из множества отсортированных блоков, расположенных рядом друг с другом.

Прямая сортировка вставки определенно хорошо работает для данных, отсортированных по большей части, но может сильно ухудшиться на множестве входных данных. Некоторые действительно хорошие сорта (например, introsort) фактически используют это свойство сортировки вставки для выполнения "шага очистки" на входе.

Ответ 5

[Sun] JDK7 имеет (или будет иметь) реализацию Tim sort (из Python). Это сортировка слияния, которая использует уже существующий в массиве порядок.

Ответ 6

Smoothsort или Timsort - отличные алгоритмы и будут разумными для использования.

Я бы добавил, что вы можете не понимать, что скромный порядок вставки является адаптивным. Действительно, для действительно упорядоченных списков, как вам кажется, мое понимание (которое я не могу поддержать ссылкой) заключается в том, что оно быстрее, чем более сложные алгоритмы. Проблема состоит в том, что если вход не является почти-отсортированным, он быстро ухудшается до O (n ^ 2). Тем не менее, это очень просто реализовать правильно, поэтому, если вы точно знаете, что ваш вход всегда почти отсортирован, это будет хороший выбор.

Ответ 7

Как раз для того, чтобы положить его на стол, хорошо реализованный пузырь-сортировка, безусловно, будет самым простым алгоритмом здесь. В худшем случае O (n * m) m является числом смещений. Часть m сильно зависит от структуры перемещений, обычно общая сложность будет равна O (n).

Ответ 8

Вы правы в невозможности достижения O (N), но, предполагая многоядерную машину (что у меня есть), мы можем немного обмануть, используя алгоритм параллельной сортировки.

Ответ 9

Реализовать то, что мы назвали в школе видом Shell. Это пузырьковые субмассивы. Субматрица с шагом k представляет собой массив элементов с индексами 0, k, 2k, 3k...

Если вы выбираете k = 3i + 1 и выполняете множественные сортировки пузырьков, начиная с более высоких значений i-s до 0, времена будут меньше на почти отсортированном массиве.

Ответ 10

Это оригинальная реализация Java Smoothsort, которая была доступна через статью в Википедии.

// by keeping these constants, we can avoid the tiresome business
// of keeping track of Dijkstra b and c. Instead of keeping
// b and c, I will keep an index into this array.

static final int LP[] = { 1, 1, 3, 5, 9, 15, 25, 41, 67, 109,
    177, 287, 465, 753, 1219, 1973, 3193, 5167, 8361, 13529, 21891,
    35421, 57313, 92735, 150049, 242785, 392835, 635621, 1028457,
    1664079, 2692537, 4356617, 7049155, 11405773, 18454929, 29860703,
    48315633, 78176337, 126491971, 204668309, 331160281, 535828591,
    866988873 // the next number is > 31 bits.
};

public static <C extends Comparable<? super C>> void sort(C[] m,
    int lo, int hi) {
  int head = lo; // the offset of the first element of the prefix into m

  // These variables need a little explaining. If our string of heaps
  // is of length 38, then the heaps will be of size 25+9+3+1, which are
  // Leonardo numbers 6, 4, 2, 1. 
  // Turning this into a binary number, we get b01010110 = 0x56. We represent
  // this number as a pair of numbers by right-shifting all the zeros and 
  // storing the mantissa and exponent as "p" and "pshift".
  // This is handy, because the exponent is the index into L[] giving the
  // size of the rightmost heap, and because we can instantly find out if
  // the rightmost two heaps are consecutive Leonardo numbers by checking
  // (p&3)==3

  int p = 1; // the bitmap of the current standard concatenation >> pshift
  int pshift = 1;

  while (head < hi) {
    if ((p & 3) == 3) {
      // Add 1 by merging the first two blocks into a larger one.
      // The next Leonardo number is one bigger.
      sift(m, pshift, head);
      p >>>= 2;
      pshift += 2;
    } else {
      // adding a new block of length 1
      if (LP[pshift - 1] >= hi - head) {
        // this block is its final size.
        trinkle(m, p, pshift, head, false);
      } else {
        // this block will get merged. Just make it trusty.
        sift(m, pshift, head);
      }

      if (pshift == 1) {
        // LP[1] is being used, so we add use LP[0]
        p <<= 1;
        pshift--;
      } else {
        // shift out to position 1, add LP[1]
        p <<= (pshift - 1);
        pshift = 1;
      }
    }
    p |= 1;
    head++;
  }

  trinkle(m, p, pshift, head, false);

  while (pshift != 1 || p != 1) {
    if (pshift <= 1) {
      // block of length 1. No fiddling needed
      int trail = Integer.numberOfTrailingZeros(p & ~1);
      p >>>= trail;
      pshift += trail;
    } else {
      p <<= 2;
      p ^= 7;
      pshift -= 2;

      // This block gets broken into three bits. The rightmost
      // bit is a block of length 1. The left hand part is split into
      // two, a block of length LP[pshift+1] and one of LP[pshift].
      // Both these two are appropriately heapified, but the root
      // nodes are not necessarily in order. We therefore semitrinkle
      // both of them

      trinkle(m, p >>> 1, pshift + 1, head - LP[pshift] - 1, true);
      trinkle(m, p, pshift, head - 1, true);
    }

    head--;
  }
}

private static <C extends Comparable<? super C>> void sift(C[] m, int pshift,
    int head) {
  // we do not use Floyd improvements to the heapsort sift, because we
  // are not doing what heapsort does - always moving nodes from near
  // the bottom of the tree to the root.

  C val = m[head];

  while (pshift > 1) {
    int rt = head - 1;
    int lf = head - 1 - LP[pshift - 2];

    if (val.compareTo(m[lf]) >= 0 && val.compareTo(m[rt]) >= 0)
      break;
    if (m[lf].compareTo(m[rt]) >= 0) {
      m[head] = m[lf];
      head = lf;
      pshift -= 1;
    } else {
      m[head] = m[rt];
      head = rt;
      pshift -= 2;
    }
  }

  m[head] = val;
}

private static <C extends Comparable<? super C>> void trinkle(C[] m, int p,
    int pshift, int head, boolean isTrusty) {

  C val = m[head];

  while (p != 1) {
    int stepson = head - LP[pshift];

    if (m[stepson].compareTo(val) <= 0)
      break; // current node is greater than head. Sift.

    // no need to check this if we know the current node is trusty,
    // because we just checked the head (which is val, in the first
    // iteration)
    if (!isTrusty && pshift > 1) {
      int rt = head - 1;
      int lf = head - 1 - LP[pshift - 2];
      if (m[rt].compareTo(m[stepson]) >= 0
          || m[lf].compareTo(m[stepson]) >= 0)
        break;
    }

    m[head] = m[stepson];

    head = stepson;
    int trail = Integer.numberOfTrailingZeros(p & ~1);
    p >>>= trail;
    pshift += trail;
    isTrusty = false;
  }

  if (!isTrusty) {
    m[head] = val;
    sift(m, pshift, head);
  }
}