← Kursa Dön
📄 Text · 12 min

Çok Boyutlu Diziler ve Matris İşlemleri

Şimdiye kadar tek boyutlu diziler gördük — bir sıra halinde elemanlar. Ama ya bir tablo gerekiyorsa? Satırlar ve sütunlardan oluşan bir not çizelgesi, bir oyun tahtası, bir piksel ekranı? İşte burada çok boyutlu diziler devreye giriyor.

Bu derste 2 boyutlu (2D) dizileri hem C-style hem de modern vector<vector<int>> yaklaşımıyla öğreneceğiz. Matris toplama ve çarpma gibi pratik örnekler yapacağız.


2D Dizi Nedir?

2D diziyi bir Excel tablosu gibi düşün. Satırlar ve sütunlar var. Her hücreye ulaşmak için iki indeks lazım: biri satır, biri sütun.

Mesela 3 öğrencinin 4 dersten notlarını düşün:

Ders 0Ders 1Ders 2Ders 3
Öğr. 090857892
Öğr. 175889570
Öğr. 260728085

Bu tabloyu bir 2D dizi ile temsil edebilirsin: notlar[satir][sutun].


2D C-Style Dizi

Tanımlama ve Başlatma

#include <iostream>
using namespace std;

int main() {
    // 3 satır, 4 sütun
    int notlar[3][4] = {
        {90, 85, 78, 92},  // 0. öğrenci
        {75, 88, 95, 70},  // 1. öğrenci
        {60, 72, 80, 85}   // 2. öğrenci
    };

    // Erişim: notlar[satir][sutun]
    cout << "1. öğrenci, 2. ders: " << notlar[1][2] << endl;  // 95

    // Değer değiştirme
    notlar[0][0] = 95;
    cout << "Güncel: " << notlar[0][0] << endl;  // 95

    return 0;
}

Döngü ile Gezinme

#include <iostream>
using namespace std;

int main() {
    int notlar[3][4] = {
        {90, 85, 78, 92},
        {75, 88, 95, 70},
        {60, 72, 80, 85}
    };

    // Klasik iç içe for döngüsü
    for (int i = 0; i < 3; i++) {
        cout << "Öğrenci " << i << ": ";
        for (int j = 0; j < 4; j++) {
            cout << notlar[i][j] << "\t";
        }
        cout << endl;
    }

    return 0;
}

Her Öğrencinin Ortalamasını Hesaplama

#include <iostream>
using namespace std;

int main() {
    const int OGRENCI = 3;
    const int DERS = 4;

    int notlar[OGRENCI][DERS] = {
        {90, 85, 78, 92},
        {75, 88, 95, 70},
        {60, 72, 80, 85}
    };

    for (int i = 0; i < OGRENCI; i++) {
        int toplam = 0;
        for (int j = 0; j < DERS; j++) {
            toplam += notlar[i][j];
        }
        double ort = static_cast<double>(toplam) / DERS;
        cout << "Öğrenci " << i << " ortalama: " << ort << endl;
    }

    return 0;
}

2D C-Style Diziyi Fonksiyona Geçirme

C-style 2D diziyi fonksiyona geçirirken sütun sayısını belirtmek zorundasın:

#include <iostream>
using namespace std;

// Sütun sayısı sabit olmalı
void yazdir(int dizi[][4], int satir_sayisi) {
    for (int i = 0; i < satir_sayisi; i++) {
        for (int j = 0; j < 4; j++) {
            cout << dizi[i][j] << "\t";
        }
        cout << endl;
    }
}

int main() {
    int notlar[3][4] = {
        {90, 85, 78, 92},
        {75, 88, 95, 70},
        {60, 72, 80, 85}
    };

    yazdir(notlar, 3);
    return 0;
}

⚠️ Dikkat: C-style 2D dizilerde fonksiyona geçerken ilk boyutu (satır) boş bırakabilirsin ama ikinci boyutu (sütun) mutlaka belirtmelisin. Bu, derleyicinin bellek düzenini hesaplaması için gerekli.


vector\<vector\<int\>\> ile 2D Yapı

Modern C++'ta 2D yapılar için vector<vector<int>> kullanılır. Avantajları: boyutlar dinamik, fonksiyona geçirmek kolay, boyut bilgisi kaybolmaz.

Tanımlama ve Kullanım

#include <iostream>
#include <vector>
using namespace std;

int main() {
    // Başlangıç değerleriyle
    vector<vector<int>> notlar = {
        {90, 85, 78, 92},
        {75, 88, 95, 70},
        {60, 72, 80, 85}
    };

    // Erişim
    cout << "notlar[1][2] = " << notlar[1][2] << endl;  // 95

    // Boyut bilgisi
    cout << "Satır sayısı: " << notlar.size() << endl;     // 3
    cout << "Sütun sayısı: " << notlar[0].size() << endl;  // 4

    // Gezinme
    for (int i = 0; i < notlar.size(); i++) {
        for (int j = 0; j < notlar[i].size(); j++) {
            cout << notlar[i][j] << "\t";
        }
        cout << endl;
    }

    return 0;
}

Dinamik Boyut: Satır ve Sütun Ekleme

#include <iostream>
#include <vector>
using namespace std;

int main() {
    vector<vector<int>> matris;

    // Satır ekleme
    matris.push_back({1, 2, 3});
    matris.push_back({4, 5, 6});
    matris.push_back({7, 8, 9});

    // Bir satıra sütun ekleme
    matris[0].push_back(99);  // ilk satırın sonuna 99 ekle

    // Dikkat: artık satırlar farklı uzunlukta olabilir (jagged array)
    for (auto& satir : matris) {
        for (int x : satir) {
            cout << x << "\t";
        }
        cout << endl;
    }

    return 0;
}

Belirli Boyutta Boş Matris Oluşturma

#include <iostream>
#include <vector>
using namespace std;

int main() {
    int satir = 3, sutun = 4;

    // satir x sutun boyutunda, hepsi 0
    vector<vector<int>> matris(satir, vector<int>(sutun, 0));

    // Kontrol
    cout << "Boyut: " << matris.size() << "x" << matris[0].size() << endl;

    for (auto& s : matris) {
        for (int x : s) cout << x << " ";
        cout << endl;
    }

    return 0;
}

Bu kalıp çok kullanışlı: vector<vector<int>>(satir, vector<int>(sutun, varsayilan_deger)).


Nested Range-Based For

C++11'den itibaren iç içe range-based for döngüsü ile 2D yapıları temiz bir şekilde gezebilirsin:

#include <iostream>
#include <vector>
using namespace std;

int main() {
    vector<vector<int>> matris = {
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9}
    };

    // Sadece okuma
    for (const auto& satir : matris) {
        for (int eleman : satir) {
            cout << eleman << "\t";
        }
        cout << endl;
    }

    // Değer değiştirme
    for (auto& satir : matris) {
        for (int& eleman : satir) {
            eleman *= 2;
        }
    }

    cout << "İkiye çarpıldıktan sonra:" << endl;
    for (const auto& satir : matris) {
        for (int eleman : satir) {
            cout << eleman << "\t";
        }
        cout << endl;
    }

    return 0;
}

💡 İpucu: Dış döngüdeki satir her zaman referans (auto& veya const auto&) olmalı. Eğer referans yapmazsan her satır vektörü kopyalanır — hem yavaş hem de değişiklik yapmak istersen orijinali etkilemez.


Matris Toplama

İki matrisin toplanması: aynı boyuttaki matrislerin karşılıklı elemanlarını topla.

A = |1 2 3|    B = |9 8 7|    C = A + B = |10 10 10|
    |4 5 6|        |6 5 4|                 |10 10 10|
#include <iostream>
#include <vector>
using namespace std;

using Matris = vector<vector<int>>;

Matris matris_topla(const Matris& A, const Matris& B) {
    int satir = A.size();
    int sutun = A[0].size();
    Matris C(satir, vector<int>(sutun, 0));

    for (int i = 0; i < satir; i++) {
        for (int j = 0; j < sutun; j++) {
            C[i][j] = A[i][j] + B[i][j];
        }
    }
    return C;
}

void matris_yazdir(const Matris& M) {
    for (const auto& satir : M) {
        for (int x : satir) {
            cout << x << "\t";
        }
        cout << endl;
    }
}

int main() {
    Matris A = {{1, 2, 3}, {4, 5, 6}};
    Matris B = {{9, 8, 7}, {6, 5, 4}};

    Matris C = matris_topla(A, B);

    cout << "A + B =" << endl;
    matris_yazdir(C);

    return 0;
}

Çıktı:

A + B =
10	10	10
10	10	10

Matris Çarpma

Matris çarpma, toplama kadar basit değil. A (m×n) ve B (n×p) matrislerini çarpmak için:

  • Sonuç C matrisi m×p boyutunda olur

  • C[i][j] = A'nın i. satırı ile B'nin j. sütununun iç çarpımı

A = |1 2|    B = |5 6|    C = A × B = |1*5+2*7  1*6+2*8| = |19 22|
    |3 4|        |7 8|                 |3*5+4*7  3*6+4*8|   |43 50|
#include <iostream>
#include <vector>
using namespace std;

using Matris = vector<vector<int>>;

Matris matris_carp(const Matris& A, const Matris& B) {
    int m = A.size();
    int n = A[0].size();
    int p = B[0].size();
    Matris C(m, vector<int>(p, 0));

    for (int i = 0; i < m; i++) {
        for (int j = 0; j < p; j++) {
            for (int k = 0; k < n; k++) {
                C[i][j] += A[i][k] * B[k][j];
            }
        }
    }
    return C;
}

void matris_yazdir(const Matris& M) {
    for (const auto& satir : M) {
        for (int x : satir) cout << x << "\t";
        cout << endl;
    }
}

int main() {
    Matris A = {{1, 2}, {3, 4}};
    Matris B = {{5, 6}, {7, 8}};

    Matris C = matris_carp(A, B);

    cout << "A x B =" << endl;
    matris_yazdir(C);
    // 19  22
    // 43  50

    return 0;
}

⚠️ Dikkat: Matris çarpması için A'nın sütun sayısı ile B'nin satır sayısı eşit olmalı. Gerçek kodda bu kontrolü eklemeyi unutma.


Pratik Örnek: Matris Transpozu

Transpoze almak = satırları sütun, sütunları satır yapmak:

#include <iostream>
#include <vector>
using namespace std;

using Matris = vector<vector<int>>;

Matris transpoze(const Matris& M) {
    int satir = M.size();
    int sutun = M[0].size();
    Matris T(sutun, vector<int>(satir));

    for (int i = 0; i < satir; i++) {
        for (int j = 0; j < sutun; j++) {
            T[j][i] = M[i][j];
        }
    }
    return T;
}

void matris_yazdir(const Matris& M) {
    for (const auto& satir : M) {
        for (int x : satir) cout << x << "\t";
        cout << endl;
    }
}

int main() {
    Matris M = {
        {1, 2, 3},
        {4, 5, 6}
    };

    cout << "Orijinal (2x3):" << endl;
    matris_yazdir(M);

    Matris T = transpoze(M);
    cout << "Transpoze (3x2):" << endl;
    matris_yazdir(T);

    return 0;
}

Pratik Örnek: Tic-Tac-Toe Tahtası

2D dizilerin güzel bir kullanım alanı: oyun tahtaları.

#include <iostream>
#include <vector>
using namespace std;

void tahta_yazdir(const vector<vector<char>>& tahta) {
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            cout << " " << tahta[i][j] << " ";
            if (j < 2) cout << "|";
        }
        cout << endl;
        if (i < 2) cout << "-----------" << endl;
    }
}

int main() {
    vector<vector<char>> tahta = {
        {'X', 'O', 'X'},
        {'.', 'X', 'O'},
        {'O', '.', 'X'}
    };

    tahta_yazdir(tahta);

    // Bir hamle yap
    tahta[1][0] = 'O';
    cout << "\nHamle sonrası:" << endl;
    tahta_yazdir(tahta);

    return 0;
}

Çıktı:

 X | O | X
-----------
 . | X | O
-----------
 O | . | X

Hamle sonrası:
 X | O | X
-----------
 O | X | O
-----------
 O | . | X

Bellek Düzeni: Row-Major vs Column-Major

C++ 2D dizileri row-major (satır öncelikli) düzende saklar. Yani dizi[0][0], dizi[0][1], dizi[0][2], dizi[1][0], ... şeklinde bellekte yan yana durur.

Bu neden önemli? Cache dostu erişim için iç döngüyü sütunlar üzerinde yapmalısın:

#include <iostream>
#include <vector>
using namespace std;

int main() {
    const int N = 1000;
    vector<vector<int>> matris(N, vector<int>(N, 1));

    long long toplam = 0;

    // İYİ — satır satır git (cache dostu)
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < N; j++) {
            toplam += matris[i][j];
        }
    }

    // KÖTÜ — sütun sütun git (cache düşmanı)
    toplam = 0;
    for (int j = 0; j < N; j++) {
        for (int i = 0; i < N; i++) {
            toplam += matris[i][j];  // her adımda farklı satıra atla
        }
    }

    cout << "Toplam: " << toplam << endl;
    return 0;
}

💡 İpucu: Büyük matrislerde iç döngünün sütun indeksini (j) gezmesi, dış döngünün satır indeksini (i) gezmesi daha performanslıdır. Bu küçük detay büyük veri setlerinde 5-10x fark yaratabilir.


3D ve Daha Fazla Boyut

İhtiyaç olursa 3D ve daha fazla boyut da kullanılabilir ama pratikte nadirdir:

#include <iostream>
#include <vector>
using namespace std;

int main() {
    // 3D: 2 sayfa, 3 satır, 4 sütun
    vector<vector<vector<int>>> kup(
        2, vector<vector<int>>(3, vector<int>(4, 0))
    );

    kup[0][1][2] = 42;
    cout << kup[0][1][2] << endl;  // 42

    // Gezinme
    for (auto& sayfa : kup) {
        for (auto& satir : sayfa) {
            for (int x : satir) {
                cout << x << " ";
            }
            cout << endl;
        }
        cout << "---" << endl;
    }

    return 0;
}

3D'den sonrası genellikle kafa karıştırıcı olur. O noktada düz bir 1D vector ile indeks = i * sutun * derinlik + j * derinlik + k formülü kullanmak daha pratik.


Özet

  • 2D dizi satır ve sütunlardan oluşan tablo yapısıdır. C-style'da int dizi[satir][sutun], modern C++'ta vector<vector<int>> ile tanımlanır.

  • C-style 2D dizileri fonksiyona geçirirken sütun sayısı sabit olarak belirtilmelidir; vector<vector<int>> bu sorunu ortadan kaldırır.

  • Nested range-based for ile 2D yapılar temiz şekilde gezilir; dış döngüde const auto& kullanmak şarttır (kopyalamayı önlemek için).

  • Matris toplama karşılıklı elemanları toplar; matris çarpma üçlü iç içe döngü gerektirir ve A'nın sütun sayısı B'nin satır sayısına eşit olmalıdır.

  • C++ dizileri row-major düzende saklanır; büyük matrislerde iç döngünün sütunları gezmesi cache performansı açısından kritiktir.

  • Pratikte vector<vector<int>> tercih et — dinamik boyut, kolay fonksiyon geçirme ve boyut bilgisi avantajları sunar.