<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://sonsuzus.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://sonsuzus.github.io/" rel="alternate" type="text/html" /><updated>2026-04-15T15:24:36+00:00</updated><id>https://sonsuzus.github.io/feed.xml</id><title type="html">SonsuzUs</title><subtitle>Programlama ve Yazılım</subtitle><author><name>Sonsuz Us</name></author><entry><title type="html">Rekabetçi Programcı Yollar ve Devreler</title><link href="https://sonsuzus.github.io/posts/rekabetci-programci-yollar-ve-devreler" rel="alternate" type="text/html" title="Rekabetçi Programcı Yollar ve Devreler" /><published>2026-04-15T00:00:00+00:00</published><updated>2026-04-15T00:00:00+00:00</updated><id>https://sonsuzus.github.io/posts/rekabetci-programci-yollar-ve-devreler</id><content type="html" xml:base="https://sonsuzus.github.io/posts/rekabetci-programci-yollar-ve-devreler"><![CDATA[<p>Bu bölümde çizgeler üzerindeki iki temel yol türü inceleniyor:</p>

<ul>
  <li><strong>Euler Yolu:</strong> Çizgedeki her kenardan tam olarak bir kez geçen yol.</li>
  <li><strong>Hamilton Yolu:</strong> Çizgedeki her düğümü tam olarak bir kez ziyaret eden yol.</li>
</ul>

<p>İlk bakışta birbirine benzer görünen bu iki kavram aslında tamamen farklı zorluktadır. Bir çizgenin Euler yolu içerip içermediğini belirlemek ve varsa bulmak çok verimli biçimde yapılabilir. Hamilton yolu ise <strong>NP-hard</strong> bir problemdir; bunu verimli çözen bilinen bir algoritma yoktur.
``</p>

<h2 id="191-euler-yolu">19.1 Euler Yolu</h2>

<p><strong>Euler yolu</strong>, çizgedeki her kenardan tam olarak bir kez geçen yoldur. Euler, bu tür yolları 1736’da Königsberg Köprüleri sorusunu araştırırken keşfetmiştir; bu olay çizge teorisinin doğuş noktası olarak kabul edilir.</p>

<p>Bir Euler yolunun aynı düğümde başlayıp bitmesi durumuna <strong>Euler devresi</strong> denir.</p>

<h3 id="euler-yolu-varlık-koşulları">Euler Yolu Varlık Koşulları</h3>

<h4 id="yönsüz-çizgeler">Yönsüz Çizgeler</h4>

<p>Tüm kenarlar aynı bağlı bileşende olduğu varsayılarak:</p>

<ul>
  <li><strong>Her düğümün derecesi çiftse</strong> → Euler devresi vardır (yani her Euler yolu aynı zamanda devredir).</li>
  <li><strong>Tam olarak iki düğümün derecesi tekse, kalanlar çiftse</strong> → Euler yolu vardır fakat devre yoktur; yol tek dereceli iki düğüm arasında uzanır.</li>
</ul>

<p>Başka hiçbir durumda Euler yolu mevcut değildir.</p>

<p>Örneğin 1, 3 ve 4. düğümlerin derecesi 2 (çift), 2 ve 5. düğümlerin derecesi 3 (tek) olan bir çizgede 2. düğümden 5. düğüme bir Euler yolu bulunur; ancak Euler devresi mevcut değildir.</p>

<h4 id="yönlü-çizgeler">Yönlü Çizgeler</h4>

<p>Tüm kenarlar aynı bileşende olduğu varsayılarak:</p>

<ul>
  <li><strong>Her düğümde iç derece = dış derece</strong> → Euler devresi vardır.</li>
  <li><strong>Bir düğümde dış derece = iç derece + 1</strong> (başlangıç), <strong>bir düğümde iç derece = dış derece + 1</strong> (bitiş), <strong>geri kalanlar eşit</strong> → Euler yolu vardır, devre yoktur.</li>
</ul>

<p>Örneğin 1, 3 ve 4. düğümlerin hem iç hem dış derecesi 1 olan; 2. düğümün iç derecesi 1, dış derecesi 2 olan; 5. düğümün iç derecesi 2, dış derecesi 1 olan bir çizge, 2. düğümden 5. düğüme bir Euler yolu içerir.</p>

<h3 id="hierholzerin-algoritması">Hierholzer’in Algoritması</h3>

<p><strong>Hierholzer’in Algoritması</strong>, Euler devresi bulmak için $O(n + m)$ zamanda çalışan verimli bir yöntemdir. Çizgenin Euler devresi içermesi gerekir; aksi hâlde algoritma çalışmaz. Çizgede yalnızca Euler <em>yolu</em> varsa, iki tek dereceli düğüm arasına yapay bir kenar eklenerek Euler devresine dönüştürülüp algoritma uygulanabilir.</p>

<p>Algoritma şu şekilde ilerler:</p>

<ol>
  <li>Başlangıç düğümünden hareket ederek çizgenin bir kısmını kapsayan bir devre oluştur.</li>
  <li>Devrede yer alıp henüz devreye katılmamış komşusu bulunan bir $x$ düğümü bul.</li>
  <li>$x$ düğümünden başlayıp devrede olmayan kenarlarla ilerle; sonunda $x$’e geri dönerek bir alt devre oluştur.</li>
  <li>Bu alt devreyi ana devreye ekle.</li>
  <li>Tüm kenarlar devreye girinceye kadar tekrarla.</li>
</ol>

<h4 id="örnek">Örnek</h4>

<p>Aşağıdaki çizgede algoritma adım adım şöyle ilerler:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>        1
      / | \
     2  3  4
     |  |  |
     5  6  7
</code></pre></div></div>

<ul>
  <li><strong>Adım 1:</strong> 1. düğümden $1 \to 2 \to 3 \to 1$ devresi oluşturulur.</li>
  <li><strong>Adım 2:</strong> 2. düğümünden $2 \to 5 \to 6 \to 2$ alt devresi ana devreye eklenir.</li>
  <li><strong>Adım 3:</strong> 6. düğümünden $6 \to 3 \to 4 \to 7 \to 6$ alt devresi ana devreye eklenir.</li>
</ul>

<p>Tüm kenarlar devreye eklendiğinden Euler devresi başarıyla elde edilir.</p>

<h2 id="192-hamilton-yolları">19.2 Hamilton Yolları</h2>

<p><strong>Hamilton yolu</strong>, çizgedeki her düğümü tam olarak bir kez ziyaret eden yoldur. Aynı düğümde başlayıp bitiyorsa <strong>Hamilton devresi</strong> adını alır.</p>

<h3 id="varlık-koşulları">Varlık Koşulları</h3>

<p>Bir çizgenin Hamilton yolu içerip içermediğini verimli biçimde belirlemenin yolu yoktur; bu problem <strong>NP-hard</strong>‘dır. Yine de bazı özel durumlarda Hamilton yolunun varlığı garantilenebilir:</p>

<ul>
  <li><strong>Tam çizgeler:</strong> Her iki düğüm arasında kenar varsa Hamilton yolu her zaman mevcuttur.</li>
  <li><strong>Dirac’ın Teoremi:</strong> Her düğümün derecesi en az $n/2$ ise çizge Hamilton yolu içerir.</li>
  <li><strong>Ore’nin Teoremi:</strong> Komşu olmayan her düğüm çiftinin derece toplamı en az $n$ ise çizge Hamilton yolu içerir.</li>
</ul>

<p>Bu teoremlerin ortak paydası şudur: çizge ne kadar çok kenar içerirse Hamilton yolu bulunma olasılığı o kadar artar. Teoremler yalnızca yeterlilik koşulu sunar; tersinin garantisi yoktur.</p>

<h3 id="hamilton-yolunun-bulunması">Hamilton Yolunun Bulunması</h3>

<p>Varlığı bile verimli belirlenemeyen Hamilton yolunu bulmak da aynı şekilde zorludur. İki yaklaşım vardır:</p>

<p><strong>Geri izleme (backtracking):</strong> Tüm olası düğüm sıralamaları denenir. Zaman karmaşıklığı $O(n!)$’dir; küçük $n$ değerleri için kullanılabilir.</p>

<p><strong>Dinamik programlama:</strong> $\text{possible}(S, x)$ fonksiyonu, $S$ düğüm alt kümesini dolaşıp $x$ düğümünde biten bir Hamilton yolunun var olup olmadığını gösterir. $S$ bir bit maskesiyle temsil edilir ve fonksiyon aşağıdaki özyinelemeyle hesaplanır:</p>

\[\text{possible}(S, x) = \bigvee_{y \in S,\, y \neq x,\, (y,x) \in E} \text{possible}(S \setminus \{x\}, y)\]

<p>Bu yaklaşım $O(2^n n^2)$ zamanda çalışır; geri izlemeye kıyasla çok daha hızlıdır (bkz. Bölüm 10.5).</p>

<h2 id="193-de-bruijn-dizileri">19.3 De Bruijn Dizileri</h2>

<p><strong>De Bruijn dizisi</strong>, $k$ karakterden oluşan alfabe üzerinde $n$ uzunluğundaki her alt yazıyı tam olarak bir kez içeren dairesel bir dizdir. Doğrusal biçimde yazıldığında uzunluğu $k^n + n - 1$ olur.</p>

<p>Örneğin $n = 3$, $k = 2$ için bir De Bruijn dizisi:</p>

\[\texttt{0001011100}\]

<p>Bu dizi ${0, 1}$ alfabesi üzerindeki tüm 3 bitlik dizileri tam birer kez içerir: $000, 001, 010, 011, 100, 101, 110, 111$.</p>

<h3 id="euler-yolu-bağlantısı">Euler Yolu Bağlantısı</h3>

<p>De Bruijn dizisi inşa etmek, bir çizgede Euler yolu bulmaya indirgenir. Çizgenin düğümleri $n - 1$ karakterlik tüm yazılara, kenarlar ise bu yazılara bir karakter ekleyerek elde edilen $n$ karakterlik geçişlere karşılık gelir.</p>

<p>Örneğin $n = 3$, $k = 2$ için dört düğüm vardır: $00, 01, 10, 11$. $00$ düğümünden $0$ karakteri eklenince $00$ düğümüne ($000$), $1$ eklenince $01$ düğümüne ($001$) gidilir. Bu çizgedeki her Euler yolu tüm $n$ uzunluğundaki alt yazıları tam birer kez kapsar.</p>

<p>Yazının uzunluğu: başlangıç düğümünün $n - 1$ karakteri ile $k^n$ kenardaki karakterlerin toplamından $k^n + n - 1$ olur.</p>

<h2 id="194-atın-turları">19.4 Atın Turları</h2>

<p><strong>Atın turu</strong>, bir satranç atının $n \times n$ tahtada her kareyi tam bir kez ziyaret ettiği hareket dizisidir. At başladığı kareye dönüyorsa <strong>kapalı tur</strong>, dönmüyorsa <strong>açık tur</strong> denir.</p>

<p>Örneğin $5 \times 5$ tahtadaki bir açık at turu:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> 1  4 11 16 25
12 17  2  5 10
 3 20  7 24 15
18 13 22  9  6
21  8 19 14 23
</code></pre></div></div>

<p>At turu, kareler düğüm ve at hareketiyle ulaşılabilecek kareler kenar olarak modellenen bir çizgede <strong>Hamilton yoluna</strong> karşılık gelir.</p>

<h3 id="warnsdorfun-kuralı">Warnsdorf’un Kuralı</h3>

<p>Warnsdorf’un Kuralı, at turu bulmak için basit ve etkili bir <strong>sezgisel (heuristic)</strong> yöntemdir (1823). Temel fikir şudur: <strong>atı her adımda, oradan yapılabilecek hareket sayısı en az olan kareye götür.</strong></p>

<p>Örneğin beş farklı kare arasından seçim yapılacak bir durumda, birinden yalnızca 1 hamle yapılabiliyorsa Warnsdorf kuralı atı o kareye yönlendirir; böylece “çıkmaz sokak” riski en aza indirilir.</p>

<p>Bu yaklaşım büyük tahtalarda dahi pratik sürelerde tur bulmayı mümkün kılar. At turu için polinom zamanlı algoritmalar da geliştirilmiştir; ancak bunlar çok daha karmaşıktır.</p>]]></content><author><name>Sonsuz Us</name></author><category term="Program" /><category term="çizge" /><category term="graph" /><category term="euler-yolu" /><category term="euler-devresi" /><category term="hamilton-yolu" /><category term="hierholzer" /><category term="de-bruijn" /><category term="atın-turu" /><category term="warnsdorf" /><category term="np-hard" /><category term="geri-izleme" /><category term="dinamik-programlama" /><category term="c" /><category term="programlama" /><category term="algoritma" /><category term="olimpiyat" /><category term="yarışma" /><category term="kodlama" /><category term="matematik" /><category term="kitap" /><summary type="html"><![CDATA[Bu bölümde çizgeler üzerindeki iki temel yol türü inceleniyor: Euler Yolu: Çizgedeki her kenardan tam olarak bir kez geçen yol. Hamilton Yolu: Çizgedeki her düğümü tam olarak bir kez ziyaret eden yol. İlk bakışta birbirine benzer görünen bu iki kavram aslında tamamen farklı zorluktadır. Bir çizgenin Euler yolu içerip içermediğini belirlemek ve varsa bulmak çok verimli biçimde yapılabilir. Hamilton yolu ise NP-hard bir problemdir; bunu verimli çözen bilinen bir algoritma yoktur.]]></summary></entry><entry><title type="html">Rekabetçi Programcı Ağaç Sorguları</title><link href="https://sonsuzus.github.io/posts/rekabetci-programci-agac-sorgulari" rel="alternate" type="text/html" title="Rekabetçi Programcı Ağaç Sorguları" /><published>2026-04-14T00:00:00+00:00</published><updated>2026-04-14T00:00:00+00:00</updated><id>https://sonsuzus.github.io/posts/rekabetci-programci-agac-sorgulari</id><content type="html" xml:base="https://sonsuzus.github.io/posts/rekabetci-programci-agac-sorgulari"><![CDATA[<p>Bu bölümde köklü ağaçların alt ağaçları ve yolları üzerinde yapılan sorguları çözme yöntemlerini inceliyoruz. Ele alınan sorgu türleri şunlardır:</p>

<ul>
  <li>Bir düğümün $k$. atası hangi düğümdür?</li>
  <li>Bir alt ağaçtaki değerlerin toplamı kaçtır?</li>
  <li>İki düğüm arasındaki yolda bulunan değerlerin toplamı kaçtır?</li>
  <li>İki düğümün en yakın ortak atası hangisidir?
``</li>
</ul>

<h2 id="181-ataları-bulmak">18.1 Ataları Bulmak</h2>

<p>Köklü bir ağaçta $x$ düğümünün <strong>$k$. atası</strong>, $x$’ten $k$ defa yukarı çıkıldığında ulaşılan düğümdür. $\text{ancestor}(x, k)$ ile gösterilir; eğer bu ata yoksa değer $0$ kabul edilir. Örneğin bir ağaçta $\text{ancestor}(2, 1) = 1$ ve $\text{ancestor}(8, 2) = 4$ olabilir.</p>

<p>Naif yöntemle $\text{ancestor}(x, k)$, ağaçta $k$ adım yukarı çıkılarak bulunur; ancak bu $O(k)$ zaman alır ve $n$ düğümlü bir zincir şeklindeki ağaçta oldukça yavaş kalabilir.</p>

<p><strong>İkili kaldırma (binary lifting)</strong> ile Bölüm 16.3’teki ardıl yol tekniğine benzer biçimde $\text{ancestor}(x, k)$ değeri $O(\log k)$ zamanda hesaplanabilir. Her düğüm için $2$’nin kuvvetlerine karşılık gelen atalar önceden hesaplanır:</p>

<table>
  <thead>
    <tr>
      <th style="text-align: center">$x$</th>
      <th style="text-align: center">1</th>
      <th style="text-align: center">2</th>
      <th style="text-align: center">3</th>
      <th style="text-align: center">4</th>
      <th style="text-align: center">5</th>
      <th style="text-align: center">6</th>
      <th style="text-align: center">7</th>
      <th style="text-align: center">8</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center">$\text{ancestor}(x, 1)$</td>
      <td style="text-align: center">0</td>
      <td style="text-align: center">1</td>
      <td style="text-align: center">4</td>
      <td style="text-align: center">1</td>
      <td style="text-align: center">1</td>
      <td style="text-align: center">2</td>
      <td style="text-align: center">4</td>
      <td style="text-align: center">7</td>
    </tr>
    <tr>
      <td style="text-align: center">$\text{ancestor}(x, 2)$</td>
      <td style="text-align: center">0</td>
      <td style="text-align: center">0</td>
      <td style="text-align: center">1</td>
      <td style="text-align: center">0</td>
      <td style="text-align: center">0</td>
      <td style="text-align: center">1</td>
      <td style="text-align: center">1</td>
      <td style="text-align: center">4</td>
    </tr>
    <tr>
      <td style="text-align: center">$\text{ancestor}(x, 4)$</td>
      <td style="text-align: center">0</td>
      <td style="text-align: center">0</td>
      <td style="text-align: center">0</td>
      <td style="text-align: center">0</td>
      <td style="text-align: center">0</td>
      <td style="text-align: center">0</td>
      <td style="text-align: center">0</td>
      <td style="text-align: center">0</td>
    </tr>
  </tbody>
</table>

<p>Ön işleme $O(n \log n)$ zaman alır; her düğüm için $O(\log n)$ değer hesaplanır. Ardından herhangi bir $\text{ancestor}(x, k)$ değeri, $k$’yı $2$’nin kuvvetlerinin toplamı olarak ifade ederek $O(\log k)$ zamanda bulunabilir.</p>

<h2 id="182-alt-ağaçlar-ve-yollar">18.2 Alt Ağaçlar ve Yollar</h2>

<p><strong>Ağaç dolaşma dizisi</strong> (tree traversal array), köklü bir ağacın düğümlerini kök düğümünden başlayan DFS ziyaret sırasına göre sıralar. Örneğin aşağıdaki ağaçta:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>        1
      / | \ \
     2  3  4  5
     |     |\ 
     6     7 8
             |
             9
</code></pre></div></div>

<p>DFS sırası ve ağaç dolaşma dizisi şöyle olur:</p>

\[[1,\ 2,\ 6,\ 3,\ 4,\ 7,\ 8,\ 9,\ 5]\]

<h3 id="alt-ağaç-sorguları">Alt Ağaç Sorguları</h3>

<p>Bir ağaçtaki her alt ağaç, dolaşma dizisinin <strong>ardışık bir alt dizisine</strong> karşılık gelir. Bu alt dizinin ilk elemanı alt ağacın kök düğümüdür. Örneğin 4. düğümün alt ağacı aşağıdaki vurgulanan bölüme denk gelir:</p>

\[[1,\ 2,\ 6,\ 3,\ \mathbf{4,\ 7,\ 8,\ 9},\ 5]\]

<p>Bu yapıyı kullanarak şu iki sorguyu verimli biçimde çözebiliriz:</p>

<ul>
  <li>Bir düğümün değerini güncelle.</li>
  <li>Bir düğümün alt ağacındaki değerlerin toplamını hesapla.</li>
</ul>

<p>Her düğüm için üç değer tutan bir dolaşma dizisi oluşturulur: düğüm kimliği, alt ağaç büyüklüğü ve düğümün değeri. Örnek ağaç için:</p>

<table>
  <thead>
    <tr>
      <th style="text-align: center">düğüm</th>
      <th style="text-align: center">1</th>
      <th style="text-align: center">2</th>
      <th style="text-align: center">6</th>
      <th style="text-align: center">3</th>
      <th style="text-align: center">4</th>
      <th style="text-align: center">7</th>
      <th style="text-align: center">8</th>
      <th style="text-align: center">9</th>
      <th style="text-align: center">5</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center">alt ağaç büyüklüğü</td>
      <td style="text-align: center">9</td>
      <td style="text-align: center">2</td>
      <td style="text-align: center">1</td>
      <td style="text-align: center">1</td>
      <td style="text-align: center">4</td>
      <td style="text-align: center">1</td>
      <td style="text-align: center">1</td>
      <td style="text-align: center">1</td>
      <td style="text-align: center">1</td>
    </tr>
    <tr>
      <td style="text-align: center">düğümün değeri</td>
      <td style="text-align: center">2</td>
      <td style="text-align: center">3</td>
      <td style="text-align: center">4</td>
      <td style="text-align: center">5</td>
      <td style="text-align: center">3</td>
      <td style="text-align: center">4</td>
      <td style="text-align: center">3</td>
      <td style="text-align: center">1</td>
      <td style="text-align: center">1</td>
    </tr>
  </tbody>
</table>

<ol>
  <li>düğümün alt ağaç toplamı alt ağaç büyüklüğü sayesinde doğrudan tespit edilir: $3 + 4 + 3 + 1 = 11$. Bu sorguları $O(\log n)$ sürede çözmek için değerleri bir <strong>ikili indeksli ağaç</strong> (binary indexed tree / Fenwick tree) ya da <strong>bölüm ağacı</strong> (segment tree) üzerinde tutmak yeterlidir.</li>
</ol>

<h3 id="yol-sorguları">Yol Sorguları</h3>

<p>Ağaç dolaşma dizisi, kök düğümden herhangi bir düğüme uzanan yoldaki değerlerin toplamını bulmak için de kullanılabilir. Çözülmek istenen sorgular:</p>

<ul>
  <li>Bir düğümün değerini değiştir.</li>
  <li>Kökten bir düğüme uzanan yoldaki değerlerin toplamını hesapla.</li>
</ul>

<p>Bu kez dolaşma dizisinin son satırında her düğüm için köke olan <strong>yol toplamı</strong> tutulur:</p>

<table>
  <thead>
    <tr>
      <th style="text-align: center">düğüm</th>
      <th style="text-align: center">1</th>
      <th style="text-align: center">2</th>
      <th style="text-align: center">6</th>
      <th style="text-align: center">3</th>
      <th style="text-align: center">4</th>
      <th style="text-align: center">7</th>
      <th style="text-align: center">8</th>
      <th style="text-align: center">9</th>
      <th style="text-align: center">5</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center">alt ağaç büyüklüğü</td>
      <td style="text-align: center">9</td>
      <td style="text-align: center">2</td>
      <td style="text-align: center">1</td>
      <td style="text-align: center">1</td>
      <td style="text-align: center">4</td>
      <td style="text-align: center">1</td>
      <td style="text-align: center">1</td>
      <td style="text-align: center">1</td>
      <td style="text-align: center">1</td>
    </tr>
    <tr>
      <td style="text-align: center">yol toplamı</td>
      <td style="text-align: center">4</td>
      <td style="text-align: center">9</td>
      <td style="text-align: center">12</td>
      <td style="text-align: center">7</td>
      <td style="text-align: center">9</td>
      <td style="text-align: center">14</td>
      <td style="text-align: center">12</td>
      <td style="text-align: center">10</td>
      <td style="text-align: center">6</td>
    </tr>
  </tbody>
</table>

<p>Bir düğümün değeri $x$ arttırıldığında, onun alt ağacındaki tüm düğümlerin yol toplamı $x$ kadar artar. Örneğin 4. düğümün değeri 1 arttırılırsa:</p>

<table>
  <thead>
    <tr>
      <th style="text-align: center">düğüm</th>
      <th style="text-align: center">1</th>
      <th style="text-align: center">2</th>
      <th style="text-align: center">6</th>
      <th style="text-align: center">3</th>
      <th style="text-align: center">4</th>
      <th style="text-align: center">7</th>
      <th style="text-align: center">8</th>
      <th style="text-align: center">9</th>
      <th style="text-align: center">5</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center">alt ağaç büyüklüğü</td>
      <td style="text-align: center">9</td>
      <td style="text-align: center">2</td>
      <td style="text-align: center">1</td>
      <td style="text-align: center">1</td>
      <td style="text-align: center">4</td>
      <td style="text-align: center">1</td>
      <td style="text-align: center">1</td>
      <td style="text-align: center">1</td>
      <td style="text-align: center">1</td>
    </tr>
    <tr>
      <td style="text-align: center">yol toplamı</td>
      <td style="text-align: center">4</td>
      <td style="text-align: center">9</td>
      <td style="text-align: center">12</td>
      <td style="text-align: center">7</td>
      <td style="text-align: center">10</td>
      <td style="text-align: center">15</td>
      <td style="text-align: center">13</td>
      <td style="text-align: center">11</td>
      <td style="text-align: center">6</td>
    </tr>
  </tbody>
</table>

<p>Bu işlem bir aralıktaki tüm değerleri artırma ve tekil bir değeri okuma sorgusuna dönüşür; her ikisi de ikili indeksli ağaç veya bölüm ağacı ile $O(\log n)$ zamanda yapılabilir (bkz. Bölüm 9.4).</p>

<h2 id="183-en-yakın-ortak-ata-lca">18.3 En Yakın Ortak Ata (LCA)</h2>

<p>İki düğümün <strong>en yakın ortak atası</strong> (Lowest Common Ancestor, LCA), köklü ağaçta her iki düğümü de alt ağacında barındıran en alttaki düğümdür. Örneğin 5 ve 8. düğümünün LCA’sı 2. düğümdür.</p>

<h3 id="1-yöntem-i̇kili-kaldırma">1. Yöntem: İkili Kaldırma</h3>

<p>Bu yöntem, $k$. atayı verimli bulan ön işlemeye (18.1) dayanır. İki adımdan oluşur:</p>

<p><strong>Adım 1 — Aynı derinliğe getir:</strong> İki düğümü gösteren işaretçiler tutulur. Daha derinde olan işaretçi, diğeriyle aynı derinliğe gelene kadar yukarı taşınır. Örneğin 8. düğümü gösteren işaretçi bir seviye yukarı çıkıp 6. düğümü gösterince 5. düğümle aynı seviyeye ulaşır.</p>

<p><strong>Adım 2 — Birlikte yukarı çık:</strong> İki işaretçi aynı düğüme ulaşana kadar gereken minimum adım sayısı ikili kaldırmayla hesaplanır; her iki işaretçi bu kadar yukarı taşınır. İşaretçilerin buluştuğu düğüm LCA’dır.</p>

<p>Her iki adım da $O(\log n)$ zamanda çalışır.</p>

<h3 id="2-yöntem-euler-turu">2. Yöntem: Euler Turu</h3>

<p>Bu yöntemde DFS sırasında her düğüm yalnızca ilk ziyarette değil, <strong>her ziyarette</strong> diziye eklenir. $k$ çocuğu olan bir düğüm dizide $k + 1$ kez yer alır; dolayısıyla toplam dizi uzunluğu $2n - 1$ olur.</p>

<p>Dizi iki değer tutar: düğüm kimliği ve derinlik. Örnek ağaç için dizi:</p>

<table>
  <thead>
    <tr>
      <th style="text-align: center">indeks</th>
      <th style="text-align: center">0</th>
      <th style="text-align: center">1</th>
      <th style="text-align: center">2</th>
      <th style="text-align: center">3</th>
      <th style="text-align: center">4</th>
      <th style="text-align: center">5</th>
      <th style="text-align: center">6</th>
      <th style="text-align: center">7</th>
      <th style="text-align: center">8</th>
      <th style="text-align: center">9</th>
      <th style="text-align: center">10</th>
      <th style="text-align: center">11</th>
      <th style="text-align: center">12</th>
      <th style="text-align: center">13</th>
      <th style="text-align: center">14</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center">düğüm</td>
      <td style="text-align: center">1</td>
      <td style="text-align: center">2</td>
      <td style="text-align: center">5</td>
      <td style="text-align: center">2</td>
      <td style="text-align: center">6</td>
      <td style="text-align: center">8</td>
      <td style="text-align: center">6</td>
      <td style="text-align: center">2</td>
      <td style="text-align: center">1</td>
      <td style="text-align: center">3</td>
      <td style="text-align: center">1</td>
      <td style="text-align: center">4</td>
      <td style="text-align: center">7</td>
      <td style="text-align: center">4</td>
      <td style="text-align: center">1</td>
    </tr>
    <tr>
      <td style="text-align: center">derinlik</td>
      <td style="text-align: center">1</td>
      <td style="text-align: center">2</td>
      <td style="text-align: center">3</td>
      <td style="text-align: center">2</td>
      <td style="text-align: center">3</td>
      <td style="text-align: center">4</td>
      <td style="text-align: center">3</td>
      <td style="text-align: center">2</td>
      <td style="text-align: center">1</td>
      <td style="text-align: center">2</td>
      <td style="text-align: center">1</td>
      <td style="text-align: center">2</td>
      <td style="text-align: center">3</td>
      <td style="text-align: center">2</td>
      <td style="text-align: center">1</td>
    </tr>
  </tbody>
</table>

<p>$a$ ve $b$ düğümlerinin LCA’sı, dizide $a$ ile $b$’nin konumları arasındaki <strong>minimum derinlikli</strong> düğüme karşılık gelir. Örneğin 5. düğüm dizinin 2. indeksinde, 8. düğüm ise 5. indeksindedir. $[2, 5]$ aralığında minimum derinlik 2 olup bu 3. indeksteki 2. düğüme karşılık gelir; dolayısıyla LCA = 2.</p>

<p>Bu sayede LCA sorgusu bir <strong>aralık minimum sorgusuna</strong> dönüşür. Dizi statik olduğundan $O(n \log n)$ ön işlemeyle sonraki her sorgu $O(1)$ zamanda yanıtlanabilir.</p>

<h3 id="düğümler-arası-mesafe">Düğümler Arası Mesafe</h3>

<p>$a$ ile $b$ arasındaki mesafe, LCA bulunduktan sonra şu formülle hesaplanır:</p>

\[\text{dist}(a, b) = \text{depth}(a) + \text{depth}(b) - 2 \cdot \text{depth}(\text{lca}(a, b))\]

<p>Örneğin 5 ve 8. düğümler için LCA = 2, $\text{depth}(5) = 3$, $\text{depth}(8) = 4$, $\text{depth}(2) = 2$ olduğundan:</p>

\[\text{dist}(5, 8) = 3 + 4 - 2 \cdot 2 = 3\]

<h2 id="184-çevrimdışı-algoritmalar">18.4 Çevrimdışı Algoritmalar</h2>

<p>Şimdiye kadar incelenen algoritmalar <strong>çevrimiçi</strong>dir; yani her sorguyu bir sonraki gelmeden yanıtlayabilirler. Ancak pek çok problemde bu özellik zorunlu değildir. <strong>Çevrimdışı</strong> algoritmalarda tüm sorgular önceden bilinir ve herhangi bir sırayla yanıtlanabilir; bu da genellikle daha kolay implementasyona olanak tanır.</p>

<h3 id="veri-yapılarını-birleştirmek">Veri Yapılarını Birleştirmek</h3>

<p>Çevrimdışı yaklaşımın bir yolu, DFS sırasında her düğümde bir veri yapısı $d[s]$ oluşturmak ve çocukların veri yapılarını bu yapıyla birleştirmektir.</p>

<p>Örneğin şu problemi düşünelim: her düğümde bir değer bulunan ağaçta, “$s$ düğümünün alt ağacında $x$ değerine sahip kaç düğüm var?” sorgularını yanıtlamak istiyoruz. Çözüm için <code class="language-plaintext highlighter-rouge">map</code> veri yapısı kullanılabilir.</p>

<p>Naif yaklaşımda her düğüm için baştan bir map oluşturmak çok yavaş olur. Bunun yerine her $s$ düğümü için yalnızca kendi değerini içeren bir başlangıç $d[s]$ oluşturulur; ardından her çocuk $u$ için $d[u]$ ile $d[s]$ birleştirilir. Birleştirme sırasında $d[s]$, $d[u]$’dan küçükse önce yer değiştirme yapılır:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">swap</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">);</span>
</code></pre></div></div>

<p>Bu fonksiyon, C++ standart kütüphanesi veri yapıları için sabit zamanda çalışır. Bu strateji sayesinde her değer ağaç dolaşımı boyunca en fazla $O(\log n)$ kez kopyalanır ve algoritma verimli kalır.</p>

<h3 id="çevrimdışı-lca-tarjanın-algoritması">Çevrimdışı LCA: Tarjan’ın Algoritması</h3>

<p>En yakın ortak ataları çevrimdışı bulmak için Tarjan’ın Algoritması kullanılabilir. Bu algoritma, <strong>union-find</strong> yapısı üzerine kurulu olup (bkz. Bölüm 15.2) bölümün başında anlatılan çevrimiçi yöntemlere kıyasla daha kolay koda dökülebilir.</p>

<p>Girdi olarak düğüm çiftleri alınır ve her çift için LCA hesaplanır. Algoritma DFS ile ağacı dolaşırken ayrık gruplar tutar; başlangıçta her düğüm ayrı bir gruptadır. Her grup için o gruptaki en üstteki düğüm kaydedilir.</p>

<p>Algoritma bir $x$ düğümünü ziyaret ettiğinde, LCA’sı $x$ ile birlikte sorgulanacak tüm $y$ düğümleri incelenir. Eğer $y$ önceden ziyaret edildiyse, $x$ ve $y$’nin LCA’sı $y$’nin grubundaki en üst düğümdür. $x$ düğümü tamamen işlendikten sonra $x$ ile ebeveyninin grupları birleştirilir.</p>

<p>Örneğin $(5, 8)$ ve $(2, 7)$ çiftlerinin LCA’larını bulmak istediğimiz bir ağaçta:</p>

<ul>
  <li>Algoritma 8. düğümü ziyaret ettiğinde 5. düğümün önceden ziyaret edildiğini görür; 5’in grubundaki en üst düğüm 2’dir. Dolayısıyla $\text{lca}(5, 8) = 2$.</li>
  <li>Algoritma 7. düğümü ziyaret ettiğinde 2. düğümün önceden ziyaret edildiğini görür; 2’nin grubundaki en üst düğüm 1’dir. Dolayısıyla $\text{lca}(2, 7) = 1$.</li>
</ul>

<p>Algoritmanın zaman karmaşıklığı, union-find işlemleriyle birlikte pratikte neredeyse lineer olan $O(n \alpha(n))$’dir.</p>]]></content><author><name>Sonsuz Us</name></author><category term="Program" /><category term="ağaç" /><category term="tree" /><category term="köklü-ağaç" /><category term="ata" /><category term="ancestor" /><category term="lca" /><category term="en-yakın-ortak-ata" /><category term="lowest-common-ancestor" /><category term="euler-turu" /><category term="ağaç-dolaşma-dizisi" /><category term="binary-lifting" /><category term="union-find" /><category term="segment-tree" /><category term="dfs" /><category term="c" /><category term="programlama" /><category term="algoritma" /><category term="olimpiyat" /><category term="yarışma" /><category term="kodlama" /><category term="matematik" /><category term="kitap" /><summary type="html"><![CDATA[Bu bölümde köklü ağaçların alt ağaçları ve yolları üzerinde yapılan sorguları çözme yöntemlerini inceliyoruz. Ele alınan sorgu türleri şunlardır: Bir düğümün $k$. atası hangi düğümdür? Bir alt ağaçtaki değerlerin toplamı kaçtır? İki düğüm arasındaki yolda bulunan değerlerin toplamı kaçtır? İki düğümün en yakın ortak atası hangisidir?]]></summary></entry><entry><title type="html">Rekabetçi Programcı Güçlü Bağlanırlık</title><link href="https://sonsuzus.github.io/posts/rekabetci-programci-guclu-baglanirlik" rel="alternate" type="text/html" title="Rekabetçi Programcı Güçlü Bağlanırlık" /><published>2026-04-11T00:00:00+00:00</published><updated>2026-04-11T00:00:00+00:00</updated><id>https://sonsuzus.github.io/posts/rekabetci-programci-guclu-baglanirlik</id><content type="html" xml:base="https://sonsuzus.github.io/posts/rekabetci-programci-guclu-baglanirlik"><![CDATA[<p>Yönlü bir çizgede kenarlar yalnızca tek yönlü geçilir. Bu nedenle çizge bağlı olsa bile her düğümden diğerine gidileceği garanti edilemez. Daha güçlü bir bağlanırlık kavramına ihtiyaç vardır.</p>

<p>Bir çizge, her düğümden diğer tüm düğümlere gidilebiliyorsa <strong>güçlü bağlanılmış</strong> (strongly connected) olarak adlandırılır. Güçlü bağlanılmamış bir çizgede ise bazı düğüm çiftleri arasında tek yönlü bir ulaşım bile mümkün olmayabilir.</p>

<p><strong>Güçlü bağlanılmış parçalar</strong> (strongly connected components, SCC), çizgeyi olabildiğince büyük güçlü bağlanılmış bölümlere ayırır. Bu parçalar, orijinal çizgenin derin yapısını ortaya koyan asiklik bir <strong>bileşen çizgesi</strong> (component graph) oluşturur.
``
Örneğin bir çizgenin $A = {1, 2}$, $B = {3, 6, 7}$, $C = {4}$ ve $D = {5}$ şeklinde dört güçlü bağlanılmış parçası varsa, bileşen çizgesi bu dört düğüm üzerinde döngüsüz bir yönlü çizge oluşturur. Bu yapı döngü içermediği için 16. Bölümdeki topolojik sıralama ve dinamik programlama teknikleri doğrudan uygulanabilir.</p>

<h2 id="171-kosarajunun-algoritması">17.1 Kosaraju’nun Algoritması</h2>

<p>Kosaraju’nun Algoritması, yönlü bir çizgedeki tüm güçlü bağlanılmış parçaları $O(n + m)$ zamanda bulan verimli bir algoritmadır. İki ayrı DFS (derinlik öncelikli arama) kullanır.</p>

<h3 id="1-arama">1. Arama</h3>

<p>İlk DFS, tüm düğümlerden geçerek her işlenmemiş düğümden bir arama başlatır. Her düğüm tamamen işlendiğinde bir listeye eklenir. Bu listedeki sıra, düğümlerin <strong>bitiş zamanlarına</strong> göre belirlenir.</p>

<p>Örneğin şu çizgede düğümler şu sırayla işlenip listeye eklenir:</p>

<table>
  <thead>
    <tr>
      <th style="text-align: center">Düğüm</th>
      <th style="text-align: center">Bitiş Zamanı</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center">4</td>
      <td style="text-align: center">5</td>
    </tr>
    <tr>
      <td style="text-align: center">5</td>
      <td style="text-align: center">6</td>
    </tr>
    <tr>
      <td style="text-align: center">2</td>
      <td style="text-align: center">7</td>
    </tr>
    <tr>
      <td style="text-align: center">1</td>
      <td style="text-align: center">8</td>
    </tr>
    <tr>
      <td style="text-align: center">6</td>
      <td style="text-align: center">12</td>
    </tr>
    <tr>
      <td style="text-align: center">7</td>
      <td style="text-align: center">13</td>
    </tr>
    <tr>
      <td style="text-align: center">3</td>
      <td style="text-align: center">14</td>
    </tr>
  </tbody>
</table>

<h3 id="2-arama">2. Arama</h3>

<p>İkinci aşamada çizgedeki <strong>tüm kenarlar ters çevrilir</strong>. Bu, ikinci aramanın güçlü bağlanılmış parçaların dışına “sızmamasını” garanti eder.</p>

<p>Ardından birinci aramada oluşturulan listedeki düğümler <strong>ters sırada</strong> ziyaret edilir. İşlenmemiş her düğümden yeni bir parça oluşturulup DFS başlatılır; bu aramada bulunan tüm düğümler söz konusu parçaya eklenir.</p>

<p>Örnek çizgede önce 3. düğümden bir parça oluşturulur. Sonraki düğümler olan 7 ve 6 zaten bu parçaya ait olduğundan atlanır; yeni parça 1. düğümde başlar. En sonda 5 ve 4. düğümler işlenerek kalan parçalar tamamlanır.</p>

<p>Algoritmanın zaman karmaşıklığı $O(n + m)$’dir; iki DFS kullanılmasına karşın her biri lineer zamanda çalışır.</p>

<h2 id="172-2sat-problemi">17.2 2SAT Problemi</h2>

<p>Güçlü bağlanırlık, <strong>2SAT</strong> adı verilen önemli bir mantıksal tatmin problemiyle de doğrudan ilişkilidir. Problemde şu biçimde bir formül verilir:</p>

\[(a_1 \lor b_1) \land (a_2 \lor b_2) \land \cdots \land (a_m \lor b_m)\]

<p>Burada her $a_i$ ve $b_i$ ya bir mantıksal değişken ($x_1, x_2, \ldots, x_n$) ya da onun karşıtıdır ($\lnot x_1, \lnot x_2, \ldots, \lnot x_n$). Görev, formülü doğru yapacak bir değer ataması bulmak ya da böyle bir atamanın mümkün olmadığını göstermektir.</p>

<h3 id="örnek-1-çözülebilir">Örnek 1 (Çözülebilir)</h3>

\[L_1 = (x_2 \lor \lnot x_1) \land (\lnot x_1 \lor \lnot x_2) \land (x_1 \lor x_3) \land (\lnot x_2 \lor \lnot x_3) \land (x_1 \lor x_4)\]

<p>Bu formül aşağıdaki atama ile sağlanır:</p>

\[x_1 = \text{false},\quad x_2 = \text{false},\quad x_3 = \text{true},\quad x_4 = \text{true}\]

<h3 id="örnek-2-çözülemez">Örnek 2 (Çözülemez)</h3>

\[L_2 = (x_1 \lor x_2) \land (x_1 \lor \lnot x_2) \land (\lnot x_1 \lor x_3) \land (\lnot x_1 \lor \lnot x_3)\]

<p>$x_1$ için ne değer verilirse verilsin bir çelişki ortaya çıkar: $x_1 = \text{false}$ ise hem $x_2$ hem $\lnot x_2$ doğru olmalıdır (imkânsız); $x_1 = \text{true}$ ise hem $x_3$ hem $\lnot x_3$ doğru olmalıdır (imkânsız).</p>

<h3 id="çizge-modeli">Çizge Modeli</h3>

<p>2SAT problemi bir çizgeye dönüştürülür. Düğümler $x_i$ ve $\lnot x_i$ değişkenlerini, kenarlar ise değişkenler arası çıkarımları temsil eder. Her $(a_i \lor b_i)$ çifti iki kenar üretir:</p>

\[\lnot a_i \to b_i \qquad \text{ve} \qquad \lnot b_i \to a_i\]

<p>Bu, “$a_i$ yanlışsa $b_i$ doğru olmalı, ya da tam tersi” mantığını ifade eder.</p>

<p>$L_1$ için oluşan çizgede $x_i$ ve $\lnot x_i$ hiçbir zaman aynı güçlü bağlanılmış parçada bulunmaz; dolayısıyla bir çözüm mevcuttur. $L_2$ için ise tüm düğümler aynı güçlü bağlanılmış parçaya aittir; dolayısıyla çözüm <strong>yoktur</strong>.</p>

<p><strong>Genel kural:</strong> Formülün bir çözümü vardır ancak ve ancak hiçbir $x_i$ ile $\lnot x_i$ aynı güçlü bağlanılmış parçada bulunmuyorsa.</p>

<h3 id="çözümü-bulmak">Çözümü Bulmak</h3>

<p>Çözüm mevcutsa değişken değerleri <strong>ters topolojik sıraya</strong> göre belirlenir. Her adımda, başka işlenmemiş parçalara kenarı olmayan bir parça seçilir:</p>

<ul>
  <li>Parçadaki değişkenlere henüz değer verilmemişse parçanın konumuna göre değer atanır.</li>
  <li>Değer zaten atanmışsa değiştirilmez.</li>
</ul>

<p>Bu işlem tüm değişkenlere birer değer verilinceye kadar sürer.</p>

<p>Örneğin $L_1$ için bileşenler $A = {\lnot x_4}$, $B = {x_1, x_2, \lnot x_3}$, $C = {\lnot x_1, \lnot x_2, x_3}$ ve $D = {x_4}$ olarak bulunur. Ters topolojik sırayla önce $D$ işlenir ve $x_4 = \text{true}$ olur. Ardından $C$ işlenir ve $x_1 = \text{false}$, $x_2 = \text{false}$, $x_3 = \text{true}$ atanır. Tüm değişkenler atandığından $A$ ve $B$ parçaları değişkenleri değiştirmez.</p>

<p>Bu yöntemin doğruluğunu şöyle anlayabiliriz: eğer $x_i$ düğümünden $x_j$ düğümüne, oradan da $\lnot x_j$ düğümüne bir yol varsa $x_i$ asla doğru olamaz; çünkü bu durumda $\lnot x_j$ düğümünden $\lnot x_i$ düğümüne de bir yol bulunur ve bu zincirleme çelişki $x_i$’yi zorla yanlış yapar.</p>

<blockquote>
  <p><strong>Not:</strong> 3SAT probleminde her parça $(a_i \lor b_i \lor c_i)$ biçimindedir. Bu problem NP-Hard’dır; yani verimli bilinen bir çözüm algoritması yoktur.</p>
</blockquote>]]></content><author><name>Sonsuz Us</name></author><category term="Program" /><category term="çizge" /><category term="graph" /><category term="yönlü-çizge" /><category term="directed-graph" /><category term="güçlü-bağlanırlık" /><category term="strongly-connected" /><category term="scc" /><category term="kosaraju" /><category term="2sat" /><category term="mantık" /><category term="dfs" /><category term="topolojik-sıralama" /><category term="c" /><category term="programlama" /><category term="algoritma" /><category term="olimpiyat" /><category term="yarışma" /><category term="kodlama" /><category term="matematik" /><category term="kitap" /><summary type="html"><![CDATA[Yönlü bir çizgede kenarlar yalnızca tek yönlü geçilir. Bu nedenle çizge bağlı olsa bile her düğümden diğerine gidileceği garanti edilemez. Daha güçlü bir bağlanırlık kavramına ihtiyaç vardır. Bir çizge, her düğümden diğer tüm düğümlere gidilebiliyorsa güçlü bağlanılmış (strongly connected) olarak adlandırılır. Güçlü bağlanılmamış bir çizgede ise bazı düğüm çiftleri arasında tek yönlü bir ulaşım bile mümkün olmayabilir. Güçlü bağlanılmış parçalar (strongly connected components, SCC), çizgeyi olabildiğince büyük güçlü bağlanılmış bölümlere ayırır. Bu parçalar, orijinal çizgenin derin yapısını ortaya koyan asiklik bir bileşen çizgesi (component graph) oluşturur.]]></summary></entry><entry><title type="html">Rekabetçi Programcı Yönlü Çizgeler</title><link href="https://sonsuzus.github.io/posts/rekabetci-programci-yonlu-cizgeler" rel="alternate" type="text/html" title="Rekabetçi Programcı Yönlü Çizgeler" /><published>2026-04-10T00:00:00+00:00</published><updated>2026-04-10T00:00:00+00:00</updated><id>https://sonsuzus.github.io/posts/rekabetci-programci-yonlu-cizgeler</id><content type="html" xml:base="https://sonsuzus.github.io/posts/rekabetci-programci-yonlu-cizgeler"><![CDATA[<p>Bu bölümde yönlü çizgelerin iki türünden bahsedeceğiz:</p>

<ul>
  <li><strong>Asiklik Çizgeler (Acyclic Graphs / DAG):</strong> Çizgede hiçbir döngü yoktur; yani bir düğümden kendisine geri dönen bir yol mevcut değildir.</li>
  <li><strong>Varis Çizgeleri (Successor Graphs):</strong> Her düğümden çıkan yalnızca 1 kenar vardır, yani her düğümün tam olarak bir ardılı vardır.</li>
</ul>

<p>Her iki durumda da bu özellikler sayesinde çeşitli verimli algoritmalar tasarlanabilir.
``</p>

<h2 id="161-topolojik-sıralama">16.1 Topolojik Sıralama</h2>

<p>Topolojik sıralama, yönlü bir çizgenin düğümlerini belirli bir sıraya koymanın yoludur. Sıralamanın kuralı şudur: eğer $a$ düğümünden $b$ düğümüne bir yol varsa, $a$ sıralamada $b$’den önce yer alır.</p>

<p>Asiklik çizgelerde her zaman en az bir topolojik sıralama vardır. Ancak çizge döngü içeriyorsa topolojik sıralama yapmak mümkün değildir; çünkü döngüdeki hiçbir düğüm sıralamada diğerlerinden önce gelemez.</p>

<h3 id="algoritma">Algoritma</h3>

<p>Fikir, tüm düğümleri ziyaret edip işlenmemiş her düğümden derinlik öncelikli arama (DFS) başlatmaktır. Her düğümün üç olası durumu vardır:</p>

<ul>
  <li><strong>0. durum (beyaz):</strong> Düğüm henüz işlenmedi.</li>
  <li><strong>1. durum (açık gri):</strong> Düğüm şu anda işleniyor.</li>
  <li><strong>2. durum (koyu gri):</strong> Düğüm tamamen işlendi.</li>
</ul>

<p>Başlangıçta tüm düğümler 0. durumdadır. Bir düğümde arama başladığında durumu 1’e geçer. Düğümün tüm devam düğümleri işlendiğinde durumu 2 olur ve düğüm bir listeye eklenir. <strong>Topolojik sıralama, bu listenin tersidir.</strong></p>

<p>Çizge döngü içeriyorsa arama sırasında durumu 1 olan bir düğümle tekrar karşılaşılır; bu, topolojik sıralamanın yapılamayacağını gösterir.</p>

<h3 id="örnek-1-asiklik-çizge">Örnek 1 (Asiklik Çizge)</h3>

<p>Arama 1. düğümden başlayıp 6. düğüme ilerler. 6. düğüm işlenip listeye eklenir; ardından 3, 2 ve 1. düğümler sırasıyla eklenir: liste $[6, 3, 2, 1]$. Sonraki arama 4. düğümde başlar ve $[6, 3, 2, 1, 5, 4]$ listesi oluşur. Listenin tersi olan $[4, 5, 1, 2, 3, 6]$ topolojik sıralamayı verir.</p>

<p>Bir çizgede birden fazla geçerli topolojik sıralama bulunabilir.</p>

<h3 id="örnek-2-döngülü-çizge">Örnek 2 (Döngülü Çizge)</h3>

<p>Döngü içeren bir çizgede arama sırasında durumu 1 olan (işlenmekte olan) bir düğüme tekrar ulaşılır. Bu, çizgede topolojik sıralama yapılamayacağının kanıtıdır. Örneğin $2 \to 3 \to 5 \to 2$ döngüsü bunu engeller.</p>

<h2 id="162-dinamik-programlama">16.2 Dinamik Programlama</h2>

<p>Yönlü çizge asiklik ise, dinamik programlama uygulanabilir. Bu teknikle aşağıdaki gibi sorular verimli biçimde cevaplanabilir:</p>

<ul>
  <li>Başlangıçtan hedefe kaç farklı yol vardır?</li>
  <li>En kısa veya en uzun yol hangisidir?</li>
  <li>Bir yoldaki minimum veya maksimum kenar sayısı nedir?</li>
  <li>Hangi düğümler her yolda kesinlikle geçilir?</li>
</ul>

<h3 id="yol-sayısını-hesaplama">Yol Sayısını Hesaplama</h3>

<p>$\text{paths}(x)$, 1. düğümden $x$ düğümüne giden yol sayısını temsil etsin. Temel durum $\text{paths}(1) = 1$ olur. Diğer değerler şu özyinelemeyle hesaplanır:</p>

\[\text{paths}(x) = \text{paths}(a_1) + \text{paths}(a_2) + \cdots + \text{paths}(a_k)\]

<p>Burada $a_1, a_2, \ldots, a_k$, $x$ düğümüne doğrudan kenar gönderen düğümlerdir. Çizge asiklik olduğu için $\text{paths}(x)$ değerleri topolojik sırayı takip ederek hesaplanabilir.</p>

<p>Örneğin aşağıdaki çizgede 1. düğümden 6. düğüme 3 yol vardır ($1{\to}2{\to}3{\to}6$, $1{\to}4{\to}5{\to}2{\to}3{\to}6$, $1{\to}4{\to}5{\to}3{\to}6$) ve düğümlerin $\text{paths}$ değerleri topolojik sırayla şöyle hesaplanır:</p>

<table>
  <thead>
    <tr>
      <th style="text-align: center">Düğüm</th>
      <th style="text-align: center">1</th>
      <th style="text-align: center">4</th>
      <th style="text-align: center">5</th>
      <th style="text-align: center">2</th>
      <th style="text-align: center">3</th>
      <th style="text-align: center">6</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center">$\text{paths}$</td>
      <td style="text-align: center">1</td>
      <td style="text-align: center">1</td>
      <td style="text-align: center">1</td>
      <td style="text-align: center">2</td>
      <td style="text-align: center">3</td>
      <td style="text-align: center">3</td>
    </tr>
  </tbody>
</table>

<h3 id="dijkstra-çıktısı-üzerinde-dp">Dijkstra Çıktısı Üzerinde DP</h3>

<p>Dijkstra’nın Algoritması’nın yan ürünü olarak bir yönlü asiklik çizge elde edilir; bu çizge her düğüme ulaşan en kısa yolları gösterir. Bu çizge üzerinde dinamik programlama ile örneğin “en kısa yolların sayısı” gibi sorular cevaplanabilir.</p>

<h3 id="herhangi-bir-dp-sorusu-çizge-olarak-gösterilebilir">Herhangi Bir DP Sorusu Çizge Olarak Gösterilebilir</h3>

<p>Her dinamik programlama sorusu, düğümlerin durumları ve kenarların geçişleri temsil ettiği yönlü asiklik bir çizge olarak modellenebilir. Örneğin ${c_1, c_2, \ldots, c_k}$ paralarıyla $n$ miktarını oluşturma problemi böyle bir çizgedir; 0. düğümden $n$. düğüme olan en kısa yol minimum para sayısını, yol sayısı ise toplam çözüm sayısını verir.</p>

<h2 id="163-ardıl-yollar">16.3 Ardıl Yollar</h2>

<p>Ardıl çizgelerde her düğümden yalnızca 1 kenar çıkar. Böyle bir çizge bir veya birkaç parçadan oluşur ve her parça bir döngü ile ona götüren yolları içerir. Ardıl çizgeler bazen <strong>fonksiyonel çizge</strong> olarak da adlandırılır; çünkü her ardıl çizge, bir düğümü alıp ardılını döndüren bir fonksiyona karşılık gelir.</p>

<p>Örneğin:</p>

<table>
  <thead>
    <tr>
      <th style="text-align: center">$x$</th>
      <th style="text-align: center">1</th>
      <th style="text-align: center">2</th>
      <th style="text-align: center">3</th>
      <th style="text-align: center">4</th>
      <th style="text-align: center">5</th>
      <th style="text-align: center">6</th>
      <th style="text-align: center">7</th>
      <th style="text-align: center">8</th>
      <th style="text-align: center">9</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center">$\text{succ}(x)$</td>
      <td style="text-align: center">3</td>
      <td style="text-align: center">5</td>
      <td style="text-align: center">7</td>
      <td style="text-align: center">6</td>
      <td style="text-align: center">2</td>
      <td style="text-align: center">2</td>
      <td style="text-align: center">1</td>
      <td style="text-align: center">6</td>
      <td style="text-align: center">3</td>
    </tr>
  </tbody>
</table>

<h3 id="olog-k-ile-ardıl-hesaplama">$O(\log k)$ ile Ardıl Hesaplama</h3>

<p>$\text{succ}(x, k)$, $x$ düğümünden tam olarak $k$ adım atıldığında ulaşılan düğümü verir. Örneğin $\text{succ}(4, 6) = 2$: $4 \to 6 \to 2 \to 5 \to 2 \to 5 \to 2$.</p>

<p>Naif yöntem $O(k)$ zaman alır. Ön işlemeyle tüm $\text{succ}(x, k)$ değerleri $k = 2^i$ kuvvetleri için önceden hesaplanırsa, herhangi bir sorgu $O(\log k)$ zamanda yanıtlanabilir. Özyineleme şöyle kurulur:</p>

\[\text{succ}(x, k) = \begin{cases} \text{succ}(x) &amp; k = 1 \\ \text{succ}(\text{succ}(x,\, k/2),\, k/2) &amp; k &gt; 1 \end{cases}\]

<p>Ön işleme $O(n \log u)$ zaman alır; $u$ atılacak maksimum adım sayısıdır. Yukarıdaki çizge için ilk birkaç satır:</p>

<table>
  <thead>
    <tr>
      <th style="text-align: center">$x$</th>
      <th style="text-align: center">1</th>
      <th style="text-align: center">2</th>
      <th style="text-align: center">3</th>
      <th style="text-align: center">4</th>
      <th style="text-align: center">5</th>
      <th style="text-align: center">6</th>
      <th style="text-align: center">7</th>
      <th style="text-align: center">8</th>
      <th style="text-align: center">9</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center">$\text{succ}(x, 1)$</td>
      <td style="text-align: center">3</td>
      <td style="text-align: center">5</td>
      <td style="text-align: center">7</td>
      <td style="text-align: center">6</td>
      <td style="text-align: center">2</td>
      <td style="text-align: center">2</td>
      <td style="text-align: center">1</td>
      <td style="text-align: center">6</td>
      <td style="text-align: center">3</td>
    </tr>
    <tr>
      <td style="text-align: center">$\text{succ}(x, 2)$</td>
      <td style="text-align: center">7</td>
      <td style="text-align: center">2</td>
      <td style="text-align: center">1</td>
      <td style="text-align: center">2</td>
      <td style="text-align: center">5</td>
      <td style="text-align: center">5</td>
      <td style="text-align: center">3</td>
      <td style="text-align: center">2</td>
      <td style="text-align: center">7</td>
    </tr>
    <tr>
      <td style="text-align: center">$\text{succ}(x, 4)$</td>
      <td style="text-align: center">3</td>
      <td style="text-align: center">2</td>
      <td style="text-align: center">7</td>
      <td style="text-align: center">2</td>
      <td style="text-align: center">5</td>
      <td style="text-align: center">5</td>
      <td style="text-align: center">1</td>
      <td style="text-align: center">2</td>
      <td style="text-align: center">3</td>
    </tr>
    <tr>
      <td style="text-align: center">$\text{succ}(x, 8)$</td>
      <td style="text-align: center">7</td>
      <td style="text-align: center">2</td>
      <td style="text-align: center">1</td>
      <td style="text-align: center">2</td>
      <td style="text-align: center">5</td>
      <td style="text-align: center">5</td>
      <td style="text-align: center">3</td>
      <td style="text-align: center">2</td>
      <td style="text-align: center">7</td>
    </tr>
  </tbody>
</table>

<p>Herhangi bir $\text{succ}(x, k)$ için $k$, 2’nin kuvvetlerinin toplamı olarak ifade edilir. Örneğin $11 = 8 + 2 + 1$ olduğundan:</p>

\[\text{succ}(x, 11) = \text{succ}(\text{succ}(\text{succ}(x, 8), 2), 1)\]

<p>Örneğin $\text{succ}(4, 11) = 5$ bu şekilde $O(\log 11)$ adımda hesaplanır.</p>

<h2 id="164-döngü-bulma">16.4 Döngü Bulma</h2>

<p>Tek bir yolu olan ve bu yol bir döngüyle biten bir ardıl çizge düşünelim. Başlangıç düğümünden ilerlemeye başladığımızda şu sorular sorulabilir: Döngüdeki ilk düğüm hangisidir? Döngü kaç düğümden oluşur?</p>

<p>Örneğin $1 \to 2 \to 3 \to 4 \to 5 \to 6 \to 4 \to \cdots$ yolunda döngüye giren ilk düğüm 4’tür ve döngü ${4, 5, 6}$ olmak üzere üç düğümden oluşur.</p>

<p>Basit bir yöntem, çizgede ilerleyerek ziyaret edilen tüm düğümleri tutmak ve bir düğüm iki kez görüldüğünde döngünün başladığına karar vermektir. Bu yöntem $O(n)$ zaman ve $O(n)$ bellek kullanır.</p>

<h3 id="floydun-algoritması">Floyd’un Algoritması</h3>

<p>Floyd’un Algoritması da $O(n)$ zamanda çalışır; ancak yalnızca $O(1)$ ek bellek kullanır. İki işaretçi $a$ ve $b$ kullanılır; $a$ her adımda 1 ileri, $b$ ise 2 ileri gider:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">a</span> <span class="o">=</span> <span class="n">succ</span><span class="p">(</span><span class="n">x</span><span class="p">);</span>
<span class="n">b</span> <span class="o">=</span> <span class="n">succ</span><span class="p">(</span><span class="n">succ</span><span class="p">(</span><span class="n">x</span><span class="p">));</span>
<span class="k">while</span> <span class="p">(</span><span class="n">a</span> <span class="o">!=</span> <span class="n">b</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">a</span> <span class="o">=</span> <span class="n">succ</span><span class="p">(</span><span class="n">a</span><span class="p">);</span>
    <span class="n">b</span> <span class="o">=</span> <span class="n">succ</span><span class="p">(</span><span class="n">succ</span><span class="p">(</span><span class="n">b</span><span class="p">));</span>
<span class="p">}</span>
</code></pre></div></div>

<p>İki işaretçi buluştuğunda $a$ işaretçisi $k$ adım atmıştır ve $b$ işaretçisi $2k$ adım atmıştır. Bu noktada döngünün uzunluğu $k$’yi tam böler. Döngüye giren ilk düğümü bulmak için $a$’yı başlangıç noktasına geri alıp her iki işaretçiyi birer birer ilerletiriz:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">a</span> <span class="o">=</span> <span class="n">x</span><span class="p">;</span>
<span class="k">while</span> <span class="p">(</span><span class="n">a</span> <span class="o">!=</span> <span class="n">b</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">a</span> <span class="o">=</span> <span class="n">succ</span><span class="p">(</span><span class="n">a</span><span class="p">);</span>
    <span class="n">b</span> <span class="o">=</span> <span class="n">succ</span><span class="p">(</span><span class="n">b</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">first</span> <span class="o">=</span> <span class="n">a</span><span class="p">;</span>
</code></pre></div></div>

<p>Son olarak döngünün uzunluğu şöyle hesaplanır:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">b</span> <span class="o">=</span> <span class="n">succ</span><span class="p">(</span><span class="n">a</span><span class="p">);</span>
<span class="n">length</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="k">while</span> <span class="p">(</span><span class="n">a</span> <span class="o">!=</span> <span class="n">b</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">b</span> <span class="o">=</span> <span class="n">succ</span><span class="p">(</span><span class="n">b</span><span class="p">);</span>
    <span class="n">length</span><span class="o">++</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>]]></content><author><name>Sonsuz Us</name></author><category term="Program" /><category term="çizge" /><category term="graph" /><category term="yönlü-çizge" /><category term="directed-graph" /><category term="dag" /><category term="topolojik-sıralama" /><category term="topological-sort" /><category term="dinamik-programlama" /><category term="ardıl-çizge" /><category term="successor-graph" /><category term="floyd" /><category term="döngü-bulma" /><category term="dfs" /><category term="c" /><category term="programlama" /><category term="algoritma" /><category term="olimpiyat" /><category term="yarışma" /><category term="kodlama" /><category term="matematik" /><category term="kitap" /><summary type="html"><![CDATA[Bu bölümde yönlü çizgelerin iki türünden bahsedeceğiz: Asiklik Çizgeler (Acyclic Graphs / DAG): Çizgede hiçbir döngü yoktur; yani bir düğümden kendisine geri dönen bir yol mevcut değildir. Varis Çizgeleri (Successor Graphs): Her düğümden çıkan yalnızca 1 kenar vardır, yani her düğümün tam olarak bir ardılı vardır. Her iki durumda da bu özellikler sayesinde çeşitli verimli algoritmalar tasarlanabilir.]]></summary></entry><entry><title type="html">Rekabetçi Programcı Kapsayan Ağaç (Spanning Trees)</title><link href="https://sonsuzus.github.io/posts/rekabetci-programci-kapsayan-agac" rel="alternate" type="text/html" title="Rekabetçi Programcı Kapsayan Ağaç (Spanning Trees)" /><published>2026-04-09T00:00:00+00:00</published><updated>2026-04-09T00:00:00+00:00</updated><id>https://sonsuzus.github.io/posts/rekabetci-programci-kapsayan-agac</id><content type="html" xml:base="https://sonsuzus.github.io/posts/rekabetci-programci-kapsayan-agac"><![CDATA[<p><strong>Kapsayan ağaç</strong> (spanning tree), bir çizgenin bütün düğümlerini bağlı olacak şekilde birleştiren, çizgenin bazı kenarlarını içeren bir ağaçtır. Ağaçlardaki gibi, kapsayan ağaçlar da bağlı ve asikliktir. Genelde, kapsayan ağaç oluşturmanın birkaç yolu vardır.</p>

<p><strong>Kapsayan ağacın ağırlığı</strong> kenar ağırlıklarının toplamıdır. <strong>En küçük kapsayan ağaç</strong> (minimum spanning tree), ağırlığı en küçük olan kapsayan ağaçtır. Benzer şekilde <strong>en büyük kapsayan ağaç</strong>, en büyük ağırlığa sahip kapsayan ağaçtır.</p>

<p>Bir çizgenin birkaç tane en küçük ve en büyük kapsayan ağacı olabilir; yani bu ağaçlardan sadece bir tane olmak zorunda değildir.</p>

<p>En küçük ve en büyük kapsayan ağaçları bazı açgözlü yöntemler kullanarak oluşturabiliriz. Bu bölümde kenarların ağırlıklarına göre sıralayarak yapılan iki algoritmadan bahsedeceğiz. Her ne kadar bölümde en küçük kapsayan ağaçları bulmaya odaklanacak olsak bile, en büyük kapsayan ağaç da kenarları ters sırada işleyerek bulunabilir.</p>

<h2 id="151-kruskalın-algoritması">15.1 Kruskal’ın Algoritması</h2>

<p>Kruskal’ın Algoritması’nda başlangıçtaki kapsayan ağaçta sadece çizgenin düğümleri bulunur ve herhangi bir kenar içermemektedir. Sonrasında algoritma kenarlara ağırlıklarına göre bakar ve eğer kenar döngü oluşturmuyorsa kenarı kapsayan ağaca ekler.</p>

<p>Algoritma, ağacın parçalarını tutar. İlk başta çizgenin her düğümü ayrı bir parçadır. Her seferinde ağaca bir kenar eklendiği zaman iki parça birleşir. Sonunda bütün düğümler aynı parçaya ait olur ve en küçük kapsayan ağaç bulunur.</p>

<h3 id="örnek">Örnek</h3>

<p>Algoritmanın ilk adımında kenarlar, ağırlıklarına göre küçükten büyüğe doğru sıralanır:</p>

<table>
  <thead>
    <tr>
      <th style="text-align: center">Kenar</th>
      <th style="text-align: center">Ağırlık</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center">5–6</td>
      <td style="text-align: center">2</td>
    </tr>
    <tr>
      <td style="text-align: center">1–2</td>
      <td style="text-align: center">3</td>
    </tr>
    <tr>
      <td style="text-align: center">3–6</td>
      <td style="text-align: center">3</td>
    </tr>
    <tr>
      <td style="text-align: center">1–5</td>
      <td style="text-align: center">5</td>
    </tr>
    <tr>
      <td style="text-align: center">2–3</td>
      <td style="text-align: center">5</td>
    </tr>
    <tr>
      <td style="text-align: center">2–5</td>
      <td style="text-align: center">6</td>
    </tr>
    <tr>
      <td style="text-align: center">4–6</td>
      <td style="text-align: center">7</td>
    </tr>
    <tr>
      <td style="text-align: center">3–4</td>
      <td style="text-align: center">9</td>
    </tr>
  </tbody>
</table>

<p>Bundan sonra algoritma listeden geçer ve kenar iki ayrı parçayı bağlıyorsa kenarı ağaca ekler. Başta her düğüm kendisine ait parçadadır. Ağaca ilk eklenen kenar <strong>5–6</strong> kenarıdır; bu kenar ${5}$ ve ${6}$ parçalarını ${5, 6}$ şeklinde birleştirir. Ardından <strong>1–2</strong>, <strong>3–6</strong> ve <strong>1–5</strong> kenarları benzer şekilde eklenir.</p>

<p>Bu adımlardan sonra ağaçta iki parça kalmıştır: ${1, 2, 3, 5, 6}$ ve ${4}$. Listedeki sonraki kenar <strong>2–3</strong>‘tür, ancak 2 ve 3 düğümleri aynı parçaya ait olduğundan bu kenar ağaca eklenmez. Aynı nedenden dolayı <strong>2–5</strong> kenarı da eklenmez. En sonunda <strong>4–6</strong> kenarı ağaca eklenir ve algoritma tamamlanır. Oluşan en küçük kapsayan ağacın ağırlığı $2 + 3 + 3 + 5 + 7 = 20$ olur.</p>

<h3 id="bu-neden-çalışır">Bu Neden Çalışır?</h3>

<p>Çizgedeki minimum ağırlıklı kenarı eklemediğimizi varsayalım. Bu durumda mevcut kapsayan ağaçtan bir kenarı çıkartıp yerine minimum ağırlıklı kenarı eklediğimizde daha küçük ağırlığa sahip bir kapsayan ağaç elde ederiz. Bu çelişki, en küçük ağırlığa sahip kenarı eklemenin her zaman optimal olduğunu gösterir. Benzer mantık sonraki kenarlar için de geçerlidir; dolayısıyla Kruskal’ın Algoritması her zaman doğru sonucu verir.</p>

<h3 id="i̇mplementasyon">İmplementasyon</h3>

<p>Kruskal’ın Algoritması’nı koda geçirirken çizgeyi bir kenar listesinde tutmak daha rahat olur. Algoritmanın ilk aşamasında listedeki kenarlar $O(m \log m)$ zamanda sıralanır. Bundan sonra algoritma en küçük kapsayan ağacı şu şekilde oluşturur:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for</span> <span class="p">(...)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">same</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">))</span> <span class="n">unite</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Döngü listedeki bütün kenarlardan geçer ve her seferinde $a$–$b$ kenarını işler. İki fonksiyon gereklidir:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">same(a, b)</code>: $a$ ve $b$ düğümlerinin aynı parçaya ait olup olmadığını belirler.</li>
  <li><code class="language-plaintext highlighter-rouge">unite(a, b)</code>: $a$ ve $b$ düğümlerini içeren parçaları birleştirir.</li>
</ul>

<p><code class="language-plaintext highlighter-rouge">same</code> fonksiyonunu bir çizge dolaşım algoritmasıyla $O(n + m)$ zamanda implement etmek mümkündür; ancak bu fonksiyon her kenar için çağrıldığından algoritma yavaş kalır. <strong>Union-find yapısı</strong> ile her iki fonksiyon da $O(\log n)$ zamanda çalışır ve Kruskal’ın Algoritması, sıralama adımından sonra $O(m \log n)$ zamanda tamamlanır.</p>

<h2 id="152-union-find-yapısı">15.2 Union-find Yapısı</h2>

<p>Union-find yapısı, parçalardan oluşan bir koleksiyonu tutar. Her eleman sadece bir parçaya aittir; yani parçalar ayrıktır. İki temel $O(\log n)$ işlemi vardır:</p>

<ul>
  <li><strong>unite</strong>: İki parçayı birleştirir.</li>
  <li><strong>find</strong>: Verilen elemanın bulunduğu parçanın temsilcisini döndürür.</li>
</ul>

<h3 id="yapı">Yapı</h3>

<p>Union-find yapısında her parçadaki bir eleman o parçanın <strong>temsilcisi</strong> (representative) olur ve parçadaki diğer tüm elemanlardan temsilciye bir bağ vardır. Örneğin ${1, 4, 7}$, ${5}$ ve ${2, 3, 6, 8}$ gibi parçalarımız olduğunu düşünelim; bu durumda temsilciler sırasıyla 4, 5 ve 2 olur.</p>

<p>Her elemanın temsilcisi, o elemandan başlayan bağı takip ederek bulunur. Örneğin 6. elemanın temsilcisi 2’dir çünkü $6 \to 3 \to 2$ bağı mevcuttur. İki elemanın temsilcisi aynıysa bu elemanlar aynı parçadadır.</p>

<p>İki parça, parçaların temsilcilerini birbirine bağlayarak birleştirilir. <strong>Verimlilik için</strong> her zaman küçük olan parçanın temsilcisini büyük olan parçanın temsilcisine bağlarız; eğer iki parça da aynı büyüklükteyse aralarından birini seçeriz. Bu teknikle herhangi bir bağın uzunluğu $O(\log n)$ olur.</p>

<h3 id="i̇mplementasyon-1">İmplementasyon</h3>

<p>Union-find yapısı dizilerle implement edilir. <code class="language-plaintext highlighter-rouge">link</code> dizisi her elemanın bağdaki sonraki elemanını (eleman temsilciyse kendisini) tutar. <code class="language-plaintext highlighter-rouge">size</code> dizisi ise her temsilcinin bulunduğu parçanın büyüklüğünü verir.</p>

<p>Başlangıçta her eleman ayrı bir parçadadır:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;=</span> <span class="n">n</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="n">link</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">i</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;=</span> <span class="n">n</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="n">size</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">find</code> fonksiyonu herhangi bir $x$ elemanının temsilcisini bulur:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="nf">find</span><span class="p">(</span><span class="kt">int</span> <span class="n">x</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">while</span> <span class="p">(</span><span class="n">x</span> <span class="o">!=</span> <span class="n">link</span><span class="p">[</span><span class="n">x</span><span class="p">])</span> <span class="n">x</span> <span class="o">=</span> <span class="n">link</span><span class="p">[</span><span class="n">x</span><span class="p">];</span>
    <span class="k">return</span> <span class="n">x</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">same</code> fonksiyonu $a$ ve $b$ düğümlerinin aynı parçaya ait olup olmadığını kontrol eder:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">bool</span> <span class="nf">same</span><span class="p">(</span><span class="kt">int</span> <span class="n">a</span><span class="p">,</span> <span class="kt">int</span> <span class="n">b</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="n">find</span><span class="p">(</span><span class="n">a</span><span class="p">)</span> <span class="o">==</span> <span class="n">find</span><span class="p">(</span><span class="n">b</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">unite</code> fonksiyonu ise $a$ ve $b$ elemanlarını içeren parçaları birleştirir. Fonksiyon önce parçaların temsilcilerini bulur, ardından küçük parçayı büyük parçaya bağlar:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">unite</span><span class="p">(</span><span class="kt">int</span> <span class="n">a</span><span class="p">,</span> <span class="kt">int</span> <span class="n">b</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">a</span> <span class="o">=</span> <span class="n">find</span><span class="p">(</span><span class="n">a</span><span class="p">);</span>
    <span class="n">b</span> <span class="o">=</span> <span class="n">find</span><span class="p">(</span><span class="n">b</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">size</span><span class="p">[</span><span class="n">a</span><span class="p">]</span> <span class="o">&lt;</span> <span class="n">size</span><span class="p">[</span><span class="n">b</span><span class="p">])</span> <span class="n">swap</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">);</span>
    <span class="n">size</span><span class="p">[</span><span class="n">a</span><span class="p">]</span> <span class="o">+=</span> <span class="n">size</span><span class="p">[</span><span class="n">b</span><span class="p">];</span>
    <span class="n">link</span><span class="p">[</span><span class="n">b</span><span class="p">]</span> <span class="o">=</span> <span class="n">a</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">find</code> fonksiyonu her bağın uzunluğunun $O(\log n)$ olduğu varsayımıyla $O(\log n)$ zamanda çalışır. <code class="language-plaintext highlighter-rouge">unite</code> fonksiyonunun küçük parçayı büyük parçaya bağlaması, bağ uzunluğunun $O(\log n)$ sınırını korumasını sağlar.</p>

<h2 id="153-primin-algoritması">15.3 Prim’in Algoritması</h2>

<p>Prim’in Algoritması, en küçük kapsayan ağacı bulmak için alternatif bir yöntemdir. Algoritma ilk başta çizgeden herhangi bir düğümü ağaca ekler. Sonra her adımda, ağaca yeni bir düğüm ekleyen en kısa kenarı seçer. En sonunda bütün düğümler ağaca eklenir ve en küçük kapsayan ağaç bulunmuş olur.</p>

<p>Prim’in Algoritması <strong>Dijkstra’nın Algoritması</strong>‘nı anımsatır. Aralarındaki fark şudur: Dijkstra’nın Algoritması her zaman başlangıç düğümünden en kısa mesafede olan kenarı seçerken, Prim’in Algoritması ağaca eklenmiş herhangi bir düğümden çıkan en kısa kenarı seçer.</p>

<h3 id="örnek-1">Örnek</h3>

<p>Başlangıç düğümü olarak 1. düğümü seçelim. İlk adımda 3 uzunluğundaki kenar eklenerek 2. düğüm ağaca katılır. Bundan sonra 5 uzunluğunda iki seçenek olduğunda önce 3. düğüm eklenir. İşlem, bütün düğümler ağaca eklenene kadar devam eder. Oluşan en küçük kapsayan ağacın ağırlığı yine $2 + 3 + 3 + 5 + 7 = 20$ olur.</p>

<h3 id="i̇mplementasyon-2">İmplementasyon</h3>

<p>Dijkstra’nın Algoritması gibi Prim’in Algoritması da <strong>öncelikli kuyruk</strong> ile verimli biçimde yazılabilir. Öncelikli kuyruk, tek kenarla eklenebilen bütün düğümleri, kenar ağırlıklarının artan sırasına göre tutar.</p>

<p>Prim’in Algoritması’nın zaman karmaşıklığı $O(n + m \log m)$ olup Dijkstra’nın Algoritması’nın zaman karmaşıklığıyla aynıdır. Pratikte hem Prim’in hem de Kruskal’ın Algoritmaları verimlidir; hangisinin kullanılacağı kişiden kişiye göre değişir. Yine de çoğu olimpiyatçı Kruskal’ın Algoritması’nı tercih eder.</p>]]></content><author><name>Sonsuz Us</name></author><category term="Program" /><category term="çizge" /><category term="graph" /><category term="ağaç" /><category term="tree" /><category term="kapsayan-ağaç" /><category term="spanning-tree" /><category term="kruskal" /><category term="prim" /><category term="union-find" /><category term="açgözlü" /><category term="greedy" /><category term="c" /><category term="programlama" /><category term="algoritma" /><category term="olimpiyat" /><category term="yarışma" /><category term="kodlama" /><category term="matematik" /><category term="kitap" /><summary type="html"><![CDATA[Kapsayan ağaç (spanning tree), bir çizgenin bütün düğümlerini bağlı olacak şekilde birleştiren, çizgenin bazı kenarlarını içeren bir ağaçtır. Ağaçlardaki gibi, kapsayan ağaçlar da bağlı ve asikliktir. Genelde, kapsayan ağaç oluşturmanın birkaç yolu vardır.]]></summary></entry><entry><title type="html">Rekabetçi Programcı Ağaç Algoritmaları</title><link href="https://sonsuzus.github.io/posts/rekabetci-programci-agac-algoritmalari" rel="alternate" type="text/html" title="Rekabetçi Programcı Ağaç Algoritmaları" /><published>2026-04-08T00:00:00+00:00</published><updated>2026-04-08T00:00:00+00:00</updated><id>https://sonsuzus.github.io/posts/rekabetci-programci-agac-algoritmalari</id><content type="html" xml:base="https://sonsuzus.github.io/posts/rekabetci-programci-agac-algoritmalari"><![CDATA[<p>Bir <strong>ağaç</strong> (tree), $n$ düğüm ve $n - 1$ kenardan oluşan bağlı ve asiklik (döngüsüz) bir çizgedir. Ağaçtan herhangi bir kenarı çıkarmak onu iki parçaya böler; herhangi bir kenar eklemek ise bir döngü oluşturur. Her iki düğüm arasında tam olarak bir yol bulunur.</p>

<p>Örneğin 8 düğüm ve 7 kenardan oluşan bir ağaçta <strong>yapraklar</strong> (leaves), derecesi 1 olan yani tek komşusu bulunan düğümlerdir. <strong>Köklü bir ağaçta</strong> düğümlerden biri kök seçilir ve diğer tüm düğümler onun altına yerleştirilir. Köklü ağaçta bir düğümün <strong>çocukları</strong> (children) onun alt komşuları, <strong>ebeveyni</strong> (parent) ise üst komşusudur. Her düğümün kök hariç tam bir ebeveyni vardır.</p>

<p>Köklü ağacın yapısı <strong>özyinelemelidir</strong>: her düğüm, kendisini ve tüm torunlarını kapsayan bir alt ağacın kökü gibi davranır.</p>

<h2 id="141-ağaç-dolaşımı">14.1 Ağaç Dolaşımı</h2>

<p>Genel çizge dolaşım algoritmaları ağaçlarda da kullanılabilir. Ancak ağaçlar döngü içermediğinden ve bir düğüme birden fazla yoldan ulaşılamadığından implementasyon genel çizgelere kıyasla çok daha sadedir.</p>

<p>Ağacı dolaşmanın klasik yolu herhangi bir düğümden DFS başlatmaktır:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">dfs</span><span class="p">(</span><span class="kt">int</span> <span class="n">s</span><span class="p">,</span> <span class="kt">int</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// s düğümü ile bir şeyler yap</span>
    <span class="k">for</span> <span class="p">(</span><span class="k">auto</span> <span class="n">u</span> <span class="o">:</span> <span class="n">adj</span><span class="p">[</span><span class="n">s</span><span class="p">])</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">u</span> <span class="o">!=</span> <span class="n">e</span><span class="p">)</span> <span class="n">dfs</span><span class="p">(</span><span class="n">u</span><span class="p">,</span> <span class="n">s</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Fonksiyon iki parametre alır: mevcut düğüm <code class="language-plaintext highlighter-rouge">s</code> ve onun ebeveyni <code class="language-plaintext highlighter-rouge">e</code>. Ebeveyn parametresi, aramanın geri dönüp sonsuz döngüye girmesini önler. Arama <code class="language-plaintext highlighter-rouge">x</code> düğümünden şöyle başlatılır:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">dfs</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
</code></pre></div></div>

<p>İlk çağrıda <code class="language-plaintext highlighter-rouge">e = 0</code> verilir çünkü kökün ebeveyni yoktur.</p>

<h3 id="dinamik-programlama-ile-alt-ağaç-hesapları">Dinamik Programlama ile Alt Ağaç Hesapları</h3>

<p>Ağaç dolaşımı sırasında dinamik programlama kullanarak her düğüm için yararlı bilgiler $O(n)$ zamanda hesaplanabilir; örneğin alt ağacındaki düğüm sayısı ya da bir düğümden yaprağa olan en uzun mesafe.</p>

<p>Her <code class="language-plaintext highlighter-rouge">s</code> düğümü için alt ağacındaki toplam düğüm sayısını veren <code class="language-plaintext highlighter-rouge">count[s]</code> değeri şöyle hesaplanır:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">dfs</span><span class="p">(</span><span class="kt">int</span> <span class="n">s</span><span class="p">,</span> <span class="kt">int</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">count</span><span class="p">[</span><span class="n">s</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
    <span class="k">for</span> <span class="p">(</span><span class="k">auto</span> <span class="n">u</span> <span class="o">:</span> <span class="n">adj</span><span class="p">[</span><span class="n">s</span><span class="p">])</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">u</span> <span class="o">==</span> <span class="n">e</span><span class="p">)</span> <span class="k">continue</span><span class="p">;</span>
        <span class="n">dfs</span><span class="p">(</span><span class="n">u</span><span class="p">,</span> <span class="n">s</span><span class="p">);</span>
        <span class="n">count</span><span class="p">[</span><span class="n">s</span><span class="p">]</span> <span class="o">+=</span> <span class="n">count</span><span class="p">[</span><span class="n">u</span><span class="p">];</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Alt ağaç, düğümün kendisini ve tüm çocuklarının alt ağaçlarını içerir; dolayısıyla <code class="language-plaintext highlighter-rouge">count[s]</code>, çocukların <code class="language-plaintext highlighter-rouge">count</code> değerlerinin toplamına 1 eklenerek bulunur.</p>

<h2 id="142-çap">14.2 Çap</h2>

<p>Bir ağacın <strong>çapı</strong> (diameter), ağaçtaki herhangi iki düğüm arasındaki en uzun mesafedir. Aynı uzunlukta birden fazla en uzun yol bulunabilir.</p>

<p>Çapı bulmak için $O(n)$ zamanda çalışan iki farklı algoritma vardır.</p>

<h3 id="1-algoritma-dinamik-programlama">1. Algoritma: Dinamik Programlama</h3>

<p>Ağacı köklendirip her alt ağaç için çapı ayrı ayrı hesaplayan bir yaklaşımdır. Köklü ağaçtaki her yolun bir <strong>en yüksek noktası</strong> (tepesi) vardır; yani yolda geçilen en üstteki düğüm. Bu gözlemden yola çıkarak her düğüm için iki değer hesaplanır:</p>

<ul>
  <li>$\text{toLeaf}(x)$: $x$ düğümünden herhangi bir yaprağa olan en uzun mesafe.</li>
  <li>$\text{maxLength}(x)$: tepe noktası $x$ olan en uzun yolun uzunluğu.</li>
</ul>

<p>Örneğin 1. düğümün kök olduğu bir ağaçta $\text{toLeaf}(1) = 2$ ise ($1 \to 2 \to 6$ yoluyla) ve $\text{maxLength}(1) = 4$ ise ($6 \to 2 \to 1 \to 4 \to 7$ yoluyla) ağacın çapı $\text{maxLength}(\text{kök})$’e eşittir.</p>

<p>Hesaplama adımları:</p>

<ul>
  <li>$\text{toLeaf}(x)$: $x$’in çocukları arasında $\text{toLeaf}(c)$ değeri en büyük olan $c$ seçilir ve $\text{toLeaf}(x) = \text{toLeaf}(c) + 1$ olur.</li>
  <li>$\text{maxLength}(x)$: En büyük $\text{toLeaf}$ değerine sahip iki farklı $a, b$ çocuğu seçilir; $\text{maxLength}(x) = \text{toLeaf}(a) + \text{toLeaf}(b) + 2$ olur.</li>
</ul>

<h3 id="2-algoritma-i̇ki-dfs">2. Algoritma: İki DFS</h3>

<p>Çapı bulmanın daha şık bir yolu iki DFS çalıştırmaktır:</p>

<ol>
  <li>Ağaçta rastgele bir $a$ düğümü seç.</li>
  <li>$a$’dan en uzaktaki $b$ düğümünü bul (BFS veya DFS ile).</li>
  <li>$b$’den en uzaktaki $c$ düğümünü bul.</li>
  <li>$b$ ile $c$ arasındaki mesafe ağacın çapıdır.</li>
</ol>

<p>Bu yöntemin neden doğru çalıştığını anlamak için ağacı çap yatay olacak şekilde düşünmek yeterlidir. Rastgele seçilen $a$ düğümü çapın dışında olsa bile, $a$’dan en uzak düğüm olan $b$ her zaman çap yolunun bir ucu olur. Çünkü $b$, çap uçlarından en az $a$’nın çapla kesişim noktasına olan mesafesi kadar uzakta olmak zorundadır ve bu da onu daima çapın bir ucuna karşılık getirir.</p>

<h2 id="143-bütün-en-uzun-yollar">14.3 Bütün En Uzun Yollar</h2>

<p>Her düğüm için o düğümden başlayan en uzun yolun uzunluğunu $\text{maxLength}(x)$ olarak hesaplamak istiyoruz. Bu, çap probleminin genelleştirilmiş halidir; sonuçların en büyüğü ağacın çapına eşit olur.</p>

<p>Örneğin aşağıdaki tablo 6 düğümlü bir ağaç için sonuçları gösterir:</p>

<table>
  <thead>
    <tr>
      <th style="text-align: center">Düğüm $x$</th>
      <th style="text-align: center">1</th>
      <th style="text-align: center">2</th>
      <th style="text-align: center">3</th>
      <th style="text-align: center">4</th>
      <th style="text-align: center">5</th>
      <th style="text-align: center">6</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center">$\text{maxLength}(x)$</td>
      <td style="text-align: center">2</td>
      <td style="text-align: center">2</td>
      <td style="text-align: center">3</td>
      <td style="text-align: center">3</td>
      <td style="text-align: center">3</td>
      <td style="text-align: center">3</td>
    </tr>
  </tbody>
</table>

<p>Problem $O(n)$ zamanda iki aşamada çözülür.</p>

<p><strong>Aşama 1:</strong> Her $x$ düğümü için çocuklarından geçen en uzun yol dinamik programlamayla hesaplanır (Bölüm 14.2’deki yaklaşımla aynıdır).</p>

<p><strong>Aşama 2:</strong> Her $x$ düğümü için ebeveyninden $p$’den geçen en uzun yol bulunur. Burada dikkat edilmesi gereken bir incelik vardır: $p$’den geçen en uzun yol bazen $x$ üzerinden geçebilir. Bu durumda $x$’e ait olmayan yönde bir çözüm gerekir.</p>

<p>Bu sorunu çözmek için her $x$ düğümüne ait <strong>iki</strong> maksimum değer saklanır:</p>

<ul>
  <li>$\text{maxLength}_1(x)$: $x$ düğümünden geçen en uzun yolun uzunluğu.</li>
  <li>$\text{maxLength}_2(x)$: $x$ düğümünden farklı bir yönde geçen ikinci en uzun yolun uzunluğu.</li>
</ul>

<p>Son adımda: eğer $p$’nin $\text{maxLength}_1$ değerini sağlayan yol $x$ üzerinden geçiyorsa cevap $\text{maxLength}_2(p) + 1$, geçmiyorsa $\text{maxLength}_1(p) + 1$ olur.</p>

<h2 id="144-i̇kili-ağaç">14.4 İkili Ağaç</h2>

<p><strong>İkili ağaç</strong> (binary tree), her düğümün <strong>sol</strong> ve <strong>sağ</strong> olmak üzere en fazla iki çocuğu bulunan köklü bir ağaçtır. Bir çocuk olmayabilir; dolayısıyla her düğümün 0, 1 veya 2 çocuğu olabilir.</p>

<h3 id="dolaşım-sıraları">Dolaşım Sıraları</h3>

<p>İkili ağacın düğümleri dolaşım biçimine göre üç farklı sırayla listelenebilir:</p>

<ul>
  <li><strong>Önce kök (pre-order):</strong> Kök → Sol alt ağaç → Sağ alt ağaç</li>
  <li><strong>Kök ortada (in-order):</strong> Sol alt ağaç → Kök → Sağ alt ağaç</li>
  <li><strong>Kök sonda (post-order):</strong> Sol alt ağaç → Sağ alt ağaç → Kök</li>
</ul>

<p>Örneğin aşağıdaki ikili ağaç için:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>      1
     / \
    2   3
   / \   \
  4   5   7
     /
    6
</code></pre></div></div>

<table>
  <thead>
    <tr>
      <th style="text-align: center">Dolaşım</th>
      <th style="text-align: center">Sıra</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center">Önce kök</td>
      <td style="text-align: center">$[1, 2, 4, 5, 6, 3, 7]$</td>
    </tr>
    <tr>
      <td style="text-align: center">Kök ortada</td>
      <td style="text-align: center">$[4, 2, 6, 5, 1, 3, 7]$</td>
    </tr>
    <tr>
      <td style="text-align: center">Kök sonda</td>
      <td style="text-align: center">$[4, 6, 5, 2, 7, 3, 1]$</td>
    </tr>
  </tbody>
</table>

<h3 id="ağacı-yeniden-oluşturmak">Ağacı Yeniden Oluşturmak</h3>

<p>İki dolaşım sırasından ağacın tam yapısını çıkarmak mümkündür; ancak hangi iki sıranın yeterli olduğu önemlidir:</p>

<ul>
  <li><strong>Önce kök + Kök ortada</strong> → Ağaç yapısı <strong>benzersiz</strong> biçimde belirlenir.</li>
  <li><strong>Kök sonda + Kök ortada</strong> → Ağaç yapısı <strong>benzersiz</strong> biçimde belirlenir.</li>
  <li><strong>Önce kök + Kök sonda</strong> → Ağaç yapısı <strong>benzersiz belirlenemez</strong>; birden fazla ağaç bu iki sırayla uyuşabilir.</li>
</ul>

<p>Örneğin önce kök sırası $[1, 2]$ ve kök sonda sırası $[2, 1]$ olan iki farklı ağaç yapısı mevcuttur: birinde 2, kökün sol çocuğu; diğerinde ise sağ çocuğudur.</p>]]></content><author><name>Sonsuz Us</name></author><category term="Program" /><category term="çizge" /><category term="graph" /><category term="ağaç" /><category term="tree" /><category term="dfs" /><category term="dinamik-programlama" /><category term="çap" /><category term="ikili-ağaç" /><category term="c" /><category term="programlama" /><category term="algoritma" /><category term="olimpiyat" /><category term="yarışma" /><category term="kodlama" /><category term="matematik" /><category term="kitap" /><summary type="html"><![CDATA[Bir ağaç (tree), $n$ düğüm ve $n - 1$ kenardan oluşan bağlı ve asiklik (döngüsüz) bir çizgedir. Ağaçtan herhangi bir kenarı çıkarmak onu iki parçaya böler; herhangi bir kenar eklemek ise bir döngü oluşturur. Her iki düğüm arasında tam olarak bir yol bulunur.]]></summary></entry><entry><title type="html">Rekabetçi Programcı En Kısa Yolu Bulmak</title><link href="https://sonsuzus.github.io/posts/rekabetci-programci-en-kisa-yolu-bulmak" rel="alternate" type="text/html" title="Rekabetçi Programcı En Kısa Yolu Bulmak" /><published>2026-04-06T00:00:00+00:00</published><updated>2026-04-06T00:00:00+00:00</updated><id>https://sonsuzus.github.io/posts/rekabetci-programci-en-kisa-yolu-bulmak</id><content type="html" xml:base="https://sonsuzus.github.io/posts/rekabetci-programci-en-kisa-yolu-bulmak"><![CDATA[<p>Bir çizgede iki düğüm arasındaki en kısa yolu bulmak, pek çok pratik uygulamaya sahip temel bir problemdir. Klasik bir örnek, yol uzunlukları bilinen bir ağda iki şehir arasındaki en kısa rotayı hesaplamaktır. Ağırlıksız çizgelerde yol uzunluğu kenar sayısına eşit olduğundan BFS ile çözülebilir; bu bölümde ise ağırlıklı çizgeler için geliştirilmiş algoritmalara bakacağız.</p>

<h2 id="131-bellmanford-algoritması">13.1 Bellman–Ford Algoritması</h2>

<p><strong>Bellman–Ford algoritması</strong><sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>, başlangıç düğümünden çizgedeki tüm düğümlere en kısa mesafeyi bulur. Negatif döngü içermeyen her türlü çizgede çalışır; üstelik çizgede negatif döngü varsa bunu tespit edebilir.</p>

<p>Algoritma, başlangıç düğümüne 0, diğer tüm düğümlere sonsuz mesafe atayarak başlar. Her turda tüm kenarlar incelenerek mesafeler kısaltılmaya çalışılır. Hiçbir mesafe artık kısalımıyorsa algoritma durur.</p>

<h3 id="örnek">Örnek</h3>

<p>Aşağıdaki 5 düğümlü ağırlıklı çizgede 1. düğümden başlayan Bellman–Ford adımları:</p>

<p><strong>Başlangıç:</strong> Mesafeler $[0, \infty, \infty, \infty, \infty]$</p>

<p><strong>1. tur</strong> — 1. düğümden çıkan tüm kenarlar mesafeleri azaltır:</p>

\[[0,\ 5,\ 3,\ 7,\ \infty]\]

<p><strong>2. tur</strong> — $2 \to 5$ ve $3 \to 4$ kenarları devreye girer:</p>

\[[0,\ 5,\ 3,\ 4,\ 7]\]

<p><strong>3. tur</strong> — Son bir güncelleme daha:</p>

\[[0,\ 5,\ 3,\ 4,\ 6]\]

<p>Artık hiçbir kenar mesafeyi azaltamaz; başlangıç düğümünden tüm düğümlere en kısa mesafeler bulunmuştur. Örneğin 1. düğümden 5. düğüme en kısa mesafe 3’tür.</p>

<h3 id="i̇mplementasyon">İmplementasyon</h3>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// distance[i]: x düğümünden i düğümüne en kısa mesafe</span>
<span class="c1">// edges: (a, b, w) formatında kenar listesi</span>

<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;=</span> <span class="n">n</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="n">distance</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">INF</span><span class="p">;</span>
<span class="n">distance</span><span class="p">[</span><span class="n">x</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;=</span> <span class="n">n</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">for</span> <span class="p">(</span><span class="k">auto</span> <span class="n">e</span> <span class="o">:</span> <span class="n">edges</span><span class="p">)</span> <span class="p">{</span>
        <span class="kt">int</span> <span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">,</span> <span class="n">w</span><span class="p">;</span>
        <span class="n">tie</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">,</span> <span class="n">w</span><span class="p">)</span> <span class="o">=</span> <span class="n">e</span><span class="p">;</span>
        <span class="n">distance</span><span class="p">[</span><span class="n">b</span><span class="p">]</span> <span class="o">=</span> <span class="n">min</span><span class="p">(</span><span class="n">distance</span><span class="p">[</span><span class="n">b</span><span class="p">],</span> <span class="n">distance</span><span class="p">[</span><span class="n">a</span><span class="p">]</span> <span class="o">+</span> <span class="n">w</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Algoritma $n - 1$ tur çalışır; her turda $m$ kenarı işler. Zaman karmaşıklığı $O(nm)$’dir. En kısa her yol en fazla $n - 1$ kenar içerdiğinden $n - 1$ tur sonunda tüm mesafeler kesinleşir. Pratikte son mesafeler çoğu zaman $n - 1$ turdan önce bulunur; bir tur boyunca hiçbir mesafe değişmezse algoritma erken sonlandırılabilir.</p>

<h3 id="negatif-döngüler">Negatif Döngüler</h3>

<p>Bellman–Ford algoritması çizgede negatif döngü bulunup bulunmadığını da kontrol eder. Örneğin $2 \to 3 \to 4 \to 2$ döngüsünün toplam ağırlığı $-4$ ise bu döngüyü içeren yolları sınırsız kısaltmak mümkün olur; bu durumda “en kısa yol” tanımsızlaşır.</p>

<p>Negatif döngüyü tespit etmek için $n$. bir tur daha koşulur. Bu turda herhangi bir mesafe hâlâ azalıyorsa çizgede negatif döngü vardır. Bu yöntem, başlangıç düğümünden bağımsız olarak çizgenin herhangi bir yerindeki negatif döngüyü yakalar.</p>

<h3 id="spfa-algoritması">SPFA Algoritması</h3>

<p><strong>SPFA</strong> (“Shortest Path Faster Algorithm”), Bellman–Ford’un optimize edilmiş bir varyantıdır. Her turda tüm kenarları değil, yalnızca mesafesi azalan düğümlere bağlı kenarları inceler. Bunun için mesafesi azalabilen düğümleri bir kuyrukta tutar. Ortalama durumda Bellman–Ford’dan hızlıdır; ancak en kötü durumda zaman karmaşıklığı yine $O(nm)$’dir.</p>

<h2 id="132-dijkstranın-algoritması">13.2 Dijkstra’nın Algoritması</h2>

<p><strong>Dijkstra’nın algoritması</strong><sup id="fnref:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup>, Bellman–Ford gibi başlangıç düğümünden tüm düğümlere en kısa mesafeleri bulur; fakat çok daha verimlidir ve daha büyük çizgelerde kullanılabilir. Tek koşul: <strong>çizgede negatif ağırlıklı kenar bulunmamalıdır.</strong></p>

<p>Bellman–Ford’dan farklı olarak Dijkstra her kenarı yalnızca bir kez işler. Bu verimliliği, negatif ağırlıklı kenar bulunmaması garantisiyle sağlanır.</p>

<h3 id="örnek-1">Örnek</h3>

<ol>
  <li>düğümden başlayarak 5 düğümlü ağırlıklı çizgede Dijkstra’nın adımları:</li>
</ol>

<p><strong>Başlangıç:</strong> $[0,\ \infty,\ \infty,\ \infty,\ \infty]$</p>

<p><strong>Adım 1</strong> — 0 mesafeli 1. düğüm seçilir; komşularının mesafeleri güncellenir:</p>

\[[0,\ 5,\ \infty,\ 9,\ 1]\]

<p><strong>Adım 2</strong> — 1 mesafeli 5. düğüm seçilir; 4. düğümün mesafesi 9’dan 3’e iner:</p>

\[[0,\ 5,\ \infty,\ 3,\ 1]\]

<p><strong>Adım 3</strong> — 3 mesafeli 4. düğüm seçilir; 3. düğümün mesafesi 9 olur:</p>

\[[0,\ 5,\ 9,\ 3,\ 1]\]

<p>Dijkstra’nın önemli özelliği: bir düğüm seçildiğinde onun mesafesi <strong>artık değişmez</strong>. Örneğin bu aşamada 1, 5 ve 4. düğümlerin mesafeleri kesinleşmiştir.</p>

<p><strong>Son durum:</strong></p>

\[[0,\ 5,\ 7,\ 3,\ 1]\]

<h3 id="negatif-kenarlar">Negatif Kenarlar</h3>

<p>Dijkstra, negatif kenarlarda hatalı sonuç üretebilir. Örneğin $-5$ ağırlıklı bir kenar varsa algoritma bu kenarın öncesindeki yüksek maliyeti geç fark eder ve yanlış en kısa yolu seçebilir. Bu durumda Bellman–Ford kullanılmalıdır.</p>

<h3 id="i̇mplementasyon-1">İmplementasyon</h3>

<p>Verimli Dijkstra implementasyonu için işlenmemiş düğümler arasından mesafesi en küçük olanı hızla bulmak gerekir. Bunun için <strong>öncelikli kuyruk</strong> (priority queue) kullanılır.</p>

<p>C++’ın varsayılan öncelikli kuyruğu maksimum elemana öncelik verdiğinden, mesafeleri negatif tutarak minimum elemanın başa gelmesi sağlanır:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// adj[a] = {b, w}: a'dan b'ye w ağırlığında kenar</span>

<span class="n">priority_queue</span><span class="o">&lt;</span><span class="n">pair</span><span class="o">&lt;</span><span class="kt">int</span><span class="p">,</span><span class="kt">int</span><span class="o">&gt;&gt;</span> <span class="n">q</span><span class="p">;</span>
<span class="kt">bool</span> <span class="n">processed</span><span class="p">[</span><span class="n">N</span><span class="p">];</span>

<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;=</span> <span class="n">n</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="n">distance</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">INF</span><span class="p">;</span>
<span class="n">distance</span><span class="p">[</span><span class="n">x</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="n">q</span><span class="p">.</span><span class="n">push</span><span class="p">({</span><span class="mi">0</span><span class="p">,</span> <span class="n">x</span><span class="p">});</span>

<span class="k">while</span> <span class="p">(</span><span class="o">!</span><span class="n">q</span><span class="p">.</span><span class="n">empty</span><span class="p">())</span> <span class="p">{</span>
    <span class="kt">int</span> <span class="n">a</span> <span class="o">=</span> <span class="n">q</span><span class="p">.</span><span class="n">top</span><span class="p">().</span><span class="n">second</span><span class="p">;</span> <span class="n">q</span><span class="p">.</span><span class="n">pop</span><span class="p">();</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">processed</span><span class="p">[</span><span class="n">a</span><span class="p">])</span> <span class="k">continue</span><span class="p">;</span>
    <span class="n">processed</span><span class="p">[</span><span class="n">a</span><span class="p">]</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
    <span class="k">for</span> <span class="p">(</span><span class="k">auto</span> <span class="n">u</span> <span class="o">:</span> <span class="n">adj</span><span class="p">[</span><span class="n">a</span><span class="p">])</span> <span class="p">{</span>
        <span class="kt">int</span> <span class="n">b</span> <span class="o">=</span> <span class="n">u</span><span class="p">.</span><span class="n">first</span><span class="p">,</span> <span class="n">w</span> <span class="o">=</span> <span class="n">u</span><span class="p">.</span><span class="n">second</span><span class="p">;</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">distance</span><span class="p">[</span><span class="n">a</span><span class="p">]</span> <span class="o">+</span> <span class="n">w</span> <span class="o">&lt;</span> <span class="n">distance</span><span class="p">[</span><span class="n">b</span><span class="p">])</span> <span class="p">{</span>
            <span class="n">distance</span><span class="p">[</span><span class="n">b</span><span class="p">]</span> <span class="o">=</span> <span class="n">distance</span><span class="p">[</span><span class="n">a</span><span class="p">]</span> <span class="o">+</span> <span class="n">w</span><span class="p">;</span>
            <span class="n">q</span><span class="p">.</span><span class="n">push</span><span class="p">({</span><span class="o">-</span><span class="n">distance</span><span class="p">[</span><span class="n">b</span><span class="p">],</span> <span class="n">b</span><span class="p">});</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Öncelikli kuyrukta aynı düğüme ait birden fazla eleman bulunabilir; ancak yalnızca en kısa mesafeli olan işlenir (<code class="language-plaintext highlighter-rouge">processed</code> kontrolü sayesinde). Zaman karmaşıklığı $O(n + m \log m)$’dir: tüm düğümler işlenir ve her kenar için kuyruğa en fazla bir eleman eklenir<sup id="fnref:3"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup>.</p>

<h2 id="133-floydwarshall-algoritması">13.3 Floyd–Warshall Algoritması</h2>

<p><strong>Floyd–Warshall algoritması</strong><sup id="fnref:4"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">4</a></sup>, tek bir kaynaktan değil, <strong>tüm düğüm çiftleri</strong> arasındaki en kısa mesafeleri tek bir koşuda bulur. Diğer iki algoritmadan farklı olarak hem negatif kenarlarda hem de negatif döngü tespitinde kullanılabilir.</p>

<p>Algoritma, iki boyutlu bir mesafe matrisi tutar. Başlangıçta matris yalnızca doğrudan kenarlarla doldurulur. Sonra her düğüm sırayla <strong>ara düğüm</strong> olarak denenerek mevcut mesafeler kısaltılmaya çalışılır.</p>

<h3 id="örnek-2">Örnek</h3>

<p>5 düğümlü ağırlıklı çizge için başlangıç matrisi (∞ = sonsuz):</p>

<table>
  <thead>
    <tr>
      <th style="text-align: center"> </th>
      <th style="text-align: center">1</th>
      <th style="text-align: center">2</th>
      <th style="text-align: center">3</th>
      <th style="text-align: center">4</th>
      <th style="text-align: center">5</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center"><strong>1</strong></td>
      <td style="text-align: center">0</td>
      <td style="text-align: center">5</td>
      <td style="text-align: center">1</td>
      <td style="text-align: center">9</td>
      <td style="text-align: center">1</td>
    </tr>
    <tr>
      <td style="text-align: center"><strong>2</strong></td>
      <td style="text-align: center">5</td>
      <td style="text-align: center">0</td>
      <td style="text-align: center">2</td>
      <td style="text-align: center">1</td>
      <td style="text-align: center">∞</td>
    </tr>
    <tr>
      <td style="text-align: center"><strong>3</strong></td>
      <td style="text-align: center">1</td>
      <td style="text-align: center">2</td>
      <td style="text-align: center">0</td>
      <td style="text-align: center">7</td>
      <td style="text-align: center">∞</td>
    </tr>
    <tr>
      <td style="text-align: center"><strong>4</strong></td>
      <td style="text-align: center">9</td>
      <td style="text-align: center">1</td>
      <td style="text-align: center">7</td>
      <td style="text-align: center">0</td>
      <td style="text-align: center">2</td>
    </tr>
    <tr>
      <td style="text-align: center"><strong>5</strong></td>
      <td style="text-align: center">1</td>
      <td style="text-align: center">∞</td>
      <td style="text-align: center">∞</td>
      <td style="text-align: center">2</td>
      <td style="text-align: center">0</td>
    </tr>
  </tbody>
</table>

<p><strong>1. tur</strong> (ara düğüm: 1) — 2 ile 4 arasında ve 2 ile 5 arasında yeni yollar keşfedilir.</p>

<p><strong>2. tur</strong> (ara düğüm: 2) — 1–3 ve 3–5 arasında yeni yollar bulunur.</p>

<p><strong>3. tur</strong> (ara düğüm: 3) — 2–4 arasındaki mesafe kısalır.</p>

<p>Tüm turlar bittikten sonra matris her iki düğüm arasındaki minimum mesafeleri tutar:</p>

<table>
  <thead>
    <tr>
      <th style="text-align: center"> </th>
      <th style="text-align: center">1</th>
      <th style="text-align: center">2</th>
      <th style="text-align: center">3</th>
      <th style="text-align: center">4</th>
      <th style="text-align: center">5</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center"><strong>1</strong></td>
      <td style="text-align: center">0</td>
      <td style="text-align: center">5</td>
      <td style="text-align: center">7</td>
      <td style="text-align: center">3</td>
      <td style="text-align: center">1</td>
    </tr>
    <tr>
      <td style="text-align: center"><strong>2</strong></td>
      <td style="text-align: center">5</td>
      <td style="text-align: center">0</td>
      <td style="text-align: center">2</td>
      <td style="text-align: center">8</td>
      <td style="text-align: center">6</td>
    </tr>
    <tr>
      <td style="text-align: center"><strong>3</strong></td>
      <td style="text-align: center">7</td>
      <td style="text-align: center">2</td>
      <td style="text-align: center">0</td>
      <td style="text-align: center">7</td>
      <td style="text-align: center">8</td>
    </tr>
    <tr>
      <td style="text-align: center"><strong>4</strong></td>
      <td style="text-align: center">3</td>
      <td style="text-align: center">8</td>
      <td style="text-align: center">7</td>
      <td style="text-align: center">0</td>
      <td style="text-align: center">2</td>
    </tr>
    <tr>
      <td style="text-align: center"><strong>5</strong></td>
      <td style="text-align: center">1</td>
      <td style="text-align: center">6</td>
      <td style="text-align: center">8</td>
      <td style="text-align: center">2</td>
      <td style="text-align: center">0</td>
    </tr>
  </tbody>
</table>

<p>Örneğin 2 ile 4. düğüm arasındaki en kısa mesafe 8 olup $2 \to 3 \to 1 \to 5 \to 4$ yoluna karşılık gelir.</p>

<h3 id="i̇mplementasyon-2">İmplementasyon</h3>

<p>Floyd–Warshall’ın en büyük avantajı son derece sade bir koda sahip olmasıdır:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Başlatma</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;=</span> <span class="n">n</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">j</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="n">j</span> <span class="o">&lt;=</span> <span class="n">n</span><span class="p">;</span> <span class="n">j</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">i</span> <span class="o">==</span> <span class="n">j</span><span class="p">)</span> <span class="n">distance</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
        <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">adj</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="p">])</span> <span class="n">distance</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="p">]</span> <span class="o">=</span> <span class="n">adj</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="p">];</span>
        <span class="k">else</span> <span class="n">distance</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="p">]</span> <span class="o">=</span> <span class="n">INF</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="c1">// En kısa mesafeleri bulma</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">k</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="n">k</span> <span class="o">&lt;=</span> <span class="n">n</span><span class="p">;</span> <span class="n">k</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;=</span> <span class="n">n</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">j</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="n">j</span> <span class="o">&lt;=</span> <span class="n">n</span><span class="p">;</span> <span class="n">j</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">distance</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="p">]</span> <span class="o">=</span> <span class="n">min</span><span class="p">(</span><span class="n">distance</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="p">],</span>
                                 <span class="n">distance</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">k</span><span class="p">]</span> <span class="o">+</span> <span class="n">distance</span><span class="p">[</span><span class="n">k</span><span class="p">][</span><span class="n">j</span><span class="p">]);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Üç iç içe döngü nedeniyle zaman karmaşıklığı $O(n^3)$’tür. Kodu sade olduğundan, tek bir en kısa yol bulmak gerekse bile çizge yeterince küçükse kullanılabilir.</p>

<h2 id="algoritmaların-karşılaştırması">Algoritmaların Karşılaştırması</h2>

<table>
  <thead>
    <tr>
      <th style="text-align: center">Algoritma</th>
      <th style="text-align: center">Kaynak</th>
      <th style="text-align: center">Negatif kenar</th>
      <th style="text-align: center">Zaman karmaşıklığı</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center">Bellman–Ford</td>
      <td style="text-align: center">Tek kaynak</td>
      <td style="text-align: center">✓ (döngü tespiti de yapar)</td>
      <td style="text-align: center">$O(nm)$</td>
    </tr>
    <tr>
      <td style="text-align: center">SPFA</td>
      <td style="text-align: center">Tek kaynak</td>
      <td style="text-align: center">✓</td>
      <td style="text-align: center">$O(nm)$ (ortalama daha hızlı)</td>
    </tr>
    <tr>
      <td style="text-align: center">Dijkstra</td>
      <td style="text-align: center">Tek kaynak</td>
      <td style="text-align: center">✗</td>
      <td style="text-align: center">$O(n + m \log m)$</td>
    </tr>
    <tr>
      <td style="text-align: center">Floyd–Warshall</td>
      <td style="text-align: center">Tüm çiftler</td>
      <td style="text-align: center">✓</td>
      <td style="text-align: center">$O(n^3)$</td>
    </tr>
  </tbody>
</table>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p>Algoritma R. E. Bellman ve L. R. Ford tarafından birbirinden habersiz biçimde sırasıyla 1958 ve 1956 yıllarında yayınlanmıştır. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2">
      <p>E. W. Dijkstra algoritmayı 1959 yılında yayınlamıştır; ancak orijinal makalesinde verimli bir implementasyondan bahsetmemiştir. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3">
      <p>Kendi öncelikli kuyruk yapısı tanımlanarak pozitif değerler de kullanılabilir; ancak implementasyon biraz daha uzun olur. <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:4">
      <p>Algoritma R. W. Floyd ve S. Warshall tarafından birbirinden habersiz biçimde 1962 yılında yayınlanmıştır. <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Sonsuz Us</name></author><category term="Program" /><category term="çizge" /><category term="graph" /><category term="en-kısa-yol" /><category term="shortest-path" /><category term="bellman-ford" /><category term="dijkstra" /><category term="floyd-warshall" /><category term="c" /><category term="programlama" /><category term="algoritma" /><category term="olimpiyat" /><category term="yarışma" /><category term="kodlama" /><category term="matematik" /><category term="kitap" /><summary type="html"><![CDATA[Bir çizgede iki düğüm arasındaki en kısa yolu bulmak, pek çok pratik uygulamaya sahip temel bir problemdir. Klasik bir örnek, yol uzunlukları bilinen bir ağda iki şehir arasındaki en kısa rotayı hesaplamaktır. Ağırlıksız çizgelerde yol uzunluğu kenar sayısına eşit olduğundan BFS ile çözülebilir; bu bölümde ise ağırlıklı çizgeler için geliştirilmiş algoritmalara bakacağız.]]></summary></entry><entry><title type="html">Rekabetçi Programcı Çizgede Dolaşma</title><link href="https://sonsuzus.github.io/posts/rekabetci-programci-cizgede-dolasma" rel="alternate" type="text/html" title="Rekabetçi Programcı Çizgede Dolaşma" /><published>2026-04-04T00:00:00+00:00</published><updated>2026-04-04T00:00:00+00:00</updated><id>https://sonsuzus.github.io/posts/rekabetci-programci-cizgede-dolasma</id><content type="html" xml:base="https://sonsuzus.github.io/posts/rekabetci-programci-cizgede-dolasma"><![CDATA[<p>Bu bölümde iki temel çizge dolaşma algoritması ele alınacaktır: <strong>derinlik öncelikli arama</strong> (DFS) ve <strong>genişlik öncelikli arama</strong> (BFS). Her ikisinin de bir başlangıç noktası vardır ve başlangıç düğümünden ulaşılabilen tüm düğümleri gezerler. İki algoritma arasındaki fark, düğümleri dolaşma sırasıdır.</p>

<h2 id="121-derinlik-öncelikli-arama-dfs">12.1 Derinlik Öncelikli Arama (DFS)</h2>

<p><strong>Derinlik öncelikli arama</strong> (Depth-First Search — DFS), sezgisel bir çizge dolaşma tekniğidir. Algoritma başlangıç düğümünden hareket ederek çizgenin kenarlarını kullanır ve ulaşılabilir tüm düğümleri ziyaret eder. DFS, yeni bir düğüm buldukça tek bir yolu izlemeye devam eder; çıkmaza girdiğinde ise geri dönerek çizgenin diğer bölgelerini keşfeder. Her düğüm yalnızca bir kez ziyaret edilip işlenir.</p>

<p>Algoritmanın zaman karmaşıklığı $O(n + m)$’dir; burada $n$ düğüm sayısı, $m$ ise kenar sayısıdır. Bu, algoritmanın her düğümü ve kenarı tam bir kez işlemesinden kaynaklanır.</p>

<h3 id="örnek">Örnek</h3>

<p>Aşağıdaki beş düğümlü çizgede DFS’in 1. düğümden nasıl ilerlediğini izleyelim:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1 — 2
|   |
4   3
 \ /
  5
</code></pre></div></div>

<p>DFS 1. düğümden başlar ve önce 2. düğüme geçer. Oradan 3, ardından 5. düğüm ziyaret edilir. 5. düğümün komşuları olan 2 ve 3 zaten ziyaret edildiği için geri dönülür. 3 ve 2. düğümlerin tüm kenarları da işlendiğinden 1. düğüme dönülür ve oradan 4. düğüme gidilir. Tüm düğümler ziyaret edildiğinde arama sona erer.</p>

<p>Ziyaret sırası: $1 \to 2 \to 3 \to 5 \to 4$</p>

<h3 id="i̇mplementasyon">İmplementasyon</h3>

<p>DFS, özyineleme (recursion) ile kolayca kodlanabilir. <code class="language-plaintext highlighter-rouge">dfs</code> fonksiyonu belirtilen düğümden aramaya başlar. Çizge komşuluk listelerinde, ziyaret edilen düğümler ise <code class="language-plaintext highlighter-rouge">visited</code> dizisinde tutulur:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">vector</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span> <span class="n">adj</span><span class="p">[</span><span class="n">N</span><span class="p">];</span>
<span class="kt">bool</span> <span class="n">visited</span><span class="p">[</span><span class="n">N</span><span class="p">];</span>

<span class="kt">void</span> <span class="nf">dfs</span><span class="p">(</span><span class="kt">int</span> <span class="n">s</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">visited</span><span class="p">[</span><span class="n">s</span><span class="p">])</span> <span class="k">return</span><span class="p">;</span>
    <span class="n">visited</span><span class="p">[</span><span class="n">s</span><span class="p">]</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
    <span class="c1">// s düğümüyle bir şeyler yap</span>
    <span class="k">for</span> <span class="p">(</span><span class="k">auto</span> <span class="n">u</span> <span class="o">:</span> <span class="n">adj</span><span class="p">[</span><span class="n">s</span><span class="p">])</span> <span class="p">{</span>
        <span class="n">dfs</span><span class="p">(</span><span class="n">u</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Başta <code class="language-plaintext highlighter-rouge">visited</code> dizisinin tüm elemanları <code class="language-plaintext highlighter-rouge">false</code>‘tur. Arama <code class="language-plaintext highlighter-rouge">s</code> düğümüne geldiğinde <code class="language-plaintext highlighter-rouge">visited[s]</code> değeri <code class="language-plaintext highlighter-rouge">true</code> olarak işaretlenir ve o düğüm bir daha işlenmez.</p>

<h2 id="122-genişlik-öncelikli-arama-bfs">12.2 Genişlik Öncelikli Arama (BFS)</h2>

<p><strong>Genişlik öncelikli arama</strong> (Breadth-First Search — BFS), düğümleri başlangıç düğümüne olan mesafeye göre artan sırada ziyaret eder. Bu özelliği sayesinde BFS, tüm düğümlerin başlangıç düğümüne olan <strong>en kısa mesafelerini</strong> hesaplamak için doğal bir araçtır. Bununla birlikte DFS’e kıyasla koda geçirilmesi biraz daha karmaşıktır.</p>

<p>BFS düğümleri katman katman (level by level) dolaşır: önce başlangıç düğümüne 1 uzaklıktaki düğümler, ardından 2 uzaklıktakiler ve böyle devam eder. Tüm düğümler ziyaret edilene kadar sürer. Zaman karmaşıklığı DFS ile aynıdır: $O(n + m)$.</p>

<h3 id="örnek-1">Örnek</h3>

<p>Altı düğümlü çizgede BFS’in 1. düğümden nasıl ilerlediğini izleyelim:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1 — 2 — 3
|       |
4   5 — 6
</code></pre></div></div>

<p>İlk adımda 1’den tek bir kenarla ulaşılan 2 ve 4 ziyaret edilir. Sonra 2’nin komşusu 3 ve bağlantılı yoldan 5 ziyaret edilir. En sonda 6 ziyaret edilir.</p>

<p>Hesaplanan mesafeler:</p>

<table>
  <thead>
    <tr>
      <th style="text-align: center">Düğüm</th>
      <th style="text-align: center">Mesafe</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center">1</td>
      <td style="text-align: center">0</td>
    </tr>
    <tr>
      <td style="text-align: center">2</td>
      <td style="text-align: center">1</td>
    </tr>
    <tr>
      <td style="text-align: center">3</td>
      <td style="text-align: center">2</td>
    </tr>
    <tr>
      <td style="text-align: center">4</td>
      <td style="text-align: center">1</td>
    </tr>
    <tr>
      <td style="text-align: center">5</td>
      <td style="text-align: center">2</td>
    </tr>
    <tr>
      <td style="text-align: center">6</td>
      <td style="text-align: center">3</td>
    </tr>
  </tbody>
</table>

<h3 id="i̇mplementasyon-1">İmplementasyon</h3>

<p>BFS bir <strong>kuyruk</strong> (queue) veri yapısıyla koda dökülür. Her adımda kuyruğun başındaki düğüm işlenir; yeni keşfedilen düğümler kuyruğun sonuna eklenir. Böylece düğümler her zaman mesafe sırasına göre işlenir.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">queue</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span> <span class="n">q</span><span class="p">;</span>
<span class="kt">bool</span> <span class="n">visited</span><span class="p">[</span><span class="n">N</span><span class="p">];</span>
<span class="kt">int</span> <span class="n">distance</span><span class="p">[</span><span class="n">N</span><span class="p">];</span>

<span class="c1">// x: başlangıç düğümü</span>
<span class="n">visited</span><span class="p">[</span><span class="n">x</span><span class="p">]</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
<span class="n">distance</span><span class="p">[</span><span class="n">x</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="n">q</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="n">x</span><span class="p">);</span>

<span class="k">while</span> <span class="p">(</span><span class="o">!</span><span class="n">q</span><span class="p">.</span><span class="n">empty</span><span class="p">())</span> <span class="p">{</span>
    <span class="kt">int</span> <span class="n">s</span> <span class="o">=</span> <span class="n">q</span><span class="p">.</span><span class="n">front</span><span class="p">();</span> <span class="n">q</span><span class="p">.</span><span class="n">pop</span><span class="p">();</span>
    <span class="c1">// s düğümüyle bir şeyler yap</span>
    <span class="k">for</span> <span class="p">(</span><span class="k">auto</span> <span class="n">u</span> <span class="o">:</span> <span class="n">adj</span><span class="p">[</span><span class="n">s</span><span class="p">])</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">visited</span><span class="p">[</span><span class="n">u</span><span class="p">])</span> <span class="k">continue</span><span class="p">;</span>
        <span class="n">visited</span><span class="p">[</span><span class="n">u</span><span class="p">]</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
        <span class="n">distance</span><span class="p">[</span><span class="n">u</span><span class="p">]</span> <span class="o">=</span> <span class="n">distance</span><span class="p">[</span><span class="n">s</span><span class="p">]</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
        <span class="n">q</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="n">u</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">visited</code> dizisi ziyaret edilen düğümleri, <code class="language-plaintext highlighter-rouge">distance</code> dizisi ise başlangıç düğümünden her düğüme olan en kısa mesafeyi tutar.</p>

<h2 id="123-uygulamalar">12.3 Uygulamalar</h2>

<p>Çizge dolaşma algoritmaları, çizgenin çeşitli özelliklerini tespit etmek için kullanılabilir. Genellikle DFS de BFS de işe yarar; ancak DFS özyinelemeli yapısıyla daha sade yazıldığından pratikte daha sık tercih edilir. Aşağıdaki uygulamalar çizgenin yönsüz olduğunu varsayar.</p>

<h3 id="bağlılık-kontrolü">Bağlılık Kontrolü</h3>

<p>Bir çizge, her iki düğümü arasında yol varsa <strong>bağlıdır</strong>. Bağlılığı kontrol etmek için herhangi bir düğümden dolaşma başlatılır ve tüm düğümlerin ziyaret edilip edilmediğine bakılır. Ziyaret edilmemiş düğüm varsa çizge bağlı değildir.</p>

<p>Çizgenin tüm bağlı <strong>parçalarını</strong> (components) bulmak için ziyaret edilmemiş her düğümden yeni bir dolaşma başlatmak yeterlidir:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;=</span> <span class="n">n</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">visited</span><span class="p">[</span><span class="n">i</span><span class="p">])</span> <span class="n">dfs</span><span class="p">(</span><span class="n">i</span><span class="p">);</span> <span class="c1">// yeni bir parça başlıyor</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="döngü-bulmak">Döngü Bulmak</h3>

<p>Dolaşma sırasında daha önce ziyaret edilmiş bir komşuya ulaşılırsa — bir önceki düğüm hariç — çizgede <strong>döngü</strong> (cycle) vardır.</p>

<p>Örneğin 2. düğümden 5. düğüme geçerken 5’in komşusu olan 3’ün zaten ziyaret edildiği görülürse $3 \to 2 \to 5 \to 3$ döngüsü tespit edilmiş olur.</p>

<p>Alternatif bir yöntem: her parçanın düğüm ve kenar sayısını karşılaştırmak. Parça $c$ düğüm içeriyorsa ve döngüsüzse tam $c - 1$ kenar içerir (yani bir ağaçtır). $c$ veya daha fazla kenar içeriyorsa o parça en az bir döngü barındırıyordur.</p>

<h3 id="i̇ki-parçalılık-kontrolü">İki Parçalılık Kontrolü</h3>

<p>Komşu düğümlerin asla aynı renge sahip olmayacağı şekilde yalnızca iki renkle boyanabilen çizge <strong>iki parçalıdır</strong> (bipartite). Bunu çizge dolaşmayla kontrol etmek oldukça kolaydır.</p>

<p>Başlangıç düğümü maviye boyanır, tüm komşuları kırmızıya, ardından o komşuların komşuları tekrar maviye boyama şeklinde alternatif renkler atanır. Eğer dolaşma sırasında iki komşu düğüm aynı renge boyanmışsa çizge iki parçalı <strong>değildir</strong>. Böyle bir çelişki oluşmazsa çizge iki parçalıdır ve elde edilen boyama geçerli bir çözümdür.</p>

<p>Örneğin tek sayıda kenara sahip bir döngü içeren çizgede bu boyama mutlaka bir çelişkiyle sonuçlanır. Başlangıç düğümüne hangi rengin atandığının önemi yoktur; seçilen renk otomatik olarak diğer tüm düğümlerin rengini belirler.</p>

<p>Genel durumda bir çizgeyi $k$ renkle boyamak ise çok daha zordur. $k = 3$ için dahi bilinen verimli bir algoritma yoktur; bu problem <strong>NP-hard</strong>‘dır.</p>]]></content><author><name>Sonsuz Us</name></author><category term="Program" /><category term="çizge" /><category term="graph" /><category term="dfs" /><category term="bfs" /><category term="derinlik-öncelikli-arama" /><category term="genişlik-öncelikli-arama" /><category term="c" /><category term="programlama" /><category term="algoritma" /><category term="olimpiyat" /><category term="yarışma" /><category term="veri-yapisi" /><category term="kodlama" /><category term="kitap" /><summary type="html"><![CDATA[Bu bölümde iki temel çizge dolaşma algoritması ele alınacaktır: derinlik öncelikli arama (DFS) ve genişlik öncelikli arama (BFS). Her ikisinin de bir başlangıç noktası vardır ve başlangıç düğümünden ulaşılabilen tüm düğümleri gezerler. İki algoritma arasındaki fark, düğümleri dolaşma sırasıdır.]]></summary></entry><entry><title type="html">Rekabetçi Programcı Çizgenin Temelleri</title><link href="https://sonsuzus.github.io/posts/rekabetci-programci-cizgenin-temelleri" rel="alternate" type="text/html" title="Rekabetçi Programcı Çizgenin Temelleri" /><published>2026-04-03T00:00:00+00:00</published><updated>2026-04-03T00:00:00+00:00</updated><id>https://sonsuzus.github.io/posts/rekabetci-programci-cizgenin-temelleri</id><content type="html" xml:base="https://sonsuzus.github.io/posts/rekabetci-programci-cizgenin-temelleri"><![CDATA[<p>Çoğu kodlama sorusu bir çizge problemi olarak modellenip uygun bir çizge algoritmasıyla çözülebilir. Tipik bir örnek, bir ülkedeki yolları ve şehirleri temsil eden ağdır. Bazen çizge sorunun içinde gizli olduğundan fark edilmesi güçleşir. Bu bölümde çizgelerle ilgili temel kavramları ele alıp algoritmalarda çizgeleri göstermenin farklı yollarını inceleyeceğiz.</p>

<h2 id="111-çizge-terminolojisi">11.1 Çizge Terminolojisi</h2>

<p>Bir <strong>çizge</strong> (graph), <strong>düğümlerden</strong> (nodes) ve <strong>kenarlardan</strong> (edges) oluşur. Bu yazıda $n$ çizgedeki toplam düğüm sayısını, $m$ ise toplam kenar sayısını belirtecektir. Düğümler $1, 2, \ldots, n$ tamsayılarıyla numaralandırılır.</p>

<h3 id="yol-ve-döngü">Yol ve Döngü</h3>

<p><strong>Yol</strong> (path), $a$ düğümünden $b$ düğümüne kenarlar kullanılarak ulaşılmasını sağlar. Yolun uzunluğu, yolda geçilen kenar sayısına eşittir. Örneğin $1 \to 3 \to 4 \to 5$ yolu, 1. düğümden 5. düğüme 3 uzunluğunda bir yoldur.</p>

<p>Başlangıç ve son düğümün aynı olduğu yola <strong>döngü</strong> (cycle) denir. Bir yolda her düğüm en fazla bir kez geçiyorsa bu yol <strong>basittir</strong> (simple).</p>

<h3 id="bağlılık">Bağlılık</h3>

<p>Her iki düğümü arasında bir yol bulunan çizge <strong>bağlıdır</strong> (connected). Bir çizgenin bağlı alt gruplarına <strong>parça</strong> (component) denir. Örneğin ${1,2,3}$, ${4,5,6,7}$ ve ${8}$ olmak üzere üç parçalı bir çizge bağlı değildir.</p>

<p>Bir çizge bağlıysa ve $n$ düğüm ile $n-1$ kenardan oluşuyorsa bu çizge bir <strong>ağaçtır</strong> (tree). Ağaçta her iki düğüm arasında tam olarak bir yol vardır.</p>

<h3 id="kenar-yönleri">Kenar Yönleri</h3>

<p>Kenarların tek yönlü olduğu çizge <strong>yönlüdür</strong> (directed). Yönlü bir çizgede $3 \to 1 \to 2 \to 5$ yolu olabilirken $5$’ten $3$’e giden herhangi bir yol olmayabilir.</p>

<h3 id="kenar-ağırlıkları">Kenar Ağırlıkları</h3>

<p><strong>Ağırlıklı</strong> (weighted) bir çizgede her kenarın bir ağırlığı vardır; bu ağırlık genellikle uzunluk olarak yorumlanır. Ağırlıklı bir çizgedeki yolun uzunluğu, yoldaki kenarların ağırlıklarının toplamıdır. Örneğin $1 \to 2 \to 5$ yolunun uzunluğu 12, $1 \to 3 \to 4 \to 5$ yolunun uzunluğu 11 olabilir; burada ikinci yol daha kısadır.</p>

<h3 id="komşular-ve-dereceler">Komşular ve Dereceler</h3>

<p>İki düğüm arasında kenar varsa bunlar <strong>komşu</strong> (neighbor) düğümlerdir. Bir düğümün <strong>derecesi</strong> (degree), komşu sayısına eşittir.</p>

<p>$m$ kenarlı bir çizgenin toplam derece sayısı her zaman $2m$’dir; çünkü her kenar iki düğümün derecesini birer artırır. Bu nedenle derece toplamı daima çifttir.</p>

<p>Yönlü çizgelerde bir düğümün <strong>iç derecesi</strong> (indegree) o düğüme gelen kenar sayısını, <strong>dış derecesi</strong> (outdegree) ise o düğümden çıkan kenar sayısını verir.</p>

<p>Her düğümün derecesi $d$ ise çizge <strong>sıradan</strong> (regular), her düğüm birbirine bağlıysa (derece $n-1$) çizge <strong>tam</strong> (complete) olarak adlandırılır.</p>

<h3 id="boyamalar">Boyamalar</h3>

<p>Çizgeyi boyarken komşu düğümlerin farklı renk almasına dikkat edilir. Yalnızca iki renkle boyanabilen çizge <strong>iki parçalıdır</strong> (bipartite). Bir çizgenin iki parçalı olabilmesi için tek sayıda kenarlı herhangi bir döngü içermemesi gerekir.</p>

<p>Örneğin altı düğümlü bir çizge iki parçalıysa düğümler iki gruba ayrılıp her kenar gruplar arasında geçer; iki renkle boyanabilir. Ama tek sayıda kenarlı bir döngü içeriyorsa boyama mümkün olmaz.</p>

<p>Genel durumda bir çizgenin $k$ renkle boyanıp boyanamayacağını bulmak zordur. $k = 3$ için dahi bilinen verimli bir algoritma yoktur — bu problem <strong>NP-hard</strong>‘dır.</p>

<h3 id="basitlik">Basitlik</h3>

<p>Aynı düğümde başlayıp biten kenar (öz-döngü) veya iki düğüm arasında birden fazla kenar içeren çizge <strong>basit değildir</strong> (not simple). Genellikle çizgelerin basit olduğu kabul edilir.</p>

<h2 id="112-çizge-gösterimi">11.2 Çizge Gösterimi</h2>

<p>Algoritmalarda çizgeleri göstermenin birkaç yaygın yolu vardır. Veri yapısının seçimi çizgenin büyüklüğüne ve algoritmanın çizgeyi işleme biçimine göre değişir.</p>

<h3 id="komşuluk-listesi-gösterimi">Komşuluk Listesi Gösterimi</h3>

<p><strong>Komşuluk listesi</strong> (adjacency list) gösteriminde her $x$ düğümüne bir liste atanır; bu liste $x$’ten çıkan kenarların ulaştığı düğümleri içerir. Komşuluk listeleri çizgeleri göstermenin en popüler yoludur ve çoğu algoritma bu yöntemle verimli biçimde kodlanabilir.</p>

<p>Komşuluk listesini oluşturmanın pratik yolu vektörlerden oluşan bir dizi kullanmaktır:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">vector</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span> <span class="n">adj</span><span class="p">[</span><span class="n">N</span><span class="p">];</span>
</code></pre></div></div>

<p>Örneğin aşağıdaki çizge için:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">adj</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="n">push_back</span><span class="p">(</span><span class="mi">2</span><span class="p">);</span>
<span class="n">adj</span><span class="p">[</span><span class="mi">2</span><span class="p">].</span><span class="n">push_back</span><span class="p">(</span><span class="mi">3</span><span class="p">);</span>
<span class="n">adj</span><span class="p">[</span><span class="mi">2</span><span class="p">].</span><span class="n">push_back</span><span class="p">(</span><span class="mi">4</span><span class="p">);</span>
<span class="n">adj</span><span class="p">[</span><span class="mi">3</span><span class="p">].</span><span class="n">push_back</span><span class="p">(</span><span class="mi">4</span><span class="p">);</span>
<span class="n">adj</span><span class="p">[</span><span class="mi">4</span><span class="p">].</span><span class="n">push_back</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>
</code></pre></div></div>

<p>Yönsüz (undirected) çizgelerde her kenar her iki yöne de eklenir. Ağırlıklı bir çizge için yapı şöyle güncellenir:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">vector</span><span class="o">&lt;</span><span class="n">pair</span><span class="o">&lt;</span><span class="kt">int</span><span class="p">,</span><span class="kt">int</span><span class="o">&gt;&gt;</span> <span class="n">adj</span><span class="p">[</span><span class="n">N</span><span class="p">];</span>
</code></pre></div></div>

<p>Bu durumda $a$ düğümünden $b$ düğümüne $w$ ağırlığında kenar varsa $a$’nın listesi $(b, w)$ çiftini içerir:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">adj</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="n">push_back</span><span class="p">({</span><span class="mi">2</span><span class="p">,</span> <span class="mi">5</span><span class="p">});</span>
<span class="n">adj</span><span class="p">[</span><span class="mi">2</span><span class="p">].</span><span class="n">push_back</span><span class="p">({</span><span class="mi">3</span><span class="p">,</span> <span class="mi">7</span><span class="p">});</span>
<span class="n">adj</span><span class="p">[</span><span class="mi">2</span><span class="p">].</span><span class="n">push_back</span><span class="p">({</span><span class="mi">4</span><span class="p">,</span> <span class="mi">6</span><span class="p">});</span>
<span class="n">adj</span><span class="p">[</span><span class="mi">3</span><span class="p">].</span><span class="n">push_back</span><span class="p">({</span><span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">});</span>
<span class="n">adj</span><span class="p">[</span><span class="mi">4</span><span class="p">].</span><span class="n">push_back</span><span class="p">({</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">});</span>
</code></pre></div></div>

<p>Komşuluk listesinin en büyük avantajı, bir düğümden ulaşılabilecek düğümlerin hızlıca dolaşılabilmesidir:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for</span> <span class="p">(</span><span class="k">auto</span> <span class="n">u</span> <span class="o">:</span> <span class="n">adj</span><span class="p">[</span><span class="n">s</span><span class="p">])</span> <span class="p">{</span>
    <span class="c1">// u düğümünü işle</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="komşuluk-matrisi-gösterimi">Komşuluk Matrisi Gösterimi</h3>

<p><strong>Komşuluk matrisi</strong> (adjacency matrix) gösterimi, çizgedeki kenarları iki boyutlu bir dizide tutar ve iki düğüm arasında kenar olup olmadığını $O(1)$ zamanda sorgular:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="n">adj</span><span class="p">[</span><span class="n">N</span><span class="p">][</span><span class="n">N</span><span class="p">];</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">adj[a][b] = 1</code> ise $a$’dan $b$’ye kenar vardır, <code class="language-plaintext highlighter-rouge">adj[a][b] = 0</code> ise yoktur. Ağırlıklı çizgelerde kenar ağırlığı da bu matriste tutulabilir.</p>

<p>Dezavantajı, $n^2$ elemanlık bir dizi gerektirmesidir. Büyük çizgeler için bellek açısından pratik değildir; özellikle matrisin büyük bölümü sıfırdan oluştuğunda (seyrek çizgeler).</p>

<h3 id="kenar-listesi-gösterimi">Kenar Listesi Gösterimi</h3>

<p><strong>Kenar listesi</strong> (edge list) gösterimi, çizgenin tüm kenarlarını rastgele bir sırada bir vektörde tutar. Algoritma tüm kenarları işliyorsa ve belirli bir düğümden başlayan kenarları bulmaya gerek yoksa bu gösterim uygundur.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">vector</span><span class="o">&lt;</span><span class="n">pair</span><span class="o">&lt;</span><span class="kt">int</span><span class="p">,</span><span class="kt">int</span><span class="o">&gt;&gt;</span> <span class="n">edges</span><span class="p">;</span>
</code></pre></div></div>

<p>Her $(a, b)$ çifti, $a$ düğümünden $b$ düğümüne bir kenar olduğunu gösterir:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">edges</span><span class="p">.</span><span class="n">push_back</span><span class="p">({</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">});</span>
<span class="n">edges</span><span class="p">.</span><span class="n">push_back</span><span class="p">({</span><span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">});</span>
<span class="n">edges</span><span class="p">.</span><span class="n">push_back</span><span class="p">({</span><span class="mi">2</span><span class="p">,</span> <span class="mi">4</span><span class="p">});</span>
<span class="n">edges</span><span class="p">.</span><span class="n">push_back</span><span class="p">({</span><span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">});</span>
<span class="n">edges</span><span class="p">.</span><span class="n">push_back</span><span class="p">({</span><span class="mi">4</span><span class="p">,</span> <span class="mi">1</span><span class="p">});</span>
</code></pre></div></div>

<p>Ağırlıklı çizgeler için yapı üçlü demet olarak genişletilir:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">vector</span><span class="o">&lt;</span><span class="n">tuple</span><span class="o">&lt;</span><span class="kt">int</span><span class="p">,</span><span class="kt">int</span><span class="p">,</span><span class="kt">int</span><span class="o">&gt;&gt;</span> <span class="n">edges</span><span class="p">;</span>
</code></pre></div></div>

<p>Her $(a, b, w)$ demeti, $a$’dan $b$’ye $w$ ağırlığında kenar olduğunu belirtir<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">edges</span><span class="p">.</span><span class="n">push_back</span><span class="p">({</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">5</span><span class="p">});</span>
<span class="n">edges</span><span class="p">.</span><span class="n">push_back</span><span class="p">({</span><span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">7</span><span class="p">});</span>
<span class="n">edges</span><span class="p">.</span><span class="n">push_back</span><span class="p">({</span><span class="mi">2</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">6</span><span class="p">});</span>
<span class="n">edges</span><span class="p">.</span><span class="n">push_back</span><span class="p">({</span><span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">});</span>
<span class="n">edges</span><span class="p">.</span><span class="n">push_back</span><span class="p">({</span><span class="mi">4</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">});</span>
</code></pre></div></div>

<h3 id="gösterimler-arasında-karşılaştırma">Gösterimler Arasında Karşılaştırma</h3>

<table>
  <thead>
    <tr>
      <th>Gösterim</th>
      <th>Bellek</th>
      <th>Kenar var mı?</th>
      <th>Düğümün komşuları</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Komşuluk listesi</td>
      <td>$O(n + m)$</td>
      <td>$O(\text{derece})$</td>
      <td>$O(\text{derece})$</td>
    </tr>
    <tr>
      <td>Komşuluk matrisi</td>
      <td>$O(n^2)$</td>
      <td>$O(1)$</td>
      <td>$O(n)$</td>
    </tr>
    <tr>
      <td>Kenar listesi</td>
      <td>$O(m)$</td>
      <td>$O(m)$</td>
      <td>$O(m)$</td>
    </tr>
  </tbody>
</table>

<p>Seyrek çizgelerde ($m \ll n^2$) komşuluk listesi tercih edilir. Yoğun çizgelerde ($m \approx n^2$) komşuluk matrisi pratik olabilir. Bellman-Ford gibi tüm kenarları tek tek işleyen algoritmalar için kenar listesi doğal bir seçimdir.</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p>Bazı eski derleyicilerde süslü parantez yerine <code class="language-plaintext highlighter-rouge">make_tuple</code> fonksiyonu kullanılmalıdır. Örneğin <code class="language-plaintext highlighter-rouge">{1, 2, 5}</code> yerine <code class="language-plaintext highlighter-rouge">make_tuple(1, 2, 5)</code> yazılır. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Sonsuz Us</name></author><category term="Program" /><category term="çizge" /><category term="graph" /><category term="c" /><category term="programlama" /><category term="algoritma" /><category term="olimpiyat" /><category term="yarışma" /><category term="veri-yapisi" /><category term="komşuluk-listesi" /><category term="komşuluk-matrisi" /><category term="kenar-listesi" /><category term="kodlama" /><category term="matematik" /><category term="kitap" /><summary type="html"><![CDATA[Çoğu kodlama sorusu bir çizge problemi olarak modellenip uygun bir çizge algoritmasıyla çözülebilir. Tipik bir örnek, bir ülkedeki yolları ve şehirleri temsil eden ağdır. Bazen çizge sorunun içinde gizli olduğundan fark edilmesi güçleşir. Bu bölümde çizgelerle ilgili temel kavramları ele alıp algoritmalarda çizgeleri göstermenin farklı yollarını inceleyeceğiz.]]></summary></entry><entry><title type="html">Rekabetçi Programcı Aralık Sorguları</title><link href="https://sonsuzus.github.io/posts/rekabetci-programci-aralik-sorgulari" rel="alternate" type="text/html" title="Rekabetçi Programcı Aralık Sorguları" /><published>2026-04-02T00:00:00+00:00</published><updated>2026-04-02T00:00:00+00:00</updated><id>https://sonsuzus.github.io/posts/rekabetci-programci-aralik-sorgulari</id><content type="html" xml:base="https://sonsuzus.github.io/posts/rekabetci-programci-aralik-sorgulari"><![CDATA[<p>Bir dizinin alt aralıklarında hızlıca sorgu yapmak rekabetçi programlamada sık karşılaşılan bir ihtiyaçtır. Bir <strong>aralık sorgusunda</strong> görev, bir dizinin belirli bir alt aralığında bir değeri hesaplamaktır. Tipik aralık sorguları şunlardır:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">sumq(a, b)</code>: $[a, b]$ aralığındaki sayıların toplamını bul</li>
  <li><code class="language-plaintext highlighter-rouge">minq(a, b)</code>: $[a, b]$ aralığındaki minimum sayıyı bul</li>
  <li><code class="language-plaintext highlighter-rouge">maxq(a, b)</code>: $[a, b]$ aralığındaki maksimum sayıyı bul</li>
</ul>

<p>Örneğin <code class="language-plaintext highlighter-rouge">[1, 3, 8, 4, 6, 1, 3, 4]</code> dizisinde $[3,6]$ aralığı için <code class="language-plaintext highlighter-rouge">sumq(3,6) = 14</code>, <code class="language-plaintext highlighter-rouge">minq(3,6) = 1</code>, <code class="language-plaintext highlighter-rouge">maxq(3,6) = 6</code> olur.</p>

<p>En basit yaklaşım aralık içindeki tüm elemanlara tek tek bakmaktır; bu $O(n)$ sürer. $q$ sorgu için toplam $O(nq)$ zaman gerekir. Hem $n$ hem de $q$ büyük olduğunda bu yavaş kalır. Neyse ki aralık sorgularını çok daha verimli yapmanın yolları vardır.</p>

<h2 id="91-statik-dizi-sorguları">9.1 Statik Dizi Sorguları</h2>

<p>Dizinin <strong>statik</strong> olduğu (sorgular sırasında değerlerin değişmediği) duruma ilk bakacağız. Bu durumda sorguları sabit zamanda yanıtlayan bir veri yapısı oluşturmak yeterlidir.</p>

<h3 id="toplam-sorguları">Toplam Sorguları</h3>

<p>Statik bir dizideki toplam sorgularını <strong>prefix toplam dizisi</strong> ile kolayca çözebiliriz. Prefix toplam dizisindeki $k$. konum, orijinal dizinin $[0, k]$ aralığının toplamına eşittir; yani <code class="language-plaintext highlighter-rouge">sumq(0, k)</code> değerini tutar. Bu dizi $O(n)$ zamanda oluşturulabilir.</p>

<p>Örneğin <code class="language-plaintext highlighter-rouge">[1, 3, 4, 8, 6, 1, 4, 2]</code> dizisine karşılık gelen prefix toplam dizisi:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1  4  8  16  22  23  27  29
0  1  2   3   4   5   6   7
</code></pre></div></div>

<p>Herhangi bir <code class="language-plaintext highlighter-rouge">sumq(a, b)</code> değerini $O(1)$ zamanda şu formülle bulabiliriz:</p>

\[\text{sumq}(a, b) = \text{sumq}(0, b) - \text{sumq}(0, a-1)\]

<p>$\text{sumq}(0, -1) = 0$ tanımlaması sayesinde formül $a = 0$ için de geçerlidir. Örneğin:</p>

\[\text{sumq}(3, 6) = \text{sumq}(0, 6) - \text{sumq}(0, 2) = 27 - 8 = 19\]

<p>Bu fikir daha fazla boyuta da genelleşir. İki boyutlu bir prefix toplam dizisi oluşturularak herhangi bir dikdörtgen alt dizi toplamı $O(1)$ zamanda hesaplanabilir. Gri alt dizinin toplamı, $S(X)$’in sol üst köşeden $X$ pozisyonuna gelen dikdörtgen alt dizi toplamı olduğu varsayılarak şu formülle bulunur:</p>

\[S(A) - S(B) - S(C) + S(D)\]

<h3 id="minimum-sorgular">Minimum Sorgular</h3>

<p>Minimum sorgular, toplam sorgularına göre daha zordur. Yine de $O(n \log n)$’lik bir ön işleme ile minimum sorguları $O(1)$ zamanda yanıtlanabilir<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>. Minimum ve maksimum sorgular benzer şekilde ele alındığından yalnızca minimuma bakacağız.</p>

<p><strong>Fikir:</strong> Uzunluğu ikinin kuvveti olan tüm <code class="language-plaintext highlighter-rouge">minq(a, b)</code> değerleri önceden hesaplanır. Örneğin <code class="language-plaintext highlighter-rouge">[1, 3, 4, 8, 6, 1, 4, 2]</code> dizisi için bu değerler uzunluk 1, 2 ve 4’lük aralıklar için aşağıdaki gibidir:</p>

<table>
  <thead>
    <tr>
      <th>a</th>
      <th>b</th>
      <th>minq(a,b)</th>
      <th> </th>
      <th>a</th>
      <th>b</th>
      <th>minq(a,b)</th>
      <th> </th>
      <th>a</th>
      <th>b</th>
      <th>minq(a,b)</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>0</td>
      <td>0</td>
      <td>1</td>
      <td> </td>
      <td>0</td>
      <td>1</td>
      <td>1</td>
      <td> </td>
      <td>0</td>
      <td>3</td>
      <td>1</td>
    </tr>
    <tr>
      <td>1</td>
      <td>1</td>
      <td>3</td>
      <td> </td>
      <td>1</td>
      <td>2</td>
      <td>3</td>
      <td> </td>
      <td>1</td>
      <td>4</td>
      <td>3</td>
    </tr>
    <tr>
      <td>2</td>
      <td>2</td>
      <td>4</td>
      <td> </td>
      <td>2</td>
      <td>3</td>
      <td>4</td>
      <td> </td>
      <td>2</td>
      <td>5</td>
      <td>1</td>
    </tr>
    <tr>
      <td>3</td>
      <td>3</td>
      <td>8</td>
      <td> </td>
      <td>3</td>
      <td>4</td>
      <td>6</td>
      <td> </td>
      <td>3</td>
      <td>6</td>
      <td>1</td>
    </tr>
    <tr>
      <td>4</td>
      <td>4</td>
      <td>6</td>
      <td> </td>
      <td>4</td>
      <td>5</td>
      <td>1</td>
      <td> </td>
      <td>4</td>
      <td>7</td>
      <td>1</td>
    </tr>
    <tr>
      <td>5</td>
      <td>5</td>
      <td>1</td>
      <td> </td>
      <td>5</td>
      <td>6</td>
      <td>1</td>
      <td> </td>
      <td>0</td>
      <td>7</td>
      <td>1</td>
    </tr>
    <tr>
      <td>6</td>
      <td>6</td>
      <td>4</td>
      <td> </td>
      <td>6</td>
      <td>7</td>
      <td>2</td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>7</td>
      <td>7</td>
      <td>2</td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
  </tbody>
</table>

<p>Toplam $O(n \log n)$ değer, aşağıdaki özyinelemeli formülle hesaplanır ($w = (b - a + 1)/2$):</p>

\[\text{minq}(a, b) = \min(\text{minq}(a,\ a+w-1),\ \text{minq}(a+w,\ b))\]

<p>Sorgu anında $b - a + 1$ değerini geçmeyen en büyük ikinin kuvveti $k$ alınır ve:</p>

\[\text{minq}(a, b) = \min(\text{minq}(a,\ a+k-1),\ \text{minq}(b-k+1,\ b))\]

<p>formülü $O(1)$ zamanda sonucu verir. Örneğin $[1, 6]$ aralığında uzunluk 6 için $k = 4$ seçilir; $[1,4]$ ve $[3,6]$ aralıklarının birleşimi olarak $\text{minq}(1,6) = \min(3, 1) = 1$ bulunur.</p>

<h2 id="92-i̇kili-i̇ndisli-ağaç-binary-indexed-tree">9.2 İkili İndisli Ağaç (Binary Indexed Tree)</h2>

<p><strong>İkili indisli ağaç</strong> ya da <strong>Fenwick ağacı</strong><sup id="fnref:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup>, prefix toplam dizisinin dinamik versiyonu olarak düşünülebilir. $O(\log n)$ zamanda iki operasyonu destekler: aralık toplamı sorgusu ve tek eleman güncellemesi. Prefix toplam dizisinden farkı, her güncellemeden sonra diziyi baştan oluşturmak yerine yalnızca ilgili konumları güncellemesidir.</p>

<h3 id="yapı">Yapı</h3>

<p>Bu bölümde diziler 1’den indislenir. $p(k)$, $k$’yı tam bölen en büyük 2’nin kuvveti olsun. İkili indisli ağaç <code class="language-plaintext highlighter-rouge">tree</code> dizisinde şu şekilde tutulur:</p>

\[\text{tree}[k] = \text{sumq}(k - p(k) + 1,\ k)\]

<p>Yani her $k$ konumu, uzunluğu $p(k)$ olan ve $k$’da biten aralığın toplamını tutar. Örneğin $p(6) = 2$ olduğundan <code class="language-plaintext highlighter-rouge">tree[6]</code>, <code class="language-plaintext highlighter-rouge">sumq(5, 6)</code> değerini saklar.</p>

<p><code class="language-plaintext highlighter-rouge">[1, 3, 4, 8, 6, 1, 4, 2]</code> dizisine karşılık gelen ikili indisli ağaç:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1  4  4  16  6  7  4  29
1  2  3   4  5  6  7   8
</code></pre></div></div>

<p>$[1, k]$ aralığı her zaman ağaçta toplamları tutulan $O(\log n)$ alt aralığa bölünebildiğinden <code class="language-plaintext highlighter-rouge">sumq(1, k)</code> değeri $O(\log n)$ zamanda hesaplanabilir. Örneğin:</p>

\[\text{sumq}(1, 7) = \text{sumq}(1,4) + \text{sumq}(5,6) + \text{sumq}(7,7) = 16 + 7 + 4 = 27\]

<p>$a &gt; 1$ durumunda ise:</p>

\[\text{sumq}(a, b) = \text{sumq}(1, b) - \text{sumq}(1, a-1)\]

<h3 id="i̇mplementasyon">İmplementasyon</h3>

<p>İkili indisli ağaç, bit operasyonları kullanılarak verimli biçimde kodlanabilir. $p(k)$ değeri şöyle hesaplanır:</p>

\[p(k) = k\ \&amp;\ {-k}\]

<p><code class="language-plaintext highlighter-rouge">sumq(1, k)</code> değerini hesaplayan fonksiyon:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="nf">sum</span><span class="p">(</span><span class="kt">int</span> <span class="n">k</span><span class="p">)</span> <span class="p">{</span>
    <span class="kt">int</span> <span class="n">s</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="k">while</span> <span class="p">(</span><span class="n">k</span> <span class="o">&gt;=</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">s</span> <span class="o">+=</span> <span class="n">tree</span><span class="p">[</span><span class="n">k</span><span class="p">];</span>
        <span class="n">k</span> <span class="o">-=</span> <span class="n">k</span> <span class="o">&amp;</span> <span class="o">-</span><span class="n">k</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="n">s</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>$k$. pozisyondaki elemanı $x$ kadar artıran fonksiyon:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">add</span><span class="p">(</span><span class="kt">int</span> <span class="n">k</span><span class="p">,</span> <span class="kt">int</span> <span class="n">x</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">while</span> <span class="p">(</span><span class="n">k</span> <span class="o">&lt;=</span> <span class="n">n</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">tree</span><span class="p">[</span><span class="n">k</span><span class="p">]</span> <span class="o">+=</span> <span class="n">x</span><span class="p">;</span>
        <span class="n">k</span> <span class="o">+=</span> <span class="n">k</span> <span class="o">&amp;</span> <span class="o">-</span><span class="n">k</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Her iki fonksiyon da $O(\log n)$ zamanda çalışır; her adımda $O(\log n)$ farklı konuma erişilir ve her hareket $O(1)$ zaman alır.</p>

<h2 id="93-segment-ağacı">9.3 Segment Ağacı</h2>

<p><strong>Segment ağacı</strong><sup id="fnref:3"><a href="#fn:3" class="footnote" rel="footnote" role="doc-noteref">3</a></sup> iki operasyonu destekleyen genel amaçlı bir veri yapısıdır: aralık sorgusu ve tek eleman güncellemesi. İkili indisli ağaca kıyasla daha geneldir; yalnızca toplam değil minimum, maksimum, EBOB, bit operasyonları (AND, OR, XOR) gibi pek çok sorguyu da $O(\log n)$ zamanda destekler. Bununla birlikte daha fazla bellek kullanır ve implementasyonu biraz daha karmaşıktır<sup id="fnref:4"><a href="#fn:4" class="footnote" rel="footnote" role="doc-noteref">4</a></sup>.</p>

<h3 id="yapı-1">Yapı</h3>

<p>Segment ağacı bir ikili ağaçtır; en alt seviyedeki yapraklar dizi elemanlarına, iç düğümler ise aralık sorgularına gereken bilgilere karşılık gelir. Dizinin boyutunun ikinin kuvveti olduğu ve sıfır tabanlı indis kullanıldığı varsayılır; gerekirse fazladan eleman eklenebilir.</p>

<p>Toplam sorgularını destekleyen <code class="language-plaintext highlighter-rouge">[5, 8, 6, 3, 2, 7, 2, 6]</code> dizisine karşılık gelen segment ağacı:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>           39
       22      17
    13    9   9   8
   5 8  6 3  2 7 2 6
</code></pre></div></div>

<p>Her iç düğüm, kendisine karşılık gelen aralığın elemanlarının toplamını tutar ve bu değer sol ile sağ çocuklarının toplamına eşittir.</p>

<p>Herhangi bir $[a, b]$ aralığı, değerleri ağaç düğümlerinde bulunan $O(\log n)$ alt aralığa bölünebilir. Örneğin $[2, 7]$ aralığı için <code class="language-plaintext highlighter-rouge">sumq(2,7) = 9 + 17 = 26</code>. Her seviyeden en fazla iki düğüm kullanıldığından toplam düğüm sayısı $O(\log n)$ olur.</p>

<p>Bir dizi güncellemesinden sonra değişen düğümdeki yapraktan köke giden yoldaki tüm iç düğümler güncellenir; bu yol $O(\log n)$ düğüm içerir.</p>

<h3 id="i̇mplementasyon-1">İmplementasyon</h3>

<p>Segment ağacı, $n$ elemanlı bir dizi için $2n$ elemanlı bir dizi içinde tutulur. <code class="language-plaintext highlighter-rouge">tree[1]</code> köke, <code class="language-plaintext highlighter-rouge">tree[2]</code> ve <code class="language-plaintext highlighter-rouge">tree[3]</code> köke bağlı çocuklara karşılık gelir. <code class="language-plaintext highlighter-rouge">tree[n]</code>‘den <code class="language-plaintext highlighter-rouge">tree[2n-1]</code>‘e kadar olan değerler yaprak düğümlerdir.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>39 22 17 13  9  9  8  5  8  6  3  2  7  2  6
 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15
</code></pre></div></div>

<p>Bu gösterimde <code class="language-plaintext highlighter-rouge">tree[k]</code>‘nin ebeveyni <code class="language-plaintext highlighter-rouge">tree[⌊k/2⌋]</code>, çocukları <code class="language-plaintext highlighter-rouge">tree[2k]</code> ve <code class="language-plaintext highlighter-rouge">tree[2k+1]</code> olur.</p>

<p><code class="language-plaintext highlighter-rouge">sumq(a, b)</code> değerini hesaplayan fonksiyon:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="nf">sum</span><span class="p">(</span><span class="kt">int</span> <span class="n">a</span><span class="p">,</span> <span class="kt">int</span> <span class="n">b</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">a</span> <span class="o">+=</span> <span class="n">n</span><span class="p">;</span> <span class="n">b</span> <span class="o">+=</span> <span class="n">n</span><span class="p">;</span>
    <span class="kt">int</span> <span class="n">s</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="k">while</span> <span class="p">(</span><span class="n">a</span> <span class="o">&lt;=</span> <span class="n">b</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">a</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span> <span class="n">s</span> <span class="o">+=</span> <span class="n">tree</span><span class="p">[</span><span class="n">a</span><span class="o">++</span><span class="p">];</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">b</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="n">s</span> <span class="o">+=</span> <span class="n">tree</span><span class="p">[</span><span class="n">b</span><span class="o">--</span><span class="p">];</span>
        <span class="n">a</span> <span class="o">/=</span> <span class="mi">2</span><span class="p">;</span> <span class="n">b</span> <span class="o">/=</span> <span class="mi">2</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="n">s</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>$k$. pozisyondaki elemanı $x$ kadar artıran fonksiyon:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">add</span><span class="p">(</span><span class="kt">int</span> <span class="n">k</span><span class="p">,</span> <span class="kt">int</span> <span class="n">x</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">k</span> <span class="o">+=</span> <span class="n">n</span><span class="p">;</span>
    <span class="n">tree</span><span class="p">[</span><span class="n">k</span><span class="p">]</span> <span class="o">+=</span> <span class="n">x</span><span class="p">;</span>
    <span class="k">for</span> <span class="p">(</span><span class="n">k</span> <span class="o">/=</span> <span class="mi">2</span><span class="p">;</span> <span class="n">k</span> <span class="o">&gt;=</span> <span class="mi">1</span><span class="p">;</span> <span class="n">k</span> <span class="o">/=</span> <span class="mi">2</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">tree</span><span class="p">[</span><span class="n">k</span><span class="p">]</span> <span class="o">=</span> <span class="n">tree</span><span class="p">[</span><span class="mi">2</span><span class="o">*</span><span class="n">k</span><span class="p">]</span> <span class="o">+</span> <span class="n">tree</span><span class="p">[</span><span class="mi">2</span><span class="o">*</span><span class="n">k</span><span class="o">+</span><span class="mi">1</span><span class="p">];</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Her iki fonksiyon da $O(\log n)$ zamanda çalışır.</p>

<h3 id="diğer-sorgular">Diğer Sorgular</h3>

<p>Segment ağaçları, bir aralığı iki parçaya bölmenin mümkün olduğu tüm sorgular için kullanılabilir: minimum/maksimum, EBOB, AND/OR/XOR gibi bit operasyonları. Örneğin minimum sorgularını destekleyen ağaçta her düğüm, ilgili aralığın minimumunu tutar.</p>

<p>Segment ağacının ikili ağaç yapısı, ikili arama uygulamasına da olanak tanır. Örneğin minimum sorgularını destekleyen bir ağaçta en küçük değere sahip eleman $O(\log n)$ zamanda bulunabilir; kökten yapraklara doğru inen yolda minimum değer izlenerek bu eleman tespit edilir.</p>

<h2 id="94-ek-teknikler">9.4 Ek Teknikler</h2>

<h3 id="i̇ndis-sıkıştırması">İndis Sıkıştırması</h3>

<p>Bu bölümdeki veri yapıları ardışık sayılardan oluşan diziler üzerine kuruludur. Büyük indisler gerektiğinde (örneğin $10^9$ indisi) bellek yetersizliği sorunu ortaya çıkar. Bu sınır <strong>indis sıkıştırması</strong> ile aşılabilir.</p>

<p>Buradaki fikir, orijinal indisleri $1, 2, 3, \ldots$ gibi ardışık indislerle değiştirmektir. Bunun için algoritma çalışmaya başlamadan önce tüm indislerin bilinmesi gerekir. Sıkıştırma fonksiyonu $c$ sıralamayı korumalıdır: eğer $a &lt; b$ ise $c(a) &lt; c(b)$ olmalıdır. Bu sayede indisler sıkıştırılmış olsa bile sorgular doğru çalışır.</p>

<p>Örneğin orijinal indisler $8$, $555$ ve $10^9$ ise yeni indisler:</p>

\[c(8) = 1, \quad c(555) = 2, \quad c(10^9) = 3\]

<h3 id="aralık-güncellemeleri">Aralık Güncellemeleri</h3>

<p>Şimdiye kadar aralık sorguları yapıp tek eleman güncelleyebilen yapılar incelendi. Tam tersi durumda — aralık güncelleyip tek eleman okumada — <strong>fark dizisi</strong> kullanılır. Fark dizisindeki her değer, mevcut değer ile orijinal değer arasındaki farkı tutar; dolayısıyla orijinal dizi, fark dizisinin prefix toplam dizisidir.</p>

<p>Örneğin <code class="language-plaintext highlighter-rouge">[3, 3, 1, 1, 1, 5, 2, 2]</code> dizisinin fark dizisi:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>3  0  -2  0  0  4  -3  0
0  1   2  3  4  5   6  7
</code></pre></div></div>

<p>Fark dizisinin avantajı, orijinal dizideki bir aralığı yalnızca iki elemanı değiştirerek güncelleyebilmesidir. Örneğin $[1, 4]$ aralığındaki tüm değerleri 5 artırmak için fark dizisindeki 1. konumu 5 artırıp 5. konumu 5 azaltmak yeterlidir:</p>

\[\text{Sonuç:}\quad 3 \; 5 \; {-2} \; 0 \; 0 \; {-1} \; {-3} \; 0\]

<p>Genel olarak $[a, b]$ aralığını $x$ kadar artırmak için $a$. konumu $x$ artırıp $b+1$. konumu $x$ azaltırız. Tek eleman güncellemesi ve toplam sorgusunun birlikte gerektiği bu yapı için ikili indisli ağaç veya segment ağacı kullanılır.</p>

<p>Hem aralık sorgularını hem de aralık güncellemelerini bir arada destekleyen daha gelişmiş yapılar Bölüm 28’de ele alınacaktır.</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p>Bu teknik sparse table metodu olarak da bilinir. Daha gelişmiş teknikler mevcuttur; ön işleme $O(n)$ zamanda tamamlanabilir, fakat bu yöntemler rekabetçi programlamada nadiren gereklidir. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2">
      <p>İkili indisli ağaç yapısı P. M. Fenwick tarafından 1994’te tanıtılmıştır. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3">
      <p>Bu bölümdeki aşağıdan yukarıya implementasyon, 1970’lerin sonlarında geometri problemleri için kullanılan benzer yapılardan esinlenilmiştir. <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:4">
      <p>Aslında iki tane ikili indisli ağaç kullanılarak minimum sorguları da yapılabilir; ancak bu yöntem segment ağacına kıyasla daha karmaşıktır. <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Sonsuz Us</name></author><category term="Program" /><category term="aralik-sorgusu" /><category term="range-query" /><category term="c" /><category term="programlama" /><category term="algoritma" /><category term="olimpiyat" /><category term="dizi" /><category term="yarışma" /><category term="segment-agaci" /><category term="fenwick" /><category term="prefix-toplam" /><category term="veri-yapisi" /><category term="kitap" /><summary type="html"><![CDATA[Bir dizinin alt aralıklarında hızlıca sorgu yapmak rekabetçi programlamada sık karşılaşılan bir ihtiyaçtır. Bir aralık sorgusunda görev, bir dizinin belirli bir alt aralığında bir değeri hesaplamaktır. Tipik aralık sorguları şunlardır:]]></summary></entry></feed>