Сортировка за O(N) операций. Сортирует, правда, только массивы целых (это принципиально). Можно и для signed, но это немного посложнее будет. Можно и строки сортировать, но тогда это уже будет bucket sort
Здравствуйте, Atilla, Вы писали:
A>Сортировка за O(N) операций. Сортирует, правда, только массивы целых (это принципиально). Можно и для signed, но это немного посложнее будет. Можно и строки сортировать, но тогда это уже будет bucket sort
A>Так что теперь, если вашу задачу проще решать на сортированном массиве, но от вас требуют линейного времени, можете смело сортировать!
Блестяще!
Для строк (и вообще для данных размером M бит) время O(N*M), что в случае со строками, естественно, неприемлимо.
Сортировка чисел со знаком (в дополнительном коде): прикол состоит в том, чтобы по старшему биту упорядочивать наоборот: единица меньше нуля.
Сортировка вещественных чисел также возможна:
union IEEE_FLOAT_32
{
struct
{
signed int sign:1; // сначала сортируем по знакуsigned int exponent:8; // затем - по порядкуsigned int mantissa:23; // и, наконец, по мантиссе
}
float value;
};
спасибо
К>Для строк (и вообще для данных размером M бит) время O(N*M), что в случае со строками, естественно, неприемлимо.
Ну для строк есть похожий метод. Там они сразу по байтам раскидываются, bucket sort называется.
К>Сортировка чисел со знаком (в дополнительном коде): прикол состоит в том, чтобы по старшему биту упорядочивать наоборот: единица меньше нуля.
угу, это я сразу сообразил, просто не захотел код усложнять.
К>Сортировка вещественных чисел также возможна:
круто. Не знал Т.е. задача сводится к сортировке signed long'ов. К>
К>union IEEE_FLOAT_32
К>{
К> struct
К> {
К> signed int sign:1; // сначала сортируем по знаку
К> signed int exponent:8; // затем - по порядку
К> signed int mantissa:23; // и, наконец, по мантиссе
К> }
К> float value;
К>};
К>
Вот только беда с этим методом в том, что O(N) — это асимптотика и реальный выигрыш для long'ов будет только при размерах массивов от 4 гигаштук и выше.
Здравствуйте, Atilla, Вы писали:
К>>Для строк (и вообще для данных размером M бит) время O(N*M), что в случае со строками, естественно, неприемлимо.
A>Ну для строк есть похожий метод. Там они сразу по байтам раскидываются, bucket sort называется.
Я имел в виду — неприемлимо долго: M=(8*max(strlen))
К>>Сортировка вещественных чисел также возможна:
A>круто. Не знал Т.е. задача сводится к сортировке signed long'ов. К>>
К>>union IEEE_FLOAT_32
К>>{
К>> struct
К>> {
К>> signed int sign:1; // сначала сортируем по знаку
К>> signed int exponent:8; // затем - по порядку
К>> signed int mantissa:23; // и, наконец, по мантиссе
К>> }
К>> float value;
К>>};
К>>
Только нужно будет учитывать особенности каждого бита.
A>Вот только беда с этим методом в том, что O(N) — это асимптотика и реальный выигрыш для long'ов будет только при размерах массивов от 4 гигаштук и выше.
Почему это?
Предположим, что фрагментация массива размером N составляет
— метрика D0 — количество элементов не на своих местах; 0 <= D0 <= N
— метрика D1 — сумма расстояний элементов до своих мест; D0 <= D1 <= N*(N+1)/2 (вроде бы)
Аксиома:
— количество сравнений у любой сортировки — не менее N-1.
— количество перестановок — не менее D0/2.
(Есть еще теорема, что достаточное количество сравнений равно log2(N!) — двоичный поиск на пространстве перестановок).
Сортировка кваксорт
— N * M сравнений
— от D0/2 до D0/2 * M перестановок (как повезет)
Поэтому кваксорт уделает пузырьковую при N > 8 (в среднем! Естественно, что для упорядоченного массива пузырек сделает ровно N-1 проверок, что является абсолютным рекордом).
среднее(D0) = N/2
среднее(D1) = (N^2)/4
Вот валидированный (доказанный) код; доказательство — см. комментарии.
Обозначение:
)j) -- элемент перед j (т.е. с индексом j-1) -- по аналогии с полуинтервалом [i,j)
inline void swap(unsigned& a, unsigned& b)
{
unsigned c = a; a = b; b = c;
}
// sort a range [i, j)void ksort_bit(unsigned* array, unsigned i0, unsigned j0, unsigned bit)
{
// precondition:
// a[0,i0), a[i0,j0), a[j0,size) higher bits are constant
// a[0,i0) < a[i0,j0) < a[j0,size)
// invariant: no changes out of [i0,j0)
// postcondition:
// a[i0,j0) is sortedif(j0-i0 < 2) return; // either empty or single itemunsigned i = i0, j = j0;
// precondition: [i,j) is valid range
// invariant: a[i0,i) bit is clear, a[j,j0) bit is set, [i,j) is valid range (may be empty)
// postcondition i == j, a[i0,i) bit is clear, a[j,j0) bit is set
// thus, a[i0,i) < a[j,j0)while(i < j)
{
// skip i while a[i] bit is clearwhile(i < j && (array[i] & bit) == 0)
i++;
// array[i0,i) bit is clear;
// i==j or array[i] bit is set
// skip j while a)j) bit is setwhile(i < j && (array[j-1] & bit) != 0)
j--;
// array[j+1,j0) bit is set
// i==j or array)j) bit is clearif(i == j) // donebreak;
// misorder: a[i] > a)j)
assert(j-i > 1); // because a[i] set, a)j) clear ==> a[i] != a)j) ==> i != j-1
// exchange boundary items of [i,j)
swap(array[i], array[j-1]);
i++;
j--;
// invariant is true
}
assert(i == j);
bit >>= 1;
if(!bit) return;
ksort_bit(array, i0, i, bit);
ksort_bit(array, i, j0, bit);
// result of recursion:
// a[i0,i) is sorted;
// a[i,j0) is sorted;
// a[i0,i) < a[i,j0) ==> a[i0,j0) is sorted
// postcondition is true
}
inline void ksort(unsigned* array, unsigned size)
{
ksort_bit(array, 0, size, 1<<(sizeof(unsigned)*8-1));
}
При желании можно сделать то же самое не на индексах, а на обобщенных итераторах.
template<class iter>
void ksort_bit(iter begin, iter end, unsigned bit);
Как обычно, указываем полуинтервал, то есть итератор конца лежит за правой границей контейнера
К> if(j0-i0 < 2) return; // either empty or single item
К>
Вот про это-то я и забыл. Все, надо ужинать и спать, а то еще не таких багов наделаю.
Зато теперь можно смело сортировать за линейное время! Общими усилиями алгоритм довели до конца. Вроде бы...
А гн-у Кодту предлагаю присвоить звание Главного Линейного Программиста
Вот реализация STL из VS7 уходит в qsort'е максимум на 1.5*log2(N) (а меньше, чем log2(N) и не бывает) шагов и те куски, которые за такое число итераций не отсортировались, сортирует heapsort'ом или insertsort'ом в зависимости от размера кусков. В общем, в худшем случае std::sort сделает 1.5*log2(N) проходов (каждый проход там имеет такую же сложность что и в ksort вроде бы). Короче, надо все на практике проверять
Вот я тут тест слабал, получилось, что при сортировке массива ULONG размером в 10 млн. эл-тов std::sort работает на 20% быстрее, а при 50 млн. — на 10%. Большие массивы в память плохо влазят
Для USHORT с 50 млн. элементами ksort выигрывает 25%.
Все сугубо на моем компе.
A>Сортировка за O(N) операций. Сортирует, правда, только массивы целых (это принципиально). Можно и для signed, но это немного посложнее будет. Можно и строки сортировать, но тогда это уже будет bucket sort
Ой-ой-ой! А есть ли строгое матеметическое доказательство???
Я тут тестик написал — нифига нелинейное время.
Да и по алгоритму это можно заметить.
Растет нелинейно — правда не так сильно.
Может это логарифм, только более слабый, чем quick sort.
void main()
{
const int count = 1000000;
unsigned *ar = new unsigned[count];
for (int i = 0; i < count; ++i)
{
ar[i] = (rand() | (rand() << 16)); // set more random bits :)
}
cout << endl;
time_t t1 = clock();
// does this give a linear time?
ksort(ar, count);
/* this should give truly linear time
for (i = 0; i < count*10; ++i)
{
ar[i%count] = ar[count-(i%count)-1]; // nothing special
}
*/
time_t t2 = clock();
cout << "time="<<(t2-t1)<<endl;
delete ar;
}
Serge.
Hасколько проще была бы жизнь, если бы она была в исходниках.
Здравствуйте, Sergeem, Вы писали:
S>Ой-ой-ой! А есть ли строгое матеметическое доказательство??? S>Я тут тестик написал — нифига нелинейное время. S>Да и по алгоритму это можно заметить. S>Растет нелинейно — правда не так сильно. S>Может это логарифм, только более слабый, чем quick sort.
Учите матчасть, товарищь!
O(N) — это значит линейное в асимптотике. И проверять это надо, действительно, аналитически (или методом внимательного всматривания), а не тестами.
И еще, не забывайте про особенности работы памяти, кэша и мн. мн. др. Так что clock() не показатель.
Здравствуйте, Atilla, Вы писали:
A>Здравствуйте, Sergeem, Вы писали:
S>>Ой-ой-ой! А есть ли строгое матеметическое доказательство??? S>>Я тут тестик написал — нифига нелинейное время. S>>Да и по алгоритму это можно заметить. S>>Растет нелинейно — правда не так сильно. S>>Может это логарифм, только более слабый, чем quick sort.
A>Учите матчасть, товарищь! A>O(N) — это значит линейное в асимптотике. И проверять это надо, действительно, аналитически (или методом внимательного всматривания), а не тестами.
A>И еще, не забывайте про особенности работы памяти, кэша и мн. мн. др. Так что clock() не показатель.
я проверял от 1млн до 20млн — разница растет, а для асимптотического приближения она должна уменьшаться.
На счет матчасти — а можно ссылочку пожалуйста?
Буду очень рад ознакомиться.
Serge.
Hасколько проще была бы жизнь, если бы она была в исходниках.
Здравствуйте, Sergeem, Вы писали:
S>я проверял от 1млн до 20млн — разница растет, а для асимптотического приближения она должна уменьшаться.
вот как раз где-то тут а начинает сказываться размер кэша 2-го уровня.
S>На счет матчасти — а можно ссылочку пожалуйста? S>Буду очень рад ознакомиться.
уж и не помню... про пределы и асимптотику написано во всех учебниках по мат. анализу
Здравствуйте, Atilla, Вы писали:
A>Здравствуйте, VVV, Вы писали:
VVV>>У меня получилось вот что: VVV>>100 432 2 19 334 22 1 10 87 VVV>>1 1 2 10 9 22 87 100 334 432
VVV>>Или это только у меня???
A>Ух ты! И у меня появилось! раньше не было
Даа, товарищь Atilla. Подобные вещи надо проверять прежде чем постить. Ну хотя бы простейшим стресс-тестированием, как у VVV. Ну это так, замечание на будущее.
На самом деле, если на практике способ работает медленнее, то что с него толку? Вот например, сложность quick sort равна O(N^2). Сложность merge sort гарантирует O(N*log(N)). Тем не менее, грамотно имплементированный quick sort кроет merge sort как бык овцу, не требуя при этом дополнительной памяти (я не говорю о случаях, когда merge sort — единственно возможный вариант). Да, quick sort затыкается на почти отсортированных массивах. Но даже в этом случае, если вероятность подать на вход отсортированный массив весьма значительна (ну, скажем, 1/1000000), то проще бывает всегда делать shuffle + quick sort, чем просто merge sort (феномен, но это так). А вообще-то, все от задачи зависит. Не бывает идеальной сортировки, так же как и не бывает универсального растворителя. На практике, qsort дает почти линейное время, но этот факт вообще-то, не имеет никакого отношения к теоретическому понятию Big-Oh-Notation.
McSeem
Я жертва цепи несчастных случайностей. Как и все мы.