66 лет целенаправленного поиска блистательных умов... А найдется ль сегодня хоть что-нибудь новое? Да, полировка особых случаев.
А если Фундаментально?
Против всякого чаяния, как ни странно, нашлось!
С уважением, Сергей Деревяго.
Хорошо, продолжаю.
Допустим, у нас есть некий вектор v={14, 11, 10, 13} и перестановка p={3, 1, 0, 2}, упорядочивающая его элементы:
i | 0 | 1 | 2 | 3 |
|---|---|---|---|---|
v[i] | 14 | 11 | 10 | 13 |
p[i] | 3 | 1 | 0 | 2 |
v[i] -> v2[p[i]] | 10 | 11 | 13 | 14 |
Т.е. используя перестановку p, мы можем перенести каждый элемент v[i] в позицию p[i] и получить упорядоченный вектор v2.
А теперь в конец вектора v мы добавим 12 -- что нужно сделать с перестановкой? Позицией 12-ти обязана стать 2, но она уже занята:
i | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
v[i] | 14 | 11 | 10 | 13 | 12 |
p[i] | 3 | 1 | 0 | 2 | |
v[i] -> v2[p[i]] | 10 | 11 | 13 | 14 |
Не беда:
1 все элементы, чье значение >=2.
2 в ее конец.
i | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
v[i] | 14 | 11 | 10 | 13 | 12 |
p[i] | 4 | 1 | 0 | 3 | 2 |
v[i] -> v2[p[i]] | 10 | 11 | 12 | 13 | 14 |
Гениальное просто. Как и функция добавления:
void append_pos(vector<short>& perm, short pos)
{
for (int i=0; i<perm.size(); i++) {
if (perm[i]>=pos) perm[i]++;
}
perm.push_back(pos);
}
|
Упражнение для читателя: а что, если декрементом? А не с нуля??
| derslib\inc\ders\dersort.hpp |
|---|
template<class T, class C>
short* dersort(const T* first, const T* last, short* firstOut, C cmp)
{
auto lastOut=firstOut;
if (last<=first || last-first>SHRT_MAX) return lastOut;
*lastOut++=0;
short base=0;
for (int i=1; i<last-first; i++) {
int psz=lastOut-firstOut, beg=0, end=psz;
for (int j=i-1; j>=0; j--) {
auto pj=firstOut[j]-base;
if (pj<beg || pj>=end) continue;
if (cmp(first[i], first[j])) {
if (pj==0) {
*lastOut++=--base;
break;
}
end=pj;
}
else {
if (pj==psz-1) {
*lastOut++=psz+base;
break;
}
beg=pj+1;
}
if (beg==end) {
append_pos(base, firstOut, lastOut, beg);
break;
}
}
}
if (base) for (auto it=firstOut; it<lastOut; it++) *it-=base;
return lastOut;
}
void append_pos(short& base, short* first, short*& last, short pos)
{
auto pb=pos+base;
int sz=last-first;
if (pos>=sz/2) {
int cnt=sz-pos;
for (auto it=last-1; cnt>0; it--) {
if (*it>=pb) {
(*it)++;
cnt--;
}
}
}
else {
int cnt=pos;
for (auto it=last-1; cnt>0; it--) {
if (*it<pb) {
(*it)--;
cnt--;
}
}
base--;
}
*last++=pos+base;
}
void to_sequence(const short* first, const short* last, short* firstOut)
{
for (int i=0, end=last-first; i<end; i++) firstOut[first[i]]=i;
}
|
А вот ключевые моменты:
dersort() никак не изменяет сам массив! Он создает перестановку.
v2[i]=v[p[i]] и вы получите отсортированный массив v2.
dersort() реализован в стиле STL: пара [first, last[ задает массив для сортировки, а перестановка будет записана в firstOut.
firstOut должен указывать на массив short той же длины: last-first.
dersort() возвращает lastOut -- конец записанной перестановки.
Обратите внимание, что сравнение элементов массива с помощью cmp(first[i], first[j]) вызывается, ТОЛЬКО если pj лежит в диапазоне [beg, end]! А сам диапазон стремительно сужается: в среднем в два раза за шаг.
Таким образом, количество сравнений элементов стремится к минимально возможному, а массив сортируется "сам о себя". Т.е. диапазон [beg, end] сам собой формирует чарующее дерево сравнений.
to_sequence()?
Я знал, что вы спросите! Для начала посмотрим на тесты и функцию show():
| perf8\main.cpp |
|---|
void show(fd_file* out)
{
// ...
vector<short> p(v.size());
dersort(v.data(), v.data()+v.size(), p.data(), [](int a, int b) { return a<b; });
print(out, "p[i]", &p);
vector<short> s(p.size());
to_sequence(p.data(), p.data()+p.size(), s.data());
print(out, "s[i]", &s);
// ...
}
|
Она выводит на экран перестановку p:
i | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|---|
v[i] | 14 | 16 | 19 | 15 | 15 | 12 | 17 |
p[i] | 1 | 4 | 6 | 2 | 3 | 0 | 5 |
Можем ли мы с ее помощью отсортировать вектор v? Да не вопрос!
Гы, лихо вы это тут! Но у матросов еще вопросы: как напечатать один за одним отсортированные элементы?
Гмм... Первый элемент перестановки -- савсэм адын, а нам нужно начать с нулевого. И потом уже будут 1, 2 и прочие... Всех придется искать перебором??
Вот поэтому нужен to_sequence()! Мы применим его к результату dersort() и получим перестановку s:
i | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|---|
v[i] | 14 | 16 | 19 | 15 | 15 | 12 | 17 |
p[i] | 1 | 4 | 6 | 2 | 3 | 0 | 5 |
s[i] | 5 | 0 | 3 | 4 | 1 | 6 | 2 |
v[s[i]] | 12 | 14 | 15 | 15 | 16 | 17 | 19 |
Все реально прекрасно, как кажется: v[s[i]] нам выводит один за одним элементы!
std::sort() (вариант Quicksort) и ins_sort() (классический Insertion sort).
Увы, но это не привычный тест на скорость, т.к.
to_sequence().
Тем не менее, есть два ключевых параметра, которые можно сравнить:
| perf8\main.cpp |
|---|
for (int j=0; j<M; j++) {
vector<int> v;
for (int i=0; i<N; i++) {
if (what=="asc") v.push_back(i);
else if (what=="desc") v.push_back(N-i);
else v.push_back(rand());
}
vector<short> p(v.size());
auto plast=dersort(v.data(), v.data()+v.size(), p.data(),
[](int a, int b) { dersCnt++; return a<b; });
perm_assert(plast-p.data()==int(p.size()));
vector<int> v2(v);
sort(v2.begin(), v2.end(),
[](int a, int b) { stdCnt++; return a<b; });
for (int i=0; i<int(v.size()); i++) {
perm_assert(v[i]==v2[p[i]]);
}
vector<int> v3(v);
ins_sort(v3.data(), v3.data()+v3.size(),
[](int a, int b) { insCnt++; return a<b; });
perm_assert(v2==v3);
}
|
Итак, случайные данные. Запускаем main с аргументом rand и любуемся:
| N | Quicksort | Insertion sort |
|---|---|---|
| 10 | 1.29 | 1.21 |
| 100 | 1.22 | 3.98 |
| 1000 | 1.09 | 22.89 |
Мы видим, что:
Уже отсортированные данные. Запускаем с аргументом asc:
| N | Quicksort | Insertion sort |
|---|---|---|
| 10 | 2.00 | 1.00 |
| 100 | 6.35 | 1.00 |
| 1000 | 10.63 | 1.00 |
Мы видим, что:
Отсортированные в обратном порядке. Запускаем с аргументом desc:
| N | Quicksort | Insertion sort |
|---|---|---|
| 10 | 1.00 | 5.00 |
| 100 | 5.31 | 50.00 |
| 1000 | 8.15 | 500.00 |
Мы видим, что:
short. И это его главная Проблема.
А дело в том, что массив short средних размеров гарантированно помещается в кэш первого уровня. И это ОГРОМНОЕ преимущество!
Вот почему Quicksort не задавил все квадратичные, а примитивный Insertion sort все еще рулит в районе тщедушных массивов. Непромедлительно сравним:
SHRT_MAX.
T.
short. Используйте лучше Quicksort!
Никакая часть данного материала не может быть использована в коммерческих целях без письменного разрешения автора.