Sloučit třídění | |
---|---|
Příklad řazení sloučení. Nejprve rozdělíme seznam na části (každý 1 prvek), poté každý prvek porovnáme s jeho sousedem, seřadíme a sloučíme. Výsledkem je, že všechny prvky jsou seřazeny a kombinovány dohromady. | |
Autor | John von Neumann |
účel | Algoritmus řazení |
Datová struktura | seznam , pole |
nejhorší čas | |
Nejlepší čas | |
Průměrný čas | |
Náklady na paměť | pro seznam, pro pole |
Mediální soubory na Wikimedia Commons |
Merge sort je třídicí algoritmus , který uspořádává seznamy (nebo jiné datové struktury , k jejichž prvkům lze přistupovat pouze sekvenčně , například streamy ) v určitém pořadí. Toto třídění je dobrým příkladem použití principu rozděl a panuj . Nejprve je úkol rozdělen do několika menších dílčích úkolů. Tyto úlohy se pak řeší rekurzivním voláním nebo přímo, pokud je jejich velikost dostatečně malá. Nakonec se jejich řešení spojí a získá se řešení původního problému.
Chcete-li vyřešit problém řazení, tyto tři kroky vypadají takto:
1.1. — 2.1. K rekurzivnímu dělení úlohy na menší dochází, dokud velikost pole nedosáhne jedničky (libovolné pole délky 1 lze považovat za uspořádané).
3.1. Kombinace dvou uspořádaných polí do jednoho.
Základní myšlenku sloučení dvou seřazených polí lze vysvětlit na následujícím příkladu. Předpokládejme, že již máme dvě podpole seřazené vzestupně. Poté:
3.2. Sloučení dvou podpolí do třetího výsledného pole.
V každém kroku vezmeme menší z prvních dvou prvků podpolí a zapíšeme jej do výsledného pole. Čítače počtu prvků výsledného pole a podpole, ze kterého byl prvek odebrán, se zvýší o 1.
3.3. "Příloha" zbytku.
Když jedno z podpolí skončí, přidáme do výsledného pole všechny zbývající prvky druhého podpole.
Příklad řazení v C
/** * Seřadí pole pomocí rekurzivního sloučení řazení * nahoru - ukazatel na pole, které se má seřadit * dolů - ukazatel na pole s alespoň stejnou velikostí jako 'nahoru', použito jako vyrovnávací paměť * levý levý okraj pole, předat 0 seřadit pole od začátku * vpravo - pravý okraj pole, předat délku pole - 1 pro seřazení pole do posledního prvku * vrátí: ukazatel na seřazené pole. Vzhledem k tomu, jak tato implementace funguje * setříděná verze pole může skončit buď v 'nahoru' nebo 'dolů' **/ int * merge_sort ( int * nahoru , int * dolů , unsigned int vlevo , unsigned int vpravo ) { if ( vlevo == vpravo ) { dolů [ vlevo ] = nahoru [ vlevo ]; vrátit se dolů ; } unsigned int middle = left + ( right - left ) / 2 ; // rozdělení a řazení int * l_buff = merge_sort ( nahoru , dolů , vlevo , uprostřed ); int * r_buff = merge_sort ( nahoru , dolů , uprostřed + 1 , vpravo ); // sloučení dvou seřazených polovin int * target = l_buff == up ? dolů : nahoru ; unsigned int l_cur = vlevo , r_cur = prostřední + 1 ; for ( unsigned int i = left ; i <= right ; i ++ ) { if ( l_cur <= střední && r_cur <= vpravo ) { if ( l_buff [ l_cur ] < r_buff [ r_cur ]) { cíl [ i ] = l_buff [ l_cur ]; l_cur ++ ; } jiný { cíl [ i ] = r_buff [ r_cur ]; r_cur ++ ; } } else if ( l_cur <= middle ) { cíl [ i ] = l_buff [ l_cur ]; l_cur ++ ; } jiný { cíl [ i ] = r_buff [ r_cur ]; r_cur ++ ; } } návratový cíl ; }Implementace v C++11 :
#include <algoritmus> #include <cstddef> #include <iterátor> #include <paměť> šablona < typenameT > _ void merge_sort ( T pole [], std :: size_t size ) noexcept { if ( velikost > 1 ) { std :: size_t const left_size = velikost / 2 ; std :: size_t const right_size = size - left_size ; merge_sort ( & pole [ 0 ], left_size ); merge_sort ( & pole [ left_size ], right_size ); std :: size_t lidx = 0 , ridx = left_size , idx = 0 ; std :: unique_ptr < T [] > tmp_array ( new T [ size ]); while ( lidx < left_size || ridx < size ) { if ( pole [ lidx ] < pole [ ridx ]) { tmp_array [ idx ++ ] = std :: move ( array [ lidx ]); lidx ++ ; } jiný { tmp_array [ idx ++ ] = std :: move ( pole [ ridx ]); ridx ++ ; } if ( lidx == left_size ) { std :: copy ( std :: make_move_iterator ( & pole [ ridx ]), std :: make_move_iterator ( & pole [ velikost ]), & tmp_array [ idx ]); zlomit ; } if ( ridx == velikost ) { std :: copy ( std :: make_move_iterator ( & pole [ lidx ]), std :: make_move_iterator ( & pole [ left_size ]), & tmp_array [ idx ]); zlomit ; } } std :: copy ( std :: make_move_iterator ( tmp_array ), std :: make_move_iterator ( & tmp_array [ velikost ]), pole ); } }Implementace v C++14 s paralelizací OpenMP
#include <algoritmus> #include <iterátor> #include <omp.h> #include <paměť> šablona < typenameIterator > _ void mergesort ( iterátor z , iterátor do ) { #pragma omp paralela { #pragma omp single nowait static_assert ( ! std :: is_same < typename std :: iterator_traits < Iterator >:: value_type , void >:: value ); auto n = std :: vzdálenost ( od , do ); if ( 1 < n ) { #pragma omp task firstprivate (od, do, n) { Iterátor l_from = from ; Iterátor l_to = l_from ; std :: záloha ( l_to , n / 2 ); mergesort ( l_od , l_do ); } #pragma omp task firstprivate (od, do, n) { Iterátor r_from = from ; std :: záloha ( r_od , n / 2 ); Iterátor r_to = r_from ; std :: záloha ( r_to , n - ( n / 2 )); mergesort ( r_from , r_to ); } #pragma omp taskwait auto tmp_array = std :: make_unique < název_typu Iterátor :: typ_hodnoty [] > ( n ); Iterátor l_iter = od ; Iterátor l_end = l_iter ; std :: záloha ( l_konec , n / 2 ); Iterátor r_iter = l_konec ; Iterátor & r_end = to ; auto tmp_iter = tmp_array . získat (); while ( l_iter != l_end || r_iter != r_end ) { if ( * l_iter < * r_iter ) { * tmp_iter = std :: move ( * l_iter ); ++ l_iter ; ++ tmp_iter ; } jiný { * tmp_iter = std :: move ( * r_iter ); ++ r_iter ; ++ tmp_iter ; } if ( l_iter == l_end ) { std :: kopie ( std :: make_move_iterator ( r_iter ), std :: make_move_iterator ( r_end ), tmp_iter ); zlomit ; } if ( r_iter == r_end ) { std :: kopie ( std :: make_move_iterator ( l_iter ), std :: make_move_iterator ( l_end ), tmp_iter ); zlomit ; } } std :: kopie ( std :: make_move_iterator ( tmp_array.get ( ) ), std :: make_move_iterator ( & tmp_array [ n ]), z ); } } }Iterativní implementace v C++ :
šablona < typenameT > _ void MergeSort ( T a [], size_t l ) { size_t BlockSizeIterator ; size_t BlockIterator ; size_t LeftBlockIterator ; size_t RightBlockIterator ; size_t MergeIterator ; size_t LeftBorder ; size_t MidBorder ; size_t RightBorder ; for ( BlockSizeIterator = 1 ; BlockSizeIterator < l ; BlockSizeIterator *= 2 ) { for ( BlockIterator = 0 ; BlockIterator < l - BlockSizeIterator ; BlockIterator += 2 * BlockSizeIterator ) { //Sloučíme se řazením dvojice bloků počínaje prvkem BlockIterator //levý s velikostí BlockSizeIterator, pravý s velikostí BlockSizeIterator nebo méně LeftBlockIterator = 0 ; RightBlockIterator = 0 ; LeftBorder = BlockIterator ; MidBorder = BlockIterator + BlockSizeIterator ; RightBorder = BlockIterator + 2 * BlockSizeIterator ; RightBorder = ( RightBorder < l ) ? RightBorder : l ; int * SortedBlock = new int [ RightBorder - LeftBorder ]; //Zatímco obě pole mají prvky, vyberte ten menší a vložte jej do seřazeného bloku , zatímco ( LeftBorder + LeftBlockIterator < MidBorder && MidBorder + RightBlockIterator < RightBorder ) { if ( a [ LeftBorder + LeftBlockIterator ] < a [ MidBorder + RightBlockIterator ]) { SortedBlock [ LeftBlockIterator + RightBlockIterator ] = a [ LeftBorder + LeftBlockIterator ]; LeftBlockIterator += 1 ; } jiný { SortedBlock [ LeftBlockIterator + RightBlockIterator ] = a [ MidBorder + RightBlockIterator ]; RightBlockIterator += 1 ; } } //Poté přivedeme zbývající prvky z levého nebo pravého bloku while ( LeftBorder + LeftBlockIterator < MidBorder ) { SortedBlock [ LeftBlockIterator + RightBlockIterator ] = a [ LeftBorder + LeftBlockIterator ]; LeftBlockIterator += 1 ; } while ( MidBorder + RightBlockIterator < RightBorder ) { SortedBlock [ LeftBlockIterator + RightBlockIterator ] = a [ MidBorder + RightBlockIterator ]; RightBlockIterator += 1 ; } for ( MergeIterator = 0 ; MergeIterator < LeftBlockIterator + RightBlockIterator ; MergeIterator ++ ) { a [ LeftBorder + MergeIterator ] = SortedBlock [ MergeIterator ]; } odstranit SortedBlock ; } } }
Implementace v Pythonu :
Pseudokód slučovacího algoritmu bez "připojení" zbytku v jazyce podobném C++ :
Algoritmus vynalezl John von Neumann v roce 1945 [1] .
Výše uvedený algoritmus v jazyce podobném C++ používá v případě rovnosti kontrolu rovnosti dvou porovnávaných prvků podpolí se samostatným blokem zpracování. Samostatná kontrola rovnosti zdvojnásobuje počet srovnání, což komplikuje programový kód. Namísto samostatné kontroly rovnosti a samostatného bloku zpracování rovnosti můžete použít dvě kontroly if(L <= R) a if(L >= R) , čímž se kód programu zmenší téměř na polovinu.
Pseudokód vylepšeného slučovacího algoritmu bez „připojení“ zbytku v jazyce podobném C++ :
L = *Inl; R = *In2; if( L <= R ) { *Out++ = L; In1++; } if( L >= R ) { *Out++ = R; In2++; }Počet kontrol lze snížit na polovinu odstraněním kontroly if(L >= R) . V tomto případě, pokud jsou L a R stejné, L bude zapsáno do Out v první iteraci a R - ve druhé. Tato možnost bude efektivně fungovat, pokud původní pole nemá duplicitní prvky převažující nad zbytkem prvků.
Pseudokód pro zkrácený slučovací algoritmus bez "připojení" zbytku v jazyce podobném C++ :
L = *Inl; R = *In2; if( L <= R ) { *Out++ = L; In1++; } jinak { *Out++ = R; In2++; }Dvě operace přenosu do proměnných L a R zjednodušují některé položky programu, což může být užitečné pro účely výuky, ale ve skutečných programech je lze odstranit, což sníží kód programu. Můžete také použít ternární operátor , který dále zmenší kód programu.
Pseudokód pro ještě pokročilejší slučovací algoritmus bez „připojování“ zbytku v jazyce podobném C++ :
Doba běhu algoritmu je O(n * log n) při absenci degradace na špatných případech, což je bolestné místo rychlého třídění (také algoritmus řádu O(n * log n), ale pouze pro průměrný případ ). Spotřeba paměti je vyšší než u rychlého řazení, s mnohem příznivějším vzorem přidělování paměti - je možné alokovat jednu oblast paměti od samého začátku a později již nepřidělovat.
Populární implementace vyžaduje jednorázově přidělenou dočasnou vyrovnávací paměť rovnající se tříděnému poli a nemá žádné rekurze. Kroky implementace:
Tato implementace také podporuje umístění pole k třídění a dočasné vyrovnávací paměti do souborů na disku, takže je vhodná pro třídění velkého množství dat. Implementace ORDER BY v MySQL při absenci vhodného indexu funguje přesně takto (zdroj: filesort.cc ve zdrojovém kódu MySQL).
Příklad implementace jednoduchého dvoucestného slučovacího algoritmu v pseudokódu:
funkce mergesort(m) var list left, right, result if length(m) ≤ 1 return m else střed = délka (m) / 2 pro každé x v m až do středu přidejte x doleva pro každé x v m po středu přidejte x doprava vlevo = mergesort(left) vpravo = mergesort (vpravo) výsledek = sloučit (vlevo, vpravo) vrátit výsledek end ifExistuje několik variant funkce merge(), ta nejjednodušší může vypadat takto:
funkce merge(left,right) var list result while length(left) > 0 and length(right) > 0 if first(left) ≤ first(right) připojit první (vlevo) k výsledku vlevo = odpočinek (vlevo) jiný připojit první (vpravo) k výsledku vpravo = odpočinek (vpravo) end if while length(left) > 0 připojit první (vlevo) k výsledku vlevo = odpočinek (vlevo) zatímco délka (vpravo) > 0 připojit první (vpravo) k výsledku vpravo = odpočinek (vpravo) vrátit výsledekvýhody:
nedostatky:
Algoritmy řazení | |
---|---|
Teorie | Složitost O zápis Objednávkový vztah Seřadit typy udržitelného Vnitřní Externí |
Výměna | |
Výběr | |
vložky | |
fúze | |
Žádné srovnání | |
hybridní | |
jiný | |
nepraktický |