Jenis perpustakaan




Ambil susunan terbalik dan terapkan penyortiran dengan sisipan sederhana .



Lihat dengan derit apa elemen berikutnya dimasukkan di tempat yang tepat. Untuk itu, Anda perlu membebaskan tempat penyisipan, karena itu Anda harus menggeser semua elemen yang dimasukkan sebelumnya.

Dan betapa bagusnya jika ada ruang kosong antara elemen yang dimasukkan sebelumnya! Maka seseorang tidak perlu menyeret barisan elemen hanya demi memasukkan satu.

Pada tahun 2004, tiga ahli ilmu komputer - Michael Bender, Martin Farah-Colton dan Miguel Mostiro - memutuskan untuk memodifikasi penyortiran dengan sisipan sederhana dengan cara ini. Mereka menyarankan untuk membentuk bagian terurut dari array, meninggalkan celah di antara elemen yang dimasukkan.
Pustakawan membutuhkan buku-buku yang disusun secara alfabetis di rak yang panjang: mulai dari kiri huruf "A", buku-buku itu berada tepat di sebelah satu sama lain hingga sangat "aku". Jika perpustakaan menerima buku baru yang terkait dengan bagian "B", maka untuk meletakkannya di rak di tempat yang tepat, Anda harus memindahkan setiap buku, mulai dari tengah bagian "B" hingga "I" terakhir. Ini disortir berdasarkan sisipan sederhana. Namun, jika pustakawan menyediakan ruang kosong di setiap bagian, itu cukup baginya untuk memindahkan hanya beberapa buku untuk memberikan ruang bagi buku baru. Ini adalah prinsip dasar pemilahan perpustakaan.

Algoritma




  • 1. Buat array bantu kosong, beberapa kali lebih besar dari yang utama.
  • 2. Untuk elemen selanjutnya, kita mencari tempat penyisipan dalam array bantu.
    • 2.1. Jika Anda menemukan tempat untuk disisipkan, maka transfer item dan kembali ke langkah 2.
    • 2.2. Jika tidak ada tempat untuk penyisipan, seimbangkan kembali array bantu dan kembali ke poin 2.
  • 3. Setelah memproses semua elemen, transfer kembali ke array utama.

Sekilas, sepertinya penyortiran itu mudah dan sederhana. Untuk menghilangkan ilusi ini, kami mempertimbangkan poin-poin kunci dari algoritma secara lebih rinci.

Ukuran Array Bantu


Meskipun tidak ada pendapat yang ditetapkan, berapa kali array bantu harus lebih besar dari yang utama.

Jika Anda mengambil terlalu banyak, maka akan ada banyak ruang di antara elemen, namun, pencarian untuk situs penyisipan dan penyeimbangan kembali akan lebih lambat, karena jarak yang besar antara elemen. Penyeimbangan ulang akan terjadi lebih jarang, tetapi mereka harus menghabiskan lebih banyak sumber daya untuk mereka. Jika Anda mengambil terlalu sedikit, maka pencarian dan penyeimbangan kembali akan lebih murah, tetapi Anda harus memformat ulang array lebih sering. Secara umum, masih perlu diuji dengan nilai yang berbeda dan metode poking ilmiah menentukan pilihan terbaik.

Jika kami memutuskan berapa kali array bantu lebih besar dari yang utama, maka rumus untuk menentukan jumlah pasti elemen untuk tampilannya seperti ini:

NewSize = ε × (Ukuran + 1) - 1

NewSize - jumlah elemen dalam array bantu
ε - berapa kali array bantu lebih besar dari yang utama
Ukuran - jumlah elemen dalam array utama

Jika kita cukup mengalikan ukuran dengan faktor: NewSize = Ukuran × ε , maka untuk distribusi yang seragam kita tidak akan memiliki cukup sel dalam jumlah ε - 1 buah. Artinya, dimungkinkan untuk mengaturnya secara merata, tetapi sel yang terisi pertama atau yang terakhir akan terletak dekat dengan tepi susunan bantu. Dan kita perlu tempat-tempat kosong di sel yang diisi untuk dicadangkan dari semua sisi - termasuk sebelum elemen pertama dan setelah yang terakhir.



Tampaknya menjadi hal yang sepele, tetapi sebenarnya penting untuk menyeimbangkan kembali, untuk menjamin tempat bebas untuk penyisipan di mana saja, termasuk saat memproses elemen terakhir dari array utama.

Cari tempat penyisipan dalam array bantu


Tentu saja, di sini Anda memerlukan pencarian biner. Namun, implementasi klasik tidak akan bekerja untuk kita.

Pertama, array bantu sebagian besar terdiri dari void. Oleh karena itu, secara mendikotomikan struktur, kita akan sebagian besar menemukan sel yang tidak terisi. Dalam kasus ini, Anda perlu sedikit ke kiri atau kanan, ke sel non-kosong terdekat. Kemudian pada akhir segmen akan ada elemen signifikan yang memungkinkan Anda untuk menghitung rata-rata aritmatika dan melanjutkan pencarian biner secara mendalam.

Kedua, jangan lupa tentang tepinya. Jika Anda perlu memasukkan elemen minimum atau maksimum, maka pencarian biner di antara yang dimasukkan sebelumnya tidak akan menghasilkan apa-apa. Oleh karena itu, ada baiknya mempertimbangkan kasus batas - pertama periksa apakah perlu untuk menempatkan elemen di dekat batas kiri atau kanan array dan jika tidak, maka gunakan pencarian biner.

Ketiga, dengan mempertimbangkan spesifik aplikasi, ada baiknya membuat amandemen tambahan untuk meminimalkan jumlah penyeimbangan array. Jika elemen yang dimasukkan sama dengan nilai di salah satu ujung segmen, maka mungkin Anda tidak harus memasukkannya di tengah segmen. Lebih logis untuk menempatkan di sebelah elemen yang nilainya setara dengannya. Ini akan lebih efisien mengisi ruang kosong dari array bantu.

Keempat, kelima, dan seterusnya. Kualitas pencarian untuk lokasi penyisipan secara langsung mempengaruhi kecepatan penyortiran, karena pemilihan lokasi penyisipan yang gagal menyebabkan penyeimbangan kembali yang tidak perlu. Misalnya, mungkin ada baiknya membagi segmen tidak tepat di tengah, tetapi lebih dekat ke tepi kiri atau kanan segmen, tergantung pada tepi mana elemen penyisipan lebih dekat nilainya?

Algoritma pencarian biner itu sendiri penuh dengan jebakan, dan dengan mempertimbangkan nuansa tambahan yang disebutkan di atas, akhirnya menjadi tugas yang tidak sepele.

Array penyeimbangan ulang


Pencarian biner bukan hal tersulit untuk diterapkan dalam penyortiran ini. Masih ada penyeimbangan kembali.

Ketika tidak ada tempat untuk penyisipan (elemen serupa ditemukan, tetapi tidak ada sel bebas di antara mereka), Anda perlu mengguncang susunan bantu sehingga tempat penyisipan dibebaskan. Guncangan array ini menyeimbangkan kembali.

Selain itu, penyeimbangan kembali bersifat lokal atau lengkap .

Penyeimbangan ulang lokal


Kami menggeser elemen sebanyak yang diperlukan untuk membebaskan titik penyisipan. Penerapan penyeimbangan seperti ini sangat sederhana, Anda hanya perlu mengetahui sel kosong terdekat dari titik penyisipan dan menggunakannya untuk memindahkan beberapa elemen.

Mungkin ada nuansa. Misalnya, ke mana mencari tempat kosong terdekat? Untuk menghindari situasi ketika pergeseran tidak mungkin (yaitu, jika di beberapa sisi semua sel ditempati ke tepi array), Anda dapat fokus pada posisi titik penyisipan relatif ke tengah array. Jika Anda perlu menyisipkan di sisi kiri array, maka bergeser ke kanan, jika di kanan - ke kiri. Jika ε ≥ 2, maka pendekatan ini menghilangkan situasi di mana pergeseran tidak mungkin (karena dalam setengah dari array bantu ada lebih dari cukup ruang untuk semua elemen).

Dalam interpretasi penulis tentang algoritma, penyeimbangan lokal tersirat.

Menyeimbangkan lengkap


Alternatif yang menarik untuk lokal adalah penyeimbangan kembali total. Artinya, dalam array bantu, geser semua elemen yang tersedia sehingga ada (hampir) ruang yang sama di antara mereka.



Saya mencoba kedua opsi dan sejauh ini saya mengamati bahwa dengan penyeimbangan lokal algoritma bekerja 1,5-2 kali lebih cepat daripada yang penuh. Namun, penyeimbangan ulang lengkap masih bisa digunakan. Sebagai contoh, jika Anda harus melakukan penyeimbangan lokal terlalu sering, maka ini berarti bahwa dalam susunan di beberapa daerah banyak "gumpalan darah" telah menumpuk yang menghambat seluruh proses. Penyeimbangan ulang lengkap dilakukan sekali, memungkinkan Anda untuk menyingkirkan semua kemacetan lokal dalam satu gerakan.

Mari kita cari tahu cara menyeimbangkan kembali sepenuhnya.

Pertama, Anda perlu menghitung berapa banyak sel maksimum yang dapat kami alokasikan untuk setiap elemen dalam array bantu. Harus diingat bahwa sel-sel kosong harus ada sebelum sel pertama dan setelah sel terisi terakhir. Rumusnya adalah:



M - jumlah sel yang dapat dialokasikan untuk setiap elemen
NewSize - ukuran array bantu
Hitung - jumlah elemen non-kosong saat ini dalam array bantu

Fraksi ini harus dikurangi menjadi nilai integer (yaitu, dibulatkan ke bawah). Jelas dari rumus bahwa semakin banyak elemen yang telah ditransfer ke array bantu, semakin sedikit sel yang dapat kita pilih untuk lingkungan masing-masing elemen.

Mengetahui M , kita dengan mudah mendapatkan posisi yang tepat untuk setiap elemen yang tidak kosong dalam array bantu di mana ia harus ditempatkan setelah penyeimbangan ulang selesai.

NewPos = Nomor Ă— M

NewPos - posisi item baru setelah penyeimbangan ulang
Nomor - apa elemen non-kosong dalam array bantu (1 ≤ Jumlah ≤ Jumlah)
M - jumlah sel yang dialokasikan untuk setiap elemen

Posisi-posisi baru diketahui, dapatkah Anda memilah elemen-elemen yang tidak kosong dalam susunan bantu dan memindahkannya ke tempat lain? Oh tidak, jangan buru-buru. Tidak hanya diperlukan untuk mentransfer elemen, penting untuk menjaga ketertiban mereka. Dan sebagai hasil pencarian dan penyisipan biner, elemen dapat berubah menjadi jauh ke kiri dan ke kanan posisi di mana mereka seharusnya setelah menyeimbangkan kembali. Dan di tempat di mana Anda ingin pindah, mungkin ada elemen lain yang juga perlu dipasang di suatu tempat. Selain itu, Anda tidak dapat mentransfer elemen jika ada elemen lain di antara posisi lama dan barunya di array bantu - jika tidak, elemen tersebut akan bercampur, dan sangat penting bagi kami untuk tidak mencampuradukkan pesanan.

Oleh karena itu, untuk menyeimbangkan kembali itu tidak akan cukup untuk melalui siklus yang biasa dan hanya menggeser setiap elemen dari satu kantong ke yang lain. Masih perlu untuk menggunakan rekursi. Jika suatu elemen tidak dapat dipindahkan ke tempat baru (elemen lain muncul di antara posisi lama dan baru), maka pertama-tama Anda perlu mencari tahu (secara rekursif) tamu tak diundang ini. Dan kemudian semuanya akan diatur dengan benar.

Kasus degenerasi


Untuk sebagian besar penyortiran berdasarkan sisipan, susunan urutan terbalik adalah situasi terburuk. Dan menyortir seorang pustakawan, sayangnya, tidak terkecuali.



Elemen cenderung ke tepi kiri array bantu, akibatnya bintik-bintik kosong cepat habis. Anda harus menyeimbangkan kembali array sangat sering.

By the way, jika kita mengambil array yang hampir teratur (kasus terbaik untuk menyortir dengan menyisipkan sederhana), maka kita mendapatkan masalah yang sama. Elemen yang baru tiba tidak akan menyumbat sisi kiri, tetapi sisi kanan susunan bantu, yang juga akan menyebabkan penyeimbangan kembali yang terlalu sering.

Sortir perpustakaan menangani set data acak secara efisien. Pemesanan sebagian (baik langsung maupun mundur) merusak kinerja kecepatan.

Kompleksitas Algoritma


Pada set besar data acak, algoritma memberikan kompleksitas waktu O ( n log n ). Tidak buruk sama sekali!

Pada set data acak unik (atau sebagian besar unik) dengan pemilihan koefisien ε yang benar dan keberhasilan implementasi pencarian biner, jumlah penyeimbangan kembali dapat diminimalkan, atau bahkan dihindari. Dapat dikatakan bahwa algoritma ini memiliki kompleksitas waktu O ( n ) terbaik.

Persentase besar data yang berulang dalam nilai, serta kehadiran dalam susunan yang dipesan (dalam urutan langsung atau terbalik) menyebabkan seringnya penyeimbangan susunan bantu dan, sebagai akibatnya, degradasi kompleksitas waktu menjadi O (n 2 ) dalam kasus yang paling merugikan.

Yang minus dari algoritma, tentu saja, adalah bahwa array tambahan memerlukan O ( n ) memori tambahan.

Kemungkinan cara untuk meningkatkan


Meskipun algoritma itu sendiri adalah instruktif dan efisien pada data acak, dalam satu setengah dekade, beberapa menunjukkan minat di dalamnya.

Jika Anda mencari di "perpustakaan semacam" permintaan, Anda akan menemukan artikel sepintas di Wikipedia bahasa Inggris, PDF penulis (yang sedikit dipahami) dan posting ulang jarang informasi ini. Plus ada visualisasi yang baik di YouTube, di mana array utama dan tambahan awalnya digabungkan. Semua tautan ada di akhir artikel.

Mencari "pemilahan perpustakaan" bahkan lebih menyenangkan - dalam sampel Anda akan menemukan berbagai pemilahan yang termasuk dalam perpustakaan yang berbeda, namun, algoritma ini tidak akan terkait dengan pemilahan perpustakaan yang asli .

Dan ada sesuatu untuk diperbaiki:

  1. Seleksi empiris dari koefisien optimal ε .
  2. Modifikasi (dengan mempertimbangkan spesifikasi algoritma umum) dari pencarian biner untuk penentuan titik penyisipan yang paling efisien.
  3. Meminimalkan biaya penyeimbangan kembali.

Jika Anda memoles tempat-tempat ini, maka mungkin perpustakaan mengurutkan dalam kecepatan bahkan sama dengan mengurutkan cepat?

Kode sumber


Saya tidak berhasil mempersiapkan implementasi dengan Python, ada versi yang berfungsi di PHP.

Algoritma dasar
function LibrarySort($arr) { global $arr_new;//  $e = 3;//     $rebalance_local = true;// (true)   (false)  //   $newSize = $e * (count($arr) + 1) - 1; $arr_new = array_fill(0, $newSize, null); //       $arr_new[LibrarySort_Pos(1, 1, $newSize)] = $arr[0]; //    -    //     $start = 0; $finish = $newSize - 1; $i = 1; //      while($i < count($arr)) { //        $pos = LibrarySort_BinarySearch($arr[$i], $start, $finish, $newSize); if($pos === false) {//        //    LibrarySort_Rebalance_Total($i, $newSize); } else {//  ,   ,    if($arr_new[$pos] !== null) {//   if($rebalance_local) {//  (+ ) LibrarySort_Rebalance_Local($arr[$i++], $pos, $newSize); } else {//  LibrarySort_Rebalance_Total($i, $newSize); } } else {//   ,   $arr_new[$pos] = $arr[$i++]; } } } //      $pos = 0; for($i = 0; $i <= $newSize - 1; $i++) { if($arr_new[$i] !== null) $arr[$pos++] = $arr_new[$i]; } return $arr; } 

Posisi baru elemen dalam array tambahan setelah penyeimbangan lengkap
 // $number-    $count //     //$number -      ( )  //$count -       //$newSize -     //$number <= $count <= count($arr) <= $newSize) function LibrarySort_Pos($number, $count, $newSize) { return $number * floor(($newSize + 1) / ($count + 1)) - 1; } 

Pencarian biner tempat penyisipan dalam array tambahan
 //       //$search -     ,      //($start, $finish) -   ,     //$newSize -     function LibrarySort_BinarySearch($search, $start, $finish, $newSize) { global $arr_new;//  //      //      ,     //  ,       while($arr_new[$start] === null && $start < $newSize - 1) { ++$start; } //         , //         if($search == $arr_new[$start]) { return LibrarySort_PosNearby($start, $newSize); //  ,        } elseif($search < $arr_new[$start]) { //      //     if($start > 0) {// $start    $finish = $start; $start = 0; return floor(($start + $finish) / 2); } else {//$start == 0,      return $start;//    ,    } } //      ,     //  ,       while($arr_new[$finish] === null && $finish > 0) { --$finish; } //         , //         if($search == $arr_new[$finish]) { return LibrarySort_PosNearby($finish, $newSize); //  ,        } elseif($search > $arr_new[$finish]) { //      //     if($finish < $newSize - 1) {// $finish    $start = $finish; $finish = $newSize - 1; return ceil(($start + $finish) / 2); } else {//$finish == $newSize - 1,      return $finish;//    ,    } } //     , //,    -   //   ,       If($finish - $start > 1) {//   ,    3  $middle = ceil(($start + $finish) / 2); //   $middle_Pos = 0; // ""     $offset = 0; //         //,    /,      while($middle - $offset > $start && $middle_Pos == 0){ if($arr_new[$middle - $offset] !== null) { $middle_Pos = $middle - $offset; } elseif($middle + $offset < $finish && $arr_new[$middle + $offset] !== null) { $middle_Pos = $middle + $offset; } ++$offset; } //    , ,     , //              If($middle_Pos) { if($arr_new[$middle_Pos] == $search) { return LibrarySort_PosNearby($middle_Pos, $newSize); } else { if($arr_new[$middle_Pos] > $search) { $finish = $middle_Pos; } else {//$arr_new[$middle_Pos] < $search $start = $middle_Pos; } return LibrarySort_BinarySearch($search, $start, $finish, $newSize); } } else {//$middle_Pos == 0 -   (   )     return $middle;//   - ,     } } else {//  ,       return floor(($start + $finish) / 2); } return false;//  ,       } 

Jika selama pencarian elemen tersebut sama dengan salah satu ujung segmen
 //    ,        //$start - ,        //$newSize -     function LibrarySort_PosNearby($start, $newSize) { global $arr_new;//  //       for($left = $start - 1; $left >= 0; $left--) { if($arr_new[$left] === null) {//  return $left;//   } elseif($arr_new[$left] <> $arr_new[$start]) {//     break; //   ,      } } //     ,    for($right = $start + 1; $right <= $newSize - 1; $right++) { if($arr_new[$right] === null) {//  return $right; //   } elseif($arr_new[$right] <> $arr_new[$start]) {//     break; //   ,      } } return $start; //          .      ,     } 

Penyeimbangan ulang lokal dari array tambahan
 //    //$insert - ,    //$pos -            //$newSize -     function LibrarySort_Rebalance_Local($insert, $pos, $newSize) { global $arr_new;//  // $pos  $insert,       while($pos - 1 >= 0 && $arr_new[$pos - 1] !== null && $arr_new[$pos - 1] > $insert) {--$pos;} while($pos + 1 <= $newSize - 1 && $arr_new[$pos + 1] !== null && $arr_new[$pos + 1] < $insert) {++$pos;} $middle = (integer) $newSize / 2;//  if($pos <= $middle) {//      if($arr_new[$pos] !== null && $arr_new[$pos] < $insert) ++$pos; //  $right = $pos; while($arr_new[++$right] !== null) {} for($i = $right; $i > $pos; $i--) { $arr_new[$i] = $arr_new[$i - 1]; } } else {//      if($arr_new[$pos] !== null && $insert < $arr_new[$pos]) --$pos; //  $left = $pos; while($arr_new[--$left] !== null) {} for($i = $left; $i < $pos; $i++) { $arr_new[$i] = $arr_new[$i + 1]; } } $arr_new[$pos] = $insert; } 

Menyetel ulang penuh array tambahan
 //    //$count -        //$newSize -     function LibrarySort_Rebalance_Total($count, $newSize) { global $arr_new;//  global $library_Number;//     global $library_LeftPos;//        $library_Number = $count; //        $library_LeftPos = $newSize - 1;// ,     //         $i = $newSize - 1; while($i >= 0) { if($arr_new[$i] !== null) {//   $pos = LibrarySort_Pos($library_Number, $count, $newSize);//   newSize=$newSize"); if($i == $pos) {//      --$i;//      } elseif($i < $pos) {//    $arr_new[$pos] = $arr_new[$i]; $arr_new[$i] = null; --$i;//      } else {//$i > $pos -     //      LibrarySort_RemoveLeft($i, $pos, $count, $newSize); $i = ($i > $library_LeftPos) ? $library_LeftPos - 1 : --$i; } --$library_Number;//      } else {// ,   --$i;//      } } } 

Gerakan elemen ke kiri dengan penyeimbangan penuh
 //     . //    ,   //$i -     ,    //$pos -       //$count -        //$newSize -     function LibrarySort_RemoveLeft($i, $pos, $count, $newSize) { global $arr_new;//  global $library_Number;//     global $library_LeftPos;//        $left = false; $left_Pos = false;//      $j = $i;//      //     while($j > 0 && $left === false) {//            --$j; //     if($arr_new[$j] !== null) $left = $j;//    } if($left === false || $left < $pos) {//   (  )    //     } else { //$left >= $pos,     --$library_Number;//,       $left_Pos = LibrarySort_Pos($library_Number, $count, $newSize);//     //        LibrarySort_RemoveLeft($left, $left_Pos, $count, $newSize); //  ,     } //    ,   $arr_new[$pos] = $arr_new[$i]; $arr_new[$i] = null; //,         if($pos < $library_LeftPos) $library_LeftPos = $pos; } 

Saya harus kode dari awal sendiri, berdasarkan pada deskripsi umum dari metode ini. Saya tidak melihat kecepatan mendekati penyortiran cepat, opsi penyortiran perpustakaan saya menyortir 10-20 kali lebih lambat daripada penyortiran cepat. Tetapi alasannya, tentu saja, adalah karena implementasi saya terlalu kasar, banyak yang belum diperhitungkan.

Saya ingin melihat versi dari pencipta algoritme. Saya akan menulis hari ini kepada penulis (dan melemparkan mereka tautan ke habrastatu ini), mereka tiba-tiba akan menjawab. Meskipun ... saya ingat, saya mencoba menghubungi Allen Beachich ( sorting ABC ) dan Jason Morrison ( sorting J ), tetapi hasilnya sama seperti jika saya menulis di Sportloto.

UPD Martin Farah-Colton menjawab saya bahwa mereka tidak pernah melakukan implementasi:
Saya khawatir kami tidak pernah mengimplementasikan algoritma itu.
Yang utama adalah Ide :)

Karakteristik Algoritma

JudulJenis perpustakaan, Jenis perpustakaan
Nama lainJenis Penyisipan yang Dipetakan
PenulisMichael A. Bender, MartĂ­n Farach-Colton, Miguel Mosteiro
Tahun2004
KelasUrutan Penyisipan
PerbandinganAda
Kompleksitas waktuyang terbaikO ( n )
rata-rataO ( n log n )
yang terburukO ( n 2 )
Kompleksitas memori ekstraO ( n )

Referensi


Jenis perpustakaan

Visualisasi semacam algoritma perpustakaan

Jenis penyisipan adalah O (n log n)

Penulis algoritma:



Michael A. Bender
Martin Farah-Colton
Miguel Mostiro



Artikel Seri:





Sortir ditambahkan ke AlgoLab. Jadi Anda bisa bereksperimen dengan set data kecil.

Dalam hal ini, Anda dapat memutuskan berapa kali array bantu lebih besar dari yang utama. Untuk memilih koefisien ε, Anda dapat mengklik kanan sel dengan “Sortasi perpustakaan” dan pilih “Ubah Catatan”. Dan dalam catatan itu, hati-hati mengatur nilai integer untuk e dari 2 hingga 5. Jika Anda memasukkan sesuatu yang lain daripada angka-angka ini, nilai default = 2 akan digunakan.

Anda juga dapat memilih jenis penyeimbangan kembali. Jika Anda menetapkan lokal = 1, maka penyeimbangan kembali lokal akan digunakan. Jika lokal = 0 - penuh.

Dan jangan lupa untuk mengatur skala optimal untuk lembar proses sebelum memulai visualisasi, jika array tambahan tidak akan muat di layar.

Source: https://habr.com/ru/post/id416653/


All Articles