*** ВНИМАНИЕ: Блог переехал на другой адрес - demin.ws ***
Показаны сообщения с ярлыком сортировка. Показать все сообщения
Показаны сообщения с ярлыком сортировка. Показать все сообщения

пятница, 2 апреля 2010 г.

Веселые перестановки. Решение (сортировка перестановками соседних элементов)

Итак, мое решение вопроса о приведении одной последовательности к другой, когда можно только переставлять два элемента.

Нас просят привести одну последовательность (исходную) к другой (целевой). То есть логично предположить, что одна последовательность в нужном порядке (целевая), а вторая (исходная) - нет. Так надо просто отсортировать исходную последовательность "в целевую".

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

Теперь, а какой алгоритм сортировки использовать, чтобы для перемещения элементов использовать только обмен соседних элементов (функция swap())? Подходит сортировка вставками, когда на каждом шаге неотсортированный элемент последовательно "пропихивается" вниз к отсортированным. Тут как раз можно обойтись только обменом соседних элементов. Сама функция insertion_sort() является универальной и не зависит от компаратора is_less().

#include <stdlib.h>
#include <stdio.h>

void swap(int* a, int i) {
  int t = a[i];
  a[i] = a[i + 1];
  a[i + 1] = t;
}

#define N 8

const int etalon[] = { 1, 5, 7, 4, 2, 9, 8, 6 };
int from[N] = { 8, 1, 4, 2, 5, 6, 9, 7 };

void insertion_sort(int* a, int n, int (*is_less)(int, int)) {
  int i, j;
  for (i = 1; i < n; ++i) 
    for (j = i - 1; j >= 0 && is_less(a[j + 1], a[j]); j--)
      swap(a, j);
}

void print_array(const char* title, const int* a, int n) {
  int i;
  printf("%9s: ", title);
  for (i = 0; i < n; ++i)
    printf("%d ", a[i]);
  printf("\n");
}

int less(int a, int b) {
  int ia = -1, ib = -1;
  int i;
  for (i = 0; i < N; ++i) {
    if (etalon[i] == a) ia = i;
    if (etalon[i] == b) ib = i;
    if (ia >= 0 && ib >= 0) break;
  }
  return ia < ib;
}

int main() {
  int i, j;

  print_array("Original", from, N);
  insertion_sort(from, N, less);
  print_array("Converted", from, N);
  print_array("Etalon", etalon, N);

  return 0;
}

Запускаем:

 Original: 8 1 4 2 5 6 9 7 
Converted: 1 5 7 4 2 9 8 6 
   Etalon: 1 5 7 4 2 9 8 6 

Вроде работает.

Теперь что со сложностью. Принято считать, что сортировка вставками - это O(N^2) для худшего случая. Так как для сравнения элементов нам приходится искать линейно по эталонной последовательности на каждом шаге, то это еще O(N). В этоге: O(N^3).

Как вариант ускорения, можно изначально сделать отсортированную по значениям копию эталонной последовательности, и хранить не только значение, но его индекс. В этом случае поиск элемента будет уже занимать не O(N), а O(log(N)), и общая сложность будет O(log(N)*N^2).

В целом, все это не обязательно писать или помнить точную программу. Достаточно запомнить два вывода: алгоритм сортировки, использующий только обмен соседних элементов - это сортировка вставками, а ключ сортировки может быть далеко нетривиальной функцией.

Указание на эти два факта лично я счел бы на однозначно достаточный ответ.

четверг, 18 июня 2009 г.

Как реализована сортировка в STL

Все началось с того, что я почему-то написал свою реализацию классического алгоритма быстрой сортировки QuickSort. И все бы хорошо, но я, конечно, решил померяться достоинством с STL'ой реализацией. Результат был очевиден, и я даже не хочу приводить цифры.

Вобщем, я открыл файл algorithm из STL'я в Visual Studio 2008 и часок покопался в нем. Вот результаты моих "исследований".

Начнем с нестабильной сортировки std::sort().
  • основа: алгоритм быстрой сортировки QuickSort (почти как у меня)
  • выбор опорного элемента — центральный по индексу элемент в сортируемом фрагменте
  • после каждого выбора опорного элемента:
    • начальный, опорный и последний элементы сортируются между собой (их всего три, так что тут это делается тремя последовательными if'ами)
    • если длина сортируемого фрагмента менее более 40, то отрезок делится 8 частeй (длина каждой части S = 1/8*N) и для троиц элементов (1, S, 2*S), (N/2 - S, N/2, N/2 + S) и (N - 2*S, N - S, N) делается такая же минисортировка, как и на предыдущем шаге (где число элементов было меньше 40)
    • далее происходит обычная для QuickSort процедура деления фрагмента на две части с использованием опорного элемента (цикл по перебрасыванию элементов, меньших опорного направо, а больших — налево)
  • затем вся процедура рекурсивно повторяется для левой и правой частей
Количество рекурсивных операций не идет до победного конца, как в чистом QuickSort. Если количество итераций (процедур разделения массива) превысило 1.5*log2(N), где N длина всего массива, то рекурсивные операции прекращаются. Если количество оставшихся недосортированных элементов меньше 32-х, то фрагмент досортируется методом вставки InsertionSort (этот метод имеет общую сложность O(N2) и для больших массивов не используется, но на малых длинах он быстрее всех из-за простоты). Если же остается более 32-х элементов, то досортировка происходит пирамидальным методом HeapSort в чистом его виде.

Видимо все эти ухищрения для уменьшения накладных расходов QuickSort на малых массивах.

Вот такая вот далеко непрямолинейная реализация.

Далее.

Стабильная сортировка std::stable_sort() реализована алгоримом слияния MergeSort. Особых ухищрений по сравнению с чистным алгоритмом я не нашел. Разве что малые фрагменты (короче 32-х элементов) досортировываются методом вставки InsertionSort, как и в случае с QuickSort.

Частичая сортировка std::partial_sort() реализована в чистом виде пирамидальным методом HeapSort.

Вывод:
Читать исходники очень интересно. Особенно хорошие исходники.