სწრაფი სორტირება და დაყოფა

სწრაფი სორტირება სტანდარტული ბიბლიოთეკების უმეტესობის პირველი არჩევანია: ადგილზე ალაგებს და პრაქტიკაში ჩვეულებრივ ყველაზე სწრაფია. მისი დაყოფის ბიჯი ცალკე ინსტრუმენტადაც გამოგადგება.

საშუალო⏱ 12 წთ
01

ისწავლე

იდეა, მექანიზმი და ფასი.

01დაყოფა საყრდენის გარშემო

სწრაფი სორტირება „დაყავი და იბატონეა“, ოღონდ სამუშაო რეკურსიამდე სრულდება და არა მის შემდეგ:

  • აირჩიე საყრდენი ელემენტი, აქ შუალედის ბოლო.
  • დაყოფა: გადაალაგე ისე, რომ საყრდენზე მცირე ყველაფერი მარცხნივ იყოს, დანარჩენი მარჯვნივ.
  • საყრდენი ახლა თავის საბოლოო დალაგებულ პოზიციაზეა. რეკურსია მარცხენა და მარჯვენა ნაწილზე.

შერწყმა საჭირო არ არის: როცა ორივე მხარე დალაგდება, მთელი შუალედიც დალაგებულია, რადგან ყოველი მარცხენა ელემენტი ყოველ მარჯვენაზე უკვე მცირეა.

ადრე (საყრდენი = ბოლო, 25)291037149311325დაყოფის შემდეგ101491325312937< 25≥ 25საბოლოო ადგილი

02ლომუტოს დაყოფა, ბიჯ-ბიჯ

დაყოფა ერთი გავლით სრულდება ორი ინდექსით. მაჩვენებელი i „< საყრდენი“ ზონის ბოლოს აღნიშნავს; მაჩვენებელი j ჯერ უნახავ ელემენტებს სკანირებს.

  • თუ a[j] < საყრდენი: გაცვალე a[i] და a[j], მერე i++. მცირე ელემენტი მარცხენა ზონას უერთდება.
  • სხვა შემთხვევაში უბრალოდ წადი წინ; ის „≥ საყრდენი“ ზონაში რჩება.
  • ბოლოს საყრდენი i პოზიციაზე გადაიტანე.

ერთი გავლა, O(n) შედარება, დამატებითი მასივის გარეშე: სწრაფი სორტირება ადგილზე ალაგებს და მხოლოდ რეკურსიის სტეკს იყენებს.

ჰოარის დაყოფა (ორი ერთმანეთისკენ მოძრავი მაჩვენებელი) ნაკლებ გაცვლას აკეთებს და ბევრ ტოლ გასაღებს უკეთ უმკლავდება; სამმხრივი დაყოფა (<, =, >) საუკეთესოა, როცა ბევრი დუბლიკატია.

< საყრდენი≥ საყრდენიჯერ არ გვინახავსსაყრდენიi ↓j ↓a[j] < საყრდენი → swap(a[i], a[j]), i++

03კარგი და ცუდი საყრდენები

თუ საყრდენი შუასთან ახლოს ხვდება, ყოველი დაყოფა შუალედს დაახლოებით შუაზე ყოფს: log n დონე O(n) სამუშაოთი, O(n log n), ისევე როგორც შერწყმით სორტირებაში.

თუ საყრდენი ყოველთვის უმცირესი ან უდიდესია, ერთი მხარე ცარიელია, მეორეში კი n − 1 ელემენტია. რეკურსია n სიღრმის ჯაჭვად იქცევა და ჯამური სამუშაოა n + (n−1) + … + 1 = O(n²). „ბოლო ელემენტი საყრდენად“ წესით ეს უკვე დალაგებულ შემავალ მონაცემებზე ხდება, რაც რეალურ მონაცემებში ძალიან ხშირია.

გამოსავალია საყრდენის შემთხვევით არჩევა (ან სამის მედიანა). მაშინ ცუდი შემავალი მონაცემები აღარ არსებობს და მოსალოდნელი დრო ნებისმიერ შემავალ მონაცემებზე O(n log n)-ია. რეალური სორტირებები, მაგალითად introsort, რეკურსიის ზედმეტად გაღრმავებისას heapsort-ზეც გადადის.

კარგი საყრდენი: სიღრმე log nცუდი საყრდენი: სიღრმე nმაგ. დალაგებულ შემავალ მონაცემებზე

04სწრაფი vs შერწყმითი, და quickselect

მასივებზე პრაქტიკაში სწრაფი სორტირება იმარჯვებს: ადგილზე მუშაობს, მეხსიერებას მიმდევრობით კითხულობს და პატარა მუდმივები აქვს. შერწყმითი იმარჯვებს, როცა გჭირდება სტაბილურობა ან უარესი შემთხვევის მკაცრი გარანტია. სწრაფი სორტირება არ არის სტაბილური.

დაყოფის იდეა სხვა ამოცანასაც ხსნის. k-ე უმცირესი ელემენტის საპოვნელად ერთხელ დაყავი: თუ საყრდენი k ინდექსზე მოხვდა, მზადაა; თუ არა, რეკურსია მხოლოდ ერთ მხარეს. მოსალოდნელი სამუშაო n + n/2 + n/4 + … = O(n). ეს არის quickselect, ხელმისაწვდომი როგორც std::nth_element.

გამოიყენე მედიანისთვის, „top k“-სთვის და პროცენტილებისთვის, როცა მთელი მასივის დალაგება არ გჭირდება.

ფასი ერთი შეხედვით

საშუალო / შემთხვევითი საყრდენიO(n log n)
უარესი (ცუდი საყრდენები)O(n²)
ერთი დაყოფაO(n)
დამატებითი მეხსიერება (სტეკი)O(log n)
Quickselect (k-ე ელემენტი)O(n) expected

დაიმახსოვრე

  1. დაყოფა საყრდენს საბოლოო ადგილზე სვამს, მცირეებს მარცხნივ და დიდებს მარჯვნივ, ერთი O(n) გავლით.
  2. დაბალანსებული საყრდენები O(n log n)-ს იძლევა; ყოველთვის მინ ან მაქს საყრდენი O(n²)-ს, ამიტომ საყრდენი შემთხვევით აირჩიე.
  3. სწრაფი სორტირება ადგილზეა და სწრაფია, მაგრამ არასტაბილური; quickselect დაყოფით k-ე ელემენტს O(n)-ში პოულობს.
02

ითამაშე

გაიარე ალგორითმი ბიჯ-ბიჯ, მერე სცადე შენს მონაცემებზე.

👀 რას უყურო: უყურე i-ს (მცირე ზონის ბოლო) და j-ს (სკანერი). ყოველი „დადგმა“ ერთ საყრდენს სამუდამოდ აფიქსირებს. სცადე 10 20 30 40 50 60 ცუდი შემთხვევის სანახავად.

ინტერაქტიული ვიზუალიზატორიაქ დააწკაპე, მერე space ← →
✎ შენი მონაცემებიმაქს. 10 რიცხვი, 1–99. სცადე უკვე დალაგებული ან უკუღმა დალაგებული.

სწრაფი სორტირება (Lomuto) · დაყოფის შუალედი [0, 7]

29
10
14
37
13
25
9
31
სწრაფი სორტირება: ავირჩიოთ საყრდენი ელემენტი, დავყოთ ისე, რომ მცირეები მარცხნივ იყოს, დიდები მარჯვნივ, მერე რეკურსია. 8 ელემენტი, საყრდენად ვიღებთ ყოველი შუალედის ბოლოს.

ფსევდოკოდი

 1 quicksort(lo, hi): 2   pivot = a[hi]; i = lo 3   for j in lo..hi-1: compare a[j] with pivot 4     if a[j] < pivot: swap a[i],a[j]; i++ 5   swap a[i],a[hi]  // pivot reaches final spot 6   recurse on [lo,i-1] and [i+1,hi]
1 / 1
03

შეამოწმე

სამი კითხვა. აირჩიე პასუხი და ნახე ახსნა.

№1

ლომუტოს დაყოფა [7, 2, 9, 4, 5]-ზე საყრდენით 5 (ბოლო ელემენტი). სად აღმოჩნდება 5?

№2

სწრაფ სორტირებას „ბოლო ელემენტი საყრდენად“ წესით უკვე დალაგებული n-ელემენტიანი მასივი მიეცა. რა ხდება?

№3

10⁷ რიცხვის მედიანა გჭირდება, ერთხელ. რომელია საუკეთესო ინსტრუმენტი?

04

ივარჯიშე

რეალური ამოცანები გასამყარებლად, მარტივიდან რთულისკენ.