Bölüm 13: Öncelikli Kuyruk ve Heap

C ile Veri Yapıları ve Algoritmalar
Öncelikli kuyrukMaksimum / minimum heapDizide tam ikili ağaçEkleme: yukarı kaydırmaSilme: aşağı kaydırmaKarşılaştırmaHeapify · d-ary
Sercan KÜLCÜ · Giresun Üniversitesi · Bilgisayar Mühendisliği

Giriş

Öğrenme Çıktıları

Tanımlamak

Öncelikli kuyruğu ve maksimum/minimum heap'i tanımlamak; heap'i ikili arama ağacından ayırmak.

Gösterimi kurmak

Tam ikili ağacı 1 tabanlı dizide saklamak: ata i/2, çocuklar 2i, 2i+1.

Ekleme ve silme

maksimum_yigin_ekle ve maksimum_yigin_al yordamlarını adım adım izlemek.

Analiz etmek

Ekleme ve silmenin neden O(lg n) olduğunu, en iyi durumun Ω(1) olduğunu göstermek.

Karşılaştırmak

Sırasız/sıralı dizi ve liste ile heap'i maliyet tablosu üzerinden kıyaslamak.

Genişletmek

Aşağıdan yukarı heap kurmanın (heapify) O(n) olduğunu açıklamak; d-ary heap'i tanımak.

Öncelikli Kuyruk

Öncelikli kuyruk: ilk çıkan en öncelikli öğe

Kuyruktan ilk alınacak (silinecek) öğe, geliş sırasına göre değil en yüksek ya da en düşük değere göre belirlenir.

  • Maksimum öncelikli kuyruk (max priority queue): önce en yüksek değer ele alınır.
  • Minimum öncelikli kuyruk (min priority queue): önce en düşük değer ele alınır.
Kuyruk (FIFO): geliş sırasıA:3B:9C:5D:7▲ baş: ilk giren (A) çıkar← arkaya ekleMaksimum öncelikli kuyrukA:3B:9C:5D:7▲ en yüksek öncelik (B:9) çıkar
Hocadan“Priority queue diyoruz, bakalım kuyruğu ne kadar biliyoruz? Bazen bir şeyleri bilmek de sakıncalı olabilir. Bildiğimiz kuyrukta sondan eklenir, baştan alınır. Bu ise ondan farklı…”

Öncelikli Kuyruk

Öncelikli kuyruğa neden gereksinim duyarız?

1 · Fotokopi makinesi

Her kullanıcı sabit ücret ödüyor (3 sayfa da 30 sayfa da aynı). Kazancı en büyük yapmak için kullanım süresi en kısa olana öncelik verilir → minimum öncelikli kuyruk.

2 · Havaalanı

İniş isteyen uçaklardan yakıtı en az olana öncelik verilmelidir → minimum öncelikli kuyruk.

Gerçek sistemlerde de böyledir: örneğin işletim sistemi işlemleri (process) öncelikli kuyruğa koyar.

Hocadan“Kazancımı maksimum yapmak istiyoruz. Kullanma süresi minimum olana öncelik vererek kazanç maksimize edilir.”
Hocadan“‘Kuyruğun arkasındasın. Yakıtın az, ama kuyruk böyle. Ne yapalım?’ diyemeyiz.”

Öncelikli Kuyruk

Soyut veri tipi: öncelikli kuyruk

/* maksimum öncelikli kuyruk arayüzü */
bool bos_mu(void);
bool dolu_mu(void);
void ekle(eleman item);     /* O(lg n) */
bool maks_al(eleman *cikan); /* sil + döndür */
bool maks_bak(eleman *ust);  /* silmeden bak */
Ekle

Öğe, önceliğiyle birlikte yapıya konur.

Al / Sil

En öncelikli öğe döndürülür ve yapıdan çıkarılır.

Öncelikli kuyruk bir arayüzdür; farklı veri yapılarıyla gerçeklenebilir. Heap yalnızca biridir, ama en dengelisidir: ekleme ve silme O(lg n).
Hocadan“Heap, nerede ve nasıl kullanılacağı kavranırsa çok çok önemli bir veri yapısıdır.”

Heap Yapısı

Heap bir dizide tam ikili ağaç olarak durur

141122731048566
0–114212374105866

Dizinin 0. indisi kullanılmaz; kök yigin[1].

Düğümİndis
kök1
i'nin atasıi / 2
sol çocuk2i
sağ çocuk2i + 1
son öğen

0 tabanlı dizide: ata (i−1)/2, çocuklar 2i+1, 2i+2.

Dizi sıralı değildir; yalnızca 1. indisteki öğenin en öncelikli (ilk alınacak) öğe olduğu garanti edilir. Tam ikili ağaçta boşluk olmadığından işaretçiye gerek kalmaz.

Heap Yapısı

Heap denince akla tam ikili ağaç gelmeli, arama ağacı değil

472655173449845293İkili arama ağacı (binary search tree)931842553524345496477178269Aynı değerlerle bir maksimum heap
  • Tam ikili ağaç: tüm düzeyler dolu, son düzey soldan sağa doğru dolu.
  • Arama ağacında sol < kök < sağ; heap'te yalnızca ata ≥ çocuk (sol-sağ arasında sıra yok).
Hocadan“‘Hocam bir ikili arama ağacı çizer misiniz?’ Genel istek üzerine çizelim, bununla karıştırmıyoruz!”

Heap Yapısı

Tanım: maksimum heap ve minimum heap

Maksimum heap (max heap): tam ikili ağaçta her düğümün değeri çocuklarından küçük değilse.

Minimum heap (min heap): tam ikili ağaçta her düğümün değeri çocuklarından büyük değilse.

1411227310485660–114212374105866916233540–192633453012520–130225maksimumheap örnekleri
21724310485660–12273441058661012028335040–1102203834501112120–111221minimumheap örnekleri

Bu tanımlar maksimum/minimum öncelikli kuyruk kullanabilmek için verilmiştir.

Heap Yapısı

Hızlı kontrol: hangisi maksimum heap?

5013024031042053560–150230340410520635(a)5013024033542051060–150230340435520610(b)91728314250–1927384152(c)

Her k ≥ 2 için yigin[k/2] ≥ yigin[k] mi? Yalnızca ata–çocuk çiftlerine bakılır; kardeşler ve kuzenler arasında sıra aranmaz.

Cevap
  1. (a) Evet: 50≥30, 50≥40, 30≥10, 30≥20, 40≥35.
  2. (b) Hayır: 4. indisteki 35, atası 30'dan büyük.
  3. (c) Evet: tam ağaç ve her ata ≥ çocuk (1 ile 2'nin 8'den küçük olması önemli değil).

Heap Yapısı

C ile tanımlar: dizi, sayaç ve makrolar

#define ELEMAN_SAYISI 200   /* maksimum öğe + 1 */
#define YIGIN_DOLUMU(n) ((n) == ELEMAN_SAYISI - 1)
#define YIGIN_BOSMU(n)  (!(n))

typedef struct {
  int x;          /* öncelik (anahtar) */
  /* diğer alanlar */
} eleman;

eleman yigin[ELEMAN_SAYISI];  /* [0] kullanılmaz */
int n = 0;                    /* öğe sayısı */
Kapasite

1..199 indisleri kullanılır; n == 199 ise heap doludur.

Makrolarda parantez

(n), YIGIN_BOSMU(a+b) gibi çağrılarda öncelik hatasını önler.

Sık yapılan hata: diziyi eleman yigin[eleman_sayisi - 1] (199 öğe, indis 0..198) diye tanımlamak. Dolu sayılmadan önce n 199'a çıkabilir ve yigin[199] dizinin dışına yazılır; boyut ELEMAN_SAYISI olmalıdır.

Ekleme

Maksimum heap'e öğe ekleme: önce yere konur, sonra kaydırılır

1411227310485660–1142123741058667891011başlangıçn = 6

Başlangıç: 14, 12, 7, 10, 8, 6. Sırasıyla 15, 109, 107, 3, 15 eklenecek.

1411227310485667boş0–1142123741058667891011+15n = 7item15

Önce yeni yer açılır: tam ağacın ilk boş konumu (7).

1411223104856677boş0–11421237410586677891011+15n = 7item15

15 > 7: ata 7 aşağı kaydırılır, boşluk 3. konuma çıkar.

1122143104856677boş0–114212314410586677891011+15n = 7item15

15 > 14: ata 14 aşağı kaydırılır, boşluk 1. konuma çıkar.

1511221431048566770–115212314410586677891011+15n = 7

15, 1. konuma yazılır. Heap özelliği yeniden sağlandı.

Hocadan“Eklenecek öğenin yeri kesinlikle hazırdır. Bundan kuşku yok. Önce o yere eklenir. Sonra eyvah! Çocuğundan büyük olmak zorundaydı. Öyleyse uygun konuma kaydırırız.”

Ekleme

maksimum_yigin_ekle: takas değil, kaydırma

void maksimum_yigin_ekle(eleman item) {
  if (YIGIN_DOLUMU(n)) {
    yigin_dolu();             /* hata iletisi */
    return;
  }
  int i = ++n;
  while (i != 1) {            /* i == 1: kökteyiz */
    if (item.x <= yigin[i / 2].x) break;
    yigin[i] = yigin[i / 2];  /* atayı aşağı kaydır */
    i /= 2;                   /* bir üst ataya bak */
  }
  yigin[i] = item;
}
Boşluk (delik) yöntemi

Takas yok: büyük atalar birer düzey aşağı kaydırılır, öğe en sonda bir kez yazılır (takas 3, kaydırma 1 atama).

Döngü koşulu

i != 1: kökte ata yoktur. item.x <= ata olduğunda yerleşim yeri bulunmuştur.

Dolu heap

yigin_dolu() bir hata iletisi yordamıdır; ekleme yapılmadan dönülür.

Ekleme · kodu adım adım çalıştır

Kod izleme: maksimum_yigin_ekle(15)

1411227310485660–1142123741058667891011ekle(15)n = 6

ekle(15) çağrılır; heap'te 6 öğe var: 14, 12, 7, 10, 8, 6.

1411227310485660–1142123741058667891011ekle(15)n = 6

YIGIN_DOLUMU(6) yanlış: 6 ≠ 199, yer var.

1411227310485667ii/20–1142123741058667891011▲ i▲ i/2ekle(15)n = 7item15

i = ++n = 7: tam ağacın ilk boş konumu. Yeni öğenin yeri kesinlikle hazır; ama önce atasıyla kıyaslanacak, item henüz yazılmaz.

1411227310485667ii/20–1142123741058667891011▲ i▲ i/2ekle(15)n = 7item15

i = 7 ≠ 1: henüz kökte değiliz, döngüye girilir.

1411227310485667ii/20–1142123741058667891011▲ i▲ i/2ekle(15)n = 7item15

15 ≤ 7? Hayır. Yeni öğe atasından büyük: ata aşağı inmeli.

1411223104856677ii/20–11421237410586677891011▲ i▲ i/2ekle(15)n = 7item15

yigin[7] = yigin[3]: ata (7) bir düzey aşağı kaydı. Takas yok; boşluk (turuncu) yukarı çıktı.

1411223104856677i0–11421237410586677891011▲ iekle(15)n = 7item15

i /= 2 → i = 3: bir üstteki ataya geçilir.

1411223104856677ii/20–11421237410586677891011▲ i▲ i/2ekle(15)n = 7item15

i = 3 ≠ 1: henüz kökte değiliz, döngüye girilir.

1411223104856677ii/20–11421237410586677891011▲ i▲ i/2ekle(15)n = 7item15

15 ≤ 14? Hayır. Yeni öğe atasından büyük: ata aşağı inmeli.

1122143104856677ii/20–114212314410586677891011▲ i▲ i/2ekle(15)n = 7item15

yigin[3] = yigin[1]: ata (14) bir düzey aşağı kaydı. Takas yok; boşluk (turuncu) yukarı çıktı.

1122143104856677i0–114212314410586677891011▲ iekle(15)n = 7item15

i /= 2 → i = 1: bir üstteki ataya geçilir.

1122143104856677i0–114212314410586677891011▲ iekle(15)n = 7item15

i == 1: köke ulaşıldı, döngü biter.

151122143104856677i0–115212314410586677891011▲ iekle(15)n = 7

yigin[1] = item: 15 yerine oturdu. Heap: 15, 12, 14, 10, 8, 6, 7.

void maksimum_yigin_ekle(eleman item) {
  if (YIGIN_DOLUMU(n)) {
    yigin_dolu();             /* hata iletisi */
    return;
  }
  int i = ++n;
  while (i != 1) {            /* i == 1: kökteyiz */
    if (item.x <= yigin[i / 2].x) break;
    yigin[i] = yigin[i / 2];  /* atayı aşağı kaydır */
    i /= 2;                   /* bir üst ataya bak */
  }
  yigin[i] = item;
}

Ekleme · kodu adım adım çalıştır

Kod izleme: maksimum_yigin_ekle(109)

1511221431048566770–115212314410586677891011ekle(109)n = 7

ekle(109): tüm öğelerden büyük bir değer. En kötü durum: köke kadar çıkacak.

1511221431048566770–115212314410586677891011ekle(109)n = 7

YIGIN_DOLUMU(7) yanlış: 7 ≠ 199, yer var.

1511221431048566778ii/20–115212314410586677891011▲ i▲ i/2ekle(109)n = 8item109

i = ++n = 8: tam ağacın ilk boş konumu. Yeni öğenin yeri kesinlikle hazır; ama önce atasıyla kıyaslanacak, item henüz yazılmaz.

1511221431048566778ii/20–115212314410586677891011▲ i▲ i/2ekle(109)n = 8item109

i = 8 ≠ 1: henüz kökte değiliz, döngüye girilir.

1511221431048566778ii/20–115212314410586677891011▲ i▲ i/2ekle(109)n = 8item109

109 ≤ 10? Hayır. Yeni öğe atasından büyük: ata aşağı inmeli.

1511221434856677108ii/20–11521231441058667781091011▲ i▲ i/2ekle(109)n = 8item109

yigin[8] = yigin[4]: ata (10) bir düzey aşağı kaydı. Takas yok; boşluk (turuncu) yukarı çıktı.

1511221434856677108i0–11521231441058667781091011▲ iekle(109)n = 8item109

i /= 2 → i = 4: bir üstteki ataya geçilir.

1511221434856677108ii/20–11521231441058667781091011▲ i▲ i/2ekle(109)n = 8item109

i = 4 ≠ 1: henüz kökte değiliz, döngüye girilir.

1511221434856677108ii/20–11521231441058667781091011▲ i▲ i/2ekle(109)n = 8item109

109 ≤ 12? Hayır. Yeni öğe atasından büyük: ata aşağı inmeli.

1512143124856677108ii/20–11521231441258667781091011▲ i▲ i/2ekle(109)n = 8item109

yigin[4] = yigin[2]: ata (12) bir düzey aşağı kaydı. Takas yok; boşluk (turuncu) yukarı çıktı.

1512143124856677108i0–11521231441258667781091011▲ iekle(109)n = 8item109

i /= 2 → i = 2: bir üstteki ataya geçilir.

1512143124856677108ii/20–11521231441258667781091011▲ i▲ i/2ekle(109)n = 8item109

i = 2 ≠ 1: henüz kökte değiliz, döngüye girilir.

1512143124856677108ii/20–11521231441258667781091011▲ i▲ i/2ekle(109)n = 8item109

109 ≤ 15? Hayır. Yeni öğe atasından büyük: ata aşağı inmeli.

1152143124856677108ii/20–11521531441258667781091011▲ i▲ i/2ekle(109)n = 8item109

yigin[2] = yigin[1]: ata (15) bir düzey aşağı kaydı. Takas yok; boşluk (turuncu) yukarı çıktı.

1152143124856677108i0–11521531441258667781091011▲ iekle(109)n = 8item109

i /= 2 → i = 1: bir üstteki ataya geçilir.

1152143124856677108i0–11521531441258667781091011▲ iekle(109)n = 8item109

i == 1: köke ulaşıldı, döngü biter.

1091152143124856677108i0–110921531441258667781091011▲ iekle(109)n = 8

yigin[1] = item: 109 yerine oturdu. Heap: 109, 15, 14, 12, 8, 6, 7, 10.

void maksimum_yigin_ekle(eleman item) {
  if (YIGIN_DOLUMU(n)) {
    yigin_dolu();             /* hata iletisi */
    return;
  }
  int i = ++n;
  while (i != 1) {            /* i == 1: kökteyiz */
    if (item.x <= yigin[i / 2].x) break;
    yigin[i] = yigin[i / 2];  /* atayı aşağı kaydır */
    i /= 2;                   /* bir üst ataya bak */
  }
  yigin[i] = item;
}

Ekleme

Art arda ekleme: +107, +3, +15

10911521431248566771080–110921531441258667781091011başlangıçn = 8

Şu an: 109, 15, 14, 12, 8, 6, 7, 10. Sırada 107, 3 ve 15 var.

10911521431248566771089boş0–110921531441258667781091011+107n = 9item107

Önce yeni yer açılır: tam ağacın ilk boş konumu (9).

10911521434856677108129boş0–11092153144125866778109121011+107n = 9item107

107 > 12: ata 12 aşağı kaydırılır, boşluk 4. konuma çıkar.

10912143154856677108129boş0–11092153144155866778109121011+107n = 9item107

107 > 15: ata 15 aşağı kaydırılır, boşluk 2. konuma çıkar.

10912143154856677108129boş0–11092153144155866778109121011+107n = 9item107

107 ≤ 109: ata yeterince büyük, durulur.

109110721431548566771081290–110921073144155866778109121011+107n = 9

107, 2. konuma yazılır. Heap özelliği yeniden sağlandı.

1091107214315485667710812910boş0–110921073144155866778109121011+3n = 10item3

Önce yeni yer açılır: tam ağacın ilk boş konumu (10).

1091107214315485667710812910boş0–110921073144155866778109121011+3n = 10item3

3 ≤ 8: ata yeterince büyük, durulur.

109110721431548566771081293100–1109210731441558667781091210311+3n = 10

3, 10. konuma yazılır. Heap özelliği yeniden sağlandı.

1091107214315485667710812931011boş0–1109210731441558667781091210311+15n = 11item15

Önce yeni yer açılır: tam ağacın ilk boş konumu (11).

1091107214315456677108129310811boş0–11092107314415586677810912103118+15n = 11item15

15 > 8: ata 8 aşağı kaydırılır, boşluk 5. konuma çıkar.

1091107214315456677108129310811boş0–11092107314415586677810912103118+15n = 11item15

15 ≤ 107: ata yeterince büyük, durulur.

1091107214315415566771081293108110–110921073144155156677810912103118+15n = 11

15, 5. konuma yazılır. Heap özelliği yeniden sağlandı.

3 eklenirken hiç kaydırma olmaz (en iyi durum, Ω(1)); 109 köke kadar çıkmıştı (en kötü durum, O(lg n)).

Ekleme

Ekleme maliyeti: en çok ağacın yüksekliği kadar

düzey 01 düğümdüzey 12 düğümdüzey 24 düğümdüzey 38 düğümdüzey 416 düğümyükseklik ⌈lg(n+1)⌉
DurumÖrnekKaydırmaMaliyet
En iyi3'ün eklenmesi0Ω(1)
En kötü109'un eklenmesiyükseklik kadarO(lg n)
Genel—0 … ⌊lg n⌋O(lg n)
Tam ikili ağaçta n öğe için yükseklik ⌈lg(n+1)⌉'dir. Döngü her turda bir düzey yukarı çıktığından karmaşıklığın üst sınırı lg n, alt sınırı 1'dir; zaman karmaşıklığı O(lg n).

Silme

Maksimum heap'ten öğe alma: kök alınır, sondaki yerleştirilir

3011522831141453667108990–130215328411514637681099başlangıçn = 9

Veri: 30, 15, 28, 11, 14, 3, 6, 10, 9. İlk alınacak öğe kökteki 30.

115228311414536671080–13021532841151463768109sil → 30n = 8 · çıkan = 30deger9

Kök (30) alındı. Sondaki öğe 9 çıkarıldı, kökteki boşluğa aday.

281152311414536671080–12821532841151463768109sil → 30n = 8 · çıkan = 30deger9

İki çocuktan büyüğü (28) deger'den (9) büyük → yukarı taşındı.

2811529311414536671080–1282153941151463768109sil → 30n = 8 · çıkan = 30

9, 3. konuma yerleşti.

Her adımda dikkat edilecek 3 değer var: iki çocuk ve sondaki öğe. En büyüğü i'nin gösterdiği yere taşınır; i yerleştirilecek konumu, j çocukları gösterir.

Hocadan“Sadece bir öğeyi almak kolay! Ama iş burada bitmiyor. Öğeyi silmeli ve max öncelikli kuyruk yapısını da korumalıyız.”

Silme

maksimum_yigin_al: düzeltilmiş sürüm

bool maksimum_yigin_al(eleman *cikan) {
  if (YIGIN_BOSMU(n)) return false;
  *cikan = yigin[1];          /* kök döndürülecek */
  eleman deger = yigin[n--];  /* yeri aranacak */
  int i = 1, j = 2;
  while (j <= n) {
    if (j < n && yigin[j].x < yigin[j + 1].x) j++;
    if (deger.x >= yigin[j].x) break;
    yigin[i] = yigin[j];      /* çocuğu yukarı al */
    i = j; j *= 2;
  }
  yigin[i] = deger;
  return true;
}
Sık yapılan hata

Boş heap'te return 0; yazmak derleme hatası verir (tamsayı bir struct'a dönüşmez).

Düzeltme

Önerilen çözüm: bool dönüş (başarı/başarısızlık) ve alınan öğe işaretçi parametre ile döndürülür.

j < n neden?

yigin[j+1] okumadan önce sağ çocuğun var olduğunu garanti eder.

Silme · kodu adım adım çalıştır

Kod izleme: maksimum_yigin_al (30)

3011522831141453667108990–130215328411514637681099maksimum_yigin_al()n = 9

Çağrı: heap'te 9 öğe var; kök 30. Çıkan öğe e'ye yazılacak.

3011522831141453667108990–130215328411514637681099maksimum_yigin_al()n = 9

YIGIN_BOSMU(9) yanlış; alınacak öğe var.

3011522831141453667108990–130215328411514637681099maksimum_yigin_al()n = 9 · çıkan = 30

*cikan = yigin[1] = 30: tanım gereği ilk alınacak öğe dizinin ilk öğesidir.

115228311414536671080–13021532841151463768109maksimum_yigin_al()n = 8 · çıkan = 30deger9

deger = yigin[n--] = 9, n = 8. Tam ağaç bozulmasın diye sondaki öğe çıkarıldı; ona kökten başlayarak yer aranacak.

11522831141453667108ij0–13021532841151463768109▲ i▲ jmaksimum_yigin_al()n = 8 · çıkan = 30deger9

i = 1 (deger'in aday konumu), j = 2 (i'nin sol çocuğu).

11522831141453667108ij0–13021532841151463768109▲ i▲ jmaksimum_yigin_al()n = 8 · çıkan = 30deger9

j = 2 ≤ n = 8: i'nin çocuğu var, döngüye girilir.

11522831141453667108ij0–13021532841151463768109▲ i▲ jmaksimum_yigin_al()n = 8 · çıkan = 30deger9

Sağ çocuk daha büyük (15 < 28) → j++, j = 3.

11522831141453667108ij0–13021532841151463768109▲ i▲ jmaksimum_yigin_al()n = 8 · çıkan = 30deger9

9 ≥ 28? Hayır: büyük çocuk yukarı çıkmalı.

28115231141453667108ij0–12821532841151463768109▲ i▲ jmaksimum_yigin_al()n = 8 · çıkan = 30deger9

yigin[1] = yigin[3]: 28 bir düzey yukarı taşındı; boşluk 3. konuma indi.

28115231141453667108ij0–12821532841151463768109▲ i▲ jmaksimum_yigin_al()n = 8 · çıkan = 30deger9

i = 3, j = 6: taşınan değerin eski konumuna ilerlenir.

28115231141453667108ij0–12821532841151463768109▲ i▲ jmaksimum_yigin_al()n = 8 · çıkan = 30deger9

j = 6 ≤ n = 8: i'nin çocuğu var, döngüye girilir.

28115231141453667108ij0–12821532841151463768109▲ i▲ jmaksimum_yigin_al()n = 8 · çıkan = 30deger9

Sağ çocuk daha büyük (3 < 6) → j++, j = 7.

28115231141453667108ij0–12821532841151463768109▲ i▲ jmaksimum_yigin_al()n = 8 · çıkan = 30deger9

9 ≥ 6? Evet: deger iki çocuktan da büyük/eşit → break.

281152931141453667108i0–1282153941151463768109▲ imaksimum_yigin_al()n = 8 · çıkan = 30

yigin[3] = deger (9). Heap: 28, 15, 9, 11, 14, 3, 6, 10.

2811529311414536671080–1282153941151463768109maksimum_yigin_al()n = 8 · çıkan = 30

return true; çağıran cikan içinde 30 değerini alır. Yol boyu ≤ ⌊lg n⌋ düzey → O(lg n).

bool maksimum_yigin_al(eleman *cikan) {
  if (YIGIN_BOSMU(n)) return false;
  *cikan = yigin[1];          /* kök döndürülecek */
  eleman deger = yigin[n--];  /* yeri aranacak */
  int i = 1, j = 2;
  while (j <= n) {
    if (j < n && yigin[j].x < yigin[j + 1].x) j++;
    if (deger.x >= yigin[j].x) break;
    yigin[i] = yigin[j];      /* çocuğu yukarı al */
    i = j; j *= 2;
  }
  yigin[i] = deger;
  return true;
}

Silme · kodu adım adım çalıştır

Kod izleme: ikinci maksimum_yigin_al (28)

2811529311414536671080–128215394115146376810maksimum_yigin_al()n = 8

İkinci çağrı: 28, 15, 9, 11, 14, 3, 6, 10. Bu kez sondaki öğe (10) yaprağa kadar inecek.

2811529311414536671080–128215394115146376810maksimum_yigin_al()n = 8

YIGIN_BOSMU(8) yanlış; alınacak öğe var.

2811529311414536671080–128215394115146376810maksimum_yigin_al()n = 8 · çıkan = 28

*cikan = yigin[1] = 28: tanım gereği ilk alınacak öğe dizinin ilk öğesidir.

11529311414536670–1282153941151463768maksimum_yigin_al()n = 7 · çıkan = 28deger10

deger = yigin[n--] = 10, n = 7. Tam ağaç bozulmasın diye sondaki öğe çıkarıldı; ona kökten başlayarak yer aranacak.

1152931141453667ij0–1282153941151463768▲ i▲ jmaksimum_yigin_al()n = 7 · çıkan = 28deger10

i = 1 (deger'in aday konumu), j = 2 (i'nin sol çocuğu).

1152931141453667ij0–1282153941151463768▲ i▲ jmaksimum_yigin_al()n = 7 · çıkan = 28deger10

j = 2 ≤ n = 7: i'nin çocuğu var, döngüye girilir.

1152931141453667ij0–1282153941151463768▲ i▲ jmaksimum_yigin_al()n = 7 · çıkan = 28deger10

Sol çocuk (15) ≥ sağ çocuk (9): j = 2 kalır.

1152931141453667ij0–1282153941151463768▲ i▲ jmaksimum_yigin_al()n = 7 · çıkan = 28deger10

10 ≥ 15? Hayır: büyük çocuk yukarı çıkmalı.

1512931141453667ij0–1152153941151463768▲ i▲ jmaksimum_yigin_al()n = 7 · çıkan = 28deger10

yigin[1] = yigin[2]: 15 bir düzey yukarı taşındı; boşluk 2. konuma indi.

1512931141453667ij0–1152153941151463768▲ i▲ jmaksimum_yigin_al()n = 7 · çıkan = 28deger10

i = 2, j = 4: taşınan değerin eski konumuna ilerlenir.

1512931141453667ij0–1152153941151463768▲ i▲ jmaksimum_yigin_al()n = 7 · çıkan = 28deger10

j = 4 ≤ n = 7: i'nin çocuğu var, döngüye girilir.

1512931141453667ij0–1152153941151463768▲ i▲ jmaksimum_yigin_al()n = 7 · çıkan = 28deger10

Sağ çocuk daha büyük (11 < 14) → j++, j = 5.

1512931141453667ij0–1152153941151463768▲ i▲ jmaksimum_yigin_al()n = 7 · çıkan = 28deger10

10 ≥ 14? Hayır: büyük çocuk yukarı çıkmalı.

1511429311453667ij0–1152143941151463768▲ i▲ jmaksimum_yigin_al()n = 7 · çıkan = 28deger10

yigin[2] = yigin[5]: 14 bir düzey yukarı taşındı; boşluk 5. konuma indi.

1511429311453667i0–1152143941151463768▲ imaksimum_yigin_al()n = 7 · çıkan = 28deger10

i = 5, j = 10: taşınan değerin eski konumuna ilerlenir.

1511429311453667i0–1152143941151463768▲ imaksimum_yigin_al()n = 7 · çıkan = 28deger10

j = 10 > n = 7: i bir yaprak; döngüden çıkılır.

151142931141053667i0–1152143941151063768▲ imaksimum_yigin_al()n = 7 · çıkan = 28

yigin[5] = deger (10). Heap: 15, 14, 9, 11, 10, 3, 6.

1511429311410536670–1152143941151063768maksimum_yigin_al()n = 7 · çıkan = 28

return true; çağıran cikan içinde 28 değerini alır. Yol boyu ≤ ⌊lg n⌋ düzey → O(lg n).

bool maksimum_yigin_al(eleman *cikan) {
  if (YIGIN_BOSMU(n)) return false;
  *cikan = yigin[1];          /* kök döndürülecek */
  eleman deger = yigin[n--];  /* yeri aranacak */
  int i = 1, j = 2;
  while (j <= n) {
    if (j < n && yigin[j].x < yigin[j + 1].x) j++;
    if (deger.x >= yigin[j].x) break;
    yigin[i] = yigin[j];      /* çocuğu yukarı al */
    i = j; j *= 2;
  }
  yigin[i] = deger;
  return true;
}

Silme

Heap boşalana dek alınır: çıkış sırası azalan sıradır

1511429311410536670–11521439411510637689durumn = 7

Şu an: 15, 14, 9, 11, 10, 3, 6. Çıkanlar: 30, 28.

1411129364105360–114211394651063789sil → 15n = 6 · çıkan = 15

15 alındı; sondaki öğe 6 4. konuma yerleşti. Çıkış sırası: 30, 28, 15

1111029364350–1112103946536789sil → 14n = 5 · çıkan = 14

14 alındı; sondaki öğe 3 5. konuma yerleşti. Çıkış sırası: 30, 28, 15, 14

1016293340–11026394356789sil → 11n = 4 · çıkan = 11

11 alındı; sondaki öğe 3 4. konuma yerleşti. Çıkış sırası: 30, 28, 15, 14, 11

9162330–192633456789sil → 10n = 3 · çıkan = 10

10 alındı; sondaki öğe 3 3. konuma yerleşti. Çıkış sırası: 30, 28, 15, 14, 11, 10

61320–16233456789sil → 9n = 2 · çıkan = 9

9 alındı; sondaki öğe 3 2. konuma yerleşti. Çıkış sırası: 30, 28, 15, 14, 11, 10, 9

310–1323456789sil → 6n = 1 · çıkan = 6

6 alındı; sondaki öğe 3 1. konuma yerleşti. Çıkış sırası: 30, 28, 15, 14, 11, 10, 9, 6

0–123456789sil → 3n = 0 · çıkan = 3

3 alındı; sondaki öğe 3 son öğeydi, heap boşaldı. Çıkış sırası: 30, 28, 15, 14, 11, 10, 9, 6, 3

Hocadan“İş inada bindi! ☺”

Silme

Silme maliyeti ve sondaki öğenin yolculuğu

Araya girebilir

1. silmede 9, üç düzey inmeden 3. konuma yerleşti.

En sona konabilir

2. silmede 10 yaprağa (5. konum) kadar indi: en kötü durum.

Maliyet

Döngü her turda bir düzey iner: en çok ⌊lg n⌋ tur, tur başına 2 karşılaştırma → O(lg n).

Yan ürün

n kez silmek öğeleri azalan sırada verir: bu, heap sort'un fikridir.

Silmede en iyi durum da Ω(1)'dir: sondaki öğe kökün iki çocuğundan büyük/eşitse hemen yerleşir (ör. heap'te 2 öğe kalmışken).

Silme

Yordamları daha güzel yazmak: global yerine yapı

typedef struct { eleman *a; int n, kapasite; } Yigin;

Yigin *yigin_olustur(int kapasite) {
  Yigin *h = malloc(sizeof *h);
  if (h == NULL) return NULL;
  h->a = malloc((kapasite + 1) * sizeof *h->a);
  if (h->a == NULL) { free(h); return NULL; }
  h->n = 0; h->kapasite = kapasite;
  return h;
}
void yigin_yok_et(Yigin *h) { if (h) { free(h->a); free(h); } }
Öneri

Başarı TRUE/FALSE ile bildirilsin; öğe ve n referansla aktarılsın.

Burada

Dizi ve n bir yapıda: birden çok heap olabilir; malloc hep denetlenir.

Hocadan“Kitaptaki kodlar zaten çok doğru kodlar değil. Ama tanımlar doğru olduğundan, benim gördüğüm kadarıyla veri yapılarını öğrenmek için son derece ideal bir kitap…”

Silme

Min hâlleri: ad ve karşılaştırmalar çevrilir

void minimum_yigin_ekle(eleman item) {
  /* ... dolu denetimi aynı ... */
  int i = ++n;
  while (i != 1) {
    if (item.x >= yigin[i / 2].x) break; /* >= ! */
    yigin[i] = yigin[i / 2];
    i /= 2;
  }
  yigin[i] = item;
}
YerMaks.Min.
ekle: duritem <= ataitem >= ata
al: çocukbüyüğüküçüğü
al: durdeger >= [j]deger <= [j]
Hocadan“Yordamın adı değiştirilip küçük ve büyük işaretleri de tersine çevrilirse başarılı bir şekilde min heap yazılmış olur.”

Silme

Öncelik zamanla değişirse: uçakların yakıtı

Gözlem

Her uçağın saniyede harcadığı yakıt farklıdır: her saniye kesme (interrupt) ile yakıtlar günlenmeli.

Heap'te karşılığı

Önceliği artan öğe yukarı, azalan öğe aşağı kaydırılır: O(lg n). Konum k bir indeks tablosunda tutulur.

/* k konumundaki öğenin önceliği arttı: yukarı kaydır */
void oncelik_artir(int k, int yeni) {
  eleman e = yigin[k];
  e.x = yeni;
  while (k != 1 && e.x > yigin[k / 2].x) {
    yigin[k] = yigin[k / 2];
    k /= 2;
  }
  yigin[k] = e;
}
Hocadan“Bir uçağın bir saniyede 10 galon (37,85 litre) yakıtı giderken diğeri 10 galonla bir saat uçabilir belki.”

Karşılaştırma

Niçin heap? Basit dizi ya da liste olmaz mı?

#YapıEkleSil (en büyük)Neden
1Sırasız diziΘ(1)Θ(n)Sona eklenir; en büyüğü bulmak için tamamına bakılır.
2Sırasız listeΘ(1)Θ(n)Başa eklenir; yine hepsinde gezinilir.
3Sıralı dizi (küçükten büyüğe)O(n)Θ(1)binsearch O(lg n) + n kaydırma; en büyük sonda.
4Sıralı bağlı liste (büyükten küçüğe)O(n)Θ(1)Yeri bulunur; baştan silinir, baş günlenir.
5Maksimum heapO(lg n)O(lg n)Yalnızca bir kök–yaprak yolu.

En iyi (Ω) ve en kötü (O) durum aynı derecedense sınır sıkıdır ve Θ ile ifade edilir. Sıralı dizide binsearch kullanılmazsa ekleme O(n) değil Θ(n) olur.

Hocadan“‘En büyük değeri bir max değişkeniyle tutsak?’ O uçak inince ne yapılacak? Öyleyse sıralı dizi kullanmak gerekirdi.”

Karşılaştırma

Maliyet tablosunu sayılara dökelim

#EkleSil
1 · sırasız dizi12.000.000
2 · sırasız liste12.000.000
3 · sıralı dizi2.000.0001
4 · sıralı liste2.000.0001
5 · maks. heap≈ 21≈ 21

Bir komut = 1 saniye varsayımı; lg 2.000.000 ≈ 20,9 (yuvarlanarak 21).

Hocadan“5 farklı yaklaşımı hiç kod yazmadan, tuşlara dokunmadan analiz ediyorum.”
Hocadan“Ben 2 milyon sayısını çok seviyorum, çünkü logaritmasını almak kolay. […] max heap yapısıyla gerçeklediğim programı şaka olsun diye 100.000 kez çalıştırıyorum. Öteki yapılarla 1 kez çalıştırmak kadar sürüyor.”

Karşılaştırma

“Kim anlayacak benim algoritmanın kötü olduğunu?”

Hocadan“‘Komut saniye değil, mikro saniye düzeyindedir.’ denebilir. […] ama bir işletim sisteminin parçası olduğunda aralarında ne kadar fark var! ‘Kardeşim! Havaalanına yarım saatte, bir saatte bir uçak geliyor. Kim anlayacak benim algoritmanın kötü olduğunu?’ […] Uzmanlık isteyen işlerde ise o şekilde yazılmış kodun akademik ve bilimsel hiçbir değeri yoktur.”
Hocadan“Problem böyle olmalı: Bir öğe ekle, en küçüğünü hemen ver. […] Hocam bana sıralı bağlı liste hoş geldi diyen varsa hâlâ, yapacak bir şey yok.”
Veri yapısı, işlem karışımına göre seçilir. Hem sık ekleme hem sık “en küçüğü ver” isteyen bir problemde iki işlemi de O(lg n)'de yapan heap, birini Θ(1) diğerini Θ(n) yapan yapılardan üstündür.

Karşılaştırma

İleri düzey: binomial ve Fibonacci heap'leri

İşlemİkili heapBinomial heapFibonacci heap*
ekleO(lg n)O(lg n)O(1)
en küçüğü bulO(1)O(lg n)O(1)
en küçüğü silO(lg n)O(lg n)O(lg n)
anahtar azaltO(lg n)O(lg n)O(1)
iki heap'i birleştirO(n)O(lg n)O(1)

* Fibonacci heap süreleri amortize (ortalama, işlem dizisi üzerinden) sürelerdir (CLRS). Dijkstra ve Prim gibi çok sayıda “anahtar azalt” yapan algoritmalarda kuramsal kazanç sağlar.

Hocadan“Bu yapı binary heap. Bundan daha ileri düzeyde fibonacci heap, binomial heap algoritmaları var. Daha hızlanıyor. Adamlar lgn yeter dememişler. — Citius! Altius! Fortius! (Daha hızlı! Daha yükseğe! Daha güçlü!)”

İleri Konular

Heapify: diziyi yerinde heap'e çevirmek

void yigin_kur(eleman a[], int n) {
  for (int k = n / 2; k >= 1; k--) {
    eleman deger = a[k];
    int i = k, j = 2 * k;
    while (j <= n) {
      if (j < n && a[j].x < a[j + 1].x) j++;
      if (deger.x >= a[j].x) break;
      a[i] = a[j];
      i = j; j *= 2;
    }
    a[i] = deger;
  }
}
Fikir

Yapraklar (k > n/2) zaten tek öğelik heap'tir. k = n/2'den köke doğru her düğüm aşağı kaydırılır: iki çocuğu heap olan bir kök düzeltilir.

Ne kazandırır?

n kez ekle O(n lg n) sürer; aşağıdan yukarı kurma O(n).

İç döngü

maksimum_yigin_al içindeki döngünün aynısı (Horowitz'de adjust).

Heapify · kodu adım adım çalıştır

Kod izleme: yigin_kur — k = 3 ve k = 2

5132839415126770–152338495161277yigin_kur(a, 7)

Girdi: 5, 3, 8, 9, 1, 12, 7 (henüz heap değil). n/2 = 3'ten başlanır.

5132839415126770–152338495161277▲ kyigin_kur(a, 7)k = 3

k = 3: 3 köklü alt ağaç düzeltilir. Yeşil düğümler zaten birer heap'in köküdür (yapraklar ve daha önce düzeltilenler).

51323941512677ij0–152338495161277▲ ki▲ jyigin_kur(a, 7)k = 3deger8

deger = a[3] = 8; i = 3, j = 6. Silmedeki aşağı kaydırmanın aynısı başlıyor.

51323941512677ij0–152338495161277▲ ki▲ jyigin_kur(a, 7)k = 3deger8

Çocuklar 12 ve 7: büyüğü a[6] = 12.

51321239415677ij0–1523312495161277▲ ki▲ jyigin_kur(a, 7)k = 3deger8

8 < 12 → a[3] = a[6]; boşluk 6. konuma iner.

51321239415677i0–1523312495161277▲ k▲ iyigin_kur(a, 7)k = 3deger8

i = 6, j = 12: boşluğun yeni yerinden devam.

51321239415677i0–1523312495161277▲ k▲ iyigin_kur(a, 7)k = 3deger8

j = 12 > n: i = 6 yaprak, döngü biter.

513212394158677i0–152331249516877▲ k▲ iyigin_kur(a, 7)k = 3

a[6] = deger (8). 3 köklü alt ağaç artık bir heap: 5, 3, 12, 9, 1, 8, 7.

5132123941586770–152331249516877▲ kyigin_kur(a, 7)k = 2

k = 2: 2 köklü alt ağaç düzeltilir. Yeşil düğümler zaten birer heap'in köküdür (yapraklar ve daha önce düzeltilenler).

51212394158677ij0–152331249516877▲ ki▲ jyigin_kur(a, 7)k = 2deger3

deger = a[2] = 3; i = 2, j = 4. Silmedeki aşağı kaydırmanın aynısı başlıyor.

51212394158677ij0–152331249516877▲ ki▲ jyigin_kur(a, 7)k = 2deger3

Çocuklar 9 ve 1: büyüğü a[4] = 9.

51921234158677ij0–152931249516877▲ ki▲ jyigin_kur(a, 7)k = 2deger3

3 < 9 → a[2] = a[4]; boşluk 4. konuma iner.

51921234158677i0–152931249516877▲ k▲ iyigin_kur(a, 7)k = 2deger3

i = 4, j = 8: boşluğun yeni yerinden devam.

51921234158677i0–152931249516877▲ k▲ iyigin_kur(a, 7)k = 2deger3

j = 8 > n: i = 4 yaprak, döngü biter.

519212334158677i0–152931243516877▲ k▲ iyigin_kur(a, 7)k = 2

a[4] = deger (3). 2 köklü alt ağaç artık bir heap: 5, 9, 12, 3, 1, 8, 7.

void yigin_kur(eleman a[], int n) {
  for (int k = n / 2; k >= 1; k--) {
    eleman deger = a[k];
    int i = k, j = 2 * k;
    while (j <= n) {
      if (j < n && a[j].x < a[j + 1].x) j++;
      if (deger.x >= a[j].x) break;
      a[i] = a[j];
      i = j; j *= 2;
    }
    a[i] = deger;
  }
}

Heapify · kodu adım adım çalıştır

Kod izleme: yigin_kur — k = 1

5192123341586770–152931243516877▲ kyigin_kur(a, 7)k = 1

k = 1: 1 köklü alt ağaç düzeltilir. Yeşil düğümler zaten birer heap'in köküdür (yapraklar ve daha önce düzeltilenler).

19212334158677ij0–152931243516877▲ ki▲ jyigin_kur(a, 7)k = 1deger5

deger = a[1] = 5; i = 1, j = 2. Silmedeki aşağı kaydırmanın aynısı başlıyor.

19212334158677ij0–152931243516877▲ ki▲ jyigin_kur(a, 7)k = 1deger5

Çocuklar 9 ve 12: büyüğü a[3] = 12.

12192334158677ij0–1122931243516877▲ ki▲ jyigin_kur(a, 7)k = 1deger5

5 < 12 → a[1] = a[3]; boşluk 3. konuma iner.

12192334158677ij0–1122931243516877▲ k▲ i▲ jyigin_kur(a, 7)k = 1deger5

i = 3, j = 6: boşluğun yeni yerinden devam.

12192334158677ij0–1122931243516877▲ k▲ i▲ jyigin_kur(a, 7)k = 1deger5

Çocuklar 8 ve 7: büyüğü a[6] = 8.

12192833415677ij0–112293843516877▲ k▲ i▲ jyigin_kur(a, 7)k = 1deger5

5 < 8 → a[3] = a[6]; boşluk 6. konuma iner.

12192833415677i0–112293843516877▲ k▲ iyigin_kur(a, 7)k = 1deger5

i = 6, j = 12: boşluğun yeni yerinden devam.

12192833415677i0–112293843516877▲ k▲ iyigin_kur(a, 7)k = 1deger5

j = 12 > n: i = 6 yaprak, döngü biter.

121928334155677i0–112293843516577▲ k▲ iyigin_kur(a, 7)k = 1

a[6] = deger (5). 1 köklü alt ağaç artık bir heap: 12, 9, 8, 3, 1, 5, 7.

1219283341556770–112293843516577yigin_kur(a, 7)

k = 0: döngü bitti. Tüm dizi bir maksimum heap: 12, 9, 8, 3, 1, 5, 7.

void yigin_kur(eleman a[], int n) {
  for (int k = n / 2; k >= 1; k--) {
    eleman deger = a[k];
    int i = k, j = 2 * k;
    while (j <= n) {
      if (j < n && a[j].x < a[j + 1].x) j++;
      if (deger.x >= a[j].x) break;
      a[i] = a[j];
      i = j; j *= 2;
    }
    a[i] = deger;
  }
}

İleri Konular

Heapify neden O(n)?

düzey 0 (kök)1 düğüm × en çok 3 iniş = 3düzey 12 düğüm × en çok 2 iniş = 4düzey 24 düğüm × en çok 1 iniş = 4düzey 3 (yapraklar)8 düğüm × en çok 0 iniş = 0

Düğümlerin yarısı yapraktır ve hiç inmez; çeyreği en çok 1, sekizde biri en çok 2 düzey iner… Toplam iş:

Σh≥0 ⌈n / 2h+1⌉ · O(h) = O(n · Σ h/2h) = O(2n) = O(n)

Örnekte 15 düğümlü tam ağaç için toplam iniş en çok 0·8 + 1·4 + 2·2 + 3·1 = 11 < n. Tek tek ekleme ise alttaki düğümleri de köke kadar çıkarabilir: n lg n.

İleri Konular

d-ary heap: her düğümün d çocuğu

95060180270320450530675740810965102511
İkili (d = 2)d-ary (0 tabanlı)
ata(i−1)/2(i−1)/d
çocuklar2i+1, 2i+2d·i+1 … d·i+d
yükseklik≈ lg n≈ logd n
ekle / anahtar artırO(lg n)O(logd n) — daha hızlı
kökü silO(lg n)O(d · logd n) — d çocuk kıyaslanır

Ekleme/anahtar azaltma çok, silme az ise (ör. yoğun çizgelerde Dijkstra) d > 2 seçmek kazançlıdır; sığ ağaç önbellek (cache) dostudur.

İleri Konular

Öncelikli kuyruk nerelerde kullanılır?

İşletim sistemi

İşlem (process) ve kesme zamanlaması, zamanlayıcı olayları.

Çizge algoritmaları

Dijkstra en kısa yol ve Prim MST: en yakın düğümü seçmek.

Heap sort

Heap kurulur, n kez kök alınır: O(n lg n), yerinde sıralama.

Olay simülasyonu

Olaylar zaman damgasına göre minimum heap'ten çekilir.

k en büyük / en küçük

Boyutu k olan min heap: akan veride O(n lg k).

Huffman kodlaması

En küçük iki frekansı art arda birleştirir.

Özet

Karmaşıklık özeti (n öğeli ikili heap)

İşlemZamanEk bellekNot
en büyüğe bakO(1)O(1)yigin[1]
ekleΩ(1) … O(lg n)O(1)yukarı kaydırma
en büyüğü alO(lg n)O(1)aşağı kaydırma
önceliği değiştirO(lg n)O(1)konum biliniyorsa
aşağıdan yukarı kurmaO(n)O(1)yerinde
n kez ekleyerek kurmaO(n lg n)O(1)
keyfi öğeyi araO(n)O(1)heap arama yapısı değildir

Özet

Sık yapılan hatalar

11 tabanlı (i/2, 2i) ve 0 tabanlı ((i−1)/2, 2i+1) formülleri karıştırmak.
2Dizi boyutunu kapasiteden bir eksik tanımlamak: yigin[199] dışarı yazılır.
3eleman döndüren yordamda return 0; yazmak (derleme hatası).
4j < n sınamasını unutup yigin[j+1]'i okumak (dizi dışı).
5Silmede büyük çocuk yerine ilk çocuğu seçmek: heap özelliği bozulur.
6Heap'i sıralı dizi ya da ikili arama ağacı sanmak.

Özet

Sınav tarzı soru: ekle, ekle, … al, al

Örnek veriyleBaşlangıçta yigin[1..6] = 14, 12, 7, 10, 8, 6 olan maksimum heap'e sırasıyla 15, 109, 107, 3, 15 ekleniyor; ardından maksimum_yigin_al iki kez çağrılıyor.
(i) Eklemelerden sonra dizinin içeriği nedir? (ii) Alınan öğeler ve son dizi nedir? (iii) Hangi ekleme en az, hangisi en çok kaydırma yaptı?
Çözüm
  1. 109, 107, 14, 15, 15, 6, 7, 10, 12, 3, 8 (n = 11).
  2. Alınanlar 109 ve 107; son dizi 15, 15, 14, 12, 3, 6, 7, 10, 8 (n = 9).
  3. 3'ün eklenmesi hiç kaydırma yapmadı (Ω(1)); 109 köke kadar 3 kaydırma yaptı (O(lg n)).

Özet

Alıştırmalar

  1. Boş bir maksimum heap'e 20, 5, 30, 10, 40 ekleyin; her eklemeden sonra diziyi yazın.
  2. maksimum_yigin_al yordamını minimum heap için yazın. Hangi satırlar değişir?
  3. n = 1000 öğeli bir heap'te ekleme en çok kaç kaydırma yapar?
  4. 1 tabanlı bir heap'te 13. düğümün atası ve çocukları hangi indislerdedir?
  5. 5, 3, 8, 9, 1, 12, 7 dizisine yigin_kur yerine art arda ekle uygulanırsa sonuç aynı mı olur?
  6. Bir heap'te en küçük öğe nerelerde olabilir? Bulma maliyeti nedir?
Cevapları göster
  1. 20 → 20, 5 → 30, 5, 20 → 30, 10, 20, 5 → 40, 30, 20, 5, 10.
  2. Çocuk seçiminde [j] > [j+1], durma koşulunda deger.x <= yigin[j].x; adı minimum_yigin_al.
  3. Yükseklik ⌈lg 1001⌉ = 10 düzey → en çok 9 kaydırma (⌊lg 1000⌋ = 9).
  4. Ata 13/2 = 6; çocuklar 26 ve 27 (n ≥ 27 ise).
  5. Hayır; art arda ekleme 12, 8, 9, 3, 1, 5, 7 verir. İkisi de geçerli heap'tir ama farklıdır.
  6. Yalnızca yapraklarda (indis > n/2); yaklaşık n/2 yaprak taranır → O(n).

Özet

Bölüm özeti

Öncelikli kuyruk

İlk alınacak öğe en yüksek (max) ya da en düşük (min) değerli öğedir.

Heap

Tam ikili ağaç + ata ≥ çocuk. 1 tabanlı dizide ata i/2, çocuklar 2i ve 2i+1.

Ekleme

İlk boş konum açılır, büyük atalar aşağı kaydırılır: O(lg n).

Silme

Kök alınır, sondaki öğeye kökten yer aranır, büyük çocuklar yukarı alınır: O(lg n).

Neden heap?

Diğer yapılar bir işlemi Θ(n)'e iter; heap ikisini de logaritmik tutar.

Ötesi

Heapify O(n), d-ary heap, binomial ve Fibonacci heap'leri.

Özet

Kaynaklar ve devam

  • E. Horowitz, S. Sahni, S. Anderson-Freed, Fundamentals of Data Structures in C, Bölüm 5.6 (Heaps), Bölüm 7 (Heap Sort).
  • T. H. Cormen vd., Introduction to Algorithms (CLRS), Bölüm 6 (Heapsort, öncelikli kuyruklar) ve Fibonacci heap'leri bölümü.
  • R. Sedgewick, Algorithms in C, Kısım 1–4, Bölüm 9 (Priority Queues and Heapsort).

Son

Bölüm 13: Öncelikli Kuyruk ve Heap
Sonraki bölüm: Bölüm 14: Hash Tabloları · Ders sayfası
1 / 1 C ile Veri Yapıları