Yhdistä lajittelu | |
---|---|
| |
Tekijä | Johannes von Neumann |
tarkoitus | Lajittelualgoritmi |
Tietorakenne | lista , joukko |
pahin aika | |
Paras aika | |
Keskimääräinen aika | |
Muistin hinta | listalle, taulukolle |
Mediatiedostot Wikimedia Commonsissa |
Yhdistämislajittelu on lajittelualgoritmi , joka järjestää luettelot (tai muut tietorakenteet , joiden elementteihin pääsee vain peräkkäin , esimerkiksi virrat ) tiettyyn järjestykseen. Tämä lajittelu on hyvä esimerkki hajota ja hallitse -periaatteen käytöstä . Ensin tehtävä on jaettu useisiin pienempiin osatehtäviin. Nämä tehtävät ratkaistaan sitten rekursiivisella kutsulla tai suoraan, jos niiden koko on tarpeeksi pieni. Lopuksi niiden ratkaisut yhdistetään ja saadaan ratkaisu alkuperäiseen ongelmaan.
Lajitteluongelman ratkaisemiseksi nämä kolme vaihetta näyttävät tältä:
1.1. — 2.1. Tehtävän rekursiivinen osiointi pienempiin osioihin tapahtuu, kunnes taulukon koko saavuttaa yhden (mikä tahansa taulukko, jonka pituus on 1, voidaan katsoa järjestetyksi).
3.1. Kahden järjestetyn taulukon yhdistäminen yhdeksi.
Perusidea kahden lajitellun taulukon yhdistämisestä voidaan selittää seuraavalla esimerkillä. Oletetaan, että meillä on kaksi aliryhmää jo lajiteltuna nousevaan järjestykseen. Sitten:
3.2. Kahden alitaulukon yhdistäminen kolmanteen tuloksena olevaan taulukkoon.
Jokaisessa vaiheessa otamme pienemmän alitaulukoiden kahdesta ensimmäisestä elementistä ja kirjoitamme sen tuloksena olevaan taulukkoon. Tuloksena olevan taulukon ja alitaulukon, josta elementti on otettu, alkioiden lukumäärän laskureita kasvatetaan yhdellä.
3.3. Lopun "liittäminen".
Kun yksi alitaulukoista on ohi, lisäämme kaikki toisen alitaulukon jäljellä olevat elementit tuloksena olevaan taulukkoon.
Lajitteluesimerkki C:ssä
/** * Lajittelee taulukon käyttämällä rekursiivista yhdistämistä lajittelu * ylös - osoitin lajiteltavaan taulukkoon * alas - osoitin taulukkoon, jonka koko on vähintään yhtä suuri kuin 'ylös', käytetään puskurina * taulukon vasen - vasen reuna, välitä 0 lajittele taulukko alusta * oikea - taulukon oikea reuna, välitä taulukon pituus - 1 lajittele taulukko viimeiseen elementtiin * palauttaa: osoitin lajiteltuun taulukkoon. Johtuen tämän toteutuksen toiminnasta * taulukon lajiteltu versio voi päätyä joko ylös tai alas **/ int * merge_sort ( int * ylös , int * alas , unsigned int vasen , unsigned int oikea ) { jos ( vasen == oikea ) { alas [ vasen ] = ylös [ vasen ]; palaa alas ; } unsigned int middle = vasen + ( oikea - vasen ) / 2 ; // jaa ja lajittele int * l_buff = merge_sort ( ylös , alas , vasen , keskellä ); int * r_buff = merge_sort ( ylös , alas , keski + 1 , oikea ); // yhdistää kaksi lajiteltua puolikasta int * target = l_buff == ylös ? alas : ylös ; unsigned int l_cur = vasen , r_cur = keski + 1 ; for ( unsigned int i = vasen ; i <= oikea ; i ++ ) { if ( l_cur <= keski && r_cur <= oikea ) { if ( l_buff [ l_cur ] < r_buff [ r_cur ]) { kohde [ i ] = l_buff [ l_cur ]; l_cur ++ ; } muu { kohde [ i ] = r_buff [ r_cur ]; r_cur ++ ; } } else if ( l_cur <= middle ) { kohde [ i ] = l_buff [ l_cur ]; l_cur ++ ; } muu { kohde [ i ] = r_buff [ r_cur ]; r_cur ++ ; } } paluutavoite ; _ }Toteutus C++11 :ssä:
#include <algoritmi> #include <cstddef> #include <iteraattori> #sisällytä <muisti> malli < typenameT > _ void merge_sort ( T array [], std :: size_t size ) noexcept { jos ( koko > 1 ) { std :: koko_t const vasen_koko = koko / 2 ; std :: koko_t const oikea_koko = koko - vasen_koko ; merge_sort ( & array [ 0 ], left_size ); merge_sort ( & array [ left_size ], right_size ); std :: koko_t lidx = 0 , ridx = vasen_koko , idx = 0 ; std :: ainutlaatuinen_ptr < T [] > tmp_array ( uusi T [ koko ]); while ( lidx < vasen_koko || ridx < koko ) { if ( array [ lidx ] < array [ ridx ]) { tmp_array [ idx ++ ] = std :: siirrä ( array [ lidx ]); lidx ++ ; } muu { tmp_array [ idx ++ ] = std :: siirrä ( array [ ridx ]); ridx ++ ; } jos ( lidx == vasen_size ) { std :: kopioi ( std :: make_move_iterator ( & array [ ridx ]), std :: make_move_iterator ( & array [ koko ]), & tmp_array [ idx ]); tauko ; } jos ( ridx == koko ) { std :: kopioi ( std :: make_move_iterator ( & array [ lidx ]), std :: make_move_iterator ( & array [ left_size ]), & tmp_array [ idx ]); tauko ; } } std :: kopioi ( std :: make_move_iterator ( tmp_array ), std :: make_move_iterator ( & tmp_array [ koko ]), joukko ); } }Toteutus C++14 :ssä OpenMP -rinnakkaisuudella
#include <algoritmi> #include <iteraattori> #include <omp.h> #sisällytä <muisti> malli < typenameIterator > _ void mergesort ( Iteraattori alkaen , Iteraattori kohteeseen ) { #pragma omp rinnakkain { #pragma omp single nowait static_assert ( ! std :: on_sama < tyypinnimi std :: iteraattorin_ominaisuudet < Iteraattori >:: arvotyyppi , void >:: arvo ); auto n = std :: etäisyys ( mistä , kohteeseen ); jos ( 1 < n ) { #pragma omp tehtävä firstprivate (from, to, n) { Iteraattori l_from = from ; Iteraattori l_to = l_from ; std :: eteenpäin ( l_to , n / 2 ); mergesort ( l_from , l_to ); } #pragma omp tehtävä firstprivate (from, to, n) { Iteraattori r_from = from ; std :: advance ( r_from , n / 2 ); Iteraattori r_to = r_from ; std :: eteenpäin ( r_to , n - ( n / 2 )); mergesort ( r_from , r_to ); } #pragma omp taskwait auto tmp_array = std :: make_unique < typename Iterator :: value_type [] > ( n ); Iteraattori l_iter = alkaen ; Iteraattori l_end = l_iter ; std :: advance ( l_end , n / 2 ); Iteraattori r_iter = l_end ; Iteraattori & r_end = to ; auto tmp_iter = tmp_array . saada (); while ( l_iter != l_end || r_iter != r_end ) { if ( * l_iter < * r_iter ) { * tmp_iter = std :: siirrä ( * l_iter ); ++ l_iter ; ++ tmp_iter ; } muu { * tmp_iter = std :: siirrä ( * r_iter ); ++ r_iter ; ++ tmp_iter ; } if ( l_iter == l_end ) { std :: kopioi ( std :: make_move_iterator ( r_iter ), std :: make_move_iterator ( r_end ), tmp_iter ); tauko ; } if ( r_iter == r_end ) { std :: kopioi ( std :: make_move_iterator ( l_iter ), std :: make_move_iterator ( l_end ), tmp_iter ); tauko ; } } std :: kopioi ( std :: make_move_iterator ( tmp_array.get ( ) ), std :: make_move_iterator ( & tmp_array [ n ]), alkaen ); } } }Iteratiivinen toteutus C++ :ssa :
malli < typenameT > _ void MergeSort ( T a [], koko_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 ) { //Yhdistetään lajittelemalla lohkopari alkaen BlockIterator-elementistä //vasen BlockSizeIterator-kokoinen, oikea BlockSizeIterator-kokoinen tai pienempi LeftBlockIterator = 0 ; RightBlockIterator = 0 ; LeftBorder = BlockIterator ; MidBorder = BlockIterator + BlockSizeIterator ; RightBorder = BlockIterator + 2 * BlockSizeIterator ; RightBorder = ( RightBorder < l ) ? Oikea reuna : l ; int * SortedBlock = uusi int [ RightBorder - LeftBorder ]; //Kun molemmissa taulukoissa on elementtejä, valitse pienempi ja laita se lajiteltuun lohkoon samalla ( LeftBorder + LeftBlockIterator < MidBorder && MidBorder + RightBlockIterator < RightBorder ) { if ( a [ LeftBorder + LeftBlockIterator ] < a [ MidBorder + RightBlockIterator ]) { SortedBlock [ LeftBlockIterator + RightBlockIterator ] = a [ LeftBorder + LeftBlockIterator ]; LeftBlockIterator += 1 ; } muu { SortedBlock [ LeftBlockIterator + RightBlockIterator ] = a [ MidBorder + RightBlockIterator ]; RightBlockIterator += 1 ; } } //Sen jälkeen tuomme jäljellä olevat elementit vasemmasta tai oikeasta lohkosta samalla kun ( 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 ] = Lajiteltu lohko [ MergeIterator ]; } poista SortedBlock ; } } }
Toteutus Pythonissa :
Yhdistämisalgoritmin pseudokoodi ilman jäännöksen "liittämistä" C++- kaltaisella kielellä:
Algoritmin keksi John von Neumann vuonna 1945 [1] .
Yllä oleva algoritmi C++-kaltaisessa kielessä käyttää aliryhmien kahden verratun elementin tasa-arvon tarkistusta erillisellä käsittelylohkolla tasa-arvon tapauksessa. Erillinen tasa-arvotarkistus kaksinkertaistaa vertailujen määrän, mikä monimutkaistaa ohjelmakoodia. Erillisen tasa-arvotarkistuksen ja erillisen tasa-arvon käsittelylohkon sijasta voidaan käyttää kahta tarkistusta if(L <= R) ja if(L >= R) , mikä melkein puolittaa ohjelman koodin.
Parannetun yhdistämisalgoritmin pseudokoodi ilman "liittämistä" C++- kaltaiseen kieleen:
L = *In1; R = *In2; if( L <= R ) { *Out++ = L; In1++; } if( L >= R ) { *Out++ = R; In2++; }Sekkien määrä voidaan puolittaa poistamalla if(L >= R) -shekki . Tässä tapauksessa, jos L ja R ovat yhtä suuret, L kirjoitetaan Out ensimmäisessä iteraatiossa ja R - toisessa. Tämä vaihtoehto toimii tehokkaasti, jos alkuperäisessä taulukossa ei ole päällekkäisiä elementtejä, jotka hallitsevat muita elementtejä.
Pseudokoodi lyhennetylle yhdistämisalgoritmille ilman "liittämistä" C++- kaltaisella kielellä:
L = *In1; R = *In2; if( L <= R ) { *Out++ = L; In1++; } muu { *Out++ = R; In2++; }Kaksi siirtooperaatiota muuttujiin L ja R yksinkertaistavat joitain ohjelmamerkintöjä, joista voi olla hyötyä opetustarkoituksiin, mutta todellisissa ohjelmissa ne voidaan poistaa, mikä vähentää ohjelmakoodia. Voit myös käyttää kolmiosaista operaattoria , joka vähentää ohjelmakoodia entisestään.
Pseudokoodi vieläkin edistyneempään yhdistämisalgoritmiin "liittämättä" loput C++ :n kaltaisella kielellä:
Algoritmin ajoaika on O(n * log n) huonojen tapausten huonontumisen puuttuessa, mikä on arkaluonteinen pikalajittelupiste (myös algoritmi, jonka suuruus on O(n * log n), mutta vain keskimääräiselle tapaukselle ). Muistin kulutus on korkeampi kuin nopealla lajittelulla, paljon edullisemmalla muistinvarauskuviolla - yksi muistialue on mahdollista varata alusta alkaen ja olla varaamatta myöhemmin.
Suosittu toteutus vaatii kertaluonteisen varatun väliaikaisen muistipuskurin, joka on sama kuin lajiteltava taulukko, eikä siinä ole rekursioita. Käyttöönoton vaiheet:
Tämä toteutus tukee myös lajiteltavan taulukon ja väliaikaisen puskurin sijoittamista levytiedostoihin, mikä tekee siitä sopivan suurten tietomäärien lajitteluun. ORDER BY:n toteutus MySQL :ssä sopivan indeksin puuttuessa toimii täsmälleen näin (lähde: filesort.cc MySQL-lähdekoodissa).
Esimerkki yksinkertaisen kaksisuuntaisen yhdistämisalgoritmin toteutuksesta pseudokoodissa:
funktio mergesort(m) var lista vasen, oikea, tulos jos pituus(m) ≤ 1 palauttaa m else keski = pituus (m) / 2 jokaiselle x :lle m : ssä keskikohtaan asti lisää x vasemmalle jokaiselle x :lle metreissä keskikohdan jälkeen lisää x oikealle vasen = yhdistä (vasemmalla) oikea = yhdistä (oikea) tulos = yhdistä (vasen, oikea) palauta tulos loppu josMerge()-funktiosta on useita muunnelmia, yksinkertaisin voisi näyttää tältä:
funktio yhdistä(vasen,oikea) var listatulos , kun pituus(vasen) > 0 ja pituus (oikea) > 0 , jos ensimmäinen(vasen) ≤ ensimmäinen(oikea) liitä ensimmäinen (vasen) tulokseen vasen = lepo (vasen) muu liitä ensimmäinen (oikea) tulokseen oikea = lepo (oikea) end if while pituus (vasemmalla) > 0 liitä ensimmäinen (vasen) tulokseen vasen = lepo (vasen) kun taas pituus (oikealla) > 0 liitä ensimmäinen (oikea) tulokseen oikea = lepo (oikea) palauttaa tuloksenEdut:
Virheet:
Lajittelualgoritmit | |
---|---|
Teoria | Monimutkaisuus O merkintä Tilaussuhde Lajittele tyypit kestävää Sisäinen Ulkoinen |
Vaihto | |
Valinta | |
insertit | |
fuusio | |
Ei vertailuja | |
hybridi | |
muu | |
epäkäytännöllistä |