diff --git a/docs/algorithms-i/img/binary_search.png b/docs/algorithms/img/binary_search.png similarity index 100% rename from docs/algorithms-i/img/binary_search.png rename to docs/algorithms/img/binary_search.png diff --git a/docs/algorithms-i/img/divide_and_conquer.png b/docs/algorithms/img/divide_and_conquer.png similarity index 100% rename from docs/algorithms-i/img/divide_and_conquer.png rename to docs/algorithms/img/divide_and_conquer.png diff --git a/docs/algorithms-i/img/inzva-logo.png b/docs/algorithms/img/inzva-logo.png similarity index 100% rename from docs/algorithms-i/img/inzva-logo.png rename to docs/algorithms/img/inzva-logo.png diff --git a/docs/algorithms-i/img/linear_search.png b/docs/algorithms/img/linear_search.png similarity index 100% rename from docs/algorithms-i/img/linear_search.png rename to docs/algorithms/img/linear_search.png diff --git a/docs/algorithms-i/img/ternary_search.png b/docs/algorithms/img/ternary_search.png similarity index 100% rename from docs/algorithms-i/img/ternary_search.png rename to docs/algorithms/img/ternary_search.png diff --git a/docs/algorithms-i/index.md b/docs/algorithms/index.md similarity index 98% rename from docs/algorithms-i/index.md rename to docs/algorithms/index.md index 9668542..81e3b9f 100644 --- a/docs/algorithms-i/index.md +++ b/docs/algorithms/index.md @@ -1,7 +1,17 @@ --- -title: Algorithms - I +title: Algorithms tags: - - "Algorithms" + - Algorithms + - Linear Search + - Binary Search + - Ternary Search + - Sorting Algorithms + - Insertion Sort + - Merge Sort + - Quick Sort + - Radix Sort + - Quickselect Algorithm + - Divide and Conquer --- **Editor:** Kadir Emre Oto diff --git a/docs/data-structures-i/index.md b/docs/data-structures-i/index.md deleted file mode 100644 index 6fa1b4c..0000000 --- a/docs/data-structures-i/index.md +++ /dev/null @@ -1,698 +0,0 @@ ---- -title: Data Structures - I -tags: - - "Data Structures" ---- - -**Editor:** Tahsin Enes Kuru - -**Reviewers:** Baha Eren Yaldız, Burak Buğrul - -**Contributors:** Kerim Kochekov - -## Giriş - -Bilgisayar biliminde veri yapıları, belirli bir eleman kümesi üzerinde verimli bir şeklide bilgi edinmemize aynı zamanda bu elemanlar üzerinde değişiklikler yapabilmemize olanak sağlayan yapılardır. Çalışma prensipleri genellikle elemanların değerlerini belirli bir kurala göre saklamak daha sonra bu yapıları kullanarak elemanlar hakkında sorulara (mesela, bir dizinin belirli bir aralığındaki en küçük sayıyı bulmak gibi) cevap aramaktır. - -## Dinamik Veri Yapıları - -### Linked List - -Linked List veri yapısında elemanlar, her eleman kendi değerini ve bir sonraki elemanın adresini tutacak şekilde saklanır. Yapıdaki elemanlar baş elemandan (head) başlanarak son elemana (tail) gidecek şekilde gezilebilir. Diziye karşın avantajı hafızanın dinamik bir şekilde kullanılmasıdır. Bu veri yapısında uygulanabilecek işlemler: - -- Veri yapısının sonuna eleman ekleme. -- Anlık veri yapısını baştan (head) sona (tail) gezme. - -
-![Örnek bir Linked List yapısı](img/linkedlist.png){ width="100%" } -
Örnek bir Linked List yapısı
-
- -```c++ -// Her bir elemani (burada sayilari, yani int) tutacak struct olusturuyoruz. -struct node { - int data; - node *next; -}; -node *head, *tail; - -void push_back(int x) { - // Yeni elemanimizi hafizada olusturuyoruz. - node *t = (node *)malloc(sizeof(node)); - t->data = x; // Elemanin verisini atiyoruz. - t->next = NULL; // Sona ekledigimizden sonraki elemanina NULL atiyoruz. - - // Eger veri yapimiza hic eleman eklenmediyse head - // ve tail elemanlarini olusturuyoruz. - if (head == NULL && tail == NULL) { - head = t; - tail = t; - } - // Eklenmisse yeni tail elemanimizi guncelliyoruz. - else { - tail->next = t; - tail = t; - } -} - -void print() { - // Dizideki tum elemanlari geziyoruz. - node *t = head; - while (t != NULL) { - printf("%d ", t->data); - t = t->next; - } -} -``` - -### Stack - -Stack veri yapısında elemanlar yapıya son giren ilk çıkar (LIFO) kuralına uygun olacak şekilde saklanır. Bu veri yapısında uygulayabildiğimiz işlemler: - -- Veri yapısının en üstüne eleman ekleme. -- Veri yapısının en üstündeki elemana erişim. -- Veri yapısının en üstündeki elemanı silme. -- Veri yapısının boş olup olmadığının kontrölü. - -C++ dilindeki STL kütüphanesinde bulunan hazır stack yapısının kullanımı aşağıdaki gibidir: - -```c++ -int main() { - stack st; - cout << st.empty() << endl; // Ilk bashta Stack bosh oldugu icin burada True donecektir. - st.push(5); // Stack'in en ustune 5'i ekler. Stack'in yeni hali: {5} - st.push(7); // Stack'in en ustune 7'yi ekler. Stack'in yeni hali: {7, 5} - st.push(6); // Stack'in en ustune 6'yi ekler. Stack'in yeni hali : {6, 7, 5} - st.pop(); // Stack'in en ustundeki elemani siler. Stack'in yeni hali : {7, 5} - st.push(1); // Stack'in en ustune 1'i ekler. Stack'in yeni hali : {1, 7, 5} - cout << st.top() << endl; // Stack'in en ustundeki elemana erisir. Ekrana 1 yazirir. - cout << st.empty() << endl; // Burada Stack bosh olmadigindan oturu False donecektir. -} -``` - -### Queue - -Queue veri yapısında elemanlar yapıya ilk giren ilk çıkar (FIFO) kuralına uygun olacak şekilde saklanır. Bu veri yapısında uygulayabildigimiz işlemler: - -- Veri yapısının en üstüne eleman ekleme. -- Veri yapısının en altındaki elemanına erişim. -- Veri yapısının en altındaki elemanı silme. -- Veri yapısının boş olup olmadığının kontrölü. - -C++ dilindeki STL kütüphanesinde bulunan hazır queue yapısının kullanımı aşağıdaki gibidir: - -```c++ -int main() { - queue q; - cout << q.empty() << endl; // Ilk bashta Queue bosh oldugu icin burada True donecektir. - q.push(5); // Queue'in en ustune 5'i ekler. Queue'in yeni hali: {5} - q.push(7); // Queue'in en ustune 7'yi ekler. Queue'in yeni hali: {7, 5} - q.push(6); // Queue'in en ustune 6'yi ekler. Queue'in yeni hali : {6, 7, 5} - q.pop(); // Queue'in en altindaki elemani siler. Queue'in yeni hali : {6, 7} - q.push(1); // Queue'in en ustune 1'i ekler. Queue'in yeni hali : {1, 6, 7} - cout << Q.front() << endl; // Queue'in en ustundeki elemana erisir. Ekrana 7 yazdirir. -} -``` - -### Deque - -Deque veri yapısı stack ve queue veri yapılarına göre daha kapsamlıdır. Bu veri yapısında yapının en üstüne eleman eklenebilirken aynı zamanda en altına da eklenebilir. Aynı şekilde yapının hem en üstündeki elemanına hem de en alttaki elemanına erişim ve silme işlemleri uygulanabilir. Bu veri yapısında uyguluyabildiğimiz işlemler: - -- Veri yapısının en üstüne eleman ekleme. -- Veri yapısının en altına eleman ekleme. -- Veri yapısının en üstündeki elemanına erişim. -- Veri yapısının en altındaki elemanına erişim. -- Veri yapısının en üstündeki elemanı silme. -- Veri yapısının en altındaki elemanı silme. - -C++ dilindeki STL kütüphanesinde bulunan hazır deque yapısının kullanımı aşağıdaki gibidir: - -```c++ -int main() { - deque q; - q.push_front(5); // deque'nin en altina 5'i ekler. - q.push_back(6); // deque'nin en ustune 6'yi ekler. - int x = q.front(); // deque'nin en altindaki elemanina erisim. - int y = q.back(); // deque'nin en ustundeki elemanina erisim. - q.pop_front(); // deque'nin en altindaki elemanini silme. - q.pop_back(); // deque'nin en ustundeki elemanini silme. -} -``` - -**P.S.** deque veri yapısı stack ve queue veri yapılarına göre daha kapsamlı olduğundan ötürü stack ve queue veri yapılarına göre 2 kat fazla memory kullandığını açıklıkla söyleyebiliriz. - -## Prefix Sum - -Prefix Sum dizisi bir dizinin prefixlerinin toplamlarıyla oluşturulan bir veri yapısıdır. Prefix sum dizisinin $i$ indeksli elemanı girdi dizisindeki $1$ indeksli elemandan $i$ indeksli elemana kadar olan elemanların toplamına eşit olacak şekilde kurulur. Başka bir deyişle: - -$$sum_i = \sum_{j=1}^{i} {a_j}$$ - -Örnek bir $A$ dizisi için prefix sum dizisi şu şekilde kurulmalıdır: - -
-| **A Dizisi** | $4$ | $6$ | $3$ | $12$ | $1$ | -|-------------------:|:---:|:-----:|:-------:|:----------:|:------------:| -| **Prefix Sum Dizisi** | $4$ | $10$ | $13$ | $25$ | $26$ | -| | $4$ | $4+6$ | $4+6+3$ | $4+6+3+12$ | $4+6+3+12+1$ | -
- -Prefix sum dizisini kullanarak herhangi bir $[l,r]$ aralığındaki elemanların toplamını şu şekilde kolaylıkla elde edebiliriz: - -$$sum_r = \sum_{j=1}^{r} {a_j}$$ - -$$sum_{l - 1} = \sum_{j=1}^{l - 1} {a_j}$$ - -$$sum_r - sum_{l-1} = \sum_{j=l}^{r} {a_j}$$ - -### Örnek Kod Parçaları - -Prefix Sum dizisini kurarken $sum_i = sum_{i - 1} + a_i$ eşitliği kolayca görülebilir ve bu eşitliği kullanarak $sum[]$ dizisini girdi dizisindeki elemanları sırayla gezerek kurabiliriz: - -```c++ -const int n; -int sum[n + 1], a[n + 1]; -// a dizisi girdi dizimiz, sum dizisi de prefix sum dizimiz olsun. - -void build() { - for (int i = 1; i <= n; i++) - sum[i] = sum[i - 1] + a[i]; - return; -} - -int query(int l, int r) { - return sum[r] - sum[l - 1]; -} -``` - -### Zaman Karmaşıklığı - -Prefix sum dizisini kurma işlemimizin zaman ve hafıza karmaşıklığı $\mathcal{O}(N)$. Her sorguya da $\mathcal{O}(1)$ karmaşıklıkta cevap verebiliyoruz. - -Prefix sum veri yapısı ile ilgili örnek bir probleme [buradan](https://codeforces.com/problemset/problem/816/B){target="_blank"} ulaşabilirsiniz. - -## Sparse Table - -Sparse table aralıklardaki elemanların toplamı, minimumu, maksimumu ve EBOB'ları gibi sorgulara $\mathcal{O}(\log N)$ zaman karmaşıklığında cevap alabilmemizi sağlayan bir veri yapısıdır. Bazı tip sorgular (aralıktaki minimum, maksimum sayıyı bulma gibi) ise $\mathcal{O}(1)$ zaman karmaşıklığında yapmaya uygundur. - -Bu veri yapısı durumu değişmeyen, sabit bir veri üzerinde ön işlemler yaparak kurulur. Dinamik veriler için kullanışlı değildir. Veri üzerinde herhangi bir değişiklik durumda Sparse table tekrardan kurulmalıdır. Bu da maliyetli bir durumdur. - -### Yapısı ve Kuruluşu - -Sparse table iki bouyutlu bir dizi şeklinde, $\mathcal{O}(N\log N)$ hafıza karmaşıklığına sahip bir veri yapısıdır. Dizinin her elemanından $2$'nin kuvvetleri uzaklıktaki elemanlara kadar olan cevaplar Sparse table'da saklanır. $ST_{x,i}$, $x$ indeksli elemandan $x + 2^i - 1$ indeksli elemana kadar olan aralığın cevabını saklayacak şekilde sparse table kurulur. - -```c++ -// Toplam sorgusu icin kurulmus Sparse Table Yapisi -const int n; -const int LOG = log2(n); -int a[n + 1], ST[2 * n][LOG + 1]; - -void build() { - for (int i = 1; i <= n; i++) { - // [i,i] araliginin cevabi dizinin i indeksli elemanina esittir. - ST[i][0] = a[i]; - } - - for (int i = 1; i <= LOG; i++) - for (int j = 1; j <= n; j++) { - // [i,i+2^(j)-1] araliginin cevabi - // [i,i+2^(j - 1) - 1] araligi ile [i+2^(j - 1),i+2^j-1] araliginin - // cevaplarinin birlesmesiyle elde edilir - ST[i][j] = ST[i][j - 1] + ST[i + (1 << (j - 1))][j - 1]; - } - - return; -} -``` - -### Sorgu Algoritması - -Herhangi bir $[l,r]$ aralığı için sorgu algoritması sırasıyla şu şekilde çalışır: - -- $[l,r]$ aralığını cevaplarını önceden hesapladığımız aralıklara parçala. - - Sadece $2$'nin kuvveti uzunluğunda parçaların cevaplarını sakladığımız için aralığımızı $2$'nin kuvveti uzunluğunda aralıklara ayırmalıyız. $[l,r]$ aralığının uzunluğunun ikilik tabanda yazdığımızda hangi aralıklara parçalamamız gerektiğini bulmuş oluruz. -- Bu aralıklardan gelen cevapları birleştirerek $[l,r]$ aralığının cevabını hesapla. - -Herhangi bir aralığın uzunluğunun ikilik tabandaki yazılışındaki $1$ rakamlarının sayısı en fazla $\log(N)$ olabileceğinden parçalayacağımız aralık sayısı da en fazla $\log(N)$ olur. Dolayısıyla sorgu işlemimiz $\mathcal{O}(\log N)$ zaman karmaşıklığında çalışır. - -Örneğin: $[4,17]$ aralığının cevabını hesaplamak için algoritmamız $[4,17]$ aralığını $[4,11]$, $[12,15]$ ve $[16,17]$ aralıklarına ayırır ve bu $3$ aralıktan gelen cevapları birleştirerek istenilen cevabı hesaplar. - -```c++ -// toplam sorgusu -int query(int l, int r) { - int res = 0; - - for (int i = LOG; i >= 0; i--) { - // her seferinde uzunlugu r - l + 1 gecmeyecek - // en buyuk araligin cevabi ekleyip l'i o araligin sonuna cekiyoruz. - if (l + (1 << i) <= r) { - res += ST[l][i]; - l += (1 << i); - } - } - - return res; -} -``` - -### Minimum ve Maksimum Sorgu - -Sparse Table veri yapısının diğer veri yapılarından farklı olarak $\mathcal{O}(1)$ zaman karmaşıklığında aralıklarda minimum veya maksimum sorgusu yapabilmesi en avantajlı özelliğidir. - -Herhangi bir aralığın cevabını hesaplarken bu aralıktaki herhangi bir elemanı birden fazla kez değerlendirmemiz cevabı etkilemez. Bu durum aralığımızı $2$'nin kuvveti uzunluğunda maksimum $2$ adet aralığa bölebilmemize ve bu aralıkların cevaplarını $\mathcal{O}(1)$ zaman karmaşıklığında birleştirebilmemize olanak sağlar. - -```c++ -int RMQ(int l, int r) { - // log[] dizisinde her sayinin onceden hesapadigimiz log2 degerleri saklidir. - int j = log[r - l + 1]; - return min(ST[l][j], ST[r - (1 << j) + 1][j]); -} -``` - -Sparse Table veri yapısı ile ilgili örnek bir probleme [buradan](https://www.spoj.com/problems/RMQSQ){target="_blank"} ulaşabilirsiniz. - -## Binary Indexed Tree - -Fenwick Tree olarak da bilinen Binary Indexed Tree, [Prefix Sum](#prefix-sum) ve [Sparse Table](#sparse-table) yapılarına benzer bir yapıda olup dizi üzerinde değişiklik yapabilmemize olanak sağlayan bir veri yapısıdır. Fenwick Tree'nin diğer veri yapılarına göre en büyük avantajı pratikte daha hızlı olması ve hafıza karmaşıklığının $\mathcal{O}(N)$ olmasıdır. Ancak Fenwick Tree'de sadece prefix cevapları (veya suffix cevapları) saklayabildiğimizden aralıklarda minimum, maksimum ve EBOB gibi bazı sorguların cevaplarını elde edemeyiz. - -### Yapısı ve Kuruluşu - -$g(x)$, $x$ sayısının bit gösteriminde yalnızca en sağdaki bitin 1 olduğu tam sayı olsun. Örneğin $20$'nin bit gösterimi $(10100)_2$ olduğundan $g(20)=4$'tür. Çünkü ilk kez sağdan $3.$ bit $1$'dir ve $(00100)_2=4$'tür. Fenwick Tree'nin $x$ indeksli düğümünde, $x - g(x) + 1$ indeksli elemandan $x$ indeksli elemana kadar olan aralığın cevabını saklayacak şekilde kurulur. - -
-![8 uzunluğundaki bir dizi için kurulmuş Fenwick Tree yapısı](img/fenwick.png){ width="80%" } -
$8$ uzunluğundaki bir dizi için kurulmuş Fenwick Tree yapısı
-
- - -### Sorgu Algoritması - -Herhangi bir $[1,x]$ aralığı için sorgu algoritması sırası ile şu şeklide çalışır: - -1. Aradığımız cevaba $[x - g(x) + 1,x]$ aralığının cevabını ekle. -2. $x$'in değerini $x - g(x)$ yap. Eğer $x$'in yeni değeri $0$'dan büyük ise $1.$ işlemden hesaplamaya devam et. - -$[1,x]$ aralığının cevabını hesaplamak için yapılan işlem sayısı $x$ sayısının $2$'lik tabandaki yazılışındaki $1$ sayısına eşittir. Çünkü her döngüde $x$'ten $2$'lik tabandaki yazılışındaki en sağdaki $1$ bitini çıkartıyoruz. Dolayısıyla sorgu işlemimiz $\mathcal{O}(\log N)$ zaman karmaşıklığında çalışır. $[l,r]$ aralığının cevabını da $[1,r]$ aralığının cevabından $[1,l - 1]$ aralığının cevabını çıkararak kolay bir şekilde elde edebiliriz. - -> NOT: $g(x)$ değerini bitwise operatörlerini kullanarak aşağıdaki eşitlikle kolay bir şekilde hesaplayabiliriz: -> \\[g(x) = x \ \& \ (-x)\\] - -### Eleman Güncelleme Algoritması - -Dizideki $x$ indeksli elemanının değerini güncellemek için kullanılan algoritma şu şeklide çalışır: - -- Ağaçta $x$ indeksli elemanı içeren tüm düğümlerin değerlerini güncelle. - -Fenwick Tree'de $x$ indeksli elemanı içeren maksimum $\log(N)$ tane aralık olduğundan güncelleme algoritması $\mathcal{O}(\log N)$ zaman karmaşıklığında çalışır. - -### Örnek Kod Parçaları - -```c++ -const int n; -int tree[n + 1], a[n + 1]; - -void add(int val, int x) { // x indeksli elemanin degerini val degeri kadar artirir. - // x indeksinin etkiledigi butun dugumleri val degeri kadar artirir. - while (x <= n) { - tree[x] += val; - x += x & (-x); - } -} - -int sum(int x) { // 1 indeksli elemandan x indeksli elemana - int res = 0; // kadar olan sayilarin toplamini verir. - while (x >= 1) { - res += tree[x]; - x -= x & (-x); - } - return res; -} - -int query(int l, int r) { // [l,r] araligindaki elemanlarin toplamini verir. - return sum(r) - sum(l - 1); -} - -void build() { // a dizisi uzerine fenwick tree yapisini kuruyoruz. - for (int i = 1; i <= n; i++) - add(a[i], i); -} -``` - -Fenwick Tree veri yapısı ile ilgili örnek bir probleme [buradan](https://www.spoj.com/problems/CSUMQ) ulaşabilirsiniz. - -### Aralık Güncelleme ve Eleman Sorgu - -Bir $a$ dizisi üzerinde işlemler yapacağımızı varsayalım daha sonra $a$ dizisi $b$ dizisinin prefix sum dizisi olacak şekilde bir $b$ dizisi tanımlayalım. Başka bir deyişle $a_i = \displaystyle\sum_{j=1}^{i} {b_j} $ olmalıdır. Sonradan oluşturduğumuz $b$ dizisi üzerine Fenwick Tree yapısını kuralım. $[l,r]$ aralığındaki her elemana -$x$ değerini eklememiz için uygulamamız gereken işlemler: - -- $b_l$ değerini $x$ kadar artır. Böylelikle $l$ indeksli elemandan dizinin sonuna kadar tüm elemanların değeri $x$ kadar artmış olur. -- $b_{r + 1}$ değerini $x$ kadar azalt. Böylelikle $r + 1$ indeksli elemandan dizinin sonuna kadar tüm elemanların değeri $x$ kadar azalmış olur. Bu işlemelerin sonucunda sadece $[l,r]$ aralığındaki elemanların değeri $x$ kadar artmış olur. - -#### Örnek Kod Parçaları - -```c++ -const int n; -int a[n + 1], b[n + 1]; - -void add(int val, int x) { // x indeksli elemanin degerini val degeri kadar artirir. - while (x <= n) { - tree[x] += val; - x += x & (-x); - } -} - -int sum(int x) { // 1 indeksli elemandan x indeksli elemana - int res = 0; // kadar olan sayilarin toplamini verir. - while (x >= 1) { - res += tree[x]; - x -= x & (-x); - } - return res; -} -void build() { - for (int i = 1; i <= n; i++) - b[i] = a[i] - a[i - 1]; // b dizisini olusturuyoruz. - - for (int i = 1; i <= n; i++) - add(b[i], i); // b dizisi uzerine fenwick tree kuruyoruz. -} - -void update(int l, int r, int x) { - add(x, l); - add(-x, r + 1); -} - -void query(int x) { return sum(x); } -``` - -## SQRT Decomposition - -Square Root Decomposition algoritması dizi üzerinde $\mathcal{O}(\sqrt{N})$ zaman karmaşıklığında sorgu yapabilmemize ve $\mathcal{O}(1)$ zaman karmaşıklığında ise değişiklik yapabilmemize olanak sağlayan bir veri yapsıdır. - -### Yapısı ve Kuruluşu - -Dizinin elemanları her biri yaklaşık $\mathcal{O}(\sqrt{N})$ uzunluğunda bloklar halinde parçalanır. Her bir blokun cevabı ayrı ayrı hesaplanır ve bir dizide saklanır. - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Blokların Cevapları$21$$13$$50$$32$
Dizideki Elemanlar$3$$6$$2$$10$$3$$1$$4$$5$$2$$7$$37$$4$$11$$6$$8$$7$
Elemanların İndeksleri$1$$2$$3$$4$$5$$6$$7$$8$$9$$10$$11$$12$$13$$14$$15$$16$
- - *Örnek bir dizi üzerinde toplam sorgusu için kurulmuş SQRT Decompostion veri yapısı.* -
- -```c++ -void build() { - for (int i = 1; i <= n; i++) { - if (i % sq == 1) { // sq = sqrt(n) - t++; // yeni blok baslangici. - st[t] = i; // t.blok i indisli elemanda baslar. - } - fn[t] = i; // t.blokun bitisini i indisli eleman olarak guncelliyoruz. - wh[i] = t; // i indeksli eleman t.blogun icinde. - sum[t] += a[i]; // t. blokun cevabina i indeksli elemani ekliyoruz. - } -} -``` - -### Sorgu Algoritması - -Herhangi bir $[l,r]$ aralığı için sorgu algoritması sırası ile şu şekilde çalışır: - -1. Cevabını aradığımız aralığın tamamen kapladığı blokların cevabını cevabımıza ekliyoruz. -2. Tamamen kaplamadığı bloklardaki aralığımızın içinde olan elemanları tek tek gezerek cevabımıza ekliyoruz. - -Cevabını aradığımız aralığın kapsadığı blok sayısı en fazla $\sqrt{N}$ olabileceğinden $1.$ işlem en fazla $\sqrt{N}$ kez çalışır. Tamamen kaplamadığı ancak bazı elemanları içeren en fazla $2$ adet blok olabilir. (Biri en solda diğeri en sağda olacak şekilde.) Bu $2$ blok için de gezmemiz gereken eleman sayısı maksimum $2\sqrt{N}$ olduğundan bir sorgu işleminde en fazla $3\sqrt{N}$ işlem yapılır, dolayısıyla sorgu işlemimiz $\mathcal{O}(\sqrt{N})$ zaman karmaşıklığında calışır. - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Blokların Cevapları$21$$13$$50$$32$
Dizideki Elemanlar$3$$6$$2$$10$$3$$1$$4$$5$$2$$7$$37$$4$$11$$6$$8$$7$
Elemanların İndeksleri$1$$2$$3$$4$$5$$6$$7$$8$$9$$10$$11$$12$$13$$14$$15$$16$
- - *Örnek dizideki $[3,13]$ aralığının cevabını $2.$ ve $3.$ blokların cevapları ile $3,4$ ve $11$ indeksli elemanların toplanmasıyla elde edilir.* -
- -```c++ -// [l,r] araligindaki elemanlarin toplamini hesaplayan fonksiyon. -int query(int l, int r) { - int res = 0; - - if (wh[l] == wh[r]) { // l ve r ayni blogun icindeyse - for (int i = l; i <= r; i++) - res += a[i]; - } else { - for (int i = wh[l] + 1; i <= wh[r] - 1; i++) - res += sum[i]; // tamamen kapladigimiz bloklarin cevaplarini ekliyoruz. - - // tamamen kaplamadigimiz bloklardaki araligimiz icindeki - // elemanlarin cevaplarini ekliyoruz. - - for (int i = st[wh[l]]; i <= fn[wh[l]]; i++) - if (i >= l && i <= r) - res += a[i]; - - for (int i = st[wh[r]]; i <= fn[wh[r]]; i++) - if (i >= l && i <= r) - res += a[i]; - } - - return res; -} -``` - -### Eleman Güncelleme Algoritması - -Herhangi bir elemanın değerini güncellerken o elemanı içeren blokun değerini güncellememiz yeterli olacaktır. Dolayısıyla güncelleme işlemimimiz $\mathcal{O}(1)$ zaman karmaşıklığında çalışır. - -```c++ -void update(int x, int val) { - // x indeksli elemanin yeni degerini val degerine esitliyoruz. - sum[wh[x]] -= a[x]; - a[x] = val; - sum[wh[x]] += a[x]; -} -``` - -SQRT Decomposition veri yapısı ile ilgili örnek bir probleme [buradan](https://codeforces.com/contest/13/problem/E){target="_blank"} ulaşabilirsiniz. - -## Segment Tree - -Segment Tree bir dizide $\mathcal{O}(\log N)$ zaman karmaşıklığında herhangi bir $[l,r]$ aralığı icin minimum, maksimum, toplam gibi sorgulara cevap verebilmemize ve bu aralıklar üzerinde güncelleme yapabilmemize olanak sağlayan bir veri yapısıdır. - -Segment Tree, [Fenwick Tree](#binary-indexed-tree) ve [Sparse Table](#sparse-table) yapılarından farklı olarak elemanlar üzerinde güncelleme yapılabilmesi ve minimum, maksimum gibi sorgulara da olanak sağlaması yönünden daha kullanışlıdır. Ayrıca Segment Tree $\mathcal{O}(N)$ hafıza karmaşıklığına sahipken Sparse Table yapısında gereken hafıza karmaşıklığı $\mathcal{O}(N \log N)$'dir. - -### Yapısı ve Kuruluşu -Segment Tree, "Complete Binary Tree" yapısına sahiptir. Segment Tree'nin yaprak düğümlerinde dizinin elemanları saklıdır ve bu düğümlerin atası olan her düğüm kendi çocuğu olan düğümlerinin cevaplarının birleşmesiyle oluşur. Bu sayede her düğümde belirli aralıkların cevapları ve root düğümünde tüm dizinin cevabı saklanır. Örneğin toplam sorgusu için kurulmuş bir Segment Tree yapısı için her düğümün değeri çocuklarının değerleri toplamına eşittir. - -
-![a = [41,67,6,30,85,43,39] dizisinde toplam sorgusu icin oluşturulmuş Segment Tree yapısı](img/segtree.png){ width="100%" } -
$a = [41,67,6,30,85,43,39]$ dizisinde toplam sorgusu icin oluşturulmuş Segment Tree yapısı
-
- -```c++ -void build(int ind, int l, int r) { - // tree[ind] dizinin [l,r] araliginin cevabini saklar. - if (l == r) { // yaprak dugum'e ulasti - tree[ind] = a[l]; // bu dugum dizinin l. elamaninin cevabini saklar - } else { - int mid = (l + r) / 2; - build(ind * 2, l, mid); - build(ind * 2 + 1, mid + 1, r); - // [l,r] araliginin cevabini - // [l,mid] ve [mid + 1,r] araliklarinin cevaplarinin birlesmesiyle olusur. - tree[ind] = tree[ind * 2] + tree[ind * 2 + 1]; - } -} -``` - -### Aralık Sorgu ve Eleman Güncelleme - -#### Sorgu Algoritması - -Herhangi bir $[l,r]$ aralığı için sorgu algoritması sırası ile şu şekilde çalışır: -- $[l,r]$ aralığını ağacımızda cevapları saklı olan en geniş aralıklara parçala. -- Bu aralıkların cevaplarını birleştirerek istenilen cevabı hesapla. - -Ağacın her derinliğinde cevabımız için gerekli aralıklardan maksimum $2$ adet bulunabilir. Bu yüzden sorgu algoritması $\mathcal{O}(\log N)$ zaman karmaşıklığında çalışır. - -
-![a = [41,67,6,30,85,43,39] dizisinde $[2,6]$ aralığında sorgu işlemi](img/segtreequery.png){ width="100%" } -
$a = [41,67,6,30,85,43,39]$ dizisinde $[2,6]$ aralığında sorgu işlemi
-
- -$a = [41,67,6,30,85,43,39]$ dizisinde $[2,6]$ aralığının cevabı $[2,3]$ ile $[4,6]$ aralıklarının cevaplarının birleşmesiyle elde edilir. Toplam sorgusu için cevap $36+167=203$ şeklinde hesaplanır. - -```c++ -// [lw,rw] sorguda cevabini aradigimiz aralik. -// [l,r] ise agactaki ind nolu node'da cevabini sakladigimiz aralik. -int query(int ind, int l, int r, int lw, int rw) { - if (l > rw or r < lw) // bulundugumuz aralik cevabini aradigimiz araligin disinda. - return 0; - if (l >= lw and r <= rw) // cevabini aradigimiz aralik bu araligi tamamen kapsiyor. - return tree[ind]; - - int mid = (l + r) / 2; - - // Agacta recursive bir sekilde araligimizi araliklara bolup gelen cevaplari birlestiyoruz. - return query(ind * 2, l, mid, lw, rw) + query(ind * 2 + 1, mid + 1, r, lw, rw); -} -``` - -#### Eleman Güncelleme Algoritması - -Dizideki $x$ indeksli elemanının değerini güncellemek için kullanılan algoritma şu şeklide çalışır: - -- Ağaçta $x$ indeksli elemanı içeren tüm düğümlerin değerlerini güncelle. - -Ağaçta $x$ indeksli elemanın cevabını tutan yaprak düğümden root düğüme kadar toplamda $\log(N)$ düğümün değerini güncellememiz yeterlidir. Dolayısıyla herhangi bir elemanın değerini güncellemenin zaman karmaşıklığı $\mathcal{O}(\log N)$'dir. - -
-![a = [41,67,6,30,85,43,39] dizisinde 5 indeksli elemanın cevabını güncellerken güncellememiz gereken düğümler şekildeki gibidir.](img/segtreeupdate.png){ width="100%" } -
$a = [41,67,6,30,85,43,39]$ dizisinde 5 indeksli elemanın cevabını güncellerken güncellememiz gereken düğümler sekildeki gibidir.
-
- -```c++ -void update(int ind, int l, int r, int x, int val) { - if (l > x || r < x) // bulundugumuz aralik x indeksli elemani icermiyor. - return; - if (l == x and r == x) { - tree[ind] = val; // x indeksli elemani iceren yaprak dugumunun cevabini guncelliyoruz. - return; - } - - int mid = (l + r) / 2; - - // recursive bir sekilde x indeksli elemani iceren - // tum araliklarin cevaplarini guncelliyoruz. - update(ind * 2, l, mid, x, val); - update(ind * 2 + 1, mid + 1, r, x, val); - tree[ind] = tree[ind * 2] + tree[ind * 2 + 1]; -} -``` - -Segment Tree veri yapısı ile ilgili örnek bir probleme [buradan](https://codeforces.com/gym/100739/problem/A){target="_blank"} ulaşabilirsiniz. - -## Örnek Problemler - -Veri yapıları üzerinde pratik yapabilmeniz için önerilen problemler: - -1. [Link](https://codeforces.com/problemset/problem/797/C){target="_blank"} -2. [Link](https://codeforces.com/contest/276/problem/C){target="_blank"} -3. [Link](https://codeforces.com/contest/380/problem/C){target="_blank"} -4. [Link](https://www.hackerearth.com/problem/algorithm/benny-and-sum-2){target="_blank"} -5. [Link](https://www.hackerearth.com/practice/data-structures/advanced-data-structures/fenwick-binary-indexed-trees/practice-problems/algorithm/counting-in-byteland){target="_blank"} - -## Faydalı Bağlantılar - -1. {target="_blank"} -2. {target="_blank"} -3. {target="_blank"} -4. {target="_blank"} -5. {target="_blank"} -6. {target="_blank"} -7. {target="_blank"} -8. {target="_blank"} -9. {target="_blank"} -10. {target="_blank"} -11. {target="_blank"} -12. {target="_blank"} -13. {target="_blank"} -14. {target="_blank"} -15. {target="_blank"} -16. {target="_blank"} -17. {target="_blank"} \ No newline at end of file diff --git a/docs/data-structures/deque.md b/docs/data-structures/deque.md new file mode 100644 index 0000000..dc29c40 --- /dev/null +++ b/docs/data-structures/deque.md @@ -0,0 +1,31 @@ +--- +title: Deque +tags: + - Data Structures + - Deque +--- + +Deque veri yapısı stack ve queue veri yapılarına göre daha kapsamlıdır. Bu veri yapısında yapının en üstüne eleman eklenebilirken aynı zamanda en altına da eklenebilir. Aynı şekilde yapının hem en üstündeki elemanına hem de en alttaki elemanına erişim ve silme işlemleri uygulanabilir. Bu veri yapısında uyguluyabildiğimiz işlemler: + +- Veri yapısının en üstüne eleman ekleme. +- Veri yapısının en altına eleman ekleme. +- Veri yapısının en üstündeki elemanına erişim. +- Veri yapısının en altındaki elemanına erişim. +- Veri yapısının en üstündeki elemanı silme. +- Veri yapısının en altındaki elemanı silme. + +C++ dilindeki STL kütüphanesinde bulunan hazır deque yapısının kullanımı aşağıdaki gibidir: + +```c++ +int main() { + deque q; + q.push_front(5); // deque'nin en altina 5'i ekler. + q.push_back(6); // deque'nin en ustune 6'yi ekler. + int x = q.front(); // deque'nin en altindaki elemanina erisim. + int y = q.back(); // deque'nin en ustundeki elemanina erisim. + q.pop_front(); // deque'nin en altindaki elemanini silme. + q.pop_back(); // deque'nin en ustundeki elemanini silme. +} +``` + +**P.S.** deque veri yapısı stack ve queue veri yapılarına göre daha kapsamlı olduğundan ötürü stack ve queue veri yapılarına göre 2 kat fazla memory kullandığını açıklıkla söyleyebiliriz. diff --git a/docs/data-structures/fenwick-tree.md b/docs/data-structures/fenwick-tree.md new file mode 100644 index 0000000..92f9dfc --- /dev/null +++ b/docs/data-structures/fenwick-tree.md @@ -0,0 +1,120 @@ +--- +title: Fenwick Tree +tags: + - Data Structures + - Fenwick Tree + - Binary Indexed Tree + - BIT +--- + +Binary Indexed Tree olarak da bilinen Fenwick Tree, [Prefix Sum](prefix-sum.md) ve [Sparse Table](sparse-table.md) yapılarına benzer bir yapıda olup dizi üzerinde değişiklik yapabilmemize olanak sağlayan bir veri yapısıdır. Fenwick Tree'nin diğer veri yapılarına göre en büyük avantajı pratikte daha hızlı olması ve hafıza karmaşıklığının $\mathcal{O}(N)$ olmasıdır. Ancak Fenwick Tree'de sadece prefix cevapları (veya suffix cevapları) saklayabildiğimizden aralıklarda minimum, maksimum ve EBOB gibi bazı sorguların cevaplarını elde edemeyiz. + +## Yapısı ve Kuruluşu + +$g(x)$, $x$ sayısının bit gösteriminde yalnızca en sağdaki bitin 1 olduğu tam sayı olsun. Örneğin $20$'nin bit gösterimi $(10100)_2$ olduğundan $g(20)=4$'tür. Çünkü ilk kez sağdan $3.$ bit $1$'dir ve $(00100)_2=4$'tür. Fenwick Tree'nin $x$ indeksli düğümünde, $x - g(x) + 1$ indeksli elemandan $x$ indeksli elemana kadar olan aralığın cevabını saklayacak şekilde kurulur. + +
+![8 uzunluğundaki bir dizi için kurulmuş Fenwick Tree yapısı](img/fenwick.png){ width="80%" } +
$8$ uzunluğundaki bir dizi için kurulmuş Fenwick Tree yapısı
+
+ + +## Sorgu Algoritması + +Herhangi bir $[1,x]$ aralığı için sorgu algoritması sırası ile şu şeklide çalışır: + +1. Aradığımız cevaba $[x - g(x) + 1,x]$ aralığının cevabını ekle. +2. $x$'in değerini $x - g(x)$ yap. Eğer $x$'in yeni değeri $0$'dan büyük ise $1.$ işlemden hesaplamaya devam et. + +$[1,x]$ aralığının cevabını hesaplamak için yapılan işlem sayısı $x$ sayısının $2$'lik tabandaki yazılışındaki $1$ sayısına eşittir. Çünkü her döngüde $x$'ten $2$'lik tabandaki yazılışındaki en sağdaki $1$ bitini çıkartıyoruz. Dolayısıyla sorgu işlemimiz $\mathcal{O}(\log N)$ zaman karmaşıklığında çalışır. $[l,r]$ aralığının cevabını da $[1,r]$ aralığının cevabından $[1,l - 1]$ aralığının cevabını çıkararak kolay bir şekilde elde edebiliriz. + +> NOT: $g(x)$ değerini bitwise operatörlerini kullanarak aşağıdaki eşitlikle kolay bir şekilde hesaplayabiliriz: +> \\[g(x) = x \ \& \ (-x)\\] + +## Eleman Güncelleme Algoritması + +Dizideki $x$ indeksli elemanının değerini güncellemek için kullanılan algoritma şu şeklide çalışır: + +- Ağaçta $x$ indeksli elemanı içeren tüm düğümlerin değerlerini güncelle. + +Fenwick Tree'de $x$ indeksli elemanı içeren maksimum $\log(N)$ tane aralık olduğundan güncelleme algoritması $\mathcal{O}(\log N)$ zaman karmaşıklığında çalışır. + +## Örnek Kod Parçaları + +```c++ +const int n; +int tree[n + 1], a[n + 1]; + +void add(int val, int x) { // x indeksli elemanin degerini val degeri kadar artirir. + // x indeksinin etkiledigi butun dugumleri val degeri kadar artirir. + while (x <= n) { + tree[x] += val; + x += x & (-x); + } +} + +int sum(int x) { // 1 indeksli elemandan x indeksli elemana + int res = 0; // kadar olan sayilarin toplamini verir. + while (x >= 1) { + res += tree[x]; + x -= x & (-x); + } + return res; +} + +int query(int l, int r) { // [l,r] araligindaki elemanlarin toplamini verir. + return sum(r) - sum(l - 1); +} + +void build() { // a dizisi uzerine fenwick tree yapisini kuruyoruz. + for (int i = 1; i <= n; i++) + add(a[i], i); +} +``` + +Fenwick Tree veri yapısı ile ilgili örnek bir probleme [buradan](https://www.spoj.com/problems/CSUMQ) ulaşabilirsiniz. + +## Aralık Güncelleme ve Eleman Sorgu + +Bir $a$ dizisi üzerinde işlemler yapacağımızı varsayalım daha sonra $a$ dizisi $b$ dizisinin prefix sum dizisi olacak şekilde bir $b$ dizisi tanımlayalım. Başka bir deyişle $a_i = \displaystyle\sum_{j=1}^{i} {b_j} $ olmalıdır. Sonradan oluşturduğumuz $b$ dizisi üzerine Fenwick Tree yapısını kuralım. $[l,r]$ aralığındaki her elemana +$x$ değerini eklememiz için uygulamamız gereken işlemler: + +- $b_l$ değerini $x$ kadar artır. Böylelikle $l$ indeksli elemandan dizinin sonuna kadar tüm elemanların değeri $x$ kadar artmış olur. +- $b_{r + 1}$ değerini $x$ kadar azalt. Böylelikle $r + 1$ indeksli elemandan dizinin sonuna kadar tüm elemanların değeri $x$ kadar azalmış olur. Bu işlemelerin sonucunda sadece $[l,r]$ aralığındaki elemanların değeri $x$ kadar artmış olur. + +### Örnek Kod Parçaları + +```c++ +const int n; +int a[n + 1], b[n + 1]; + +void add(int val, int x) { // x indeksli elemanin degerini val degeri kadar artirir. + while (x <= n) { + tree[x] += val; + x += x & (-x); + } +} + +int sum(int x) { // 1 indeksli elemandan x indeksli elemana + int res = 0; // kadar olan sayilarin toplamini verir. + while (x >= 1) { + res += tree[x]; + x -= x & (-x); + } + return res; +} +void build() { + for (int i = 1; i <= n; i++) + b[i] = a[i] - a[i - 1]; // b dizisini olusturuyoruz. + + for (int i = 1; i <= n; i++) + add(b[i], i); // b dizisi uzerine fenwick tree kuruyoruz. +} + +void update(int l, int r, int x) { + add(x, l); + add(-x, r + 1); +} + +void query(int x) { return sum(x); } +``` diff --git a/docs/data-structures-i/img/fenwick.png b/docs/data-structures/img/fenwick.png similarity index 100% rename from docs/data-structures-i/img/fenwick.png rename to docs/data-structures/img/fenwick.png diff --git a/docs/data-structures-i/img/linkedlist.png b/docs/data-structures/img/linkedlist.png similarity index 100% rename from docs/data-structures-i/img/linkedlist.png rename to docs/data-structures/img/linkedlist.png diff --git a/docs/data-structures-i/img/segtree.png b/docs/data-structures/img/segtree.png similarity index 100% rename from docs/data-structures-i/img/segtree.png rename to docs/data-structures/img/segtree.png diff --git a/docs/data-structures-i/img/segtreequery.png b/docs/data-structures/img/segtreequery.png similarity index 100% rename from docs/data-structures-i/img/segtreequery.png rename to docs/data-structures/img/segtreequery.png diff --git a/docs/data-structures-i/img/segtreeupdate.png b/docs/data-structures/img/segtreeupdate.png similarity index 100% rename from docs/data-structures-i/img/segtreeupdate.png rename to docs/data-structures/img/segtreeupdate.png diff --git a/docs/data-structures/index.md b/docs/data-structures/index.md new file mode 100644 index 0000000..75c5bdb --- /dev/null +++ b/docs/data-structures/index.md @@ -0,0 +1,60 @@ +--- +title: Data Structures +tags: + - Data Structures +--- + +**Editor:** Tahsin Enes Kuru + +**Reviewers:** Baha Eren Yaldız, Burak Buğrul + +**Contributors:** Kerim Kochekov + +## Giriş + +Bilgisayar biliminde veri yapıları, belirli bir eleman kümesi üzerinde verimli bir şeklide bilgi edinmemize aynı zamanda bu elemanlar üzerinde değişiklikler yapabilmemize olanak sağlayan yapılardır. Çalışma prensipleri genellikle elemanların değerlerini belirli bir kurala göre saklamak daha sonra bu yapıları kullanarak elemanlar hakkında sorulara (mesela, bir dizinin belirli bir aralığındaki en küçük sayıyı bulmak gibi) cevap aramaktır. + +## Dinamik Veri Yapıları + +### [Linked List](linked-list.md) +### [Stack](stack.md) +### [Queue](queue.md) +### [Deque](deque.md) +### [Fenwick Tree](fenwick-tree.md) +### [Segment Tree](segment-tree.md) + +## Statik Veri Yapıları + +### [Prefix Sum](prefix-sum.md) +### [Sparse Table](sparse-table.md) +### [SQRT Decomposition](sqrt-decomposition.md) + +## Örnek Problemler + +Veri yapıları üzerinde pratik yapabilmeniz için önerilen problemler: + +1. [Link](https://codeforces.com/problemset/problem/797/C){target="_blank"} +2. [Link](https://codeforces.com/contest/276/problem/C){target="_blank"} +3. [Link](https://codeforces.com/contest/380/problem/C){target="_blank"} +4. [Link](https://www.hackerearth.com/problem/algorithm/benny-and-sum-2){target="_blank"} +5. [Link](https://www.hackerearth.com/practice/data-structures/advanced-data-structures/fenwick-binary-indexed-trees/practice-problems/algorithm/counting-in-byteland){target="_blank"} + +## Faydalı Bağlantılar + +1. {target="_blank"} +2. {target="_blank"} +3. {target="_blank"} +4. {target="_blank"} +5. {target="_blank"} +6. {target="_blank"} +7. {target="_blank"} +8. {target="_blank"} +9. {target="_blank"} +10. {target="_blank"} +11. {target="_blank"} +12. {target="_blank"} +13. {target="_blank"} +14. {target="_blank"} +15. {target="_blank"} +16. {target="_blank"} +17. {target="_blank"} diff --git a/docs/data-structures/linked-list.md b/docs/data-structures/linked-list.md new file mode 100644 index 0000000..2f9a203 --- /dev/null +++ b/docs/data-structures/linked-list.md @@ -0,0 +1,53 @@ +--- +title: Linked List +tags: + - Data Structures + - Linked List +--- + +Linked List veri yapısında elemanlar, her eleman kendi değerini ve bir sonraki elemanın adresini tutacak şekilde saklanır. Yapıdaki elemanlar baş elemandan (head) başlanarak son elemana (tail) gidecek şekilde gezilebilir. Diziye karşın avantajı hafızanın dinamik bir şekilde kullanılmasıdır. Bu veri yapısında uygulanabilecek işlemler: + +- Veri yapısının sonuna eleman ekleme. +- Anlık veri yapısını baştan (head) sona (tail) gezme. + +
+![Örnek bir Linked List yapısı](img/linkedlist.png){ width="100%" } +
Örnek bir Linked List yapısı
+
+ +```c++ +// Her bir elemani (burada sayilari, yani int) tutacak struct olusturuyoruz. +struct node { + int data; + node *next; +}; +node *head, *tail; + +void push_back(int x) { + // Yeni elemanimizi hafizada olusturuyoruz. + node *t = (node *)malloc(sizeof(node)); + t->data = x; // Elemanin verisini atiyoruz. + t->next = NULL; // Sona ekledigimizden sonraki elemanina NULL atiyoruz. + + // Eger veri yapimiza hic eleman eklenmediyse head + // ve tail elemanlarini olusturuyoruz. + if (head == NULL && tail == NULL) { + head = t; + tail = t; + } + // Eklenmisse yeni tail elemanimizi guncelliyoruz. + else { + tail->next = t; + tail = t; + } +} + +void print() { + // Dizideki tum elemanlari geziyoruz. + node *t = head; + while (t != NULL) { + printf("%d ", t->data); + t = t->next; + } +} +``` diff --git a/docs/data-structures/prefix-sum.md b/docs/data-structures/prefix-sum.md new file mode 100644 index 0000000..1adc190 --- /dev/null +++ b/docs/data-structures/prefix-sum.md @@ -0,0 +1,53 @@ +--- +title: Prefix Sum +tags: + - Data Structures + - Prefix Sum +--- + +Prefix Sum dizisi bir dizinin prefixlerinin toplamlarıyla oluşturulan bir veri yapısıdır. Prefix sum dizisinin $i$ indeksli elemanı girdi dizisindeki $1$ indeksli elemandan $i$ indeksli elemana kadar olan elemanların toplamına eşit olacak şekilde kurulur. Başka bir deyişle: + +$$sum_i = \sum_{j=1}^{i} {a_j}$$ + +Örnek bir $A$ dizisi için prefix sum dizisi şu şekilde kurulmalıdır: + +
+| **A Dizisi** | $4$ | $6$ | $3$ | $12$ | $1$ | +|-------------------:|:---:|:-----:|:-------:|:----------:|:------------:| +| **Prefix Sum Dizisi** | $4$ | $10$ | $13$ | $25$ | $26$ | +| | $4$ | $4+6$ | $4+6+3$ | $4+6+3+12$ | $4+6+3+12+1$ | +
+ +Prefix sum dizisini kullanarak herhangi bir $[l,r]$ aralığındaki elemanların toplamını şu şekilde kolaylıkla elde edebiliriz: + +$$sum_r = \sum_{j=1}^{r} {a_j}$$ + +$$sum_{l - 1} = \sum_{j=1}^{l - 1} {a_j}$$ + +$$sum_r - sum_{l-1} = \sum_{j=l}^{r} {a_j}$$ + +## Örnek Kod Parçaları + +Prefix Sum dizisini kurarken $sum_i = sum_{i - 1} + a_i$ eşitliği kolayca görülebilir ve bu eşitliği kullanarak $sum[]$ dizisini girdi dizisindeki elemanları sırayla gezerek kurabiliriz: + +```c++ +const int n; +int sum[n + 1], a[n + 1]; +// a dizisi girdi dizimiz, sum dizisi de prefix sum dizimiz olsun. + +void build() { + for (int i = 1; i <= n; i++) + sum[i] = sum[i - 1] + a[i]; + return; +} + +int query(int l, int r) { + return sum[r] - sum[l - 1]; +} +``` + +## Zaman Karmaşıklığı + +Prefix sum dizisini kurma işlemimizin zaman ve hafıza karmaşıklığı $\mathcal{O}(N)$. Her sorguya da $\mathcal{O}(1)$ karmaşıklıkta cevap verebiliyoruz. + +Prefix sum veri yapısı ile ilgili örnek bir probleme [buradan](https://codeforces.com/problemset/problem/816/B){target="_blank"} ulaşabilirsiniz. diff --git a/docs/data-structures/queue.md b/docs/data-structures/queue.md new file mode 100644 index 0000000..c772608 --- /dev/null +++ b/docs/data-structures/queue.md @@ -0,0 +1,28 @@ +--- +title: Queue +tags: + - Data Structures + - Queue +--- + +Queue veri yapısında elemanlar yapıya ilk giren ilk çıkar (FIFO) kuralına uygun olacak şekilde saklanır. Bu veri yapısında uygulayabildigimiz işlemler: + +- Veri yapısının en üstüne eleman ekleme. +- Veri yapısının en altındaki elemanına erişim. +- Veri yapısının en altındaki elemanı silme. +- Veri yapısının boş olup olmadığının kontrölü. + +C++ dilindeki STL kütüphanesinde bulunan hazır queue yapısının kullanımı aşağıdaki gibidir: + +```c++ +int main() { + queue q; + cout << q.empty() << endl; // Ilk bashta Queue bosh oldugu icin burada True donecektir. + q.push(5); // Queue'in en ustune 5'i ekler. Queue'in yeni hali: {5} + q.push(7); // Queue'in en ustune 7'yi ekler. Queue'in yeni hali: {7, 5} + q.push(6); // Queue'in en ustune 6'yi ekler. Queue'in yeni hali : {6, 7, 5} + q.pop(); // Queue'in en altindaki elemani siler. Queue'in yeni hali : {6, 7} + q.push(1); // Queue'in en ustune 1'i ekler. Queue'in yeni hali : {1, 6, 7} + cout << Q.front() << endl; // Queue'in en ustundeki elemana erisir. Ekrana 7 yazdirir. +} +``` \ No newline at end of file diff --git a/docs/data-structures/segment-tree.md b/docs/data-structures/segment-tree.md new file mode 100644 index 0000000..b202dba --- /dev/null +++ b/docs/data-structures/segment-tree.md @@ -0,0 +1,101 @@ +--- +title: Segment Tree +tags: + - Data Structures + - Segment Tree +--- + +Segment Tree bir dizide $\mathcal{O}(\log N)$ zaman karmaşıklığında herhangi bir $[l,r]$ aralığı icin minimum, maksimum, toplam gibi sorgulara cevap verebilmemize ve bu aralıklar üzerinde güncelleme yapabilmemize olanak sağlayan bir veri yapısıdır. + +Segment Tree, [Fenwick Tree](fenwick-tree.md) ve [Sparse Table](sparse-table.md) yapılarından farklı olarak elemanlar üzerinde güncelleme yapılabilmesi ve minimum, maksimum gibi sorgulara da olanak sağlaması yönünden daha kullanışlıdır. Ayrıca Segment Tree $\mathcal{O}(N)$ hafıza karmaşıklığına sahipken Sparse Table yapısında gereken hafıza karmaşıklığı $\mathcal{O}(N \log N)$'dir. + +## Yapısı ve Kuruluşu +Segment Tree, "Complete Binary Tree" yapısına sahiptir. Segment Tree'nin yaprak düğümlerinde dizinin elemanları saklıdır ve bu düğümlerin atası olan her düğüm kendi çocuğu olan düğümlerinin cevaplarının birleşmesiyle oluşur. Bu sayede her düğümde belirli aralıkların cevapları ve root düğümünde tüm dizinin cevabı saklanır. Örneğin toplam sorgusu için kurulmuş bir Segment Tree yapısı için her düğümün değeri çocuklarının değerleri toplamına eşittir. + +
+![a = [41,67,6,30,85,43,39] dizisinde toplam sorgusu icin oluşturulmuş Segment Tree yapısı](img/segtree.png){ width="100%" } +
$a = [41,67,6,30,85,43,39]$ dizisinde toplam sorgusu icin oluşturulmuş Segment Tree yapısı
+
+ +```c++ +void build(int ind, int l, int r) { + // tree[ind] dizinin [l,r] araliginin cevabini saklar. + if (l == r) { // yaprak dugum'e ulasti + tree[ind] = a[l]; // bu dugum dizinin l. elamaninin cevabini saklar + } else { + int mid = (l + r) / 2; + build(ind * 2, l, mid); + build(ind * 2 + 1, mid + 1, r); + // [l,r] araliginin cevabini + // [l,mid] ve [mid + 1,r] araliklarinin cevaplarinin birlesmesiyle olusur. + tree[ind] = tree[ind * 2] + tree[ind * 2 + 1]; + } +} +``` + +## Aralık Sorgu ve Eleman Güncelleme + +### Sorgu Algoritması + +Herhangi bir $[l,r]$ aralığı için sorgu algoritması sırası ile şu şekilde çalışır: +- $[l,r]$ aralığını ağacımızda cevapları saklı olan en geniş aralıklara parçala. +- Bu aralıkların cevaplarını birleştirerek istenilen cevabı hesapla. + +Ağacın her derinliğinde cevabımız için gerekli aralıklardan maksimum $2$ adet bulunabilir. Bu yüzden sorgu algoritması $\mathcal{O}(\log N)$ zaman karmaşıklığında çalışır. + +
+![a = [41,67,6,30,85,43,39] dizisinde $[2,6]$ aralığında sorgu işlemi](img/segtreequery.png){ width="100%" } +
$a = [41,67,6,30,85,43,39]$ dizisinde $[2,6]$ aralığında sorgu işlemi
+
+ +$a = [41,67,6,30,85,43,39]$ dizisinde $[2,6]$ aralığının cevabı $[2,3]$ ile $[4,6]$ aralıklarının cevaplarının birleşmesiyle elde edilir. Toplam sorgusu için cevap $36+167=203$ şeklinde hesaplanır. + +```c++ +// [lw,rw] sorguda cevabini aradigimiz aralik. +// [l,r] ise agactaki ind nolu node'da cevabini sakladigimiz aralik. +int query(int ind, int l, int r, int lw, int rw) { + if (l > rw or r < lw) // bulundugumuz aralik cevabini aradigimiz araligin disinda. + return 0; + if (l >= lw and r <= rw) // cevabini aradigimiz aralik bu araligi tamamen kapsiyor. + return tree[ind]; + + int mid = (l + r) / 2; + + // Agacta recursive bir sekilde araligimizi araliklara bolup gelen cevaplari birlestiyoruz. + return query(ind * 2, l, mid, lw, rw) + query(ind * 2 + 1, mid + 1, r, lw, rw); +} +``` + +### Eleman Güncelleme Algoritması + +Dizideki $x$ indeksli elemanının değerini güncellemek için kullanılan algoritma şu şeklide çalışır: + +- Ağaçta $x$ indeksli elemanı içeren tüm düğümlerin değerlerini güncelle. + +Ağaçta $x$ indeksli elemanın cevabını tutan yaprak düğümden root düğüme kadar toplamda $\log(N)$ düğümün değerini güncellememiz yeterlidir. Dolayısıyla herhangi bir elemanın değerini güncellemenin zaman karmaşıklığı $\mathcal{O}(\log N)$'dir. + +
+![a = [41,67,6,30,85,43,39] dizisinde 5 indeksli elemanın cevabını güncellerken güncellememiz gereken düğümler şekildeki gibidir.](img/segtreeupdate.png){ width="100%" } +
$a = [41,67,6,30,85,43,39]$ dizisinde 5 indeksli elemanın cevabını güncellerken güncellememiz gereken düğümler sekildeki gibidir.
+
+ +```c++ +void update(int ind, int l, int r, int x, int val) { + if (l > x || r < x) // bulundugumuz aralik x indeksli elemani icermiyor. + return; + if (l == x and r == x) { + tree[ind] = val; // x indeksli elemani iceren yaprak dugumunun cevabini guncelliyoruz. + return; + } + + int mid = (l + r) / 2; + + // recursive bir sekilde x indeksli elemani iceren + // tum araliklarin cevaplarini guncelliyoruz. + update(ind * 2, l, mid, x, val); + update(ind * 2 + 1, mid + 1, r, x, val); + tree[ind] = tree[ind * 2] + tree[ind * 2 + 1]; +} +``` + +Segment Tree veri yapısı ile ilgili örnek bir probleme [buradan](https://codeforces.com/gym/100739/problem/A){target="_blank"} ulaşabilirsiniz. diff --git a/docs/data-structures/sparse-table.md b/docs/data-structures/sparse-table.md new file mode 100644 index 0000000..5cc9351 --- /dev/null +++ b/docs/data-structures/sparse-table.md @@ -0,0 +1,84 @@ +--- +title: Sparse Table +tags: + - Data Structures + - Sparse Table +--- + +Sparse table aralıklardaki elemanların toplamı, minimumu, maksimumu ve EBOB'ları gibi sorgulara $\mathcal{O}(\log N)$ zaman karmaşıklığında cevap alabilmemizi sağlayan bir veri yapısıdır. Bazı tip sorgular (aralıktaki minimum, maksimum sayıyı bulma gibi) ise $\mathcal{O}(1)$ zaman karmaşıklığında yapmaya uygundur. + +Bu veri yapısı durumu değişmeyen, sabit bir veri üzerinde ön işlemler yaparak kurulur. Dinamik veriler için kullanışlı değildir. Veri üzerinde herhangi bir değişiklik durumda Sparse table tekrardan kurulmalıdır. Bu da maliyetli bir durumdur. + +## Yapısı ve Kuruluşu + +Sparse table iki bouyutlu bir dizi şeklinde, $\mathcal{O}(N\log N)$ hafıza karmaşıklığına sahip bir veri yapısıdır. Dizinin her elemanından $2$'nin kuvvetleri uzaklıktaki elemanlara kadar olan cevaplar Sparse table'da saklanır. $ST_{x,i}$, $x$ indeksli elemandan $x + 2^i - 1$ indeksli elemana kadar olan aralığın cevabını saklayacak şekilde sparse table kurulur. + +```c++ +// Toplam sorgusu icin kurulmus Sparse Table Yapisi +const int n; +const int LOG = log2(n); +int a[n + 1], ST[2 * n][LOG + 1]; + +void build() { + for (int i = 1; i <= n; i++) { + // [i,i] araliginin cevabi dizinin i indeksli elemanina esittir. + ST[i][0] = a[i]; + } + + for (int i = 1; i <= LOG; i++) + for (int j = 1; j <= n; j++) { + // [i,i+2^(j)-1] araliginin cevabi + // [i,i+2^(j - 1) - 1] araligi ile [i+2^(j - 1),i+2^j-1] araliginin + // cevaplarinin birlesmesiyle elde edilir + ST[i][j] = ST[i][j - 1] + ST[i + (1 << (j - 1))][j - 1]; + } + + return; +} +``` + +## Sorgu Algoritması + +Herhangi bir $[l,r]$ aralığı için sorgu algoritması sırasıyla şu şekilde çalışır: + +- $[l,r]$ aralığını cevaplarını önceden hesapladığımız aralıklara parçala. + - Sadece $2$'nin kuvveti uzunluğunda parçaların cevaplarını sakladığımız için aralığımızı $2$'nin kuvveti uzunluğunda aralıklara ayırmalıyız. $[l,r]$ aralığının uzunluğunun ikilik tabanda yazdığımızda hangi aralıklara parçalamamız gerektiğini bulmuş oluruz. +- Bu aralıklardan gelen cevapları birleştirerek $[l,r]$ aralığının cevabını hesapla. + +Herhangi bir aralığın uzunluğunun ikilik tabandaki yazılışındaki $1$ rakamlarının sayısı en fazla $\log(N)$ olabileceğinden parçalayacağımız aralık sayısı da en fazla $\log(N)$ olur. Dolayısıyla sorgu işlemimiz $\mathcal{O}(\log N)$ zaman karmaşıklığında çalışır. + +Örneğin: $[4,17]$ aralığının cevabını hesaplamak için algoritmamız $[4,17]$ aralığını $[4,11]$, $[12,15]$ ve $[16,17]$ aralıklarına ayırır ve bu $3$ aralıktan gelen cevapları birleştirerek istenilen cevabı hesaplar. + +```c++ +// toplam sorgusu +int query(int l, int r) { + int res = 0; + + for (int i = LOG; i >= 0; i--) { + // her seferinde uzunlugu r - l + 1 gecmeyecek + // en buyuk araligin cevabi ekleyip l'i o araligin sonuna cekiyoruz. + if (l + (1 << i) <= r) { + res += ST[l][i]; + l += (1 << i); + } + } + + return res; +} +``` + +## Minimum ve Maksimum Sorgu + +Sparse Table veri yapısının diğer veri yapılarından farklı olarak $\mathcal{O}(1)$ zaman karmaşıklığında aralıklarda minimum veya maksimum sorgusu yapabilmesi en avantajlı özelliğidir. + +Herhangi bir aralığın cevabını hesaplarken bu aralıktaki herhangi bir elemanı birden fazla kez değerlendirmemiz cevabı etkilemez. Bu durum aralığımızı $2$'nin kuvveti uzunluğunda maksimum $2$ adet aralığa bölebilmemize ve bu aralıkların cevaplarını $\mathcal{O}(1)$ zaman karmaşıklığında birleştirebilmemize olanak sağlar. + +```c++ +int RMQ(int l, int r) { + // log[] dizisinde her sayinin onceden hesapadigimiz log2 degerleri saklidir. + int j = log[r - l + 1]; + return min(ST[l][j], ST[r - (1 << j) + 1][j]); +} +``` + +Sparse Table veri yapısı ile ilgili örnek bir probleme [buradan](https://www.spoj.com/problems/RMQSQ){target="_blank"} ulaşabilirsiniz. diff --git a/docs/data-structures/sqrt-decomposition.md b/docs/data-structures/sqrt-decomposition.md new file mode 100644 index 0000000..07e0de5 --- /dev/null +++ b/docs/data-structures/sqrt-decomposition.md @@ -0,0 +1,191 @@ +--- +title: SQRT Decomposition +tags: + - Data Structures + - SQRT Decomposition + - Square Root Decomposition +--- + +Square Root Decomposition algoritması dizi üzerinde $\mathcal{O}(\sqrt{N})$ zaman karmaşıklığında sorgu yapabilmemize ve $\mathcal{O}(1)$ zaman karmaşıklığında ise değişiklik yapabilmemize olanak sağlayan bir veri yapsıdır. + +## Yapısı ve Kuruluşu + +Dizinin elemanları her biri yaklaşık $\mathcal{O}(\sqrt{N})$ uzunluğunda bloklar halinde parçalanır. Her bir blokun cevabı ayrı ayrı hesaplanır ve bir dizide saklanır. + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Blokların Cevapları$21$$13$$50$$32$
Dizideki Elemanlar$3$$6$$2$$10$$3$$1$$4$$5$$2$$7$$37$$4$$11$$6$$8$$7$
Elemanların İndeksleri$1$$2$$3$$4$$5$$6$$7$$8$$9$$10$$11$$12$$13$$14$$15$$16$
+ + *Örnek bir dizi üzerinde toplam sorgusu için kurulmuş SQRT Decompostion veri yapısı.* +
+ +```c++ +void build() { + for (int i = 1; i <= n; i++) { + if (i % sq == 1) { // sq = sqrt(n) + t++; // yeni blok baslangici. + st[t] = i; // t.blok i indisli elemanda baslar. + } + fn[t] = i; // t.blokun bitisini i indisli eleman olarak guncelliyoruz. + wh[i] = t; // i indeksli eleman t.blogun icinde. + sum[t] += a[i]; // t. blokun cevabina i indeksli elemani ekliyoruz. + } +} +``` + +## Sorgu Algoritması + +Herhangi bir $[l,r]$ aralığı için sorgu algoritması sırası ile şu şekilde çalışır: + +1. Cevabını aradığımız aralığın tamamen kapladığı blokların cevabını cevabımıza ekliyoruz. +2. Tamamen kaplamadığı bloklardaki aralığımızın içinde olan elemanları tek tek gezerek cevabımıza ekliyoruz. + +Cevabını aradığımız aralığın kapsadığı blok sayısı en fazla $\sqrt{N}$ olabileceğinden $1.$ işlem en fazla $\sqrt{N}$ kez çalışır. Tamamen kaplamadığı ancak bazı elemanları içeren en fazla $2$ adet blok olabilir. (Biri en solda diğeri en sağda olacak şekilde.) Bu $2$ blok için de gezmemiz gereken eleman sayısı maksimum $2\sqrt{N}$ olduğundan bir sorgu işleminde en fazla $3\sqrt{N}$ işlem yapılır, dolayısıyla sorgu işlemimiz $\mathcal{O}(\sqrt{N})$ zaman karmaşıklığında calışır. + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Blokların Cevapları$21$$13$$50$$32$
Dizideki Elemanlar$3$$6$$2$$10$$3$$1$$4$$5$$2$$7$$37$$4$$11$$6$$8$$7$
Elemanların İndeksleri$1$$2$$3$$4$$5$$6$$7$$8$$9$$10$$11$$12$$13$$14$$15$$16$
+ + *Örnek dizideki $[3,13]$ aralığının cevabını $2.$ ve $3.$ blokların cevapları ile $3,4$ ve $11$ indeksli elemanların toplanmasıyla elde edilir.* +
+ +```c++ +// [l,r] araligindaki elemanlarin toplamini hesaplayan fonksiyon. +int query(int l, int r) { + int res = 0; + + if (wh[l] == wh[r]) { // l ve r ayni blogun icindeyse + for (int i = l; i <= r; i++) + res += a[i]; + } else { + for (int i = wh[l] + 1; i <= wh[r] - 1; i++) + res += sum[i]; // tamamen kapladigimiz bloklarin cevaplarini ekliyoruz. + + // tamamen kaplamadigimiz bloklardaki araligimiz icindeki + // elemanlarin cevaplarini ekliyoruz. + + for (int i = st[wh[l]]; i <= fn[wh[l]]; i++) + if (i >= l && i <= r) + res += a[i]; + + for (int i = st[wh[r]]; i <= fn[wh[r]]; i++) + if (i >= l && i <= r) + res += a[i]; + } + + return res; +} +``` + +## Eleman Güncelleme Algoritması + +Herhangi bir elemanın değerini güncellerken o elemanı içeren blokun değerini güncellememiz yeterli olacaktır. Dolayısıyla güncelleme işlemimimiz $\mathcal{O}(1)$ zaman karmaşıklığında çalışır. + +```c++ +void update(int x, int val) { + // x indeksli elemanin yeni degerini val degerine esitliyoruz. + sum[wh[x]] -= a[x]; + a[x] = val; + sum[wh[x]] += a[x]; +} +``` + +SQRT Decomposition veri yapısı ile ilgili örnek bir probleme [buradan](https://codeforces.com/contest/13/problem/E){target="_blank"} ulaşabilirsiniz. diff --git a/docs/data-structures/stack.md b/docs/data-structures/stack.md new file mode 100644 index 0000000..1e2f686 --- /dev/null +++ b/docs/data-structures/stack.md @@ -0,0 +1,29 @@ +--- +title: Stack +tags: + - Data Structures + - Stack +--- + +Stack veri yapısında elemanlar yapıya son giren ilk çıkar (LIFO) kuralına uygun olacak şekilde saklanır. Bu veri yapısında uygulayabildiğimiz işlemler: + +- Veri yapısının en üstüne eleman ekleme. +- Veri yapısının en üstündeki elemana erişim. +- Veri yapısının en üstündeki elemanı silme. +- Veri yapısının boş olup olmadığının kontrölü. + +C++ dilindeki STL kütüphanesinde bulunan hazır stack yapısının kullanımı aşağıdaki gibidir: + +```c++ +int main() { + stack st; + cout << st.empty() << endl; // Ilk bashta Stack bosh oldugu icin burada True donecektir. + st.push(5); // Stack'in en ustune 5'i ekler. Stack'in yeni hali: {5} + st.push(7); // Stack'in en ustune 7'yi ekler. Stack'in yeni hali: {7, 5} + st.push(6); // Stack'in en ustune 6'yi ekler. Stack'in yeni hali : {6, 7, 5} + st.pop(); // Stack'in en ustundeki elemani siler. Stack'in yeni hali : {7, 5} + st.push(1); // Stack'in en ustune 1'i ekler. Stack'in yeni hali : {1, 7, 5} + cout << st.top() << endl; // Stack'in en ustundeki elemana erisir. Ekrana 1 yazirir. + cout << st.empty() << endl; // Burada Stack bosh olmadigindan oturu False donecektir. +} +``` \ No newline at end of file diff --git a/docs/dynamic-programming/bitmask_dp.md b/docs/dynamic-programming/bitmask-dp.md similarity index 98% rename from docs/dynamic-programming/bitmask_dp.md rename to docs/dynamic-programming/bitmask-dp.md index 9a0bae1..64459d6 100644 --- a/docs/dynamic-programming/bitmask_dp.md +++ b/docs/dynamic-programming/bitmask-dp.md @@ -1,4 +1,9 @@ -# Bitmask DP +--- +title: Bitmask DP +tags: + - Dynamic Programming + - Bitmask DP +--- ## What is Bitmask? diff --git a/docs/dynamic-programming/common_dp_problems.md b/docs/dynamic-programming/common-dp-problems.md similarity index 98% rename from docs/dynamic-programming/common_dp_problems.md rename to docs/dynamic-programming/common-dp-problems.md index f0e46aa..0ab2a15 100644 --- a/docs/dynamic-programming/common_dp_problems.md +++ b/docs/dynamic-programming/common-dp-problems.md @@ -1,4 +1,9 @@ -# Common Dynamic Programming Problems +--- +title: Common Dynamic Programming Problems +tags: + - Dynamic Programming + - Common Dynamic Programming Problems +--- ## Coin Problem diff --git a/docs/dynamic-programming/digit_dp.md b/docs/dynamic-programming/digit-dp.md similarity index 98% rename from docs/dynamic-programming/digit_dp.md rename to docs/dynamic-programming/digit-dp.md index e5e99a5..41caf05 100644 --- a/docs/dynamic-programming/digit_dp.md +++ b/docs/dynamic-programming/digit-dp.md @@ -1,4 +1,9 @@ -# Digit DP +--- +title: Digit DP +tags: + - Dynamic Programming + - Digit DP +--- Problems that require the calculation of how many numbers there are between two values (say, \( A \) and \( B \)) that satisfy a particular property can be solved using digit dynamic programming (Digit DP). diff --git a/docs/dynamic-programming/dp_on_directed_acyclic_graphs.md b/docs/dynamic-programming/dp-on-dags.md similarity index 96% rename from docs/dynamic-programming/dp_on_directed_acyclic_graphs.md rename to docs/dynamic-programming/dp-on-dags.md index 0c986ec..6cf5424 100644 --- a/docs/dynamic-programming/dp_on_directed_acyclic_graphs.md +++ b/docs/dynamic-programming/dp-on-dags.md @@ -1,4 +1,9 @@ -# DP on Directed Acyclic Graphs (DAGs) +--- +title: DP on Directed Acyclic Graphs (DAGs) +tags: + - Dynamic Programming + - DP on Directed Acyclic Graphs (DAGs) +--- As we know, the nodes of a directed acyclic graph (DAG) can be sorted topologically, and DP can be implemented efficiently using this topological order. diff --git a/docs/dynamic-programming/dp_on_rooted_trees.md b/docs/dynamic-programming/dp-on-rooted-trees.md similarity index 96% rename from docs/dynamic-programming/dp_on_rooted_trees.md rename to docs/dynamic-programming/dp-on-rooted-trees.md index 3a133e1..23aadf4 100644 --- a/docs/dynamic-programming/dp_on_rooted_trees.md +++ b/docs/dynamic-programming/dp-on-rooted-trees.md @@ -1,4 +1,9 @@ -# DP on Rooted Trees +--- +title: DP on Rooted Trees +tags: + - Dynamic Programming + - DP on Rooted Trees +--- In dynamic programming (DP) on rooted trees, we define functions for the nodes of the tree, which are calculated recursively based on the children of each node. One common DP state is usually associated with a node \(i\), representing the sub-tree rooted at node \(i\). diff --git a/docs/dynamic-programming/dynamic_programming.md b/docs/dynamic-programming/dynamic-programming.md similarity index 97% rename from docs/dynamic-programming/dynamic_programming.md rename to docs/dynamic-programming/dynamic-programming.md index 1259a50..77799f1 100644 --- a/docs/dynamic-programming/dynamic_programming.md +++ b/docs/dynamic-programming/dynamic-programming.md @@ -1,4 +1,8 @@ -# Dynamic Programming +--- +title: Dynamic Programming +tags: + - Dynamic Programming +--- Dynamic programming (DP) is a technique used to avoid computing the same sub-solution multiple times in a recursive algorithm. A sub-solution of the problem is constructed from the previously found ones. DP solutions have a polynomial complexity, which ensures a much faster running time than other techniques like backtracking or brute-force. @@ -45,7 +49,7 @@ def fibonacci(n): ### Dynamic Programming -- **Top-Down - Memoization:** +- **Top-Down - Memoization:** Recursion leads to unnecessary repeated calculations. Memoization solves this by caching the results of previously computed Fibonacci numbers, so they don't have to be recalculated. ```python @@ -70,7 +74,7 @@ def fibonacci(n): -- **Bottom-Up:** +- **Bottom-Up:** The bottom-up approach eliminates recursion by computing the Fibonacci numbers in order, starting from the base cases and building up to the desired value. ```python diff --git a/docs/dynamic-programming/greedy_algorithms.md b/docs/dynamic-programming/greedy-algorithms.md similarity index 98% rename from docs/dynamic-programming/greedy_algorithms.md rename to docs/dynamic-programming/greedy-algorithms.md index 1063eee..373a378 100644 --- a/docs/dynamic-programming/greedy_algorithms.md +++ b/docs/dynamic-programming/greedy-algorithms.md @@ -1,4 +1,9 @@ -# Greedy Algorithms +--- +title: Greedy Algorithms +tags: + - Dynamic Programming + - Greedy Algorithms +--- A *greedy algorithm* is an algorithm that follows the problem solving heuristic of making the locally optimal choice at each stage with the hope of finding a global optimum. A greedy algorithm never takes back its choices, but directly constructs the final solution. For this reason, greedy algorithms are usually very efficient. diff --git a/docs/dynamic-programming/index.md b/docs/dynamic-programming/index.md index d9ede23..b23c10b 100644 --- a/docs/dynamic-programming/index.md +++ b/docs/dynamic-programming/index.md @@ -1,7 +1,7 @@ --- title: Dynamic Programming tags: - - "Dynamic Programming" + - Dynamic Programming --- **Editor:** Halil Çetiner @@ -11,23 +11,15 @@ tags: ## Introduction Next section is about the *Greedy Algorithms* and *Dynamic Programming*. It will be quite a generous introduction to the concepts and will be followed by some common problems. -- [Greedy Algorithms](./greedy_algorithms.md) - -- [Dynamic Programming](./dynamic_programming.md) - -- [Common DP Problems](./common_dp_problems.md) - -- [Bitmask DP](./bitmask_dp.md) - -- [DP on Rooted Trees](./dp_on_rooted_trees.md) - -- [DP on Directed Acyclic Graphs](./dp_on_directed_acyclic_graphs.md) - -- [Digit DP](./digit_dp.md) - -- [Walk Counting using Matrix Exponentiation](./walk_counting_with_matrix.md) - -- [Tree Child-Sibling Notation](./tree_child_sibling_notation.md) +### [Greedy Algorithms](greedy-algorithms.md) +### [Dynamic Programming](dynamic-programming.md) +### [Common DP Problems](common-dp-problems.md) +### [Bitmask DP](bitmask-dp.md) +### [DP on Rooted Trees](dp-on-rooted-trees.md) +### [DP on Directed Acyclic Graphs](dp-on-dags.md) +### [Digit DP](./digit-dp.md) +### [Walk Counting using Matrix Exponentiation](./walk-counting-with-matrix.md) +### [Tree Child-Sibling Notation](./tree-child-sibling-notation.md) ## References diff --git a/docs/dynamic-programming/tree_child_sibling_notation.md b/docs/dynamic-programming/tree-child-sibling-notation.md similarity index 93% rename from docs/dynamic-programming/tree_child_sibling_notation.md rename to docs/dynamic-programming/tree-child-sibling-notation.md index a6f7f9f..57ea2c8 100644 --- a/docs/dynamic-programming/tree_child_sibling_notation.md +++ b/docs/dynamic-programming/tree-child-sibling-notation.md @@ -1,4 +1,9 @@ -# Tree Child-Sibling Notation +--- +title: Tree Child-Sibling Notation +tags: + - Dynamic Programming + - Tree Child-Sibling Notation +--- In this method, we change the structure of the tree. In a standard tree, each parent node is connected to all of its children. However, in the **child-sibling notation**, a node stores a pointer to only one of its children. Additionally, the node also stores a pointer to its immediate right sibling. @@ -43,4 +48,4 @@ These operations can be done efficiently using the LCRS structure, making it con - [Link to the Figure used](https://contribute.geeksforgeeks.org/wp-content/uploads/new.jpeg) -- [LCRS possible uses Stackoverflow](https://stackoverflow.com/questions/14015525/what-is-the-left-child-right-sibling-representation-of-a-tree-why-would-you-us) \ No newline at end of file +- [LCRS possible uses Stackoverflow](https://stackoverflow.com/questions/14015525/what-is-the-left-child-right-sibling-representation-of-a-tree-why-would-you-us) diff --git a/docs/dynamic-programming/walk_counting_with_matrix.md b/docs/dynamic-programming/walk-counting-with-matrix.md similarity index 95% rename from docs/dynamic-programming/walk_counting_with_matrix.md rename to docs/dynamic-programming/walk-counting-with-matrix.md index a6d66fb..274ab72 100644 --- a/docs/dynamic-programming/walk_counting_with_matrix.md +++ b/docs/dynamic-programming/walk-counting-with-matrix.md @@ -1,4 +1,9 @@ -# Walk Counting using Matrix Exponentiation +--- +title: Walk Counting using Matrix Exponentiation +tags: + - Dynamic Programming + - Walk Counting using Matrix Exponentiation +--- Matrix exponentiation can be used to count the number of walks of a given length on a graph. diff --git a/docs/static/stylesheets/main.css b/docs/static/stylesheets/main.css new file mode 100644 index 0000000..029fe9f --- /dev/null +++ b/docs/static/stylesheets/main.css @@ -0,0 +1,4 @@ +.md-sidebar--primary { + display: none; + visibility: hidden; +} diff --git a/mkdocs.yml b/mkdocs.yml index 54fccb7..19420a3 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -3,9 +3,9 @@ site_url: https://inzva.github.io/Algorithm-Program/ nav: - Home: index.md - Introduction: introduction/index.md + - Data Structures: data-structures/index.md + - Algorithms: algorithms/index.md - Dynamic Programming: dynamic-programming/index.md - - Algorithms - I: algorithms-i/index.md - - Data Structures - I: data-structures-i/index.md theme: name: material custom_dir: docs/overrides @@ -14,9 +14,6 @@ theme: features: - toc.follow - navigation.tabs - - navigation.sections - - toc.integrate - - navigation.top - search.suggest - search.highlight - content.tabs.link @@ -35,7 +32,7 @@ theme: icon: material/weather-sunny name: Switch to light mode primary: black - accent: purple + accent: white extra: social: @@ -54,6 +51,7 @@ extra_javascript: - https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.7/contrib/auto-render.min.js extra_css: + - static/stylesheets/main.css - https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.7/katex.min.css markdown_extensions: