powered by simpleCommunicator - 2.0.59     © 2025 Programmizd 02
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Форумы / C++ [игнор отключен] [закрыт для гостей] / Тяпничная пирамида
25 сообщений из 126, страница 4 из 6
Тяпничная пирамида
    #39395917
Фотография mayton
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Dima TРезультат 5-ти видов сортировки на массиве в 10 млн. элементов. Места ставил по времени.
Несортированный
АлгоритмВремя мсКопированийСравненийПримечаниеmerge_sort()1 990243 222 784230 068 109из инетаstd::sort()2 035338 720 497375 556 538MSVC2015insert_sort()2 049363 169 751376 875 415моя поделкаquick_sort()2 510168 421 944389 659 279из инетаqsort()3 045н/д277 090 450MSVC2015
Сортированный
АлгоритмВремя мсКопированийСравненийinsert_sort()520 19 999 998std::sort()48017 415 788220 483 043quick_sort()5526 465 373243 606 228merge_sort()827243 222 784129 428 083qsort()1 054н/д237 187 025
Респект за проделанную работу. Я-бы еще предложил-бы дать на вход варианты данных (N штук):
1) Белый шум из N элементов.
2) Белый шум с низкой кардинальностью (всего 3-10 уникальных элементов из N)
3) Серии монотонных под-последовательностей 3-5 штук из N. Монотонные возрастающие-убывающие ("пила", синусоида).
4) Прочие краевые случаи. Например - неблагоприятные наборы на которых q-sort
"переключается" в обычный алгоритм или валится со stackoverflow (depends on implementation)
...
Рейтинг: 0 / 0
Тяпничная пирамида
    #39395967
Dima T
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
maytonРеспект за проделанную работу. Я-бы еще предложил-бы дать на вход варианты данных (N штук):
1) Белый шум из N элементов.
2) Белый шум с низкой кардинальностью (всего 3-10 уникальных элементов из N)
3) Серии монотонных под-последовательностей 3-5 штук из N. Монотонные возрастающие-убывающие ("пила", синусоида).
4) Прочие краевые случаи. Например - неблагоприятные наборы на которых q-sort
"переключается" в обычный алгоритм или валится со stackoverflow (depends on implementation)
У меня 1й вариант реализован. Надо функцию fill() допилить: на вход параметр давать какого типа последовательность нужна.
По п.4 не знаю как сгенерить, предлагай алгоритм.
...
Рейтинг: 0 / 0
Тяпничная пирамида
    #39396052
Фотография mayton
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Друзья. Как вы заметили в последнее время я редко пишу посты. Чортова
работа + курсы придавили меня к земле и не дают продохнуть. :)

Но я читаю все посты где я участник так что - мысленно с вами :)
...
Рейтинг: 0 / 0
Тяпничная пирамида
    #39396356
kealon(Ruslan)
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
mayton4) Прочие краевые случаи. Например - неблагоприятные наборы на которых q-sort
"переключается" в обычный алгоритм или валится со stackoverflow (depends on implementation)
при правильной реализации QSort stackoverflow невозможен
...
Рейтинг: 0 / 0
Тяпничная пирамида
    #39396559
Dima T
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Добавил разные исходные заполнения
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
#define FILL_ORDERED		2 // Отсортированный
#define FILL_RANDOM		3 // Случайные (диапазон 0...TEST_SIZE*2)
#define FILL_RANDOM_DOUBLES	4 // Случайные с повторами (диапазон 0...100)
#define FILL_STAIRS		5 // Лесенка по 10 (1,2,3,...8,9,1,2,3...)
#define FILL_SINUS		6 // "Синусоида" по 10 (1,2,3,...8,9,8,7,6...)
#define FILL_NEARLY		7 // Почти сортированный (20 значений смещено на 20 позиций)
#define FILL_REVERSE		8 // Обратно сортированный

Исходник
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
// Заполнение тестового массива
void fill(int type) {
	uint32_t k = 1234567;
	bool up = true;
	switch(type) {
	case FILL_NONE:
		break;

	case FILL_ORDERED:
		k = 0;
		for (auto it = a.begin(); it != a.end(); it++, k++) {
			*it = k;
		}
		printf("ordered  ");
		break;

	case FILL_RANDOM:
		for (auto it = a.begin(); it != a.end(); it++) {
			*it = k % (TEST_SIZE * 2);
			k = (k * 65537 + 2017);
		}
		printf("random  ");
		break;

	case FILL_RANDOM_DOUBLES:
		for(auto it = a.begin(); it != a.end(); it++) {
			*it = k % 100;
			k = (k * 65537 + 2017);
		}
		printf("random (max 100)  ");
		break;

	case FILL_STAIRS:
		k = 0;
		for (auto it = a.begin(); it != a.end(); it++) {
			k++;
			if (k == 10) k = 0;
			*it = k;
		}
		printf("stairs  ");
		break;

	case FILL_SINUS:
		k = 0;
		for (auto it = a.begin(); it != a.end(); it++) {
			if(up) {
				k++;
				if (k == 10) up = false;
			} else {
				k--;
				if (k == 0) up = true;
			}
			*it = k;
		}
		printf("sinus  ");
		break;

	case FILL_NEARLY:
		k = 0;
		for (auto it = a.begin(); it != a.end(); it++, k++) {
			*it = k;
			if (k % (TEST_SIZE / 20) == 0) {
				if(up) {
					*it = k + 20;
				} else {
					*it = k - 20;
				}
				up = !up;
			}
		}
		printf("nearly ordered  ");
		break;

	case FILL_REVERSE:
		k = TEST_SIZE;
		for (auto it = a.begin(); it != a.end(); it++, k--) {
			*it = k;
		}
		printf("reverse  ");
		break;

	default:
		printf("!!! Unknown fill type %d  ", type);
	}
	if(a.size() != TEST_SIZE) printf("!!! Wrong size  ");
}


Убрал qsort() добавил std::stable_sort()

Время мс
АлгоритмNEARLYORDEREDRANDOMRANDOM_DOUBLESREVERSESINUSSTAIRSinsert_sort()13131311369319128138merge_sort()64363817711164652835836quick_sort()1631621119579176415418std::sort()2712681255344305115127std::stable_sort()4254301238818502601608

Операций (копирований + сравнений)
АлгоритмNEARLYORDEREDRANDOMRANDOM_DOUBLESREVERSESINUSSTAIRSinsert_sort()10 004 6959 999 999738 371 870199 030 889302 776 605131 526 867142 030 677merge_sort()362 011 134362 010 944463 267 963462 644 325357 657 408453 386 928453 368 256quick_sort()236 447 825236 445 593541 750 828403 557 861256 445 617387 576 172382 637 354std::sort()228 952 229228 951 438737 463 387186 343 940282 625 386111 500 018122 000 152std::stable_sort()325 963 938325 963 568565 121 728563 475 795468 497 124547 301 462542 329 730
Подробнее
Копирования
АлгоритмNEARLYORDEREDRANDOMRANDOM_DOUBLESREVERSESINUSSTAIRSinsert_sort()7900369 072 660102 318 66272 827 95476 243 23879 998 904merge_sort()243 222 784243 222 784243 222 784243 222 784243 222 784243 222 784243 222 784quick_sort()5 807 0165 805 696169 379 23263 116 09720 805 69737 944 05239 390 073std::sort()17 903 42017 902 850363 987 48889 248 99552 799 54856 249 95860 000 051std::stable_sort()209 375 190209 375 000291 879 725291 127 627369 375 000286 375 000280 500 000


Сравнения
АлгоритмNEARLYORDEREDRANDOMRANDOM_DOUBLESREVERSESINUSSTAIRSinsert_sort()10 003 9059 999 999369 299 21096 712 227229 948 65155 283 62962 031 773merge_sort()118 788 350118 788 160220 045 179219 421 541114 434 624210 164 144210 145 472quick_sort()230 640 809230 639 897372 371 596340 441 764235 639 920349 632 120343 247 281std::sort()211 048 809211 048 588373 475 89997 094 945229 825 83855 250 06062 000 101std::stable_sort()116 588 748116 588 568273 242 003272 348 16899 122 124260 926 462261 829 730

В предыдущий замер сравнения неккоректно считались, т.к. перед записью был std::is_sorted(), который тоже счетчик менял.


PS C предыдущими замерами не сравнивать, т.к. элемент массива с char[10] поменял на int64_t, чтобы порядок удобнее было задавать.
...
Рейтинг: 0 / 0
Тяпничная пирамида
    #39396733
Dima T
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
maytonДалее. По поводу последовательности

Код: plaintext
1.
1,9,2,3,4,5,6,7,8



Я-бы выделил в ней две под-последовательности
Код: plaintext
1.
[1,9] , [2,3,4,5,6,7,8]



Они - монотонные. Неубывающие. И хорошо проходят через merge-sort.

В противоположность. Если на вход пойдет белый шум длиной N элементов - то нужно эвристически
принять решение о его НЕПРИГОДНОСТИ к частичным методам даже если случайно
в нем будут меньше чем N подпоследовательностей. Ну в общем надо исходить из
какой-то разумной пропорции.
Зацепил ты меня этим постом, писать некогда, пока просто размышляю.

Про белый шум не согласен, см. тест выше - отлично сортируется, т.е. можно сортировать что угодно.

Идея как эффективно смержить две соседних возрастающих последовательности уже есть. Непонятки дальше.

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

Например есть последовательности [4][2][55][48][2][12] число в скобках это количество элементов в каждой последовательности. В каком порядке их мержить?

Может ты что предложишь? У меня мысли есть по этому поводу, но не нравятся они мне.
...
Рейтинг: 0 / 0
Тяпничная пирамида
    #39396748
Фотография mayton
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Dima T, вопрос интересный. Приду домой - по думаю. Но сразу скажу что я не такой уж теоретик и знания у меня
Весьма осколочные. Но гонять бенчмаркинг на разных данных - очень важно.
...
Рейтинг: 0 / 0
Тяпничная пирамида
    #39396789
Фотография SashaMercury
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
[quot Dima T]maytonДалее. По поводу последовательности



Например есть последовательности [4][2][55][48][2][12] число в скобках это количество элементов в каждой последовательности. В каком порядке их мержить?

Может ты что предложишь? У меня мысли есть по этому поводу, но не нравятся они мне.

Судя по всему, асимптотика merge двух массивов составляет . В твоем случае, такой порядок будет оптимальным: 2 merge 2, 4 merge 4,8 merge 12, 20 merge 48, 55 merge 68. Итого 2+4+12+48+68 операций. Возможно лучшим алгоритмом будет выбор двух минимальных по размеру массивов перед каждым merge(в этом я до конца не уверен). Но перед последней итерацией, идеальным будет merge двух равных по размеру массивов
...
Рейтинг: 0 / 0
Тяпничная пирамида
    #39396794
Фотография SashaMercury
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
А, да это же задача о рюкзаке с определенными ограничениями)) Не зря я сомневался в выборе двух минимальных на каждой итерации
...
Рейтинг: 0 / 0
Тяпничная пирамида
    #39396800
Фотография SashaMercury
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
ДмитрийМожет ты что предложишь? У меня мысли есть по этому поводу, но не нравятся они мне.

Потому не зря они тебе не нравились))
...
Рейтинг: 0 / 0
Тяпничная пирамида
    #39396857
Фотография mayton
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Если каналов доступа к памяти два. И "удачно лягут карты" - то можно попробовать в 2 потока запустить merge sort.

+Если мы храним min/max для каждой серии то возможно стоит изучить вариант просто тривиальной конкатенации
серий (опять-же если мы имеем дело с неким синтетическим источником данных).
...
Рейтинг: 0 / 0
Тяпничная пирамида
    #39396868
booby
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Dima T...
Например есть последовательности [4][2][55][48][2][12] число в скобках это количество элементов в каждой последовательности. В каком порядке их мержить?
...

Есть такой алгоритм - "естественная двусторонняя сортировка слиянием" (nature merge sort
или nature 2-way merge sort)
(у которой есть "неестественные" вариации - вроде двоичной двусторонней бла-бла-бла, и далее - многоветочные истории, которые, правда, вроде как скисают на большом числе веток и больших сортируемых объемах - я в них не разбирался детально.)
не поленись, погугли.

Идею алгоритма приписывают Кнуту.
Смысл в том, чтобы сливать с двух краев в середину (свеча слияния горит с двух концов).
То есть на первом шаге алгоритма слияние идет в таком порядке:

[4][2][55][48][2][12] || x||x xx||xx
при этом на фазе самого слияния используют двоичный поиск.

То есть
Сортированный_результат = ГоримСДвухСторон(Разбиение_на_Сортированные_диапазоны(Сортируемый_Список))

В принципе, алгоритму не требует наличия сколь-нибудь протяженных предварительно сортированных поддиапазонов.
Но, если сортируемый список представляет чистый белый шум,
то у некоторых имплементаторов есть мнение, что разумно натравить на БелыйШум
сортировку вставкой, с размером итоговых сортированных поддиапазонов на 8 - 16 элементов, что и создаст стартовую точку для горения с двух сторон.

Еще есть изобретения in-place слияния.
имхо, годное к разглядыванию обсуждение:
http://stackoverflow.com/questions/2571049/how-to-sort-in-place-using-the-merge-sort-algorithm
...
Рейтинг: 0 / 0
Тяпничная пирамида
    #39396873
booby
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
booby,

существенная деталь - в свечном производстве
с головы ищут неубывающую подпоследовательность, а с хвоста - невозрастающую.
т.е. в исходной идее, в голове - [4][2][55] - это возрастающие поддиапазоны (asc),
а [48][2][12] - убывающие (desc) поддиапазоны
...
Рейтинг: 0 / 0
Тяпничная пирамида
    #39397027
kealon(Ruslan)
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
booby,

очень заманчиво выглядит по сравнению с наивной квадратичной реализацией,
...
Рейтинг: 0 / 0
Тяпничная пирамида
    #39397064
Dima T
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
У меня мысль минимизировать копирования, т.к. из-за них тормоза на почти сортированной последовательности.

Поэтому видится вариант работы внутри исходного массива. Т.е. ограничится только слиянием соседних подмассивов.
Слияние двух соседних сортированных подмассивов
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
// start1,start2 индекс начала каждого, end индекс начала следующего
void merge_near(std::vector<item_t>& a, size_t start1, size_t start2, size_t end) {
	// Поиск места вставки
	item_t x = a[start2];
	int r = start1, l = start2 - 1;
	while (r < l) {
		int c = (r + l) / 2;
		int cmp = item_t::compare(x, a[c]);
		if (cmp <= 0) { // x <= a[c]
			l = c;
			if (cmp == 0) break;
		}
		else { // x > a[c]
			r = c + 1;
		}
	}
	// Слияние начиная с a[l]
	size_t i1 = l, i2 = start2;
	std::queue<item_t> queue;
	for (size_t write = l; write < end; write++) {
		if(write == i1) { // место записи занято
			if(i1 < start2) {
				queue.push(a[i1]);
				i1++;
			}
		}
		if (queue.empty()) break;

		if(i2 == end || queue.front() < a[i2]) {
			a[write] = queue.front();
			queue.pop();
		} else {
			a[write] = a[i2];
			i2++;
		}
	}
}


Из-за очереди тормозит, надо ее на массив менять, но для замера количества операций сойдет

Затестил последовательное слияние, т.е. первый со вторым, результат с третьим и т.д.
Итого на 1000 элементах в 50 раз больше операций чем у std::sort() на обратносортированной, т.е. алгоритм выродился в сортировку вставками. На "белом шуме" больше "всего" в 14 раз.
booby, тоже самое будет со свечой, разве что вдвое быстрее, т.е. вместо O(n 2 ) получим O(n 2 /2).

Затестил вариант слияния попарно, т.е.
Код: plaintext
1.
2.
3.
результат1 = список1 + список2
результат2 = список3 + список4
итого = результат1 + результат2


тут результаты получше
Заполнениеstd::sort()merge_ordered()СоотношениеRANDOM737 463 387592 349 26880%RANDOM_DOUBLES186 343 940588 526 298316%ORDERED228 951 4389 999 9994%STAIRS122 000 152488 230 344400%SINUS111 500 018552 390 781495%NEARLY228 952 22910 001 4324%REVERSE282 625 386613 296 588217%Максимум737 463 387613 296 58883%
Цифры это всего операций (сравнений + копирований). Размер массива 10 млн.

В принципе уже неплохо. Но мне кажется можно еще улучшить, хотя бы до показателей merge_sort() на несортированных.

Надо придумать алгоритм типа такого:
перебираем в цикле массив подмассивов. Каждый subarr[i] сравниваем с соседними subarr[i-1] и subarr[i+1], затем принимаем решение с каким из них смержить или оставить как есть на данном проходе. И так по кругу в цикле пока все не смержатся.

Осталось придумать как сравнивать подмассивы. По ним есть размер и минимальный/максимальный элемент.
Склеивание парами
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
void merge_ordered(std::vector<item_t>& a) {
	size_t min_size = 16; // минимальный размер интервала, ниже которого сразу объединять
	// начала монотонных подмассивов
	std::vector<size_t> idx; 
	idx.push_back(0);
	for (size_t i = 1; i != a.size(); i++) {
		if (a[i - 1] > a[i]) {
			if(idx.size() == 1 || i - idx[idx.size() - 2] > min_size) {
				idx.push_back(i);
			} else { // Интервал слишком мал, объединяем с предыдущим
				merge_near(a, idx[idx.size() - 2], idx[idx.size() - 1], i);
				idx[idx.size() - 1] = i;
			}
		}
	}
	idx.push_back(a.size());
	// объединение парами
	size_t end = idx.size() - 1;
	while(end > 1) {
		size_t i = 0, w = 0;
		for (; i < end; i += 2, w++) {
			if(i + 1 != end) { // не последний без пары
				merge_near(a, idx[i], idx[i+1], idx[i+2]);
			}
			idx[w] = idx[i];
		}
		idx[w] = idx[end];
		end = w;
	}
}

...
Рейтинг: 0 / 0
Тяпничная пирамида
    #39397112
booby
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Dima T,
код внимательно разглядывать не могу, но,
кажется, ты не обратил внимание на то, что свечка при слиянии подмассивов
заведомо рассчитывает на то, что имеет дело с уже отсортированными диапазонами.
Поэтому не боится использовать двоичный поиск на фазе слияния.

Пусть сливаются отрезки с длинами а)[4](левый) и б)[12] (правый)


/*текущая позиция в а */ и = старт_а
голова_цикла:
номер_позиции_а(и)_в_б = двоичным поиском ищем позицию(б)
от текущего начала не перелитой части б до найденной и вываливаем все б(ж) в область слияния
вываливаем в область слияния элементы а, пока они не больше а(и)
устанавливаемся в новую позицию и в а
Если диапазоны не кончились, то ГОУ ТУ голова_цикла

Тут надо обратить внимание на то, что, с одной стороны,
в принципе всегда хотелось бы иметь ведущий диапазон меньшего размера,
а тот, в котором будем искать двоичным поиском - большего.
Но, если их переставлять не глядя, то нарушится стабильность, присущая исходному merge sort.
Поэтому фига из трех пальцев - либо плюнуть на стабильность, либо плюнуть на разборки - кто из них длиннее,
либо озаботиться устабилизацией обмена диапазонов.
...
Рейтинг: 0 / 0
Тяпничная пирамида
    #39397179
Dima T
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
boobyDima T,
код внимательно разглядывать не могу, но,
кажется, ты не обратил внимание на то, что свечка при слиянии подмассивов
заведомо рассчитывает на то, что имеет дело с уже отсортированными диапазонами.
Поэтому не боится использовать двоичный поиск на фазе слияния.
Двочный поиск у меня есть. В merge_near() им ищется место откуда слияние начинать. Все подмассивы считаются сортированными.

Может я неправильно понял описание твоего алгоритма. Пишу как понял, например массив:
Код: plaintext
1.
[4][2][55][48][2][12]


где то что в [] это уже сортированные подмассивы, цифры - количество элементов
далее смерживаем
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
//Возрастающая половина
[4][2] => [6]
[6][55] => [61]
//Убывающая половина
[12][2] => [14]
[14][48] => [62]
// Итого
[61][62] => [123]



Если так то, я почти так и делал, только полсвечи, т.е.
Код: plaintext
1.
2.
3.
4.
5.
[4][2] => [6]
[6][55] => [61]
[61][48] => [109]
[109][2] => [111]
[111][12] => [123]


Такой подход тормозит на обратносортированном, твоя свеча тоже будет тормозить, т.к. добавление будет всегда по одному элементу в начало, т.е. сортировка вставками, а двоичный поиск только лишние тормоза даст, т.к. достаточно сравнения первого с первым элемента.
...
Рейтинг: 0 / 0
Тяпничная пирамида
    #39397201
kealon(Ruslan)
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Dima T,

сортировка слияниями базируется на том что слияние двух сортированных массивов имеет сложность не больше O(a+b), где a и b размеры сливаемых массивов

собственно на каждом уровне итерации - k необходимо произвести 2^k слияний мощностью (N/2^k), в сумме для уровня <= O(N)
всего уровней log2(N) - общая сложность - O(N* Ln(N))

если процедура слияния не удовлетворяет крайней оценке O(a+b), то ни о каких O(N* Ln(N)) для сортировки речи идти не может

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

с другой стороны если использовать дополнительный массив, то тот же heapsort легко избавляется от своего главного недостатка - построение обратной кучи на первом этапе
...
Рейтинг: 0 / 0
Тяпничная пирамида
    #39397253
Dima T
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
kealon(Ruslan)Dima T,

сортировка слияниями базируется на том что слияние двух сортированных массивов имеет сложность не больше O(a+b), где a и b размеры сливаемых массивов

собственно на каждом уровне итерации - k необходимо произвести 2^k слияний мощностью (N/2^k), в сумме для уровня <= O(N)
всего уровней log2(N) - общая сложность - O(N* Ln(N))
При слиянии парами у меня так и получилось, например
Код: plaintext
1.
[4][2][55][48][2][12] => [6][103][14] => [109][14] => [123]


Но это O(N* Ln(N)) на уровне слияний, но сами слияния тоже отличаются например [48][2][12] можно слить
Код: plaintext
1.
[48][2][12] => [50][12]

а можно
Код: plaintext
1.
[48][2][12] => [48][14]


ИМХО второй вариант предпочтительней, т.е. предпочтение надо как-то отдать слиянию меньших подмассивов.

Есть одна мысль, вечером потестю, выложу результаты.
...
Рейтинг: 0 / 0
Тяпничная пирамида
    #39397470
Dima T
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Сделал склеивание только тех подмассивов, которые дадут итого меньше двух средних размеров
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
void merge_ordered(std::vector<item_t>& a) {
	size_t min_size = 16; // минимальный размер интервала, ниже которого сразу объединять
	// начала монотонных подмассивов
	std::vector<size_t> idx; 
	idx.push_back(0);
	for (size_t i = 1; i != a.size(); i++) {
		if (a[i - 1] > a[i]) {
			if(idx.size() == 1 || i - idx[idx.size() - 2] > min_size) {
				idx.push_back(i);
			} else { // Интервал слишком мал, объединяем с предыдущим
				merge_near(a, idx[idx.size() - 2], idx[idx.size() - 1], i);
				idx[idx.size() - 1] = i;
			}
		}
	}
	idx.push_back(a.size());
	// объединение парами
	size_t end = idx.size() - 1;
	while(end > 1) {
		size_t i = 0, w = 0;
		size_t avg = 2 * (idx[end] / (end - 1));

		for (; i < end; i++, w++) {
			if(i + 1 != end && (idx[i + 2] - idx[i]) <= avg) { // есть пара и результат будет размером менее 2х средних
				merge_near(a, idx[i], idx[i+1], idx[i+2]);
				idx[w] = idx[i];
				i++;
			} else {
				idx[w] = idx[i];
			}
		}
		if(w == end) { // Ничего не смержилось, защита от зацикливания
			printf("circle %d\n", w);
			merge_near(a, idx[w - 2], idx[w - 1], idx[w]);
			w--;
		}
		idx[w] = idx[end];
		end = w;
	}
}


Особых изменений нет, чуть-чуть получше местами. Думаю в разы этот алгоритм не улучшить.

std::sort()insert_sort()%merge_ordered()%Заполнение737 463 387738 371 870100%600 849 36181%Случайные (диапазон 0...TEST_SIZE*2)186 343 940199 030 889107%596 760 728320%Случайные с повторами (диапазон 0...100)228 951 4389 999 9994%9 999 9994%Отсортированный122 000 152142 030 677116%488 230 344400%Лесенка по 10 (1 2 3 ...8 9 0 1 ...)111 500 018131 526 867118%549 117 750492%Синусоида по 10 (1 2 3...8 9 8 7 ...)228 952 22910 004 6954%10 001 4324%Почти сортированный (20 значений смещено на 20 позиций)282 625 386302 776 605107%613 296 588217%Обратно сортированный730 224 311711 355 83597%80 000 07811%4 одинаковых подмассива
Посчитаны операции (сравнения + копирования) Проценты по отношению к std::sort()

ИМХО insert_sort() (сортировка вставками + двоичный поиск + std::sort()) в общем случае быстрее. Исключение последний тест, где массив это 4 сортированных подмассива. Но это редкий случай и его можно заменить на слияние во время объединения подмассивов. Еще Белый шум получше сортирует, но по времени в 7 раз медленнее, есть что оптимизировать (std::queue на массив заменить), но сомневаюсь что оптимизаторством такой разрыв времени можно компенсировать. Да и памяти дополнительной надо прилично.

insert_sort() победил
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
void insert_sort(std::vector<item_t>& a) {
	// Сортировка почти отсортированного массива
	int size = a.size();
	if (size <= 1) return;
	int cnt = size * 2; // Количество дополнительных операций, после которого считаем несортированным
	for (int i = 1; i < size; i++) {
		if (a[i - 1] > a[i]) { // a[i] надо сдвинуть назад
			if (cnt < 0) { // несортированно
				std::sort(a.begin(), a.end()); // полноценная сортировка
				return;
			}
			//assert(a[i].data != 30);
			// Поиск места вставки
			item_t x = a[i];
			int r = 0, l = i - 1;
			while (r < l) {
				cnt--;
				int c = (r + l) / 2;
				int cmp = item_t::compare(x, a[c]);
				if (cmp <= 0) { // x <= a[c]
					l = c;
					if (cmp == 0) break;
				}
				else { // x > a[c]
					r = c + 1;
				}
			}
			// Сдвиг влево [l,i-1]
			cnt -= i - l;
			for (int j = i; j > l; j--) {
				a[j] = a[j - 1];
			}
			a[l] = x;
		}
	}
}

...
Рейтинг: 0 / 0
Тяпничная пирамида
    #39397691
kealon(Ruslan)
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Dima T,

"Поиск места вставки" можно чуток оптимизировать не сразу задавая int r = 0
а
расширять диапазон в нужную сторону от прошлого места вставки с помощью бинарного расширения в нужную сторону
и к этому диапазону потом применить бинарный поиск

худшую асимптотику поиска это не увеличит, а вот на частично-отсортированных массивах ускорит основательно
...
Рейтинг: 0 / 0
Тяпничная пирамида
    #39397700
Фотография SashaMercury
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
boobyDima T,
код внимательно разглядывать не могу, но,
кажется, ты не обратил внимание на то, что свечка при слиянии подмассивов
заведомо рассчитывает на то, что имеет дело с уже отсортированными диапазонами.
Поэтому не боится использовать двоичный поиск на фазе слияния.

Пусть сливаются отрезки с длинами а)[4](левый) и б)[12] (правый)


/*текущая позиция в а */ и = старт_а
голова_цикла:
номер_позиции_а(и)_в_б = двоичным поиском ищем позицию(б)
от текущего начала не перелитой части б до найденной и вываливаем все б(ж) в область слияния
вываливаем в область слияния элементы а, пока они не больше а(и)
устанавливаемся в новую позицию и в а
Если диапазоны не кончились, то ГОУ ТУ голова_цикла

Тут надо обратить внимание на то, что, с одной стороны,
в принципе всегда хотелось бы иметь ведущий диапазон меньшего размера,
а тот, в котором будем искать двоичным поиском - большего.
Но, если их переставлять не глядя, то нарушится стабильность, присущая исходному merge sort.
Поэтому фига из трех пальцев - либо плюнуть на стабильность, либо плюнуть на разборки - кто из них длиннее,
либо озаботиться устабилизацией обмена диапазонов.

А что будет с вашим алгоритмом, если массивы будут стыковаться как две расчески?

Асимптотика слияния О(max(a_s,b_s)), не понятно о чем дискуссия. Оптимальный способ порядка сливания множества массивов - NP полная задача. Свечка возможно даст хорошее решение, но не оптимальное. Да и то, судя по всему необходимо будет проводите действия по переупорядочиванию элементов свечи, что также накладно.
...
Рейтинг: 0 / 0
Тяпничная пирамида
    #39397709
booby
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
SashaMercuryА что будет с вашим алгоритмом, если массивы будут стыковаться как две расчески?


(Кнута это алгоритм, Дональда Эрвина,
который русский выучил только за то, что им разговаривал Андрей Петрович Ершов.
Чтобы книжки его читать в оригинале.
)

Вопрос правильный.
Остановится при перехлесте.
Так как слежение за перехлестом встроено в (реальный) алгоритм.

Без опасности перехлеста работает 2-way.
Который не смотрит на то, есть ли естественным образом сортированные куски, а вменяет сливаемым диапазонам быть сортированными путем слияния диапазонов, начиная 1.

А так как первые пять шагов слияния в 2way, стартующего с единичных крайних элементов занимаются слиянием слишком коротких массивов, сам Кнут настаивает - не надо так делать.
Надо натравить сортировку вставками для разбиения массива на сортированные диапазоны
размером в 16 в качестве первого шага, а потом можно включать 2way
...
Рейтинг: 0 / 0
Тяпничная пирамида
    #39397725
booby
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Dima TМожет я неправильно понял описание твоего алгоритм

Сейчас покопался еще раз в началах и концах.
Нашел следующее - в варианте, описанном у Кнута (5.2.4), действительно, слева ищутся
возрастающие, справа убывающие диапазоны.
При этом само слияние совмещено с просмотром.
Такая версия подачи затрудняет общее понимание идеи.

Стартуя с приведенной мной ссылки можно добраться до книжки "Элементарные алгоритмы",
в которой изложена упрощенная версия, не ждущая убывающего диапазона с правой стороны.

Имхо этот вариант проще для понимания и после него легче будет смотреть в книжку Кнута.

Пусть дан для сортировки массив
M[8, 12, 14 , 0, 1, 4, 11, 2, 3, 5, 9, 13, 10, 6, 15, 7]

И известно, что элементов в нем 16

N=16;
merge_step = 0

0) Выделим память размером в полученный массив (N)
M[8, 12, 14 , 0, 1, 4, 11, 2, 3, 5, 9, 13, 10, 6, 15, 7]
-- доп. память N=16
P[x, xx, xx , x, x, x, xx, x, x, x, x, xx, xx, xx, xx, x]

<<ДЕЛАЙ ВСЕГДА>>
ileft_start = 1; //начало левого диапазона
ileft_stop = 1; //конец левого диапазона
iright_start = N; // начало правого диапазона
iright_stop = N; // конец правого диапазона

merge_leftpos_start = 1; // позиция, начиная с которой будем дописывать в массив промежуточного результата
// мы считаем с 1, хоть форум и по C++

merge_rightpos_start = N; // 16 - позиция, в которую будем дописывать справа

//шаг слияния, первый рабочий шаг будет 1 (нечетный шаг)
(на нечетных шагах будем дописывать в левую часть выделенной памяти, на четной - в правую
)
merge_step = 1;
<<ЦИКЛ ПОКА ПРОБЕГА ПОКА НЕ ПРОСМОТРИМ M ЦЕЛИКОМ (ileft_stop < iright_start)>>

//ищем позицию в которой завершается первый неубывающий диапазон слева
ileft_stop = поиск_слева(ileft_start ); // здесь получим 3 на первом шаге для конкретного массива

ЕСЛИ ileft_stop >= N то алгоритм завершен, т.к. массив полностью сортирован.

// вот что мы нашли
M[ 8, 12, 14 | , 0, 1, 4, 11, 2, 3, 5, 9, 13, 10, 6, 15, 7]
//
//ищем позицию упорядоченного убывающего диапазона справа
iright_start = поиск_справа(iright_stop)// получим 15 на первом просмотре

ЕСЛИ ileft_stop >=iright_start то случился перехлест, любим левый край: iright_start = ileft_stop + 1
//
//вот что мы нашли
M[ 8, 12, 14 |, 0, 1, 4, 11, 2, 3, 5, 9, 13, 10, 6,| 15, 7 ]
// здесь мы уже знаем какой размер займет результат
// - 5 элементов = (ileft_stop - ileft_start +1) + (iright_stop - iright_start+1)
// но, т.к. у нас две позиции вставки - левая и правая, то пусть их формирует процедура
//слияния, которой мы будет рассказывать, в какую сторону - левую или правую ей надо доливать, а она нам за это вернет новую позицию после долива

Если Нечетно(merge_step)
Сливаем_в_M(ileft_start, ileft_stop, iright_start, iright_stop,merge_leftpos_start,Слева_Если_Нечетно(merge_step));
Иначе
Сливаем_в_M(ileft_start, ileft_stop, iright_start, iright_stop,merge_rightpos_start, Слева_Если_Нечетно(merge_step));

// результат
M[ 8, 12, 14 | , 0, 1, 4, 11, 2, 3, 5, 9, 13, 10, 6, 15, 7]
P[7, 8, 12, 14, 15, x, xx, x, x, x, x, xx, xx, xx, xx, x]

merge_step = NOT merge_step
ileft_start = ileft_stop + 1; // 4 после первого прохода
iright_stop = iright_start - 1; //14 после первого прохода
<<КОНЕЦ ЦИКЛА ПОКА>>
// завершен один полный пробег по M
// меняем местами M и P - swap
ОБМЕНЯТЬ(M,P);
<<КОНЕЦ ДЕЛАЙ ВСЕГДА>>
---------- ------------------------------------
//т.к. у нас не программа а лог ее выполнения - пишем дальше.
//сейчас мы на втором проходе внутри цикла <<ЦИКЛ ПОКА ПРОБЕГА ПОКА НЕ ПРОСМОТРИМ M ЦЕЛИКОМ (ileft_stop < iright_start)>>
// продолжаем...
//ищем позицию в которой завершается первый неубывающий диапазон слева
// начало у нас сейчас в 4
ileft_stop = поиск_слева(ileft_start ); // здесь получим 7 на втором шаге для конкретного массива
// вот что мы нашли
M[8, 12, 14, 0, 1, 4, 11 |, 2, 3, 5, 9, 13, 10, 6, 15, 7]

//ищем позицию упорядоченного убывающего диапазона справа
//стоп у нас в 14
iright_start = поиск_справа(iright_stop)// получим 12 на втором просмотре
// вот что мы нашли
M[8, 12, 14, 0, 1, 4, 11 |, 2, 3, 5, 9,| 13, 10, 6 |, 15, 7]
// шаг четиный, потому сливаем вправо
//!! В правый угол пишем в обратной последовательности
Если нечетно
Сливаем_в_M(ileft_start, ileft_stop, iright_start, iright_stop,merge_leftpos_start, Слева_Если_Нечетно(merge_step));
Иначе
Сливаем_в_M(ileft_start, ileft_stop, iright_start, iright_stop,merge_rightpos_start, Слева_Если_Нечетно(merge_step));
// резултьтат
M[8, 12, 14, 0, 1, 4, 11 |, 2, 3, 5, 9,| 13, 10, 6 |, 15, 7]
P[7, 8, 12, 14, 15, x, xx, x, x, 13, 11, 10, 6, 4, 1, 0]

и так далее...
-------------------------------------
натуральной такую свечку Кнут называет из-за того, что она двигается в поисках однородной последовательности столько, сколько может.

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

Кнут оценивает ее как в среднем уступающую быстрой сортировке, но выигрывающей на почти сортированном массиве.
А на таком специальном почти сортированном, который за одно слияние становится сортированным, ее трудно обогнать.
В записи Кнута просмотры осуществляются с обоих концов разом и совмещены с копированием в результат (слиянием).
Главный недостаток - в этой версии алгоритму всегда неизвестно, где расположены сейчас границы диапазонов.
Хотя по мере заполнения P все размеры получающихся диапазонов известны, и после первого swap их можно было бы поддерживать.
Но, такую версию, вроде как, выписывать не принято.

Считается, что если входной массив сортированный, но не настолько, чтобы обойтись одним-двумя проходами
главного цикла, то дешевле и умнее сразу вооружиться 2way версией
(подкрученной до стартовой insertion sort), которая путем применения вмененной арифметики
всегда знает границы своих сортированных частей для четного случая.

А нечетный сводится к четному путем определения места для нечетного элемента
двоичным поиском на последнем шаге сортировки.
...
Рейтинг: 0 / 0
Тяпничная пирамида
    #39397726
booby
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
не ждущая убывающего диапазона с правой стороны.
это дефект - считать не написанным
...
Рейтинг: 0 / 0
25 сообщений из 126, страница 4 из 6
Форумы / C++ [игнор отключен] [закрыт для гостей] / Тяпничная пирамида
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]