<?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://program.sonsuz.us/feed.xml" rel="self" type="application/atom+xml" /><link href="https://program.sonsuz.us/" rel="alternate" type="text/html" /><updated>2026-09-25T00:05:43+00:00</updated><id>https://program.sonsuz.us/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">Ağaç İçi Mesafelerin Gizli Silahı: Centroid Decomposition</title><link href="https://program.sonsuz.us/posts/agac-ici-mesafelerin-gizli-silahi-centroid-decomposition/" rel="alternate" type="text/html" title="Ağaç İçi Mesafelerin Gizli Silahı: Centroid Decomposition" /><published>2026-09-24T00:00:00+00:00</published><updated>2026-09-24T00:00:00+00:00</updated><id>https://program.sonsuz.us/posts/agac-ici-mesafelerin-gizli-silahi-centroid-decomposition</id><content type="html" xml:base="https://program.sonsuz.us/posts/agac-ici-mesafelerin-gizli-silahi-centroid-decomposition/"><![CDATA[<p>Bir ağaçta iki düğüm arasındaki mesafeyi hesaplamak kolaydır; fakat binlerce güncelleme ve sorgu geldiğinde işler hızla dallanıp budaklanır. Centroid Decomposition, ağacı dengeli biçimde parçalara ayırarak mesafe problemlerini yaklaşık $O(\log n)$ katman üzerinden çözmemizi sağlar. Kısacası ağacın fiziksel yapısını değiştirmeden, onun üzerinde ikinci ve dengeli bir “centroid ağacı” kurarız.</p>

<p><img src="/img/agac-ici-mesafelerin-68.svg" alt="agac-ici-mesafelerin-68" /></p>

<p>``</p>

<h2 id="centroid-nedir">Centroid nedir?</h2>

<p>Bir ağacın centroid’i, kaldırıldığında geriye kalan bağlı bileşenlerin hiçbirinin toplam düğüm sayısının yarısından büyük olmadığı düğümdür. $n$ düğümlü bir ağaçta centroid $c$ ise her bileşen için şu koşul geçerlidir:</p>

\[\text{bileşenBoyutu} \leq \frac{n}{2}\]

<p>Her ağacın en az bir, en fazla iki centroid’i vardır. Algoritmada bunlardan herhangi birini seçebiliriz. Centroid’i kaldırır, oluşan alt ağaçların her birine aynı işlemi özyineli uygularız. Her seviyede parça boyutu en az yarıya indiğinden decomposition yüksekliği $O(\log n)$ olur.</p>

<table>
  <thead>
    <tr>
      <th>Yaklaşım</th>
      <th style="text-align: right">Güncelleme</th>
      <th style="text-align: right">Mesafe sorgusu</th>
      <th style="text-align: right">Ön işleme</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Her sorguda DFS/BFS</td>
      <td style="text-align: right">$O(1)$</td>
      <td style="text-align: right">$O(n)$</td>
      <td style="text-align: right">$O(1)$</td>
    </tr>
    <tr>
      <td>LCA ile sabit düğümler</td>
      <td style="text-align: right">Yok</td>
      <td style="text-align: right">$O(\log n)$</td>
      <td style="text-align: right">$O(n\log n)$</td>
    </tr>
    <tr>
      <td>Centroid Decomposition</td>
      <td style="text-align: right">$O(\log n)$</td>
      <td style="text-align: right">$O(\log n)$</td>
      <td style="text-align: right">$O(n\log n)$</td>
    </tr>
  </tbody>
</table>

<p>LCA iki belirli düğümün mesafesinde harikadır. Centroid Decomposition ise “işaretli en yakın düğüm”, “$k$ mesafedeki düğüm sayısı” veya dinamik renk güncellemeleri gibi toplu problemlerde parlar.</p>

<h2 id="centroid-nasıl-bulunur">Centroid nasıl bulunur?</h2>

<p>Önce alt ağaç boyutlarını hesaplarız. Ardından boyutu toplamın yarısından büyük olan bir çocuk varsa o yöne ilerleriz. Böyle bir çocuk kalmadığında centroid’i bulmuş oluruz.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="nf">calcSize</span><span class="p">(</span><span class="kt">int</span> <span class="n">u</span><span class="p">,</span> <span class="kt">int</span> <span class="n">p</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">sub</span><span class="p">[</span><span class="n">u</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="kt">int</span> <span class="n">v</span> <span class="o">:</span> <span class="n">graph</span><span class="p">[</span><span class="n">u</span><span class="p">])</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">v</span> <span class="o">!=</span> <span class="n">p</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">removed</span><span class="p">[</span><span class="n">v</span><span class="p">])</span>
            <span class="n">sub</span><span class="p">[</span><span class="n">u</span><span class="p">]</span> <span class="o">+=</span> <span class="n">calcSize</span><span class="p">(</span><span class="n">v</span><span class="p">,</span> <span class="n">u</span><span class="p">);</span>
    <span class="k">return</span> <span class="n">sub</span><span class="p">[</span><span class="n">u</span><span class="p">];</span>
<span class="p">}</span>

<span class="kt">int</span> <span class="nf">findCentroid</span><span class="p">(</span><span class="kt">int</span> <span class="n">u</span><span class="p">,</span> <span class="kt">int</span> <span class="n">p</span><span class="p">,</span> <span class="kt">int</span> <span class="n">total</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">v</span> <span class="o">:</span> <span class="n">graph</span><span class="p">[</span><span class="n">u</span><span class="p">])</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">v</span> <span class="o">!=</span> <span class="n">p</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">removed</span><span class="p">[</span><span class="n">v</span><span class="p">]</span> <span class="o">&amp;&amp;</span> <span class="n">sub</span><span class="p">[</span><span class="n">v</span><span class="p">]</span> <span class="o">&gt;</span> <span class="n">total</span> <span class="o">/</span> <span class="mi">2</span><span class="p">)</span>
            <span class="k">return</span> <span class="n">findCentroid</span><span class="p">(</span><span class="n">v</span><span class="p">,</span> <span class="n">u</span><span class="p">,</span> <span class="n">total</span><span class="p">);</span>
    <span class="k">return</span> <span class="n">u</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">decompose</span><span class="p">(</span><span class="kt">int</span> <span class="n">entry</span><span class="p">,</span> <span class="kt">int</span> <span class="n">parent</span><span class="p">)</span> <span class="p">{</span>
    <span class="kt">int</span> <span class="n">total</span> <span class="o">=</span> <span class="n">calcSize</span><span class="p">(</span><span class="n">entry</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">);</span>
    <span class="kt">int</span> <span class="n">c</span> <span class="o">=</span> <span class="n">findCentroid</span><span class="p">(</span><span class="n">entry</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="n">total</span><span class="p">);</span>
    <span class="n">centroidParent</span><span class="p">[</span><span class="n">c</span><span class="p">]</span> <span class="o">=</span> <span class="n">parent</span><span class="p">;</span>
    <span class="n">removed</span><span class="p">[</span><span class="n">c</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="kt">int</span> <span class="n">v</span> <span class="o">:</span> <span class="n">graph</span><span class="p">[</span><span class="n">c</span><span class="p">])</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">removed</span><span class="p">[</span><span class="n">v</span><span class="p">])</span> <span class="n">decompose</span><span class="p">(</span><span class="n">v</span><span class="p">,</span> <span class="n">c</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">removed</code> dizisi centroid olarak ayrılmış düğümlerin sonraki hesaplamalara katılmasını engeller. <code class="language-plaintext highlighter-rouge">centroidParent</code> ise yeni centroid ağacındaki ebeveyn ilişkisini saklar.</p>

<h2 id="en-yakın-işaretli-düğüm-uygulaması">En yakın işaretli düğüm uygulaması</h2>

<p>Başlangıçta bazı düğümlerin kırmızı olduğunu düşünelim. Bir düğümü kırmızıya boyamak ve verilen $u$ düğümüne en yakın kırmızının mesafesini bulmak istiyoruz. Her centroid için kendisine en yakın kırmızı düğümün mesafesini <code class="language-plaintext highlighter-rouge">best</code> dizisinde tutarız.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">paint</span><span class="p">(</span><span class="kt">int</span> <span class="n">u</span><span class="p">)</span> <span class="p">{</span>
    <span class="kt">int</span> <span class="n">x</span> <span class="o">=</span> <span class="n">u</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="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">best</span><span class="p">[</span><span class="n">x</span><span class="p">]</span> <span class="o">=</span> <span class="n">min</span><span class="p">(</span><span class="n">best</span><span class="p">[</span><span class="n">x</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="n">x</span><span class="p">));</span>
        <span class="n">x</span> <span class="o">=</span> <span class="n">centroidParent</span><span class="p">[</span><span class="n">x</span><span class="p">];</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kt">int</span> <span class="nf">query</span><span class="p">(</span><span class="kt">int</span> <span class="n">u</span><span class="p">)</span> <span class="p">{</span>
    <span class="kt">int</span> <span class="n">answer</span> <span class="o">=</span> <span class="n">INF</span><span class="p">;</span>
    <span class="kt">int</span> <span class="n">x</span> <span class="o">=</span> <span class="n">u</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="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">answer</span> <span class="o">=</span> <span class="n">min</span><span class="p">(</span><span class="n">answer</span><span class="p">,</span> <span class="n">best</span><span class="p">[</span><span class="n">x</span><span class="p">]</span> <span class="o">+</span> <span class="n">distance</span><span class="p">(</span><span class="n">u</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">centroidParent</span><span class="p">[</span><span class="n">x</span><span class="p">];</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="n">answer</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Buradaki <code class="language-plaintext highlighter-rouge">distance(a, b)</code> değeri LCA ile $O(\log n)$ sürede veya centroid atalarına ait önceden saklanmış mesafelerle $O(1)$ sürede alınabilir. İkinci seçenek kullanıldığında güncelleme ve sorgu doğrudan $O(\log n)$ olur.</p>

<p>Temel fikir şudur: Her düğüm yalnızca $O(\log n)$ centroid atasına sahiptir. Olası cevapları bu dengeli ata zinciri üzerinden birleştiririz. Böylece kocaman ağacı her sorguda dolaşmak yerine birkaç stratejik ağırlık merkezine uğrarız; algoritmik anlamda tam bir “merkeze çekilme” hareketi!</p>]]></content><author><name>Sonsuz Us</name></author><category term="Bilgi" /><category term="centroid decomposition" /><category term="ağaç algoritmaları" /><category term="graf teorisi" /><category term="mesafe sorguları" /><category term="c++" /><category term="rekabetçi programlama" /><summary type="html"><![CDATA[Bir ağaçta iki düğüm arasındaki mesafeyi hesaplamak kolaydır; fakat binlerce güncelleme ve sorgu geldiğinde işler hızla dallanıp budaklanır. Centroid Decomposition, ağacı dengeli biçimde parçalara ayırarak mesafe problemlerini yaklaşık $O(\log n)$ katman üzerinden çözmemizi sağlar. Kısacası ağacın fiziksel yapısını değiştirmeden, onun üzerinde ikinci ve dengeli bir “centroid ağacı” kurarız.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://program.sonsuz.us/img/agac-ici-mesafelerin-68.png" /><media:content medium="image" url="https://program.sonsuz.us/img/agac-ici-mesafelerin-68.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Ağaçlarda Euler Turu Tekniği: Alt Ağaçları Tek Boyutlu Dizide Yakalamak</title><link href="https://program.sonsuz.us/posts/agaclarda-euler-turu-teknigi-alt-agaclari-tek-boyutlu-dizide-yakalamak/" rel="alternate" type="text/html" title="Ağaçlarda Euler Turu Tekniği: Alt Ağaçları Tek Boyutlu Dizide Yakalamak" /><published>2026-09-24T00:00:00+00:00</published><updated>2026-09-24T00:00:00+00:00</updated><id>https://program.sonsuz.us/posts/agaclarda-euler-turu-teknigi-alt-agaclari-tek-boyutlu-dizide-yakalamak</id><content type="html" xml:base="https://program.sonsuz.us/posts/agaclarda-euler-turu-teknigi-alt-agaclari-tek-boyutlu-dizide-yakalamak/"><![CDATA[<p><img src="/img/agaclarda-euler-turu-67.svg" alt="agaclarda-euler-turu-67" /></p>

<p>Bir şirket hiyerarşisindeki yöneticileri, dosya sistemindeki klasörleri veya bir oyundaki yetenek ağacını düşünün. Bu yapılarda “X düğümünün altındaki bütün değerlerin toplamı nedir?” gibi sorgular sıkça karşımıza çıkar. Ağacı her sorguda yeniden dolaşmak pahalıdır; Euler Turu Tekniği (ETT), alt ağaçları tek boyutlu ve kesintisiz dizi aralıklarına dönüştürerek segment ağacının süper güçlerinden yararlanmamızı sağlar.</p>

<p>``</p>

<h2 id="temel-fikir-ağacı-ütülemek">Temel fikir: Ağacı ütülemek</h2>

<p>Köklü bir ağaçta DFS dolaşması yaparken her düğüme giriş zamanını <code class="language-plaintext highlighter-rouge">tin[v]</code> olarak kaydedelim. Düğümün bütün çocuklarını ziyaret ettikten sonra da alt ağacının bittiği konumu <code class="language-plaintext highlighter-rouge">tout[v]</code> ile işaretleyelim.</p>

<p>DFS, bir düğüme girdikten sonra başka bir dala geçmeden önce o düğümün tüm torunlarını ziyaret eder. Dolayısıyla <code class="language-plaintext highlighter-rouge">v</code> düğümünün alt ağacındaki düğümler, oluşturduğumuz dizide şu kesintisiz aralığa yerleşir:</p>

\[subtree(v) \longleftrightarrow [tin[v],\ tout[v]]\]

<p>Düğüm değerlerini giriş sırasına göre <code class="language-plaintext highlighter-rouge">flat</code> dizisine yazarsak alt ağaç toplamı artık ağaç üzerinde değil, sıradan bir aralık üzerinde hesaplanır:</p>

\[S(v)=\sum_{i=tin[v]}^{tout[v]} flat[i]\]

<p>Örneğin DFS sırası <code class="language-plaintext highlighter-rouge">A, B, D, E, C, F</code> olsun. <code class="language-plaintext highlighter-rouge">B</code> düğümünün altında <code class="language-plaintext highlighter-rouge">B, D, E</code> bulunuyorsa bunlar dizide yan yana durur. Ağaç karmaşık görünse bile sorgu yalnızca <code class="language-plaintext highlighter-rouge">[1, 3]</code> aralığıdır. Kısacası ağaç ütülenmiş, kırışıklıklar kaybolmuştur!</p>

<table>
  <thead>
    <tr>
      <th>İşlem</th>
      <th style="text-align: right">Doğrudan DFS</th>
      <th style="text-align: right">ETT + Segment Ağacı</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Ön işleme</td>
      <td style="text-align: right">Yok</td>
      <td style="text-align: right">$O(n)$</td>
    </tr>
    <tr>
      <td>Alt ağaç toplamı</td>
      <td style="text-align: right">$O(k)$</td>
      <td style="text-align: right">$O(\log n)$</td>
    </tr>
    <tr>
      <td>Tek düğüm güncelleme</td>
      <td style="text-align: right">$O(1)$</td>
      <td style="text-align: right">$O(\log n)$</td>
    </tr>
    <tr>
      <td>Bellek</td>
      <td style="text-align: right">$O(n)$</td>
      <td style="text-align: right">$O(n)$</td>
    </tr>
  </tbody>
</table>

<p>Buradaki $k$, sorgulanan alt ağaçtaki düğüm sayısıdır. Ağaç zincir şeklindeyse $k$, $n$ kadar büyüyebilir.</p>

<h2 id="euler-dizisini-oluşturmak">Euler dizisini oluşturmak</h2>

<p>Aşağıdaki Python kodu, her düğümü yalnızca giriş anında diziye ekleyen yaygın ETT çeşidini uygular:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">build_euler</span><span class="p">(</span><span class="n">graph</span><span class="p">,</span> <span class="n">values</span><span class="p">,</span> <span class="n">root</span><span class="o">=</span><span class="mi">0</span><span class="p">):</span>
    <span class="n">n</span> <span class="o">=</span> <span class="nf">len</span><span class="p">(</span><span class="n">graph</span><span class="p">)</span>
    <span class="n">tin</span> <span class="o">=</span> <span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">*</span> <span class="n">n</span>
    <span class="n">tout</span> <span class="o">=</span> <span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">*</span> <span class="n">n</span>
    <span class="n">flat</span> <span class="o">=</span> <span class="p">[]</span>

    <span class="k">def</span> <span class="nf">dfs</span><span class="p">(</span><span class="n">node</span><span class="p">,</span> <span class="n">parent</span><span class="p">):</span>
        <span class="n">tin</span><span class="p">[</span><span class="n">node</span><span class="p">]</span> <span class="o">=</span> <span class="nf">len</span><span class="p">(</span><span class="n">flat</span><span class="p">)</span>
        <span class="n">flat</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">values</span><span class="p">[</span><span class="n">node</span><span class="p">])</span>

        <span class="k">for</span> <span class="n">child</span> <span class="ow">in</span> <span class="n">graph</span><span class="p">[</span><span class="n">node</span><span class="p">]:</span>
            <span class="k">if</span> <span class="n">child</span> <span class="o">!=</span> <span class="n">parent</span><span class="p">:</span>
                <span class="nf">dfs</span><span class="p">(</span><span class="n">child</span><span class="p">,</span> <span class="n">node</span><span class="p">)</span>

        <span class="n">tout</span><span class="p">[</span><span class="n">node</span><span class="p">]</span> <span class="o">=</span> <span class="nf">len</span><span class="p">(</span><span class="n">flat</span><span class="p">)</span> <span class="o">-</span> <span class="mi">1</span>

    <span class="nf">dfs</span><span class="p">(</span><span class="n">root</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">tin</span><span class="p">,</span> <span class="n">tout</span><span class="p">,</span> <span class="n">flat</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">tin[node]</code>, düğümün değerinin <code class="language-plaintext highlighter-rouge">flat</code> içindeki konumudur. <code class="language-plaintext highlighter-rouge">tout[node]</code> ise son torunun konumunu gösterir. Böylece alt ağaç aralığı iki indeksle temsil edilir.</p>

<h2 id="segment-ağacıyla-sorgulamak">Segment ağacıyla sorgulamak</h2>

<p>Aşağıdaki sınıf noktasal güncelleme ve aralık toplamı işlemlerini $O(\log n)$ zamanda gerçekleştirir:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">SegmentTree</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">data</span><span class="p">):</span>
        <span class="n">size</span> <span class="o">=</span> <span class="mi">1</span>
        <span class="k">while</span> <span class="n">size</span> <span class="o">&lt;</span> <span class="nf">len</span><span class="p">(</span><span class="n">data</span><span class="p">):</span>
            <span class="n">size</span> <span class="o">*=</span> <span class="mi">2</span>
        <span class="n">self</span><span class="p">.</span><span class="n">size</span> <span class="o">=</span> <span class="n">size</span>
        <span class="n">self</span><span class="p">.</span><span class="n">tree</span> <span class="o">=</span> <span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">*</span> <span class="p">(</span><span class="mi">2</span> <span class="o">*</span> <span class="n">size</span><span class="p">)</span>

        <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">value</span> <span class="ow">in</span> <span class="nf">enumerate</span><span class="p">(</span><span class="n">data</span><span class="p">):</span>
            <span class="n">self</span><span class="p">.</span><span class="n">tree</span><span class="p">[</span><span class="n">size</span> <span class="o">+</span> <span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">value</span>
        <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">size</span> <span class="o">-</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">):</span>
            <span class="n">self</span><span class="p">.</span><span class="n">tree</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="n">tree</span><span class="p">[</span><span class="mi">2</span> <span class="o">*</span> <span class="n">i</span><span class="p">]</span> <span class="o">+</span> <span class="n">self</span><span class="p">.</span><span class="n">tree</span><span class="p">[</span><span class="mi">2</span> <span class="o">*</span> <span class="n">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">]</span>

    <span class="k">def</span> <span class="nf">update</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">index</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span>
        <span class="n">i</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="n">size</span> <span class="o">+</span> <span class="n">index</span>
        <span class="n">self</span><span class="p">.</span><span class="n">tree</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">value</span>
        <span class="k">while</span> <span class="n">i</span> <span class="o">&gt;</span> <span class="mi">1</span><span class="p">:</span>
            <span class="n">i</span> <span class="o">//=</span> <span class="mi">2</span>
            <span class="n">self</span><span class="p">.</span><span class="n">tree</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="n">tree</span><span class="p">[</span><span class="mi">2</span> <span class="o">*</span> <span class="n">i</span><span class="p">]</span> <span class="o">+</span> <span class="n">self</span><span class="p">.</span><span class="n">tree</span><span class="p">[</span><span class="mi">2</span> <span class="o">*</span> <span class="n">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">]</span>

    <span class="k">def</span> <span class="nf">query</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">left</span><span class="p">,</span> <span class="n">right</span><span class="p">):</span>
        <span class="n">left</span> <span class="o">+=</span> <span class="n">self</span><span class="p">.</span><span class="n">size</span>
        <span class="n">right</span> <span class="o">+=</span> <span class="n">self</span><span class="p">.</span><span class="n">size</span>
        <span class="n">total</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="k">while</span> <span class="n">left</span> <span class="o">&lt;=</span> <span class="n">right</span><span class="p">:</span>
            <span class="k">if</span> <span class="n">left</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">total</span> <span class="o">+=</span> <span class="n">self</span><span class="p">.</span><span class="n">tree</span><span class="p">[</span><span class="n">left</span><span class="p">]</span>
                <span class="n">left</span> <span class="o">+=</span> <span class="mi">1</span>
            <span class="k">if</span> <span class="n">right</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">total</span> <span class="o">+=</span> <span class="n">self</span><span class="p">.</span><span class="n">tree</span><span class="p">[</span><span class="n">right</span><span class="p">]</span>
                <span class="n">right</span> <span class="o">-=</span> <span class="mi">1</span>
            <span class="n">left</span> <span class="o">//=</span> <span class="mi">2</span>
            <span class="n">right</span> <span class="o">//=</span> <span class="mi">2</span>
        <span class="k">return</span> <span class="n">total</span>
</code></pre></div></div>

<p>Kullanımı oldukça nettir:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">tin</span><span class="p">,</span> <span class="n">tout</span><span class="p">,</span> <span class="n">flat</span> <span class="o">=</span> <span class="nf">build_euler</span><span class="p">(</span><span class="n">graph</span><span class="p">,</span> <span class="n">values</span><span class="p">)</span>
<span class="n">segment</span> <span class="o">=</span> <span class="nc">SegmentTree</span><span class="p">(</span><span class="n">flat</span><span class="p">)</span>

<span class="n">subtree_sum</span> <span class="o">=</span> <span class="n">segment</span><span class="p">.</span><span class="nf">query</span><span class="p">(</span><span class="n">tin</span><span class="p">[</span><span class="n">v</span><span class="p">],</span> <span class="n">tout</span><span class="p">[</span><span class="n">v</span><span class="p">])</span>
<span class="n">segment</span><span class="p">.</span><span class="nf">update</span><span class="p">(</span><span class="n">tin</span><span class="p">[</span><span class="n">v</span><span class="p">],</span> <span class="n">new_value</span><span class="p">)</span>
</code></pre></div></div>

<h2 id="i̇nce-noktalar">İnce noktalar</h2>

<p>ETT’nin farklı tanımları vardır. Bazı sürümlerde düğüm hem girişte hem çıkışta kaydedilir. Alt ağaç sorgularında genellikle her düğümü bir kez yazan sürüm daha pratiktir. Aralıkların kapalı mı <code class="language-plaintext highlighter-rouge">[l, r]</code>, yoksa yarı açık mı <code class="language-plaintext highlighter-rouge">[l, r)</code> olduğunu da baştan belirlemek önemlidir; indeks hatalarının doğal yaşam alanı tam olarak burasıdır.</p>

<p>Ağaç değişmiyor fakat değerler güncelleniyorsa ETT mükemmel bir seçimdir. Yalnızca toplam gerekiyorsa Fenwick ağacı da kullanılabilir. Minimum, maksimum veya daha gelişmiş birleştirme işlemlerinde ise segment ağacı daha esnektir. Sonuç olarak ETT tek başına sorguları cevaplamaz; ağacın geometrisini, güçlü dizi veri yapılarına uygun bir forma dönüştüren zekice bir köprüdür.</p>]]></content><author><name>Sonsuz Us</name></author><category term="Bilgi" /><category term="algoritma" /><category term="euler-turu" /><category term="ağaçlar" /><category term="segment-ağacı" /><category term="veri-yapıları" /><category term="rekabetçi-programlama" /><summary type="html"><![CDATA[Bir şirket hiyerarşisindeki yöneticileri, dosya sistemindeki klasörleri veya bir oyundaki yetenek ağacını düşünün. Bu yapılarda “X düğümünün altındaki bütün değerlerin toplamı nedir?” gibi sorgular sıkça karşımıza çıkar. Ağacı her sorguda yeniden dolaşmak pahalıdır; Euler Turu Tekniği (ETT), alt ağaçları tek boyutlu ve kesintisiz dizi aralıklarına dönüştürerek segment ağacının süper güçlerinden yararlanmamızı sağlar.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://program.sonsuz.us/img/agaclarda-euler-turu-67.png" /><media:content medium="image" url="https://program.sonsuz.us/img/agaclarda-euler-turu-67.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Aho-Corasick ve Dinamik Programlama ile Yasaklı Kelimelerden Kaçınan Metin Üretimi</title><link href="https://program.sonsuz.us/posts/aho-corasick-ve-dinamik-programlama-ile-yasakli-kelimelerden-kacinan-metin-uretimi/" rel="alternate" type="text/html" title="Aho-Corasick ve Dinamik Programlama ile Yasaklı Kelimelerden Kaçınan Metin Üretimi" /><published>2026-09-24T00:00:00+00:00</published><updated>2026-09-24T00:00:00+00:00</updated><id>https://program.sonsuz.us/posts/aho-corasick-ve-dinamik-programlama-ile-yasakli-kelimelerden-kacinan-metin-uretimi</id><content type="html" xml:base="https://program.sonsuz.us/posts/aho-corasick-ve-dinamik-programlama-ile-yasakli-kelimelerden-kacinan-metin-uretimi/"><![CDATA[<p><img src="/img/aho-corasick-ve-37.svg" alt="aho-corasick-ve-37" /></p>

<p>Bir alfabenin karakterlerini kullanarak belirli uzunlukta metinler üretmek kolaydır: alfabe boyutu $K$, metin uzunluğu $N$ ise toplam $K^N$ seçenek vardır. Ancak metnin içinde “abc”, “kedi” veya “virus” gibi yasaklı kelimelerin hiç geçmemesini istediğimizde işler karışır. Her dizgiyi tek tek üretip kontrol etmek astronomik derecede pahalıdır. Neyse ki Aho-Corasick otomatı ile dinamik programlamayı birleştirerek tüm geçerli dizgileri üretmeden sayabiliriz.</p>

<p>``</p>

<h2 id="problemi-neden-bir-otomat-olarak-düşünmeliyiz">Problemi neden bir otomat olarak düşünmeliyiz?</h2>

<p>Yasaklı kelimeleri kontrol ederken geçmişin tamamını hatırlamamız gerekmez. Yalnızca şimdiye kadar yazdığımız metnin, yasaklı kelimelerden birinin öneki olabilecek en uzun son ekini bilmek yeterlidir.</p>

<p>Örneğin yasaklı kelimeler <code class="language-plaintext highlighter-rouge">aba</code> ve <code class="language-plaintext highlighter-rouge">bab</code> olsun. Üretilen metin <code class="language-plaintext highlighter-rouge">...ab</code> ile bitiyorsa yeni karakter önemlidir: <code class="language-plaintext highlighter-rouge">a</code> eklenirse <code class="language-plaintext highlighter-rouge">aba</code> tamamlanır ve durum geçersiz olur. Aho-Corasick, bu tür bilgileri trie düğümleri ve failure bağlantılarıyla temsil eder.</p>

<table>
  <thead>
    <tr>
      <th>Yapı</th>
      <th>Görevi</th>
      <th>Sağladığı avantaj</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Trie kenarı</td>
      <td>Bir karakterle sonraki öneke geçer</td>
      <td>Kelimelerin ortak öneklerini birleştirir</td>
    </tr>
    <tr>
      <td>Failure bağlantısı</td>
      <td>Eşleşme bozulunca uygun son eke döner</td>
      <td>Baştan aramayı önler</td>
    </tr>
    <tr>
      <td>Terminal işareti</td>
      <td>Yasaklı kelimenin tamamlandığını belirtir</td>
      <td>Geçersiz durumları tanır</td>
    </tr>
    <tr>
      <td>Otomat durumu</td>
      <td>Metnin ilgili son ekini saklar</td>
      <td>DP için kompakt hafıza oluşturur</td>
    </tr>
  </tbody>
</table>

<p>Bir düğüm terminalse veya failure zincirinde terminal bir düğüme ulaşıyorsa o durum “tehlikeli” kabul edilmelidir. Çünkü yasaklı kelime metnin sonunda zaten görülmüştür.</p>

<h2 id="dinamik-programlama-modeli">Dinamik programlama modeli</h2>

<p>$dp[i][s]$, uzunluğu $i$ olan ve Aho-Corasick otomatının $s$ durumunda biten geçerli dizgilerin sayısı olsun. Başlangıçta boş dizgi kök durumundadır:</p>

\[dp[0][0] = 1\]

<p>Her durumdan alfabedeki tüm karakterleri deneriz. Otomatın geçiş fonksiyonu $go(s,c)$ ile gösterilirse, hedef durum tehlikeli olmadığı sürece geçiş şöyledir:</p>

\[dp[i+1][go(s,c)] += dp[i][s]\]

<p>Sonuç, $N$ karakter işlendiğinde güvenli durumların toplamıdır:</p>

\[C_N = \sum_{s \in Safe} dp[N][s]\]

<p>Bu yaklaşımın zaman karmaşıklığı $O(N \cdot S \cdot K)$ olur. Burada $S$ otomat durumlarının, $K$ ise alfabenin büyüklüğüdür. Kaba kuvvetin $O(K^N)$ maliyetiyle karşılaştırıldığında fark oldukça dramatiktir.</p>

<h2 id="c-ile-örnek-dp">C++ ile örnek DP</h2>

<p>Aşağıdaki bölüm, otomatın daha önce oluşturulduğunu varsayar. <code class="language-plaintext highlighter-rouge">nextState[s][c]</code> geçişleri, <code class="language-plaintext highlighter-rouge">bad[s]</code> ise yasaklı bir eşleşmeye ulaşan durumları tutar:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">const</span> <span class="kt">long</span> <span class="kt">long</span> <span class="n">MOD</span> <span class="o">=</span> <span class="mi">1'000'000'007</span><span class="p">;</span>

<span class="kt">long</span> <span class="kt">long</span> <span class="nf">countValid</span><span class="p">(</span><span class="kt">int</span> <span class="n">n</span><span class="p">,</span> <span class="kt">int</span> <span class="n">stateCount</span><span class="p">,</span> <span class="kt">int</span> <span class="n">alphabetSize</span><span class="p">,</span>
                     <span class="k">const</span> <span class="n">vector</span><span class="o">&lt;</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;&gt;&amp;</span> <span class="n">nextState</span><span class="p">,</span>
                     <span class="k">const</span> <span class="n">vector</span><span class="o">&lt;</span><span class="kt">bool</span><span class="o">&gt;&amp;</span> <span class="n">bad</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">vector</span><span class="o">&lt;</span><span class="kt">long</span> <span class="kt">long</span><span class="o">&gt;</span> <span class="n">current</span><span class="p">(</span><span class="n">stateCount</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="n">next</span><span class="p">(</span><span class="n">stateCount</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
    <span class="n">current</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="c1">// Boş metin kök durumunda başlar.</span>

    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">length</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">length</span> <span class="o">&lt;</span> <span class="n">n</span><span class="p">;</span> <span class="o">++</span><span class="n">length</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">fill</span><span class="p">(</span><span class="n">next</span><span class="p">.</span><span class="n">begin</span><span class="p">(),</span> <span class="n">next</span><span class="p">.</span><span class="n">end</span><span class="p">(),</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">state</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">state</span> <span class="o">&lt;</span> <span class="n">stateCount</span><span class="p">;</span> <span class="o">++</span><span class="n">state</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">bad</span><span class="p">[</span><span class="n">state</span><span class="p">]</span> <span class="o">||</span> <span class="n">current</span><span class="p">[</span><span class="n">state</span><span class="p">]</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="k">continue</span><span class="p">;</span>

            <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">ch</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">ch</span> <span class="o">&lt;</span> <span class="n">alphabetSize</span><span class="p">;</span> <span class="o">++</span><span class="n">ch</span><span class="p">)</span> <span class="p">{</span>
                <span class="kt">int</span> <span class="n">target</span> <span class="o">=</span> <span class="n">nextState</span><span class="p">[</span><span class="n">state</span><span class="p">][</span><span class="n">ch</span><span class="p">];</span>
                <span class="k">if</span> <span class="p">(</span><span class="n">bad</span><span class="p">[</span><span class="n">target</span><span class="p">])</span> <span class="k">continue</span><span class="p">;</span>

                <span class="n">next</span><span class="p">[</span><span class="n">target</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="n">next</span><span class="p">[</span><span class="n">target</span><span class="p">]</span> <span class="o">+</span> <span class="n">current</span><span class="p">[</span><span class="n">state</span><span class="p">])</span> <span class="o">%</span> <span class="n">MOD</span><span class="p">;</span>
            <span class="p">}</span>
        <span class="p">}</span>
        <span class="n">current</span><span class="p">.</span><span class="n">swap</span><span class="p">(</span><span class="n">next</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="kt">long</span> <span class="kt">long</span> <span class="n">answer</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">state</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">state</span> <span class="o">&lt;</span> <span class="n">stateCount</span><span class="p">;</span> <span class="o">++</span><span class="n">state</span><span class="p">)</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">bad</span><span class="p">[</span><span class="n">state</span><span class="p">])</span> <span class="n">answer</span> <span class="o">=</span> <span class="p">(</span><span class="n">answer</span> <span class="o">+</span> <span class="n">current</span><span class="p">[</span><span class="n">state</span><span class="p">])</span> <span class="o">%</span> <span class="n">MOD</span><span class="p">;</span>

    <span class="k">return</span> <span class="n">answer</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Kodda yalnızca önceki uzunluğun değerleri gerektiği için iki DP satırı kullanılır. Böylece bellek karmaşıklığı $O(N \cdot S)$ yerine $O(S)$ seviyesine iner.</p>

<h2 id="dikkat-edilmesi-gereken-ayrıntılar">Dikkat edilmesi gereken ayrıntılar</h2>

<p>Failure bağlantısından terminal bilgisini miras almak kritik önemdedir. Aksi hâlde otomat, daha uzun bir durumun son eki olarak tamamlanan yasaklı kelimeleri kaçırabilir. Ayrıca sonuçlar hızla büyüdüğünden çoğu yarışma probleminde modüler aritmetik kullanılır.</p>

<p>Çok büyük $N$ değerlerinde aynı model bir geçiş matrisi hâline getirilebilir ve matris hızlı üs alma ile yaklaşık $O(S^3 \log N)$ sürede çözülebilir. Böylece Aho-Corasick yalnızca kelime arayan bir algoritma olmaktan çıkar; güvenli metinlerin evrenini sayan güçlü bir kombinatorik makineye dönüşür.</p>]]></content><author><name>Sonsuz Us</name></author><category term="Bilgi" /><category term="aho-corasick" /><category term="dinamik programlama" /><category term="otomat" /><category term="algoritma" /><category term="metin işleme" /><category term="kombinatorik" /><summary type="html"><![CDATA[Bir alfabenin karakterlerini kullanarak belirli uzunlukta metinler üretmek kolaydır: alfabe boyutu $K$, metin uzunluğu $N$ ise toplam $K^N$ seçenek vardır. Ancak metnin içinde “abc”, “kedi” veya “virus” gibi yasaklı kelimelerin hiç geçmemesini istediğimizde işler karışır. Her dizgiyi tek tek üretip kontrol etmek astronomik derecede pahalıdır. Neyse ki Aho-Corasick otomatı ile dinamik programlamayı birleştirerek tüm geçerli dizgileri üretmeden sayabiliriz.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://program.sonsuz.us/img/aho-corasick-ve-37.png" /><media:content medium="image" url="https://program.sonsuz.us/img/aho-corasick-ve-37.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Brian Kernighan Algoritmasıyla Set Bitlerini Şimşek Hızında Saymak</title><link href="https://program.sonsuz.us/posts/brian-kernighan-algoritmasiyla-set-bitlerini-simsek-hizinda-saymak/" rel="alternate" type="text/html" title="Brian Kernighan Algoritmasıyla Set Bitlerini Şimşek Hızında Saymak" /><published>2026-09-24T00:00:00+00:00</published><updated>2026-09-24T00:00:00+00:00</updated><id>https://program.sonsuz.us/posts/brian-kernighan-algoritmasiyla-set-bitlerini-simsek-hizinda-saymak</id><content type="html" xml:base="https://program.sonsuz.us/posts/brian-kernighan-algoritmasiyla-set-bitlerini-simsek-hizinda-saymak/"><![CDATA[<p>Bir tam sayının ikili gösteriminde kaç tane <code class="language-plaintext highlighter-rouge">1</code> bulunduğunu saymak ilk bakışta sıradan bir döngü problemi gibi görünür. Ancak düşük seviyeli programlamanın büyüsü, bazen bütün bitleri tek tek dolaşmak yerine yalnızca ilgilendiğimiz bitlere dokunabilmemizdir. Brian Kernighan algoritması, her turda sağdaki bir adet set bitini temizleyerek gereksiz döngüleri ortadan kaldıran zarif bir bit maskeleme hilesidir.
``</p>
<h2 id="önce-problemi-ikili-düşünelim">Önce problemi ikili düşünelim</h2>

<p>Bir bitin değeri <code class="language-plaintext highlighter-rouge">1</code> ise o bit <strong>set edilmiş</strong>, <code class="language-plaintext highlighter-rouge">0</code> ise temizlenmiş kabul edilir. Örneğin $n=52$ sayısının ikili gösterimi şöyledir:</p>

\[52 = (110100)_2\]

<p>Bu gösterimde üç tane <code class="language-plaintext highlighter-rouge">1</code> bulunduğundan sonuç 3’tür. Klasik yaklaşım, sayının en sağındaki biti <code class="language-plaintext highlighter-rouge">n &amp; 1</code> ile kontrol eder ve ardından sayıyı sağa kaydırır. Sayı $b$ bit uzunluğundaysa bu yöntem yaklaşık $b$ tur çalışır.</p>

<table>
  <thead>
    <tr>
      <th>Yaklaşım</th>
      <th>Her turdaki işlem</th>
      <th style="text-align: right">Döngü sayısı</th>
      <th style="text-align: right">Karmaşıklık</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Sağa kaydırma</td>
      <td>Her biti kontrol eder</td>
      <td style="text-align: right">Bit uzunluğu kadar</td>
      <td style="text-align: right">$O(b)$</td>
    </tr>
    <tr>
      <td>Brian Kernighan</td>
      <td>Yalnızca bir set bitini siler</td>
      <td style="text-align: right">Set bit sayısı kadar</td>
      <td style="text-align: right">$O(k)$</td>
    </tr>
    <tr>
      <td>Donanımsal <code class="language-plaintext highlighter-rouge">popcount</code></td>
      <td>İşlemci komutu kullanır</td>
      <td style="text-align: right">Mimariye bağlı</td>
      <td style="text-align: right">Genellikle sabit</td>
    </tr>
  </tbody>
</table>

<p><img src="/img/brian-kernighan-algoritmasiyla-11.svg" alt="brian-kernighan-algoritmasiyla-11" /></p>

<p>Burada $k$, sayının içindeki <code class="language-plaintext highlighter-rouge">1</code> bitlerinin sayısıdır. Seyrek bitli sayılarda $k \ll b$ olduğundan fark oldukça belirgin olabilir.</p>

<h2 id="hilenin-kalbi-n--n---1">Hilenin kalbi: <code class="language-plaintext highlighter-rouge">n &amp; (n - 1)</code></h2>

<p>Algoritmanın temel ifadesi şudur:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>n = n &amp; (n - 1)
</code></pre></div></div>

<p>Bir sayıdan 1 çıkarıldığında, en sağdaki set bit <code class="language-plaintext highlighter-rouge">0</code> olur; onun sağındaki bütün <code class="language-plaintext highlighter-rouge">0</code> bitleri ise <code class="language-plaintext highlighter-rouge">1</code> olur. Ardından eski sayı ile yeni sayı arasında AND işlemi yapıldığında bu değişen alt bitler temizlenir. Sonuç olarak sayının en sağındaki tek bir <code class="language-plaintext highlighter-rouge">1</code> yok edilir.</p>

<p>Örneğin $52$ üzerinden ilerleyelim:</p>

<table>
  <thead>
    <tr>
      <th style="text-align: right">Tur</th>
      <th style="text-align: right"><code class="language-plaintext highlighter-rouge">n</code> değeri</th>
      <th>İkili gösterim</th>
      <th>Temizlenen bit sonrası</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: right">1</td>
      <td style="text-align: right">52</td>
      <td><code class="language-plaintext highlighter-rouge">110100</code></td>
      <td><code class="language-plaintext highlighter-rouge">110000</code></td>
    </tr>
    <tr>
      <td style="text-align: right">2</td>
      <td style="text-align: right">48</td>
      <td><code class="language-plaintext highlighter-rouge">110000</code></td>
      <td><code class="language-plaintext highlighter-rouge">100000</code></td>
    </tr>
    <tr>
      <td style="text-align: right">3</td>
      <td style="text-align: right">32</td>
      <td><code class="language-plaintext highlighter-rouge">100000</code></td>
      <td><code class="language-plaintext highlighter-rouge">000000</code></td>
    </tr>
  </tbody>
</table>

<p>Sayı sıfıra üç turda ulaştığı için set bit sayısı da 3’tür. Algoritma, aradaki sıfırlarla hiç vakit kaybetmez.</p>

<h2 id="c-ile-uygulama">C ile uygulama</h2>

<p>Aşağıdaki fonksiyon, işaretsiz bir tam sayıdaki set bitlerini sayar:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">unsigned</span> <span class="kt">int</span> <span class="nf">count_set_bits</span><span class="p">(</span><span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">n</span><span class="p">)</span> <span class="p">{</span>
    <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">count</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">n</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">n</span> <span class="o">&amp;=</span> <span class="p">(</span><span class="n">n</span> <span class="o">-</span> <span class="mi">1</span><span class="p">);</span>  <span class="c1">// En sağdaki set bitini temizler.</span>
        <span class="n">count</span><span class="o">++</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="n">count</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">unsigned int</code> kullanılması önemlidir. İşaretli negatif sayıların gösterimi ve taşma davranışları dile göre kafa karıştırabilir. İşaretsiz tür, işlemin sabit genişlikte bir bit deseni üzerinde gerçekleştiğini daha açık biçimde ifade eder.</p>

<p>Python’da aynı fikir neredeyse birebir yazılabilir:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">count_set_bits</span><span class="p">(</span><span class="n">n</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
    <span class="k">if</span> <span class="n">n</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">:</span>
        <span class="k">raise</span> <span class="nc">ValueError</span><span class="p">(</span><span class="sh">"</span><span class="s">n negatif olmamalıdır</span><span class="sh">"</span><span class="p">)</span>

    <span class="n">count</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="k">while</span> <span class="n">n</span><span class="p">:</span>
        <span class="n">n</span> <span class="o">&amp;=</span> <span class="n">n</span> <span class="o">-</span> <span class="mi">1</span>
        <span class="n">count</span> <span class="o">+=</span> <span class="mi">1</span>
    <span class="k">return</span> <span class="n">count</span>
</code></pre></div></div>

<h2 id="ne-zaman-tercih-edilmeli">Ne zaman tercih edilmeli?</h2>

<p>Bu yöntem; bit bayraklarını analiz ederken, izin sistemlerinde etkin seçenekleri sayarken, grafik algoritmalarındaki bit kümelerinde ve gömülü sistemlerde oldukça kullanışlıdır. Yine de modern dillerin <code class="language-plaintext highlighter-rouge">bit_count</code>, <code class="language-plaintext highlighter-rouge">popcount</code> veya benzeri yerleşik fonksiyonları işlemcinin özel komutlarından yararlanabilir. Python’da örneğin <code class="language-plaintext highlighter-rouge">n.bit_count()</code> çoğunlukla elle yazılan döngüden daha hızlıdır.</p>

<p>Brian Kernighan yaklaşımının asıl değeri yalnızca performans değildir. <code class="language-plaintext highlighter-rouge">n &amp; (n - 1)</code> ifadesi, bitlerin matematiksel yapısını kullanarak problemi dönüştürür: bütün bitleri aramak yerine bulunan her <code class="language-plaintext highlighter-rouge">1</code> doğrudan silinir. Kısacası algoritma samanlıkta iğne aramaz; iğneleri sırayla ortadan kaldırır.</p>]]></content><author><name>Sonsuz Us</name></author><category term="Bilgi" /><category term="bit-maskeleme" /><category term="brian-kernighan" /><category term="algoritma" /><category term="popcount" /><category term="düşük-seviye-programlama" /><summary type="html"><![CDATA[Bir tam sayının ikili gösteriminde kaç tane 1 bulunduğunu saymak ilk bakışta sıradan bir döngü problemi gibi görünür. Ancak düşük seviyeli programlamanın büyüsü, bazen bütün bitleri tek tek dolaşmak yerine yalnızca ilgilendiğimiz bitlere dokunabilmemizdir. Brian Kernighan algoritması, her turda sağdaki bir adet set bitini temizleyerek gereksiz döngüleri ortadan kaldıran zarif bir bit maskeleme hilesidir.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://program.sonsuz.us/img/brian-kernighan-algoritmasiyla-11.png" /><media:content medium="image" url="https://program.sonsuz.us/img/brian-kernighan-algoritmasiyla-11.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Circuit Breaker Pattern: Çöken Servisin Sistemi Peşinden Sürüklemesini Önlemek</title><link href="https://program.sonsuz.us/posts/circuit-breaker-pattern-coken-servisin-sistemi-pesinden-suruklemesini-onlemek/" rel="alternate" type="text/html" title="Circuit Breaker Pattern: Çöken Servisin Sistemi Peşinden Sürüklemesini Önlemek" /><published>2026-09-24T00:00:00+00:00</published><updated>2026-09-24T00:00:00+00:00</updated><id>https://program.sonsuz.us/posts/circuit-breaker-pattern-coken-servisin-sistemi-pesinden-suruklemesini-onlemek</id><content type="html" xml:base="https://program.sonsuz.us/posts/circuit-breaker-pattern-coken-servisin-sistemi-pesinden-suruklemesini-onlemek/"><![CDATA[<p>Bir mikroservis çöktüğünde sorun çoğu zaman yalnızca o servisle sınırlı kalmaz. Ona istek gönderen uygulamalar yanıt bekler, bağlantı havuzları dolar, iş parçacıkları tükenir ve masum servisler de domino taşları gibi devrilmeye başlar. <strong>Circuit Breaker Pattern</strong>, elektrik sigortasına benzeyen bir koruma mekanizması kurarak bu zincirleme felaketi durdurur.
``</p>
<h2 id="circuit-breaker-neyi-çözer">Circuit Breaker neyi çözer?</h2>

<p>Bir sipariş servisinin ödeme servisini çağırdığını düşünelim. Ödeme servisi yanıt veremiyorsa her isteği tekrar tekrar ona göndermek problemi çözmez; aksine ağ trafiğini ve kaynak tüketimini artırır. Circuit Breaker, belirli sayıdaki başarısızlıktan sonra çağrıları geçici olarak engeller ve hızlı hata döndürür.</p>

<p>Bir çağrının ortalama bekleme süresi $T$, eş zamanlı istek sayısı $N$ ise bloke edilen toplam kaynak baskısını kabaca şöyle düşünebiliriz:</p>

\[Yuk = N \times T\]

<p>Örneğin 500 istek 10 saniye boyunca beklerse sistemde $5000$ istek-saniyelik baskı oluşur. Devre açıkken çağrının 10 milisaniyede reddedilmesi bu maliyeti dramatik biçimde azaltır.</p>

<h2 id="üç-temel-durum">Üç temel durum</h2>

<p>Circuit Breaker küçük bir durum makinesi olarak çalışır:</p>

<table>
  <thead>
    <tr>
      <th>Durum</th>
      <th>Davranış</th>
      <th>Geçiş koşulu</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Closed</strong></td>
      <td>İstekler hedef servise gider</td>
      <td>Hata eşiği aşılırsa Open</td>
    </tr>
    <tr>
      <td><strong>Open</strong></td>
      <td>İstekler gönderilmeden reddedilir</td>
      <td>Bekleme süresi dolunca Half-Open</td>
    </tr>
    <tr>
      <td><strong>Half-Open</strong></td>
      <td>Sınırlı sayıda deneme yapılır</td>
      <td>Başarılıysa Closed, hatalıysa Open</td>
    </tr>
  </tbody>
</table>

<p><img src="/img/circuit-breaker-pattern-70.svg" alt="circuit-breaker-pattern-70" /></p>

<p>İsimler ilk bakışta ters gelebilir. Elektrik devresinde <strong>closed</strong>, akımın geçtiği; <strong>open</strong> ise akımın kesildiği durumdur. Burada “akım”, servis çağrılarıdır.</p>

<p>Hata oranı $E$, başarısız çağrı sayısı $F$ ve toplam çağrı sayısı $R$ ile hesaplanabilir:</p>

\[E = \frac{F}{R} \times 100\]

<p>Son 20 çağrının 12’si başarısızsa hata oranı yüzde 60’tır. Eşik yüzde 50 olarak ayarlanmışsa devre açılabilir. Ancak yalnızca oran kullanmak risklidir: İki çağrıdan birinin başarısız olması da yüzde 50 eder. Bu nedenle minimum örnek sayısı belirlemek önemlidir.</p>

<h2 id="javascript-ile-sade-bir-uygulama">JavaScript ile sade bir uygulama</h2>

<p>Aşağıdaki sınıf, art arda gelen hataları sayar; eşik aşılınca çağrıları durdurur ve belirlenen sürenin ardından kontrollü bir denemeye izin verir:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">CircuitBreaker</span> <span class="p">{</span>
  <span class="nf">constructor</span><span class="p">(</span><span class="nx">action</span><span class="p">,</span> <span class="nx">threshold</span> <span class="o">=</span> <span class="mi">3</span><span class="p">,</span> <span class="nx">resetTime</span> <span class="o">=</span> <span class="mi">5000</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">action</span> <span class="o">=</span> <span class="nx">action</span><span class="p">;</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">threshold</span> <span class="o">=</span> <span class="nx">threshold</span><span class="p">;</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">resetTime</span> <span class="o">=</span> <span class="nx">resetTime</span><span class="p">;</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">failures</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">state</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">CLOSED</span><span class="dl">"</span><span class="p">;</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">openedAt</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="k">async</span> <span class="nf">execute</span><span class="p">(...</span><span class="nx">args</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if </span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">OPEN</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
      <span class="kd">const</span> <span class="nx">elapsed</span> <span class="o">=</span> <span class="nb">Date</span><span class="p">.</span><span class="nf">now</span><span class="p">()</span> <span class="o">-</span> <span class="k">this</span><span class="p">.</span><span class="nx">openedAt</span><span class="p">;</span>
      <span class="k">if </span><span class="p">(</span><span class="nx">elapsed</span> <span class="o">&lt;</span> <span class="k">this</span><span class="p">.</span><span class="nx">resetTime</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">throw</span> <span class="k">new</span> <span class="nc">Error</span><span class="p">(</span><span class="dl">"</span><span class="s2">Devre açık: çağrı engellendi</span><span class="dl">"</span><span class="p">);</span>
      <span class="p">}</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">state</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">HALF_OPEN</span><span class="dl">"</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">try</span> <span class="p">{</span>
      <span class="kd">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nf">action</span><span class="p">(...</span><span class="nx">args</span><span class="p">);</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">failures</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">state</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">CLOSED</span><span class="dl">"</span><span class="p">;</span>
      <span class="k">return</span> <span class="nx">result</span><span class="p">;</span>
    <span class="p">}</span> <span class="k">catch </span><span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">failures</span><span class="o">++</span><span class="p">;</span>
      <span class="k">if </span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">HALF_OPEN</span><span class="dl">"</span> <span class="o">||</span> <span class="k">this</span><span class="p">.</span><span class="nx">failures</span> <span class="o">&gt;=</span> <span class="k">this</span><span class="p">.</span><span class="nx">threshold</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">state</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">OPEN</span><span class="dl">"</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">openedAt</span> <span class="o">=</span> <span class="nb">Date</span><span class="p">.</span><span class="nf">now</span><span class="p">();</span>
      <span class="p">}</span>
      <span class="k">throw</span> <span class="nx">error</span><span class="p">;</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Sınıf gerçek projelerde zaman aşımı, kayan pencere, eş zamanlı Half-Open denemelerini sınırlama ve metrik toplama gibi özelliklerle genişletilmelidir. Hazır çözümler için Java ekosisteminde Resilience4j, .NET tarafında Polly kullanılabilir.</p>

<h2 id="retry-ile-aynı-şey-mi">Retry ile aynı şey mi?</h2>

<table>
  <thead>
    <tr>
      <th>Yaklaşım</th>
      <th>Temel amaç</th>
      <th>Hatalı kullanım riski</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Retry</td>
      <td>Geçici hatada çağrıyı yinelemek</td>
      <td>Çöken servise daha fazla yük bindirmek</td>
    </tr>
    <tr>
      <td>Timeout</td>
      <td>Sonsuz beklemeyi engellemek</td>
      <td>Çok kısa sürede geçerli çağrıları kesmek</td>
    </tr>
    <tr>
      <td>Circuit Breaker</td>
      <td>Arızalı hedefe trafiği durdurmak</td>
      <td>Yanlış eşikle devreyi gereksiz açmak</td>
    </tr>
    <tr>
      <td>Fallback</td>
      <td>Alternatif yanıt üretmek</td>
      <td>Eski veya eksik veri sunmak</td>
    </tr>
  </tbody>
</table>

<p>Bu desenler rakip değil, ekip arkadaşıdır. İyi bir akışta önce timeout uygulanır, yalnızca geçici hatalar dikkatli biçimde retry edilir, hata yoğunluğu artarsa devre açılır ve mümkünse önbellekten fallback yanıtı sunulur.</p>

<h2 id="sonuç">Sonuç</h2>

<p>Circuit Breaker bozuk servisi tamir etmez; sistemin geri kalanına nefes alacak alan kazandırır. Başarı için eşikleri trafik karakterine göre ayarlamak, devre durumlarını izlemek ve alarm üretmek gerekir. Çünkü dağıtık sistemlerde soru “Bir servis çöker mi?” değil, “Çöktüğünde sistem ne kadar zarif davranır?” olmalıdır.</p>]]></content><author><name>Sonsuz Us</name></author><category term="Bilgi" /><category term="circuit breaker" /><category term="mikroservis" /><category term="dağıtık sistemler" /><category term="dayanıklılık" /><category term="hata yönetimi" /><category term="javascript" /><summary type="html"><![CDATA[Bir mikroservis çöktüğünde sorun çoğu zaman yalnızca o servisle sınırlı kalmaz. Ona istek gönderen uygulamalar yanıt bekler, bağlantı havuzları dolar, iş parçacıkları tükenir ve masum servisler de domino taşları gibi devrilmeye başlar. Circuit Breaker Pattern, elektrik sigortasına benzeyen bir koruma mekanizması kurarak bu zincirleme felaketi durdurur.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://program.sonsuz.us/img/circuit-breaker-pattern-70.png" /><media:content medium="image" url="https://program.sonsuz.us/img/circuit-breaker-pattern-70.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Commit’ten Üretime: CI/CD Pipeline’larının Anatomisi</title><link href="https://program.sonsuz.us/posts/committen-uretime-cicd-pipelinelarinin-anatomisi/" rel="alternate" type="text/html" title="Commit’ten Üretime: CI/CD Pipeline’larının Anatomisi" /><published>2026-09-24T00:00:00+00:00</published><updated>2026-09-24T00:00:00+00:00</updated><id>https://program.sonsuz.us/posts/committen-uretime-cicd-pipelinelarinin-anatomisi</id><content type="html" xml:base="https://program.sonsuz.us/posts/committen-uretime-cicd-pipelinelarinin-anatomisi/"><![CDATA[<p>Bir geliştirici kodunu depoya gönderdiğinde görünmez bir fabrika çalışmaya başlar: kaynak kod derlenir, testlerden geçirilir, paketlenir ve kontrollü biçimde kullanıcılarla buluşturulur. CI/CD pipeline adı verilen bu otomatik yolculuk, “Benim bilgisayarımda çalışıyordu!” cümlesini tarihe gömmeyi hedefleyen teknik aşamalar ile güvenlik kapılarından oluşur.</p>

<p><img src="/img/committen-uretime-cicd-48.svg" alt="committen-uretime-cicd-48" /></p>

<p>``</p>

<h2 id="ci-ve-cd-gerçekte-ne-anlama-gelir">CI ve CD gerçekte ne anlama gelir?</h2>

<p><strong>Continuous Integration (CI)</strong>, geliştiricilerin değişikliklerini sık aralıklarla ortak kod tabanına birleştirmesidir. Her commit sonrasında otomatik kontroller çalıştırılarak hatanın kaynağı henüz sıcakken bulunur.</p>

<p><strong>Continuous Delivery</strong>, doğrulanmış sürümün üretime gönderilmeye her an hazır tutulmasıdır; üretim dağıtımı insan onayı gerektirebilir. <strong>Continuous Deployment</strong> ise bu son onayı da otomatikleştirir. Testleri geçen değişiklik doğrudan üretime ilerler.</p>

<table>
  <thead>
    <tr>
      <th>Yaklaşım</th>
      <th style="text-align: right">Otomatik test</th>
      <th style="text-align: right">Otomatik paketleme</th>
      <th>Üretime geçiş</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Continuous Integration</td>
      <td style="text-align: right">Evet</td>
      <td style="text-align: right">Genellikle</td>
      <td>Kapsam dışı</td>
    </tr>
    <tr>
      <td>Continuous Delivery</td>
      <td style="text-align: right">Evet</td>
      <td style="text-align: right">Evet</td>
      <td>Manuel onaylı</td>
    </tr>
    <tr>
      <td>Continuous Deployment</td>
      <td style="text-align: right">Evet</td>
      <td style="text-align: right">Evet</td>
      <td>Tam otomatik</td>
    </tr>
  </tbody>
</table>

<p>Pipeline başarısını basitçe şu fikirle değerlendirebiliriz:</p>

\[Güven = Test\ Kapsamı \times Tekrarlanabilirlik \times Gözlemlenebilirlik\]

<p>Çarpanlardan biri sıfıra yaklaşıyorsa sisteme duyulan güven de hızla azalır. Yüzde yüz test kapsamı bile farklı ortamlarda farklı sonuç veren bir derleme sürecini kurtaramaz.</p>

<h2 id="yolculuğun-temel-durakları">Yolculuğun temel durakları</h2>

<h3 id="1-commit-ve-tetikleme">1. Commit ve tetikleme</h3>

<p>Süreç, Git deposuna yapılan <code class="language-plaintext highlighter-rouge">push</code> veya açılan pull request ile başlar. Pipeline önce değişen dosyaları, dal kurallarını ve commit bilgisini inceler. Küçük ve sık commit’ler geri bildirim süresini kısaltır; devasa commit’ler ise hata avını polisiye romana dönüştürür.</p>

<h3 id="2-build-ve-bağımlılıklar">2. Build ve bağımlılıklar</h3>

<p>Kaynak kod çalıştırılabilir pakete dönüştürülür. Bağımlılık sürümleri kilit dosyalarıyla sabitlenmeli, derleme temiz bir ortamda tekrarlanabilmelidir. Docker bu noktada oldukça kullanışlıdır:</p>

<div class="language-dockerfile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">FROM</span><span class="s"> node:22-alpine</span>
<span class="k">WORKDIR</span><span class="s"> /app</span>
<span class="k">COPY</span><span class="s"> package*.json ./</span>
<span class="k">RUN </span>npm ci
<span class="k">COPY</span><span class="s"> . .</span>
<span class="k">RUN </span>npm run build
<span class="k">CMD</span><span class="s"> ["npm", "start"]</span>
</code></pre></div></div>

<p>Bu Dockerfile bağımlılıkları deterministik biçimde kurar, uygulamayı derler ve her ortamda aynı imajın çalıştırılmasını sağlar. Ortamı değil, aynı paketlenmiş ürünü taşırsınız.</p>

<h3 id="3-kalite-ve-güvenlik-kapıları">3. Kalite ve güvenlik kapıları</h3>

<p>Pipeline sırasıyla lint, birim testi, entegrasyon testi ve güvenlik taraması çalıştırabilir. Hızlı kontrollerin önce çalıştırılması önemlidir; iki saniyede bulunabilecek biçim hatası için yirmi dakikalık uçtan uca testi beklemek anlamsızdır.</p>

<table>
  <thead>
    <tr>
      <th>Test türü</th>
      <th style="text-align: right">Hız</th>
      <th>Kapsadığı alan</th>
      <th>Amaç</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Birim testi</td>
      <td style="text-align: right">Çok hızlı</td>
      <td>Fonksiyon veya sınıf</td>
      <td>İzole davranışı doğrulamak</td>
    </tr>
    <tr>
      <td>Entegrasyon testi</td>
      <td style="text-align: right">Orta</td>
      <td>Servisler ve veritabanı</td>
      <td>Bileşen iletişimini sınamak</td>
    </tr>
    <tr>
      <td>Uçtan uca test</td>
      <td style="text-align: right">Yavaş</td>
      <td>Tüm sistem</td>
      <td>Kullanıcı akışını doğrulamak</td>
    </tr>
  </tbody>
</table>

<p>Toplam geri bildirim süresi yaklaşık olarak</p>

\[T_{pipeline}=T_{build}+T_{test}+T_{scan}+T_{deploy}\]

<p>şeklinde düşünülebilir. Bağımsız işler paralel çalıştırıldığında toplam süre, sürelerin toplamından çok en uzun işin süresine yaklaşır.</p>

<h3 id="4-artifact-ve-dağıtım">4. Artifact ve dağıtım</h3>

<p>Başarılı build sonucunda üretilen Docker imajı, JAR veya paket bir <strong>artifact</strong> deposuna sürüm numarasıyla kaydedilir. Aynı artifact test, staging ve üretim ortamlarında ilerletilmelidir; her ortam için yeniden build almak sürprizlere davetiye çıkarır.</p>

<p>Basitleştirilmiş bir GitHub Actions adımı şöyle olabilir:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Testleri çalıştır</span>
  <span class="na">run</span><span class="pi">:</span> <span class="s">npm test</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">İmajı oluştur</span>
  <span class="na">run</span><span class="pi">:</span> <span class="s">docker build -t uygulama:${{ github.sha }} .</span>
</code></pre></div></div>

<p>Commit SHA kullanılması, üretimde çalışan imajın hangi kod değişikliğine ait olduğunu izlenebilir kılar.</p>

<h2 id="üretimde-güvenli-iniş">Üretimde güvenli iniş</h2>

<p>Dağıtım yalnızca dosya kopyalamak değildir. <strong>Rolling deployment</strong> sunucuları sırayla günceller; <strong>blue-green deployment</strong> eski ve yeni ortam arasında trafik değiştirir; <strong>canary deployment</strong> ise yeni sürümü önce küçük bir kullanıcı grubuna sunar.</p>

<p>Pipeline üretime ulaşınca bitmez. Hata oranı, gecikme ve kaynak tüketimi izlenmeli; kritik eşikler aşılırsa otomatik rollback uygulanmalıdır. İyi bir CI/CD sistemi hızlı olmaktan önce güvenilir, ölçülebilir ve geri alınabilirdir. Amaç geliştiriciyi hız trenine bindirmek değil, commit’ten kullanıcıya uzanan yolu korkuluklarla güvenceye almaktır.</p>]]></content><author><name>Sonsuz Us</name></author><category term="Bilgi" /><category term="ci/cd" /><category term="devops" /><category term="otomasyon" /><category term="docker" /><category term="test" /><category term="deployment" /><summary type="html"><![CDATA[Bir geliştirici kodunu depoya gönderdiğinde görünmez bir fabrika çalışmaya başlar: kaynak kod derlenir, testlerden geçirilir, paketlenir ve kontrollü biçimde kullanıcılarla buluşturulur. CI/CD pipeline adı verilen bu otomatik yolculuk, “Benim bilgisayarımda çalışıyordu!” cümlesini tarihe gömmeyi hedefleyen teknik aşamalar ile güvenlik kapılarından oluşur.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://program.sonsuz.us/img/committen-uretime-cicd-48.png" /><media:content medium="image" url="https://program.sonsuz.us/img/committen-uretime-cicd-48.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">CQRS: Okuma ve Yazma Modellerini Neden Ayırıyoruz?</title><link href="https://program.sonsuz.us/posts/cqrs-okuma-ve-yazma-modellerini-neden-ayiriyoruz/" rel="alternate" type="text/html" title="CQRS: Okuma ve Yazma Modellerini Neden Ayırıyoruz?" /><published>2026-09-24T00:00:00+00:00</published><updated>2026-09-24T00:00:00+00:00</updated><id>https://program.sonsuz.us/posts/cqrs-okuma-ve-yazma-modellerini-neden-ayiriyoruz</id><content type="html" xml:base="https://program.sonsuz.us/posts/cqrs-okuma-ve-yazma-modellerini-neden-ayiriyoruz/"><![CDATA[<p>Bir e-ticaret uygulamasında ürün satın almak ile ürün listesini görüntülemek aynı veri üzerinde çalışıyor gibi görünür. Ancak satın alma işlemi stok kontrolü, ödeme ve iş kuralları gerektirirken listeleme işlemi yalnızca hızlı ve zengin bir görünüm ister. CQRS, bu iki farklı ihtiyacı tek bir modelin omuzlarına yüklemek yerine okuma ve yazma taraflarını birbirinden ayırır.</p>

<p><img src="/img/cqrs-okuma-ve-67.svg" alt="cqrs-okuma-ve-67" /></p>

<p>``</p>

<h2 id="cqrs-tam-olarak-nedir">CQRS tam olarak nedir?</h2>

<p>CQRS, <strong>Command Query Responsibility Segregation</strong>, yani Komut ve Sorgu Sorumluluklarının Ayrılması anlamına gelir. Temel fikir basittir:</p>

<ul>
  <li><strong>Command</strong>, sistemin durumunu değiştiren istektir: sipariş oluştur, adres güncelle, ürünü iptal et.</li>
  <li><strong>Query</strong>, sistemin durumunu değiştirmeden veri okuyan istektir: siparişleri listele, ürün detayını getir.</li>
</ul>

<p>Bu ayrım yalnızca <code class="language-plaintext highlighter-rouge">CreateOrder()</code> ve <code class="language-plaintext highlighter-rouge">GetOrders()</code> metotlarını farklı dosyalara koymak değildir. Gerçek CQRS yaklaşımında iki tarafın modelleri, servisleri ve gerekirse veri depoları farklı olabilir.</p>

<table>
  <thead>
    <tr>
      <th>Özellik</th>
      <th>Yazma modeli</th>
      <th>Okuma modeli</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Ana hedef</td>
      <td>Tutarlılık ve iş kuralları</td>
      <td>Hızlı veri sunumu</td>
    </tr>
    <tr>
      <td>Model yapısı</td>
      <td>Davranış odaklı domain modeli</td>
      <td>Ekrana uygun DTO veya görünüm</td>
    </tr>
    <tr>
      <td>Veri biçimi</td>
      <td>Normalize edilmiş olabilir</td>
      <td>Denormalize edilebilir</td>
    </tr>
    <tr>
      <td>Ölçekleme</td>
      <td>Yazma trafiğine göre</td>
      <td>Okuma trafiğine göre</td>
    </tr>
    <tr>
      <td>Doğrulama</td>
      <td>Yoğun iş kuralı kontrolü</td>
      <td>Genellikle daha hafif</td>
    </tr>
  </tbody>
</table>

<h2 id="tek-model-neden-her-zaman-yeterli-değildir">Tek model neden her zaman yeterli değildir?</h2>

<p>Klasik CRUD yaklaşımında aynı model hem kaydedilir hem de kullanıcıya gösterilir. Küçük projelerde bu son derece mantıklıdır. Sistem büyüdüğünde ise okuma ekranları tablolar arasında çok sayıda <code class="language-plaintext highlighter-rouge">JOIN</code> isterken yazma tarafı güçlü tutarlılık kurallarına ihtiyaç duyar.</p>

<p>Bir sistemde saniyedeki okuma sayısı $R$, yazma sayısı $W$ olsun. Çoğu uygulamada:</p>

\[R \gg W\]

<p>Örneğin $R=10.000$ ve $W=200$ olabilir. Tek veritabanını iki iş yükü için aynı biçimde ölçeklemek, yalnızca okumalar yüzünden pahalı yazma altyapısı kurmak anlamına gelebilir. CQRS sayesinde okuma tarafına önbellek, arama motoru veya salt okunur replikalar eklenebilir.</p>

<h2 id="basit-bir-uygulama-örneği">Basit bir uygulama örneği</h2>

<p>Aşağıdaki C# örneğinde komut, sipariş oluşturma niyetini; sorgu ise arayüzün ihtiyaç duyduğu sonucu temsil eder:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">record</span> <span class="nc">CreateOrderCommand</span><span class="p">(</span>
    <span class="n">Guid</span> <span class="n">CustomerId</span><span class="p">,</span>
    <span class="n">IReadOnlyList</span><span class="p">&lt;</span><span class="n">OrderItem</span><span class="p">&gt;</span> <span class="n">Items</span><span class="p">);</span>

<span class="k">public</span> <span class="k">record</span> <span class="nc">GetOrderSummaryQuery</span><span class="p">(</span><span class="n">Guid</span> <span class="n">OrderId</span><span class="p">);</span>

<span class="k">public</span> <span class="k">sealed</span> <span class="k">class</span> <span class="nc">CreateOrderHandler</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">Guid</span><span class="p">&gt;</span> <span class="nf">Handle</span><span class="p">(</span><span class="n">CreateOrderCommand</span> <span class="n">command</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="kt">var</span> <span class="n">order</span> <span class="p">=</span> <span class="n">Order</span><span class="p">.</span><span class="nf">Create</span><span class="p">(</span><span class="n">command</span><span class="p">.</span><span class="n">CustomerId</span><span class="p">,</span> <span class="n">command</span><span class="p">.</span><span class="n">Items</span><span class="p">);</span>
        <span class="k">await</span> <span class="n">orderRepository</span><span class="p">.</span><span class="nf">Save</span><span class="p">(</span><span class="n">order</span><span class="p">);</span>
        <span class="k">await</span> <span class="n">eventBus</span><span class="p">.</span><span class="nf">Publish</span><span class="p">(</span><span class="k">new</span> <span class="nf">OrderCreated</span><span class="p">(</span><span class="n">order</span><span class="p">.</span><span class="n">Id</span><span class="p">));</span>
        <span class="k">return</span> <span class="n">order</span><span class="p">.</span><span class="n">Id</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Burada <code class="language-plaintext highlighter-rouge">Order.Create</code> stok, müşteri ve toplam tutar gibi kuralları korur. Okuma tarafı ise aynı domain nesnesini yeniden kurmak zorunda değildir:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">OrderSummaryDto</span><span class="p">?&gt;</span> <span class="nf">Handle</span><span class="p">(</span><span class="n">GetOrderSummaryQuery</span> <span class="n">query</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">return</span> <span class="k">await</span> <span class="n">readDatabase</span><span class="p">.</span><span class="n">OrderSummaries</span>
        <span class="p">.</span><span class="nf">SingleOrDefaultAsync</span><span class="p">(</span><span class="n">x</span> <span class="p">=&gt;</span> <span class="n">x</span><span class="p">.</span><span class="n">Id</span> <span class="p">==</span> <span class="n">query</span><span class="p">.</span><span class="n">OrderId</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">OrderSummaryDto</code>, ekranın istediği müşteri adı, toplam tutar ve durum bilgisini önceden birleştirilmiş şekilde taşıyabilir. Böylece sorgu hem sadeleşir hem hızlanır.</p>

<h2 id="peki-iki-taraf-nasıl-senkron-kalır">Peki iki taraf nasıl senkron kalır?</h2>

<p>Yazma tamamlandığında <code class="language-plaintext highlighter-rouge">OrderCreated</code> gibi bir olay yayımlanır. Bu olayı dinleyen süreç, okuma modelini günceller. Güncelleme anlık değilse sistem kısa süreliğine eski veri gösterebilir. Buna <strong>eventual consistency</strong>, yani nihai tutarlılık denir.</p>

<table>
  <thead>
    <tr>
      <th>Yaklaşım</th>
      <th>Avantaj</th>
      <th>Risk</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Aynı veritabanı</td>
      <td>Yönetimi kolay</td>
      <td>Modeller birbirine bağımlı kalabilir</td>
    </tr>
    <tr>
      <td>Ayrı veritabanları</td>
      <td>Bağımsız ölçekleme</td>
      <td>Senkronizasyon karmaşıklığı</td>
    </tr>
    <tr>
      <td>Mesaj kuyruğu</td>
      <td>Dayanıklı olay aktarımı</td>
      <td>Tekrar işleme ve sıralama sorunları</td>
    </tr>
  </tbody>
</table>

<p>Bu nedenle olay tüketicileri idempotent tasarlanmalıdır. Aynı olay iki kez işlendiğinde iki sipariş özeti üretmemelidir.</p>

<h2 id="cqrs-her-projede-kullanılmalı-mı">CQRS her projede kullanılmalı mı?</h2>

<p>Hayır. Basit yönetim panellerinde veya küçük CRUD servislerinde CQRS; ek sınıflar, mesaj altyapısı ve operasyonel yük getirerek gereksiz karmaşıklık yaratabilir. Okuma ve yazma ihtiyaçları belirgin biçimde farklıysa, yoğun trafik varsa ya da domain kuralları karmaşıksa güçlü bir seçenektir.</p>

<p>Kısacası CQRS, “iki veritabanı kullanalım” modası değildir. Amaç, farklı problemlere farklı modellerle yaklaşmaktır. Doğru yerde kullanıldığında performans, ölçeklenebilirlik ve kod okunabilirliği kazandırır; yanlış yerde kullanıldığında ise mimariyi küçük bir uzay programına çevirebilir.</p>]]></content><author><name>Sonsuz Us</name></author><category term="Bilgi" /><category term="cqrs" /><category term="yazılım mimarisi" /><category term="domain driven design" /><category term="eventual consistency" /><category term="backend" /><category term="ölçeklenebilirlik" /><summary type="html"><![CDATA[Bir e-ticaret uygulamasında ürün satın almak ile ürün listesini görüntülemek aynı veri üzerinde çalışıyor gibi görünür. Ancak satın alma işlemi stok kontrolü, ödeme ve iş kuralları gerektirirken listeleme işlemi yalnızca hızlı ve zengin bir görünüm ister. CQRS, bu iki farklı ihtiyacı tek bir modelin omuzlarına yüklemek yerine okuma ve yazma taraflarını birbirinden ayırır.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://program.sonsuz.us/img/cqrs-okuma-ve-67.png" /><media:content medium="image" url="https://program.sonsuz.us/img/cqrs-okuma-ve-67.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Dinamik Bağlantılı Bileşenler: Zamanı Böl, Grafı Fethet</title><link href="https://program.sonsuz.us/posts/dinamik-baglantili-bilesenler-zamani-bol-grafi-fethet/" rel="alternate" type="text/html" title="Dinamik Bağlantılı Bileşenler: Zamanı Böl, Grafı Fethet" /><published>2026-09-24T00:00:00+00:00</published><updated>2026-09-24T00:00:00+00:00</updated><id>https://program.sonsuz.us/posts/dinamik-baglantili-bilesenler-zamani-bol-grafi-fethet</id><content type="html" xml:base="https://program.sonsuz.us/posts/dinamik-baglantili-bilesenler-zamani-bol-grafi-fethet/"><![CDATA[<p>Bir sosyal ağda arkadaşlıklar kuruluyor, bozuluyor ve arada “Ali ile Ayşe hâlâ dolaylı olarak bağlantılı mı?” soruları geliyor. Graf sürekli değişirken her sorguda baştan DFS çalıştırmak mümkündür; fakat performansınız kısa sürede dramatik bir vedaya hazırlanır. Çevrimdışı dinamik bağlantılılık, tüm işlemleri önceden bilmenin avantajını kullanarak zamanı parçalara ayırır ve bağlantıları geri alınabilir bir DSU ile takip eder.</p>

<p>``</p>

<h2 id="problem-neden-zor">Problem neden zor?</h2>

<p>Yalnızca kenar eklenen bir grafta Disjoint Set Union, yani DSU, mükemmel çalışır. <code class="language-plaintext highlighter-rouge">union(u, v)</code> bileşenleri birleştirir; <code class="language-plaintext highlighter-rouge">find(u) == find(v)</code> ise iki düğümün bağlı olup olmadığını söyler. Ancak standart DSU, silinen bir kenarı unutamaz. Çünkü birleşim sonrasında hangi değişikliklerin geri alınması gerektiğini saklamaz.</p>

<p>Elimizde zaman sırasına göre şu işlemler bulunsun:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">add(u, v)</code>: Kenarı ekle.</li>
  <li><code class="language-plaintext highlighter-rouge">remove(u, v)</code>: Kenarı sil.</li>
  <li><code class="language-plaintext highlighter-rouge">query(u, v)</code>: Düğümler bağlı mı?</li>
</ul>

<p>Her kenarın etkin olduğu aralığı $[L, R)$ şeklinde belirleyebiliriz. Burada kenar $L$ anında eklenmiş, $R$ anında kaldırılmıştır. Hiç kaldırılmayan kenarlar için $R = Q$ alınır; $Q$ toplam işlem sayısıdır.</p>

<h2 id="zaman-üzerinde-bölümleme">Zaman üzerinde bölümleme</h2>

<p>İşlem zamanlarını kapsayan bir segment ağacı kurarız. Bir kenar hangi zaman aralığında etkinse onu, bu aralığı tamamen örten segment ağacı düğümlerine ekleriz. Böylece her kenar en fazla $O(log Q)$ düğümde tutulur.</p>

<table>
  <thead>
    <tr>
      <th>Yaklaşım</th>
      <th style="text-align: right">Kenar silme</th>
      <th style="text-align: right">Sorgu maliyeti</th>
      <th>Temel fikir</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Her sorguda DFS/BFS</td>
      <td style="text-align: right">Kolay</td>
      <td style="text-align: right">$O(N+M)$</td>
      <td>Grafı yeniden tara</td>
    </tr>
    <tr>
      <td>Standart DSU</td>
      <td style="text-align: right">Desteklemez</td>
      <td style="text-align: right">Yaklaşık $O(1)$</td>
      <td>Yalnızca birleşim</td>
    </tr>
    <tr>
      <td>Segment ağacı + rollback DSU</td>
      <td style="text-align: right">Destekler</td>
      <td style="text-align: right">Çevrimdışı verimli</td>
      <td>Zamanı böl, durumu geri al</td>
    </tr>
  </tbody>
</table>

<p><img src="/img/dinamik-baglantili-bilesenler-37.svg" alt="dinamik-baglantili-bilesenler-37" /></p>

<p>Segment ağacında DFS yaparken bir düğüme ait bütün kenarları DSU’ya ekleriz. Yaprak noktası tek bir zamanı temsil eder; dolayısıyla o andaki sorguyu güncel DSU ile cevaplarız. Alt ağaçtan dönerken yapılan birleşimleri geri alırız. Algoritmanın küçük numarası tam olarak budur: zamanda ileri giderken birleştir, geriye çıkarken hafızayı temizle.</p>

<h2 id="geri-alınabilir-dsu">Geri alınabilir DSU</h2>

<p>Rollback yapılabilmesi için yol sıkıştırma kullanmamalıyız. Yol sıkıştırma çok sayıda ebeveyn değerini sessizce değiştirerek geri alma işlemini karmaşıklaştırır. Bunun yerine union-by-size kullanılır; böylece ağaç yüksekliği $O(log N)$ sınırında kalır.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="nc">RollbackDSU</span> <span class="p">{</span>
    <span class="n">vector</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span> <span class="n">parent</span><span class="p">,</span> <span class="n">size</span><span class="p">;</span>
    <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">history</span><span class="p">;</span>

    <span class="n">RollbackDSU</span><span class="p">(</span><span class="kt">int</span> <span class="n">n</span><span class="p">)</span> <span class="o">:</span> <span class="n">parent</span><span class="p">(</span><span class="n">n</span><span class="p">),</span> <span class="n">size</span><span class="p">(</span><span class="n">n</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">iota</span><span class="p">(</span><span class="n">parent</span><span class="p">.</span><span class="n">begin</span><span class="p">(),</span> <span class="n">parent</span><span class="p">.</span><span class="n">end</span><span class="p">(),</span> <span class="mi">0</span><span class="p">);</span>
    <span class="p">}</span>

    <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">parent</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">parent</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>

    <span class="kt">int</span> <span class="nf">snapshot</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="n">history</span><span class="p">.</span><span class="n">size</span><span class="p">();</span> <span class="p">}</span>

    <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">a</span> <span class="o">==</span> <span class="n">b</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">history</span><span class="p">.</span><span class="n">push_back</span><span class="p">({</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">});</span>
            <span class="k">return</span><span class="p">;</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">history</span><span class="p">.</span><span class="n">push_back</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="n">parent</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="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="p">}</span>

    <span class="kt">void</span> <span class="nf">rollback</span><span class="p">(</span><span class="kt">int</span> <span class="n">snap</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">while</span> <span class="p">((</span><span class="kt">int</span><span class="p">)</span><span class="n">history</span><span class="p">.</span><span class="n">size</span><span class="p">()</span> <span class="o">&gt;</span> <span class="n">snap</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">auto</span> <span class="p">[</span><span class="n">b</span><span class="p">,</span> <span class="n">oldSize</span><span class="p">]</span> <span class="o">=</span> <span class="n">history</span><span class="p">.</span><span class="n">back</span><span class="p">();</span>
            <span class="n">history</span><span class="p">.</span><span class="n">pop_back</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="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="k">continue</span><span class="p">;</span>
            <span class="kt">int</span> <span class="n">a</span> <span class="o">=</span> <span class="n">parent</span><span class="p">[</span><span class="n">b</span><span class="p">];</span>
            <span class="n">parent</span><span class="p">[</span><span class="n">b</span><span class="p">]</span> <span class="o">=</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">oldSize</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">};</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">snapshot</code>, mevcut geçmiş uzunluğunu kaydeder. <code class="language-plaintext highlighter-rouge">rollback</code>, bu uzunluğa ulaşana kadar değişiklikleri tersine çevirir. Başarısız birleşimler için de işaret bırakılması, her çağrının geçmişte karşılığının bulunmasını sağlar.</p>

<h2 id="karmaşıklık-ve-kullanım-alanları">Karmaşıklık ve kullanım alanları</h2>

<p>Her etkinlik aralığı segment ağacına $O(log Q)$ kez yerleştirilir. Union-by-size ile <code class="language-plaintext highlighter-rouge">find</code> maliyeti $O(log N)$ olduğundan toplam süre kabaca $O((M log Q + Q) log N)$, bellek tüketimi ise $O(M log Q + N)$ olur.</p>

<p>Bu yöntem ağ bağlantıları, oyuncu eşleştirme kümeleri, sürüm geçmişleri ve zaman damgalı sosyal ağ analizlerinde oldukça kullanışlıdır. Bedeli, cevapların çevrimiçi üretilememesidir: gelecekteki işlemleri önceden bilmek gerekir. Eğer bütün senaryo elinizdeyse zaman artık düşman değil, segmentlere ayrılmayı bekleyen yardımsever bir boyuttur.</p>]]></content><author><name>Sonsuz Us</name></author><category term="Bilgi" /><category term="graf" /><category term="algoritma" /><category term="böl-ve-fethet" /><category term="dsu" /><category term="çevrimdışı-sorgular" /><category term="dinamik-bağlantılılık" /><summary type="html"><![CDATA[Bir sosyal ağda arkadaşlıklar kuruluyor, bozuluyor ve arada “Ali ile Ayşe hâlâ dolaylı olarak bağlantılı mı?” soruları geliyor. Graf sürekli değişirken her sorguda baştan DFS çalıştırmak mümkündür; fakat performansınız kısa sürede dramatik bir vedaya hazırlanır. Çevrimdışı dinamik bağlantılılık, tüm işlemleri önceden bilmenin avantajını kullanarak zamanı parçalara ayırır ve bağlantıları geri alınabilir bir DSU ile takip eder.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://program.sonsuz.us/img/dinamik-baglantili-bilesenler-37.png" /><media:content medium="image" url="https://program.sonsuz.us/img/dinamik-baglantili-bilesenler-37.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Dinamik Programlamada Hız Sihri: Knuth Optimizasyonunun Sırları</title><link href="https://program.sonsuz.us/posts/dinamik-programlamada-hiz-sihri-knuth-optimizasyonunun-sirlari/" rel="alternate" type="text/html" title="Dinamik Programlamada Hız Sihri: Knuth Optimizasyonunun Sırları" /><published>2026-09-24T00:00:00+00:00</published><updated>2026-09-24T00:00:00+00:00</updated><id>https://program.sonsuz.us/posts/dinamik-programlamada-hiz-sihri-knuth-optimizasyonunun-sirlari</id><content type="html" xml:base="https://program.sonsuz.us/posts/dinamik-programlamada-hiz-sihri-knuth-optimizasyonunun-sirlari/"><![CDATA[<p>Dinamik programlama bazen doğru bağıntıyı bulduğumuz anda bizi sevindirir, ardından $O(N^3)$ zaman karmaşıklığıyla moralimizi bozar. Özellikle bir aralığı en uygun noktadan bölmeye dayanan problemlerde aynı geçişler tekrar tekrar incelenir. Knuth optimizasyonu, optimum bölme noktalarının düzenli hareket ettiğini matematiksel olarak kanıtlayabildiğimiz durumlarda bu kübik maliyeti $O(N^2)$ seviyesine indiren zarif bir tekniktir.</p>

<p>``</p>

<h2 id="kübik-karmaşıklık-nereden-geliyor">Kübik karmaşıklık nereden geliyor?</h2>

<p>Elimizde $[i,j]$ aralığının minimum maliyetini hesaplayan bir interval DP bulunduğunu düşünelim. Yaygın bağıntı şöyledir:</p>

\[dp[i][j] = \min_{i \le k &lt; j} \{dp[i][k] + dp[k+1][j] + C(i,j)\}\]

<p>Burada $k$ bölme noktası, $C(i,j)$ ise mevcut aralığı birleştirmenin veya işlemenin maliyetidir. Toplam $O(N^2)$ farklı aralık vardır. Her aralık için $O(N)$ bölme noktası denendiğinde sonuç kaçınılmazdır:</p>

\[O(N^2) \cdot O(N) = O(N^3)\]

<p>Knuth’un temel fikri, her aralıkta bütün $k$ değerlerini denememektir. $opt[i][j]$, $dp[i][j]$ sonucunu veren en iyi bölme noktası olsun. Gerekli matematiksel koşullar sağlanıyorsa şu monotonluk ortaya çıkar:</p>

\[opt[i][j-1] \le opt[i][j] \le opt[i+1][j]\]

<p>Yani yeni optimum, komşu aralıkların optimumları arasında bulunur. Arama alanı dramatik biçimde küçülür.</p>

<h2 id="hangi-koşullarda-kullanılabilir">Hangi koşullarda kullanılabilir?</h2>

<p>Knuth optimizasyonu her interval DP bağıntısına serpilebilen sihirli bir baharat değildir. Maliyet fonksiyonunun monotonluk ve dörtgen eşitsizliği olarak bilinen özellikleri sağlaması gerekir. $a \le b \le c \le d$ için yaygın yeterli koşullar şunlardır:</p>

\[C(b,c) \le C(a,d)\]

\[C(a,c) + C(b,d) \le C(a,d) + C(b,c)\]

<p>İkinci ifade, maliyet matrisinin Monge benzeri bir yapıya sahip olduğunu söyler. Başka bir deyişle, aralıklar genişledikçe optimum kararlar rastgele sağa sola sıçramaz.</p>

<table>
  <thead>
    <tr>
      <th>Özellik</th>
      <th style="text-align: right">Klasik Interval DP</th>
      <th style="text-align: right">Knuth Optimizasyonu</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Durum sayısı</td>
      <td style="text-align: right">$O(N^2)$</td>
      <td style="text-align: right">$O(N^2)$</td>
    </tr>
    <tr>
      <td>Geçiş aralığı</td>
      <td style="text-align: right">En fazla $O(N)$</td>
      <td style="text-align: right">Komşu optimumlarla sınırlı</td>
    </tr>
    <tr>
      <td>Toplam süre</td>
      <td style="text-align: right">$O(N^3)$</td>
      <td style="text-align: right">$O(N^2)$</td>
    </tr>
    <tr>
      <td>Bellek</td>
      <td style="text-align: right">$O(N^2)$</td>
      <td style="text-align: right">$O(N^2)$</td>
    </tr>
    <tr>
      <td>Ek gereksinim</td>
      <td style="text-align: right">Yok</td>
      <td style="text-align: right">Monoton optimum ve uygun maliyet</td>
    </tr>
  </tbody>
</table>

<h2 id="c-ile-uygulama">C++ ile uygulama</h2>

<p>Aşağıdaki örnek, elemanları ardışık biçimde birleştirmenin minimum maliyetini hesaplar. $C(i,j)$, prefix sum kullanılarak aralık toplamı olarak alınır:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;bits/stdc++.h&gt;</span><span class="cp">
</span><span class="k">using</span> <span class="k">namespace</span> <span class="n">std</span><span class="p">;</span>

<span class="kt">long</span> <span class="kt">long</span> <span class="nf">knuthMergeCost</span><span class="p">(</span><span class="k">const</span> <span class="n">vector</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;&amp;</span> <span class="n">a</span><span class="p">)</span> <span class="p">{</span>
    <span class="kt">int</span> <span class="n">n</span> <span class="o">=</span> <span class="n">a</span><span class="p">.</span><span class="n">size</span><span class="p">();</span>
    <span class="k">const</span> <span class="kt">long</span> <span class="kt">long</span> <span class="n">INF</span> <span class="o">=</span> <span class="p">(</span><span class="mi">1LL</span> <span class="o">&lt;&lt;</span> <span class="mi">62</span><span class="p">);</span>

    <span class="n">vector</span><span class="o">&lt;</span><span class="kt">long</span> <span class="kt">long</span><span class="o">&gt;</span> <span class="n">prefix</span><span class="p">(</span><span class="n">n</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</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">0</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="o">++</span><span class="n">i</span><span class="p">)</span>
        <span class="n">prefix</span><span class="p">[</span><span class="n">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="n">prefix</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">+</span> <span class="n">a</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>

    <span class="n">vector</span><span class="o">&lt;</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">long</span> <span class="kt">long</span><span class="o">&gt;&gt;</span> <span class="n">dp</span><span class="p">(</span><span class="n">n</span><span class="p">,</span> <span class="n">vector</span><span class="o">&lt;</span><span class="kt">long</span> <span class="kt">long</span><span class="o">&gt;</span><span class="p">(</span><span class="n">n</span><span class="p">,</span> <span class="mi">0</span><span class="p">));</span>
    <span class="n">vector</span><span class="o">&lt;</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;&gt;</span> <span class="n">opt</span><span class="p">(</span><span class="n">n</span><span class="p">,</span> <span class="n">vector</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span><span class="p">(</span><span class="n">n</span><span class="p">,</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">0</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="o">++</span><span class="n">i</span><span class="p">)</span>
        <span class="n">opt</span><span class="p">[</span><span class="n">i</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">len</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span> <span class="n">len</span> <span class="o">&lt;=</span> <span class="n">n</span><span class="p">;</span> <span class="o">++</span><span class="n">len</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">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">+</span> <span class="n">len</span> <span class="o">&lt;=</span> <span class="n">n</span><span class="p">;</span> <span class="o">++</span><span class="n">i</span><span class="p">)</span> <span class="p">{</span>
            <span class="kt">int</span> <span class="n">j</span> <span class="o">=</span> <span class="n">i</span> <span class="o">+</span> <span class="n">len</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span>
            <span class="n">dp</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="kt">int</span> <span class="n">left</span> <span class="o">=</span> <span class="n">opt</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span> <span class="o">-</span> <span class="mi">1</span><span class="p">];</span>
            <span class="kt">int</span> <span class="n">right</span> <span class="o">=</span> <span class="n">min</span><span class="p">(</span><span class="n">j</span> <span class="o">-</span> <span class="mi">1</span><span class="p">,</span> <span class="n">opt</span><span class="p">[</span><span class="n">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">][</span><span class="n">j</span><span class="p">]);</span>
            <span class="kt">long</span> <span class="kt">long</span> <span class="n">cost</span> <span class="o">=</span> <span class="n">prefix</span><span class="p">[</span><span class="n">j</span> <span class="o">+</span> <span class="mi">1</span><span class="p">]</span> <span class="o">-</span> <span class="n">prefix</span><span class="p">[</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">k</span> <span class="o">=</span> <span class="n">left</span><span class="p">;</span> <span class="n">k</span> <span class="o">&lt;=</span> <span class="n">right</span><span class="p">;</span> <span class="o">++</span><span class="n">k</span><span class="p">)</span> <span class="p">{</span>
                <span class="kt">long</span> <span class="kt">long</span> <span class="n">candidate</span> <span class="o">=</span> <span class="n">dp</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">dp</span><span class="p">[</span><span class="n">k</span> <span class="o">+</span> <span class="mi">1</span><span class="p">][</span><span class="n">j</span><span class="p">]</span> <span class="o">+</span> <span class="n">cost</span><span class="p">;</span>
                <span class="k">if</span> <span class="p">(</span><span class="n">candidate</span> <span class="o">&lt;</span> <span class="n">dp</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="p">{</span>
                    <span class="n">dp</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">candidate</span><span class="p">;</span>
                    <span class="n">opt</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">k</span><span class="p">;</span>
                <span class="p">}</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="n">dp</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="n">n</span> <span class="o">-</span> <span class="mi">1</span><span class="p">];</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Kod aralıkları kısa uzunluktan başlayarak hesaplar; böylece ihtiyaç duyulan alt problemler ve komşu <code class="language-plaintext highlighter-rouge">opt</code> değerleri hazır olur. Prefix sum sayesinde $C(i,j)$ maliyeti $O(1)$ zamanda bulunur.</p>

<h2 id="en-önemli-tuzak">En önemli tuzak</h2>

<p>Optimum noktaların örnek testlerde monoton görünmesi bir kanıt değildir. Koşullar sağlanmadan arama aralığını daraltmak, hızlı fakat yanlış bir algoritma üretir. Önce bağıntıyı standart interval DP biçimine dönüştürün, ardından maliyet fonksiyonunu matematiksel olarak inceleyin. Uygunsa Knuth optimizasyonu yalnızca performans artışı değil, kübik bir çözümü uygulanabilir hâle getiren gerçek bir algoritmik süper güçtür.</p>

<p><img src="/img/dinamik-programlamada-hiz-66.svg" alt="dinamik-programlamada-hiz-66" /></p>]]></content><author><name>Sonsuz Us</name></author><category term="Bilgi" /><category term="dinamik programlama" /><category term="knuth optimizasyonu" /><category term="algoritma" /><category term="cpp" /><category term="zaman karmaşıklığı" /><category term="interval dp" /><summary type="html"><![CDATA[Dinamik programlama bazen doğru bağıntıyı bulduğumuz anda bizi sevindirir, ardından $O(N^3)$ zaman karmaşıklığıyla moralimizi bozar. Özellikle bir aralığı en uygun noktadan bölmeye dayanan problemlerde aynı geçişler tekrar tekrar incelenir. Knuth optimizasyonu, optimum bölme noktalarının düzenli hareket ettiğini matematiksel olarak kanıtlayabildiğimiz durumlarda bu kübik maliyeti $O(N^2)$ seviyesine indiren zarif bir tekniktir.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://program.sonsuz.us/img/dinamik-programlamada-hiz-66.png" /><media:content medium="image" url="https://program.sonsuz.us/img/dinamik-programlamada-hiz-66.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Eşleştirme Oyunlarında Grundy Sayıları: Kazandıran Hamlenin Matematiği</title><link href="https://program.sonsuz.us/posts/eslestirme-oyunlarinda-grundy-sayilari-kazandiran-hamlenin-matematigi/" rel="alternate" type="text/html" title="Eşleştirme Oyunlarında Grundy Sayıları: Kazandıran Hamlenin Matematiği" /><published>2026-09-24T00:00:00+00:00</published><updated>2026-09-24T00:00:00+00:00</updated><id>https://program.sonsuz.us/posts/eslestirme-oyunlarinda-grundy-sayilari-kazandiran-hamlenin-matematigi</id><content type="html" xml:base="https://program.sonsuz.us/posts/eslestirme-oyunlarinda-grundy-sayilari-kazandiran-hamlenin-matematigi/"><![CDATA[<p>Bir masanın üzerindeki çubukları, taşları veya eşleşebilen kartları sırayla kaldırdığınızı düşünün. Kurallar basit görünür: Hamle yapamayan kaybeder. Fakat hangi hamlenin kazandıracağını bulmak, özellikle bağımsız oyun parçaları birleştiğinde, sezgiden fazlasını gerektirir. Grundy sayıları ya da diğer adıyla <strong>nimber</strong>, her oyun durumunu tek bir sayıyla özetleyerek bu karmaşayı yönetilebilir bir matematik problemine dönüştürür.</p>

<p>``</p>

<h2 id="önce-oyun-türünü-tanıyalım">Önce oyun türünü tanıyalım</h2>

<p>Sprague–Grundy teorisi, <strong>tarafsız kombinatoryal oyunlar</strong> için çalışır. Tarafsızlık, aynı konumda iki oyuncunun da aynı hamle seçeneklerine sahip olmasıdır. Ayrıca şans unsuru bulunmamalı, oyuncular bütün durumu görebilmeli ve oyun sonlu sayıda hamlede bitmelidir.</p>

<table>
  <thead>
    <tr>
      <th>Özellik</th>
      <th>Uygun oyun</th>
      <th>Uygun olmayan oyun</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Hamle hakları</td>
      <td>İki oyuncu için aynı</td>
      <td>Oyuncuya göre değişiyor</td>
    </tr>
    <tr>
      <td>Bilgi</td>
      <td>Tamamen görünür</td>
      <td>Gizli kartlar var</td>
    </tr>
    <tr>
      <td>Rastlantı</td>
      <td>Yok</td>
      <td>Zar veya kura var</td>
    </tr>
    <tr>
      <td>Bitiş</td>
      <td>Sonlu</td>
      <td>Sonsuza uzanabilir</td>
    </tr>
  </tbody>
</table>

<p>Klasik Nim bu şartları karşılar. Eşleştirme oyunları da durumları bağımsız bileşenlere ayrılabiliyorsa aynı matematiksel çerçevede incelenebilir.</p>

<h2 id="grundy-sayısı-nasıl-hesaplanır">Grundy sayısı nasıl hesaplanır?</h2>

<p>Bir durumun Grundy sayısı, o durumdan tek hamlede ulaşılabilen durumların Grundy sayılarında bulunmayan en küçük negatif olmayan tam sayıdır. Bu işlem <strong>mex</strong> yani “minimum excluded value” olarak adlandırılır:</p>

\[g(s)=\operatorname{mex}\{g(t)\mid t\text{, }s\text{ durumundan erişilebilir}\}\]

<p>Hamle yapılamayan bitiş durumunun değeri sıfırdır:</p>

\[g(\text{bitiş})=0\]

<p>Örneğin bir taş yığınından her turda 1 veya 2 taş alınabilsin. Sıfır taşın nimberi 0’dır. Bir taşlık durum yalnızca 0’a gidebildiği için $g(1)=\operatorname{mex}{0}=1$ olur. İki taşlık durum 0 ve 1’e ulaşır; dolayısıyla $g(2)=2$ bulunur. Üç taş ise Grundy değerleri 1 ve 2 olan durumlara gider. Bu kez eksik en küçük sayı 0’dır: $g(3)=0$.</p>

<table>
  <thead>
    <tr>
      <th style="text-align: right">Taş sayısı</th>
      <th>Ulaşılabilen nimberler</th>
      <th style="text-align: right">Grundy değeri</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: right">0</td>
      <td>Yok</td>
      <td style="text-align: right">0</td>
    </tr>
    <tr>
      <td style="text-align: right">1</td>
      <td>{0}</td>
      <td style="text-align: right">1</td>
    </tr>
    <tr>
      <td style="text-align: right">2</td>
      <td>{0, 1}</td>
      <td style="text-align: right">2</td>
    </tr>
    <tr>
      <td style="text-align: right">3</td>
      <td>{1, 2}</td>
      <td style="text-align: right">0</td>
    </tr>
    <tr>
      <td style="text-align: right">4</td>
      <td>{2, 0}</td>
      <td style="text-align: right">1</td>
    </tr>
  </tbody>
</table>

<p>Sıfır değerli durum, sırası gelen oyuncu için kaybeden konumdur. Sıfırdan farklı değer ise doğru bir hamleyle rakibe sıfır değerli durum bırakılabileceğini gösterir.</p>

<h2 id="bağımsız-oyunları-xor-ile-birleştirmek">Bağımsız oyunları XOR ile birleştirmek</h2>

<p>Asıl sihir, birden fazla bağımsız bileşen olduğunda ortaya çıkar. Toplam oyunun nimberi, bileşenlerin Grundy sayılarının normal toplamı değil, bitsel XOR işlemidir:</p>

\[G=g_1\oplus g_2\oplus\cdots\oplus g_n\]

<p>Eğer $G=0$ ise konum kaybedendir; $G\neq0$ ise en az bir hamle toplam XOR değerini sıfıra indirebilir. Kazanma stratejisi de tam olarak bu hamleyi aramaktır. Örneğin nimberleri 1, 2 ve 3 olan üç bileşen için $1\oplus2\oplus3=0$ çıkar. Kusursuz oynayan rakibe karşı bu durum pek iç açıcı değildir!</p>

<h2 id="dinamik-programlamayla-hesaplama">Dinamik programlamayla hesaplama</h2>

<p>Aşağıdaki Python kodu, bir yığından izin verilen miktarlarda taş alınan oyunun Grundy değerlerini hesaplar:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">grundy_tablosu</span><span class="p">(</span><span class="n">n</span><span class="p">,</span> <span class="n">hamleler</span><span class="p">):</span>
    <span class="n">grundy</span> <span class="o">=</span> <span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">*</span> <span class="p">(</span><span class="n">n</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span>

    <span class="k">for</span> <span class="n">tas</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">n</span> <span class="o">+</span> <span class="mi">1</span><span class="p">):</span>
        <span class="n">erisilen</span> <span class="o">=</span> <span class="p">{</span>
            <span class="n">grundy</span><span class="p">[</span><span class="n">tas</span> <span class="o">-</span> <span class="n">h</span><span class="p">]</span>
            <span class="k">for</span> <span class="n">h</span> <span class="ow">in</span> <span class="n">hamleler</span>
            <span class="k">if</span> <span class="n">h</span> <span class="o">&lt;=</span> <span class="n">tas</span>
        <span class="p">}</span>

        <span class="n">mex</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="k">while</span> <span class="n">mex</span> <span class="ow">in</span> <span class="n">erisilen</span><span class="p">:</span>
            <span class="n">mex</span> <span class="o">+=</span> <span class="mi">1</span>
        <span class="n">grundy</span><span class="p">[</span><span class="n">tas</span><span class="p">]</span> <span class="o">=</span> <span class="n">mex</span>

    <span class="k">return</span> <span class="n">grundy</span>

<span class="nf">print</span><span class="p">(</span><span class="nf">grundy_tablosu</span><span class="p">(</span><span class="mi">12</span><span class="p">,</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>Kod, küçük durumlardan başlayıp sonuçları saklar. Her konum için erişilebilir nimberler bir kümede toplanır ve ilk eksik sayı bulunur. Böylece aynı alt durum tekrar tekrar hesaplanmaz.</p>

<p>Bir eşleştirme oyunu grafik, kart grubu veya ayrı tahta bölgelerinden oluşuyorsa önce durumun bağımsız bileşenleri belirlenir. Ardından her bileşenin Grundy değeri hesaplanır ve XOR’lanır. Her olası hamle denenerek toplamı sıfır yapan seçenek seçilir.</p>

<p>Grundy sayıları geleceği tahmin eden sihirli bir küre değildir; bütün oyun ağacını zekice sıkıştıran bir etikettir. Kurallar tarafsız ve bileşenler bağımsız olduğunda “Bence bu hamle iyi” demek yerine, matematiksel olarak kazandıran hamleyi gösterebilirsiniz.</p>

<p><img src="/img/eslestirme-oyunlarinda-grundy-83.svg" alt="eslestirme-oyunlarinda-grundy-83" /></p>]]></content><author><name>Sonsuz Us</name></author><category term="Bilgi" /><category term="grundy sayıları" /><category term="nimber" /><category term="kombinatoryal oyunlar" /><category term="oyun teorisi" /><category term="algoritma" /><category term="dinamik programlama" /><summary type="html"><![CDATA[Bir masanın üzerindeki çubukları, taşları veya eşleşebilen kartları sırayla kaldırdığınızı düşünün. Kurallar basit görünür: Hamle yapamayan kaybeder. Fakat hangi hamlenin kazandıracağını bulmak, özellikle bağımsız oyun parçaları birleştiğinde, sezgiden fazlasını gerektirir. Grundy sayıları ya da diğer adıyla nimber, her oyun durumunu tek bir sayıyla özetleyerek bu karmaşayı yönetilebilir bir matematik problemine dönüştürür.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://program.sonsuz.us/img/eslestirme-oyunlarinda-grundy-83.png" /><media:content medium="image" url="https://program.sonsuz.us/img/eslestirme-oyunlarinda-grundy-83.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>