Bölüm 5: Polinomlar ve Seyrek Matrisler

C ile Veri Yapıları ve Algoritmalar
Polinom ADTTerim dizisipaddBağlı liste ile çarpımÜçlü gösterimtranspose · fast_transposeSeyrek çarpımCSR
Sercan KÜLCÜ · Giresun Üniversitesi · Bilgisayar Mühendisliği

Giriş

Öğrenme Çıktıları

Gösterim seçmek

Polinom için katsayı dizisi ile terim dizisini bellek ve zaman açısından karşılaştırmak.

Birleştirme (merge) mantığı

Sıralı iki terim dizisini padd ile O(n + m)'de toplamak ve analiz etmek.

Bağlı liste ile çarpım

Polinomları bağlı listeyle çarpıp aynı üslü terimleri birleştirmek.

Seyrek matris

Matrisi ⟨row, col, value⟩ üçlüleriyle tutup ne zaman kazandırdığını söylemek.

Transpoz almak

transpose ve fast_transpose'u izleyip O(cols·terms) ile O(cols + terms)'ü karşılaştırmak.

Çarpmak

Seyrek matris çarpımını transpoz yardımıyla kurmak; CSR gösterimini tanımak.

Polinom ADT ve Gösterim · Horowitz 2.4

Polinom: ⟨üs, katsayı⟩ çiftlerinden oluşan küme

A(x) = anxn + … + a1x + a0; her terim aixi bir katsayı ve bir üstür. Derece: sıfır olmayan en büyük üs.

İşlemAnlamı
Zero(), IsZero(p)sıfır polinomu kur / sına
Coef(p, e)e üslü terimin katsayısı (yoksa 0)
LeadExp(p)en büyük üs
Attach(p, c, e), Remove(p, e)terim ekle / çıkar
SingleMult(p, c, e)p · c xe
Add(p1, p2), Mult(p1, p2)toplam, çarpım
Toplama

A(x) + B(x) = Σ (ai + bi)·xi: aynı üslü katsayılar toplanır.

Çarpma

A(x)·B(x) = Σ (aixi · Σ bjxj): üsler toplanır, katsayılar çarpılır.

Polinom ADT ve Gösterim

1. yöntem (kötü): tüm katsayıları tut

#define MAX_DEGREE 101   /* azami derece + 1 */
typedef struct {
  int degree;               /* en büyük üs */
  float coef[MAX_DEGREE];   /* coef[i]: x^i katsayısı */
} polynomial;
polynomial P;
Artısı

degree saklanınca derece her seferinde hesaplanmaz; Coef ve toplama çok basittir.

Eksisi

Katsayısı 0 olan terimler de tutulur: seyrek polinomda büyük bellek israfı.

00102030405060…9901005P(x) = 5x¹⁰⁰ için coef[0..99] boşuna sıfır tutar: 101 float, 1 anlamlı

Polinom ADT ve Gösterim

2. yöntem (daha iyi): yalnız sıfır olmayan terimler

#define MAX_TERMS 101   /* azami terim sayısı */
typedef struct {
  float coef;            /* katsayı */
  int expon;             /* üs */
} polynomial;
polynomial P[MAX_TERMS]; /* tüm polinomlar burada */
int avail = 0;           /* ilk boş konum */

Terimler büyük üsten küçüğe sıralı tutulur; her polinom P içinde bir [start, finish] aralığıdır.

Hocadan “Merge mantığı, sıralı yapılar üzerinde daha etkin algoritmalar üretmemize olanak sağlar.”
coefexpon[0]320[1]25[2]40[3]15[4]103[5]32[6]10[7][8]▲ sA▲ fA▲ sB▲ fB▲ avail

A(x) = 3x²⁰ + 2x⁵ + 4  ·  B(x) = x⁵ + 10x³ + 3x² + 1

Polinom Toplama

İki işaretçiyle birleştirerek toplamak

  • Üsler eşit: katsayılar toplanır; toplam ≠ 0 ise sonuca eklenir; iki işaretçi de ilerler.
  • A'nınki büyük: “A'daki üsse sahip bir terim B'de yoktur” — B'nin kalan terimleri daha küçüktür. A'nın terimi eklenir, yalnız A ilerler.
  • B'ninki büyük: simetrik; B'nin terimi eklenir.
  • Birinde terim kalmayınca diğerinin kalanı olduğu gibi kopyalanır.
#define COMPARE(x, y) \
  ((x) < (y) ? -1 : (y) < (x) ? 1 : 0)

void attach(float coef, int expon) {
  if (avail >= MAX_TERMS) {
    fprintf(stderr, "Dizi tasmasi!\n"); exit(1);
  }
  P[avail].coef = coef;
  P[avail++].expon = expon;
}
#define EK(k) attach(P[k].coef, P[k].expon)
attach(int coef, …) yazılırsa float katsayı int’e kırpılır (2.5 → 2). Parametre float olmalı.

Polinom Toplama · Kodu adım adım çalıştır

padd: A + B terim terim

coefexpon[0]43[1]22[2]50[3]33[4]-22[5]11[6][7][8][9]▲ sa▲ sb▲ avail

A = 4x³ + 2x² + 5 → P[0..2], B = 3x³ − 2x² + x → P[3..5]. Sonuç D, avail = 6'dan başlar: *sd = 6.

coefexpon[0]43[1]22[2]50[3]33[4]-22[5]11[6][7][8][9]▲ sa▲ sb▲ avail

COMPARE(3, 3) = 0: üsler eşit.

coefexpon[0]43[1]22[2]50[3]33[4]-22[5]11[6][7][8][9]▲ sa▲ sb▲ avail

c = 4 + (3) = 7.

coefexpon[0]43[1]22[2]50[3]33[4]-22[5]11[6]73[7][8][9]▲ sa▲ sb▲ avail

c ≠ 0 → attach(7, 3): P[6] = 7x³.

coefexpon[0]43[1]22[2]50[3]33[4]-22[5]11[6]73[7][8][9]▲ sa▲ sb▲ avail

İki işaretçi de ilerler.

coefexpon[0]43[1]22[2]50[3]33[4]-22[5]11[6]73[7][8][9]▲ sa▲ sb▲ avail

COMPARE(2, 2) = 0: üsler eşit.

coefexpon[0]43[1]22[2]50[3]33[4]-22[5]11[6]73[7][8][9]▲ sa▲ sb▲ avail

c = 2 + (-2) = 0.

coefexpon[0]43[1]22[2]50[3]33[4]-22[5]11[6]73[7][8][9]▲ sa▲ sb▲ avail

c = 0 → terim sonuca eklenmez (x² birbirini götürdü).

coefexpon[0]43[1]22[2]50[3]33[4]-22[5]11[6]73[7][8][9]▲ sa▲ sb▲ avail

İki işaretçi de ilerler.

coefexpon[0]43[1]22[2]50[3]33[4]-22[5]11[6]73[7][8][9]▲ sa▲ sb▲ avail

COMPARE(0, 1) = -1: B'nin üssü büyük.

coefexpon[0]43[1]22[2]50[3]33[4]-22[5]11[6]73[7]11[8][9]▲ sa▲ avail

B'nin terimi (x) D'ye eklenir; yalnız sb ilerler.

coefexpon[0]43[1]22[2]50[3]33[4]-22[5]11[6]73[7]11[8][9]▲ sa▲ avail

sb = 6 > fb = 5 → B bitti, döngüden çıkılır.

coefexpon[0]43[1]22[2]50[3]33[4]-22[5]11[6]73[7]11[8]50[9]▲ avail

A'da kalan 5 olduğu gibi kopyalanır.

coefexpon[0]43[1]22[2]50[3]33[4]-22[5]11[6]73[7]11[8]50[9]▲ avail

*fd = 8. D(x) = P[6..8] = 7x³ + x + 5.

void padd(int sa,int fa,int sb,int fb,int*sd,int*fd){
  float c; *sd = avail;               /* D'nin başı */
  while (sa <= fa && sb <= fb)
    switch (COMPARE(P[sa].expon, P[sb].expon)) {
    case -1: EK(sb); sb++; break;     /* B büyük */
    case  1: EK(sa); sa++; break;     /* A büyük */
    case  0: c = P[sa].coef + P[sb].coef;
      if (c) attach(c, P[sa].expon); /* 0: ekleme */
      sa++; sb++;
    }
  for (; sa <= fa; sa++) EK(sa);      /* A'da kalan */
  for (; sb <= fb; sb++) EK(sb);      /* B'de kalan */
  *fd = avail - 1;
}

Polinom Toplama

padd'in karmaşıklığı

  • A'da n, B'de m terim: her döngü turunda en az bir işaretçi ilerler → en çok n + m tur.
  • Kalan kopyalama döngüleri de toplamda n + m'yi aşmaz → O(n + m).
  • En iyi durum: tüm üsler ortak (n = m); her tur case 0, iki işaretçi birlikte ilerler → Ω(n).
  • Sıralı olmasaydı: her terim için sonuçta arama/araya ekleme (kaydırma) → O(n²) düzeyine çıkardı.
Hocadan “İstenen O(n+m) ise, daha iyisi yapılabiliyorsa ne âlâ!”
Ortak P dizisinin bedeli

Sonuçlar hep avail'dan eklenir; artık polinomların yeri geri kazanılmaz. P dolunca sıkıştırma (compaction) gerekir — ya da bağlı liste.

Polinom Toplama · ödev

ÖDEV 2: pmult, n polinomu toplamak ve çarpmak

  1. İki polinomu (P dizisi gösterimiyle) çarpan pmult yordamını yazın ve performans analizini yapın.
  2. n tane polinomu nasıl toplardınız? Veri yapısını kurun.
  3. n tane polinomu nasıl çarpardınız? Veri yapısını kurun.
Çözüm
  1. A'nın her aixei terimi için SingleMult ile B çarpılıp (m terim) ara sonuca padd ile eklenir. Ara sonuç en çok n·m terim → toplam O(n·(nm + m)) = O(n²m). Küçük polinom dış döngüye alınırsa O(min(n,m)²·max(n,m)).
  2. Polinomlar P'de start[k], finish[k] dizileriyle tutulur. Art arda padd: toplam N terimde en kötü O(n·N). Daha iyisi: n işaretçiyi aynı anda ilerletip her adımda en büyük üssü seçmek; bir öncelikli kuyruk (heap) ile O(N log n).
  3. Soldan sağa çarpım ara sonuçları büyütür; ikişer ikişer (ağaç biçiminde) çarpmak dengeli boyutlarla çalışır. Ara sonuçlar için aynı start/finish tabloları ya da bağlı listeler kullanılır.

Bağlı Liste ile Polinom · Horowitz 4.4

Polinomu bağlı listeyle tutmak

typedef struct pol_oge *pol_gosterge;
typedef struct pol_oge {
  int katsayi;
  int us;
  pol_gosterge bag;
} pol_oge;
  • Yalnız sıfır olmayan terimler, üsse göre azalan sırada.
  • Terim sayısı önceden bilinmez; ortak dizi, avail ve sıkıştırma derdi yok.
  • Araya ekleme/silme işaretçi değiştirerek O(1) (yeri bulunduktan sonra).
katsayiusbag3142810∅

a = 3x¹⁴ + 2x⁸ + 1

Toplama yine birleştirme

Liste padd'i de iki işaretçiyle yürür: O(n + m); sonuç düğümleri malloc ile gelir, iş bitince free edilmeli (Horowitz 4.4).

Bağlı Liste ile Polinom

Yardımcılar: YeniTerimEkle ve SonrakiTerimiSil

/* terim oluştur; Onceki varsa peşine ekle */
pol_gosterge YeniTerimEkle(int Us, int Katsayi,
                           pol_gosterge Onceki) {
  pol_gosterge p = malloc(sizeof *p);
  if (!p) { printf("Bellek yetmiyor!\n"); exit(1); }
  p->us = Us;  p->katsayi = Katsayi;
  if (Onceki) {              /* araya ya da sona */
    p->bag = Onceki->bag;    /* önce yeni düğüm */
    Onceki->bag = p;         /* sonra önceki */
  } else p->bag = NULL;      /* tek başına */
  return p;
}
/* Onceki'den sonrakini sil */
void SonrakiTerimiSil(
        pol_gosterge Onceki) {
  pol_gosterge Silinecek =
        Onceki->bag;
  if (!Silinecek) return;
  Onceki->bag =
        Silinecek->bag;
  free(Silinecek);
}

Adres önce Silinecek’de tutulur: bağ güncellendikten sonra free edilebilsin; “bellekteki kalıntı temizlenmelidir”.

Bağlı Liste ile Polinom

Liste olarak verilen iki polinomu çarpmak

pol_gosterge yerlestir(pol_gosterge r,
                       int us, int kat) {
  while (r->bag && r->bag->us > us) r = r->bag;
  if (!r->bag || r->bag->us < us)
    YeniTerimEkle(us, kat, r);         /* yeni üs */
  else if ((r->bag->katsayi += kat) == 0)
    SonrakiTerimiSil(r);               /* 0 oldu */
  return r;
}
  • A'nın her terimi B'nin her terimiyle çarpılır: üsler toplanır, katsayılar çarpılır.
  • Sonuç listesi sıralı kalmalı; aynı üslü terim varsa katsayı güncellenir, 0 olursa terim silinir.
  • baslangic (bas): p'nin bir sonraki terimiyle çarpımlar, bir öncekinin q1 ile çarpımının önünden başlayamaz → arama oradan başlar.
  • Okunaklılık için iç döngü gövdesi yerlestir yordamına ayrıldı.

Bağlı Liste ile Polinom · Kodu adım adım çalıştır

PolCarp: (x + 1)·(x − 1)

katsayiusbagrBas = NULL (boş liste)A = x + 1 · B = x − 1

A = x + 1, B = x − 1 çarpılacak (beklenen x² − 1). Sonuç listesi boş.

katsayiusbagrBas = NULL (boş liste)p: x q: —

p = x. r = bas = NULL (henüz liste yok).

katsayiusbagrBas = NULL (boş liste)p: x q: x

us = 1 + 1 = 2, kat = 1·(1) = 1.

katsayiusbag12∅▲ rBas, r, basp: x q: x

Liste boş → ilk düğüm x²; rBas = r = bas.

katsayiusbag12∅▲ r, basp: x q: x

q ilk terimdi → bas = r = &(1,2): p'nin sonraki terimi buradan aramaya başlayacak.

katsayiusbag12∅▲ r, basp: x q: −1

us = 1 + 0 = 1, kat = 1·(-1) = -1.

katsayiusbag12-11∅▲ r, basp: x q: −1

yerlestir: r'den sonra 1 üslü terim yok → yeni düğüm −x r'nin peşine eklenir.

katsayiusbag12-11∅▲ r, basp: 1 q: —

p = 1. r = bas: arama baştan değil, &(1,2) düğümünden başlar.

katsayiusbag12-11∅▲ r, basp: 1 q: x

us = 0 + 1 = 1, kat = 1·(1) = 1.

katsayiusbag12∅▲ r, basp: 1 q: x

yerlestir: 1 üslü terim zaten var; katsayı −1 + 1 = 0 → SonrakiTerimiSil(r) ile silinir.

katsayiusbag12∅▲ r, basp: 1 q: x

q ilk terimdi → bas = r = &(1,2): p'nin sonraki terimi buradan aramaya başlayacak.

katsayiusbag12∅▲ r, basp: 1 q: −1

us = 0 + 0 = 0, kat = 1·(-1) = -1.

katsayiusbag12-10∅▲ r, basp: 1 q: −1

yerlestir: r'den sonra 0 üslü terim yok → yeni düğüm −1 r'nin peşine eklenir.

katsayiusbag12-10∅▲ rBas(x + 1)(x − 1) = x² − 1

Sonuç: x² − 1. n·m = 4 çarpım yapıldı; aynı üslü −x ve +x birleşip silindi.

pol_gosterge PolCarp(pol_gosterge pBas,
                     pol_gosterge qBas) {
  pol_gosterge p, q, r = NULL, bas = NULL, rBas=NULL;
  for (p = pBas; p; p = p->bag) {
    r = bas;               /* aramaya buradan başla */
    for (q = qBas; q; q = q->bag) {
      int us = p->us + q->us;
      int kat = p->katsayi * q->katsayi;
      if (rBas) r = yerlestir(r, us, kat);
      else rBas=r=bas=YeniTerimEkle(us, kat, NULL);
      if (q == qBas) bas = r;  /* sonraki p için */
    }
  }
  return rBas;
}

Bağlı Liste ile Polinom

Problemin diğer çözümleri (A: n, B: m terim)

yöntemkarmaşıklık
1Hepsi çarpılır, her gelen sona eklenir; sonra sıralanır ve birleştirilirO(n²m²)
2Hepsi çarpılır; her seferinde baştan taranıp uygun konuma eklenirO(n²m²)
3A'nın her terimiyle çarpım için bir liste kurulur, her adımda sonuçla birleştirilir (merge)O(n²m)
43. yöntem, küçük polinom dıştaO(min(n,m)²·max(n,m))
5A'nın her terimi için ayrı liste; en sonda hepsi (ikişer ikişer) birleştirilirO(m·n·lg n)
65. yöntem, küçük polinom dıştaO(m·n·lg min(m,n))
7Dizi açılır, n. indis n. üssü tutar—
Hocadan “n. indisin n. üssü tuttuğu bir dizi mi? ASLA!”

Seyrek Matris

Seyrek matris: öğelerin çoğu sıfır

00151020322405-15101130002000-60030000004910000050028000

36 hücreden yalnız 8'i dolu

Kesin tanımı zordur; 2B dizide tutmak çok sayıda sıfır için bellek kaybıdır. Çözüm: her dolu öğeyi ⟨row, col, value⟩ üçlüsüyle tutmak.

#define MAX_TERMS 101
typedef struct {
  int row, col, value;   /* satır, sütun, değer */
} term;
term M[MAX_TERMS];

M[0] başlıktır: satır sayısı, sütun sayısı, dolu öğe sayısı. Üçlüler önce satıra, sonra sütuna göre sıralıdır.

Seyrek Matris

Üçlü tablo ve ne zaman kazandırır?

rowcolvalueM[0]668M[1]0015M[2]0322M[3]05-15M[4]1111M[5]123M[6]23-6M[7]4091M[8]5228
Bellek

rows·cols tamsayı yerine 3·(t + 1). t ≈ rows·cols/3 olunca eşitlenir; yani matrisin 3'te 1'i dolu olduğunda bellek maliyeti aynıdır. Daha doluysa bu yapı tercih edilmez.

Bu örnekte

36 tamsayı yerine 3·9 = 27 tamsayı.

ADT işlemleri

Create(max_row, max_col), Transpose(a), Add(a, b), Multiply(a, b) — Horowitz ADT 2.3. Başka gösterimler de var (bağlı listeli, CSR…).

Transpoz Alma

Transpoz: M[i][j] → M′[j][i]

Satırları sütun olarak yazarız:

M1304→M′1034
  • Üçlülerde basitçe row ile col'u değiştirmek yetmez: sonuç satır sırasını bozar.
  • Değiştirip sonra sıralamak ya da her öğeyi yerine kaydırarak eklemek pahalıdır.
  • Çözüm: transpozun satırları = M'nin sütunları; sütun sütun taranıp sırayla üretilir.
Hocadan “Kaydırma, taşıma kullanılıyorsa veri yapısının hakkı verilmiyor demektir!”
transpose iç döngüsünü j = 0’dan başlatmak başlık M[0]’ı da tarar. (M[0].col = sütun sayısı hiçbir i’ye eşit olmadığından sonuç bozulmuyor, ama gereksiz.) Doğrusu j = 1.

Transpoz Alma · Kodu adım adım çalıştır

transpose: her sütun için tüm terimleri tara

rowcolvalueM[0]344M[1]007M[2]033M[3]114M[4]202rowcolvalueT[0]434T[1]T[2]T[3]T[4]0071020331040022000M (3×4)

Başlık: T 4×3 olur (satır ↔ sütun), yine 4 terim.

rowcolvalueM[0]344M[1]007M[2]033M[3]114M[4]202rowcolvalueT[0]434T[1]007T[2]T[3]T[4]sütun 00071020331040022000M (3×4)

Sütun 0: M[1].col = 0 eşit → T[1] = (0, 0, 7), cur = 2.

rowcolvalueM[0]344M[1]007M[2]033M[3]114M[4]202rowcolvalueT[0]434T[1]007T[2]T[3]T[4]sütun 00071020331040022000M (3×4)

Sütun 0: M[2].col = 3 ≠ 0 → atla.

rowcolvalueM[0]344M[1]007M[2]033M[3]114M[4]202rowcolvalueT[0]434T[1]007T[2]T[3]T[4]sütun 00071020331040022000M (3×4)

Sütun 0: M[3].col = 1 ≠ 0 → atla.

rowcolvalueM[0]344M[1]007M[2]033M[3]114M[4]202rowcolvalueT[0]434T[1]007T[2]022T[3]T[4]sütun 00071020331040022000M (3×4)

Sütun 0: M[4].col = 0 eşit → T[2] = (0, 2, 2), cur = 3.

rowcolvalueM[0]344M[1]007M[2]033M[3]114M[4]202rowcolvalueT[0]434T[1]007T[2]022T[3]T[4]sütun 10071020331040022000M (3×4)

Sütun 1: M[1].col = 0 ≠ 1 → atla.

rowcolvalueM[0]344M[1]007M[2]033M[3]114M[4]202rowcolvalueT[0]434T[1]007T[2]022T[3]T[4]sütun 10071020331040022000M (3×4)

Sütun 1: M[2].col = 3 ≠ 1 → atla.

rowcolvalueM[0]344M[1]007M[2]033M[3]114M[4]202rowcolvalueT[0]434T[1]007T[2]022T[3]114T[4]sütun 10071020331040022000M (3×4)

Sütun 1: M[3].col = 1 eşit → T[3] = (1, 1, 4), cur = 4.

rowcolvalueM[0]344M[1]007M[2]033M[3]114M[4]202rowcolvalueT[0]434T[1]007T[2]022T[3]114T[4]sütun 10071020331040022000M (3×4)

Sütun 1: M[4].col = 0 ≠ 1 → atla.

rowcolvalueM[0]344M[1]007M[2]033M[3]114M[4]202rowcolvalueT[0]434T[1]007T[2]022T[3]114T[4]sütun 20071020331040022000M (3×4)

Sütun 2: M[1].col = 0 ≠ 2 → atla.

rowcolvalueM[0]344M[1]007M[2]033M[3]114M[4]202rowcolvalueT[0]434T[1]007T[2]022T[3]114T[4]sütun 20071020331040022000M (3×4)

Sütun 2: M[2].col = 3 ≠ 2 → atla.

rowcolvalueM[0]344M[1]007M[2]033M[3]114M[4]202rowcolvalueT[0]434T[1]007T[2]022T[3]114T[4]sütun 20071020331040022000M (3×4)

Sütun 2: M[3].col = 1 ≠ 2 → atla.

rowcolvalueM[0]344M[1]007M[2]033M[3]114M[4]202rowcolvalueT[0]434T[1]007T[2]022T[3]114T[4]sütun 20071020331040022000M (3×4)

Sütun 2: M[4].col = 0 ≠ 2 → atla.

rowcolvalueM[0]344M[1]007M[2]033M[3]114M[4]202rowcolvalueT[0]434T[1]007T[2]022T[3]114T[4]sütun 30071020331040022000M (3×4)

Sütun 3: M[1].col = 0 ≠ 3 → atla.

rowcolvalueM[0]344M[1]007M[2]033M[3]114M[4]202rowcolvalueT[0]434T[1]007T[2]022T[3]114T[4]303sütun 30071020331040022000M (3×4)

Sütun 3: M[2].col = 3 eşit → T[4] = (3, 0, 3), cur = 5.

rowcolvalueM[0]344M[1]007M[2]033M[3]114M[4]202rowcolvalueT[0]434T[1]007T[2]022T[3]114T[4]303sütun 30071020331040022000M (3×4)

Sütun 3: M[3].col = 1 ≠ 3 → atla.

rowcolvalueM[0]344M[1]007M[2]033M[3]114M[4]202rowcolvalueT[0]434T[1]007T[2]022T[3]114T[4]303sütun 30071020331040022000M (3×4)

Sütun 3: M[4].col = 0 ≠ 3 → atla.

rowcolvalueM[0]344M[1]007M[2]033M[3]114M[4]202rowcolvalueT[0]434T[1]007T[2]022T[3]114T[4]3030071020331040022000M (3×4)

Bitti: T satır sırasına göre kendiliğinden sıralı (M'de satırlar sıralıydı). 4 sütun × 4 terim = 16 karşılaştırma.

void transpose(term M[], term T[]) {
  int n = M[0].value, i, j, cur;
  T[0].row = M[0].col;  T[0].col = M[0].row;
  T[0].value = n;
  if (n > 0) {
    cur = 1;                       /* T'de sıradaki */
    for (i = 0; i < M[0].col; i++)     /* her sütun */
      for (j = 1; j <= n; j++)         /* her terim */
        if (M[j].col == i) {
          T[cur].row = M[j].col; T[cur].col = M[j].row;
          T[cur].value = M[j].value;  cur++;
        }
  }
}

Transpoz Alma

transpose'un karmaşıklığı

  • Dış döngü cols kez, iç döngü terms kez → O(cols · terms).
  • Matris doluysa terms = rows·cols → O(cols² · rows): 2B dizide transpoz O(rows·cols) iken daha kötü!
  • Yani bellekten kazanırken zamandan kaybettik — terms ≪ rows·cols olduğunda anlamlı.
  • Sorun: aynı M dizisini her sütun için yeniden taramak.
Çıkış yolu

Her terimin T'de nereye gideceğini önceden bilseydik, M'yi bir kez tarayıp her terimi doğrudan yerine koyardık.

Örnek

Transpozun 0. satırında 2, 1. satırında 3 öğe olacaksa: 0. satır T[1]'den, 1. satır T[3]'ten, 2. satır T[6]'dan başlar.

Transpoz Alma

fast_transpose: önce sayma, sonra yerleştirme

void fast_transpose(term M[], term T[]) {
  int i, j, nc = M[0].col, nt = M[0].value;
  int row_terms[MAX_COL], start_pos[MAX_COL];
  T[0].row = nc;  T[0].col = M[0].row;  T[0].value = nt;
  if (nt <= 0) return;                    /* boş matris */
  for (i = 0; i < nc; i++) row_terms[i] = 0;
  for (i = 1; i <= nt; i++) row_terms[M[i].col]++;
  start_pos[0] = 1;
  for (i = 1; i < nc; i++)
    start_pos[i] = start_pos[i-1] + row_terms[i-1];
  for (i = 1; i <= nt; i++) {
    j = start_pos[M[i].col]++;       /* yerini al, artır */
    T[j].row = M[i].col;  T[j].col = M[i].row;
    T[j].value = M[i].value;
  }
}
rowcolvalueT[0]668T[1]0015T[2]0491T[3]1111T[4]213T[5]2528T[6]3022T[7]32-6T[8]50-15

6×6 örnekte row_terms = [2, 1, 2, 2, 0, 1], start_pos = [1, 3, 4, 6, 8, 8].

Transpoz Alma · Kodu adım adım çalıştır

fast_transpose (1): say ve başlangıçları bul

rowcolvalueM[0]344M[1]007M[2]033M[3]114M[4]202row_termsstart_possütun 0sütun 1sütun 2sütun 3

Başlık kopyalanır: T 4×3, 4 terim. nt > 0 → devam.

rowcolvalueM[0]344M[1]007M[2]033M[3]114M[4]202row_terms0000start_possütun 0sütun 1sütun 2sütun 3

row_terms sıfırlanır: T'nin her satırı (= M'nin her sütunu) için sayaç.

rowcolvalueM[0]344M[1]007M[2]033M[3]114M[4]202row_terms1000start_possütun 0sütun 1sütun 2sütun 3

M[1] sütun 0'da → row_terms[0]++ = 1.

rowcolvalueM[0]344M[1]007M[2]033M[3]114M[4]202row_terms1001start_possütun 0sütun 1sütun 2sütun 3

M[2] sütun 3'te → row_terms[3]++ = 1.

rowcolvalueM[0]344M[1]007M[2]033M[3]114M[4]202row_terms1101start_possütun 0sütun 1sütun 2sütun 3

M[3] sütun 1'de → row_terms[1]++ = 1.

rowcolvalueM[0]344M[1]007M[2]033M[3]114M[4]202row_terms2101start_possütun 0sütun 1sütun 2sütun 3

M[4] sütun 0'da → row_terms[0]++ = 2.

rowcolvalueM[0]344M[1]007M[2]033M[3]114M[4]202row_terms2101start_pos1sütun 0sütun 1sütun 2sütun 3

0. sütunun terimleri T[1]'den başlayacak: start_pos[0] = 1.

rowcolvalueM[0]344M[1]007M[2]033M[3]114M[4]202row_terms2101start_pos13sütun 0sütun 1sütun 2sütun 3

start_pos[1] = 1 + 2 = 3.

rowcolvalueM[0]344M[1]007M[2]033M[3]114M[4]202row_terms2101start_pos134sütun 0sütun 1sütun 2sütun 3

start_pos[2] = 3 + 1 = 4.

rowcolvalueM[0]344M[1]007M[2]033M[3]114M[4]202row_terms2101start_pos1344sütun 0sütun 1sütun 2sütun 3

start_pos[3] = 4 + 0 = 4 (2. sütun boş: 3. sütun da 4'ten başlar).

void fast_transpose(term M[], term T[]) {
  int i, j, nc = M[0].col, nt = M[0].value;
  int row_terms[MAX_COL], start_pos[MAX_COL];
  T[0].row = nc;  T[0].col = M[0].row;
  T[0].value = nt;
  if (nt <= 0) return;                  /* boş */
  for (i = 0; i < nc; i++) row_terms[i] = 0;
  for (i = 1; i <= nt; i++) row_terms[M[i].col]++;
  start_pos[0] = 1;
  for (i = 1; i < nc; i++)
    start_pos[i] = start_pos[i-1] + row_terms[i-1];
  /* 2. aşama: terimleri yerine koy */

Transpoz Alma · Kodu adım adım çalıştır

fast_transpose (2): terimleri yerine koy

rowcolvalueM[0]344M[1]007M[2]033M[3]114M[4]202row_terms2101start_pos1344sütun 0sütun 1sütun 2sütun 3rowcolvalueT[0]434T[1]T[2]T[3]T[4]

M bir kez, baştan sona taranır; her terim doğrudan yerine gider.

rowcolvalueM[0]344M[1]007M[2]033M[3]114M[4]202row_terms2101start_pos2344sütun 0sütun 1sütun 2sütun 3rowcolvalueT[0]434T[1]007T[2]T[3]T[4]

M[1] = (0, 0, 7): j = start_pos[0]++ → j = 1, start_pos[0] = 2.

rowcolvalueM[0]344M[1]007M[2]033M[3]114M[4]202row_terms2101start_pos2344sütun 0sütun 1sütun 2sütun 3rowcolvalueT[0]434T[1]007T[2]T[3]T[4]

T[1] = (0, 0, 7): satır/sütun yer değiştirdi.

rowcolvalueM[0]344M[1]007M[2]033M[3]114M[4]202row_terms2101start_pos2345sütun 0sütun 1sütun 2sütun 3rowcolvalueT[0]434T[1]007T[2]T[3]T[4]303

M[2] = (0, 3, 3): j = start_pos[3]++ → j = 4, start_pos[3] = 5.

rowcolvalueM[0]344M[1]007M[2]033M[3]114M[4]202row_terms2101start_pos2345sütun 0sütun 1sütun 2sütun 3rowcolvalueT[0]434T[1]007T[2]T[3]T[4]303

T[4] = (3, 0, 3): satır/sütun yer değiştirdi.

rowcolvalueM[0]344M[1]007M[2]033M[3]114M[4]202row_terms2101start_pos2445sütun 0sütun 1sütun 2sütun 3rowcolvalueT[0]434T[1]007T[2]T[3]114T[4]303

M[3] = (1, 1, 4): j = start_pos[1]++ → j = 3, start_pos[1] = 4.

rowcolvalueM[0]344M[1]007M[2]033M[3]114M[4]202row_terms2101start_pos2445sütun 0sütun 1sütun 2sütun 3rowcolvalueT[0]434T[1]007T[2]T[3]114T[4]303

T[3] = (1, 1, 4): satır/sütun yer değiştirdi.

rowcolvalueM[0]344M[1]007M[2]033M[3]114M[4]202row_terms2101start_pos3445sütun 0sütun 1sütun 2sütun 3rowcolvalueT[0]434T[1]007T[2]022T[3]114T[4]303

M[4] = (2, 0, 2): j = start_pos[0]++ → j = 2, start_pos[0] = 3.

rowcolvalueM[0]344M[1]007M[2]033M[3]114M[4]202row_terms2101start_pos3445sütun 0sütun 1sütun 2sütun 3rowcolvalueT[0]434T[1]007T[2]022T[3]114T[4]303

T[2] = (0, 2, 2): satır/sütun yer değiştirdi.

rowcolvalueM[0]344M[1]007M[2]033M[3]114M[4]202row_terms2101start_pos3445sütun 0sütun 1sütun 2sütun 3rowcolvalueT[0]434T[1]007T[2]022T[3]114T[4]303

Bitti — sonuç transpose ile aynı. İki doğrusal tarama: O(cols + terms).

  /* 1. aşama: row_terms ve start_pos hazır */
  for (i = 1; i <= nt; i++) {
    j = start_pos[M[i].col]++;  /* oku, artır */
    T[j].row = M[i].col;
    T[j].col = M[i].row;
    T[j].value = M[i].value;
  }
}
Hocadan “Programcı, program yazdığı dilin işleç önceliklerini iyi bilmelidir.”

Transpoz Alma

Analiz ve tek dizili sürüm

Döngüler: num_cols + num_terms + num_cols + num_terms (+ sabitler):

2·terms + 2·cols + 3 = O(cols + terms)

Dolu matriste O(rows·cols): 2B dizideki transpozla aynı derece. Bedeli: iki yardımcı dizi (O(cols) bellek).

Hocadan “Hızlı transpoz tek bellekle olur denmişti ya, işte bu o…”
/* row_terms'ü yerinde start_pos'a çevir */
for (i = 0, pos = 1; i < nc; i++) {
  int c = cnt[i];      /* bu sütundaki terim sayısı */
  cnt[i] = pos;        /* artık başlangıç konumu */
  pos += c;
}
for (i = 1; i <= nt; i++) {
  j = cnt[M[i].col]++;
  T[j].row = M[i].col;  T[j].col = M[i].row;
  T[j].value = M[i].value;
}

Hızlı transpoz tek bir dizi kullanılarak da yazılabilir. Bu, sayarak sıralamanın (counting sort) ta kendisi: anahtar = sütun no.

Seyrek Matris Çarpımı · Horowitz 2.5

Dm×p = Am×n · Bn×p

dij = Σk=0n−1 aik · bkj

A'nın i. satırı ile B'nin j. sütunu “çarpılıp toplanır”. İki seyrek matrisin çarpımı seyrek olmak zorunda değil:

A100100100·B111000000=D111111111
  • Üçlülerde A'nın i. satırı ardışık durur, ama B'nin j. sütunu dağınıktır.
  • Bu yüzden önce B'nin transpozu alınır (fast_transpose): B'nin sütunları Bᵀ'nin ardışık satırları olur.
  • Sonra A'nın her satırı ile Bᵀ'nin her satırı, col alanına göre birleştirilerek (merge) çarpılır.
“Önce ilk matrisin transpozu alınır” demek yanlıştır; Horowitz’in mmult’ında (ve mantık olarak) transpozu alınan ikinci matris B’dir.

Seyrek Matris Çarpımı · Horowitz Program 2.9 uyarlaması

Yardımcılar: satır sonu, nokta çarpım, sakla

/* s'deki terimin satırının bittiği yer */
int son(term t[], int s) {
  int e = s;
  while (e <= t[0].value &&
         t[e].row == t[s].row) e++;
  return e;
}
/* a[x..ex) satırı · b[y..ey) satırı */
int carp(term a[], int x, int ex,
         term b[], int y, int ey) {
  int sum = 0;
  while (x < ex && y < ey)
    if (a[x].col < b[y].col) x++;
    else if (a[x].col > b[y].col) y++;
    else sum += a[x++].value * b[y++].value;
  return sum;
}
void sakla(term d[], int *nd,
      int r, int c, int s) {
  if (s == 0) return;
  if (++*nd >= MAX_TERMS) {
    fprintf(stderr, "Cok terim\n");
    exit(1);
  }
  d[*nd].row = r;
  d[*nd].col = c;
  d[*nd].value = s;
}

carp, padd gibi iki işaretçili birleştirmedir: yalnız aynı k (col) değerli terimler çarpılır. Sıfır toplam saklanmaz.

Seyrek Matris Çarpımı · Kodu adım adım çalıştır

Seyrek çarpım: A'nın satırı · Bᵀ'nin satırı

rowcolvaluea[0]233a[1]001a[2]022a[3]113rowcolvaluebt[0]233bt[1]004bt[2]026bt[3]115rowcolvalued[0]220d[1]d[2]A (2×3)Bᵀ (B: 3×2)D = A·B (2×2)

A = [[1,0,2],[0,3,0]], B = [[4,0],[0,5],[6,0]]; B'nin transpozu bt önceden alındı.

rowcolvaluea[0]233a[1]001a[2]022a[3]113rowcolvaluebt[0]233bt[1]004bt[2]026bt[3]115rowcolvalued[0]220d[1]d[2]A (2×3)Bᵀ (B: 3×2)D = A·B (2×2)

A'nın 0. satırı: a[1..2].

rowcolvaluea[0]233a[1]001a[2]022a[3]113rowcolvaluebt[0]233bt[1]004bt[2]026bt[3]115rowcolvalued[0]220d[1]d[2]A (2×3)Bᵀ (B: 3×2)D = A·B (2×2)

B'nin 0. sütunu = bt[1..2].

rowcolvaluea[0]233a[1]001a[2]022a[3]113rowcolvaluebt[0]233bt[1]004bt[2]026bt[3]115rowcolvalued[0]221d[1]0016d[2]A (2×3)Bᵀ (B: 3×2)D = A·B (2×2)

carp: 1·4 + 2·6 = 16 → d[1] = (0, 0, 16).

rowcolvaluea[0]233a[1]001a[2]022a[3]113rowcolvaluebt[0]233bt[1]004bt[2]026bt[3]115rowcolvalued[0]221d[1]0016d[2]A (2×3)Bᵀ (B: 3×2)D = A·B (2×2)

B'nin 1. sütunu = bt[3..3].

rowcolvaluea[0]233a[1]001a[2]022a[3]113rowcolvaluebt[0]233bt[1]004bt[2]026bt[3]115rowcolvalued[0]221d[1]0016d[2]A (2×3)Bᵀ (B: 3×2)D = A·B (2×2)

carp: ortak col yok = 0 → sıfır, saklanmaz.

rowcolvaluea[0]233a[1]001a[2]022a[3]113rowcolvaluebt[0]233bt[1]004bt[2]026bt[3]115rowcolvalued[0]221d[1]0016d[2]A (2×3)Bᵀ (B: 3×2)D = A·B (2×2)

A'nın 1. satırı: a[3..3].

rowcolvaluea[0]233a[1]001a[2]022a[3]113rowcolvaluebt[0]233bt[1]004bt[2]026bt[3]115rowcolvalued[0]221d[1]0016d[2]A (2×3)Bᵀ (B: 3×2)D = A·B (2×2)

B'nin 0. sütunu = bt[1..2].

rowcolvaluea[0]233a[1]001a[2]022a[3]113rowcolvaluebt[0]233bt[1]004bt[2]026bt[3]115rowcolvalued[0]221d[1]0016d[2]A (2×3)Bᵀ (B: 3×2)D = A·B (2×2)

carp: ortak col yok = 0 → sıfır, saklanmaz.

rowcolvaluea[0]233a[1]001a[2]022a[3]113rowcolvaluebt[0]233bt[1]004bt[2]026bt[3]115rowcolvalued[0]221d[1]0016d[2]A (2×3)Bᵀ (B: 3×2)D = A·B (2×2)

B'nin 1. sütunu = bt[3..3].

rowcolvaluea[0]233a[1]001a[2]022a[3]113rowcolvaluebt[0]233bt[1]004bt[2]026bt[3]115rowcolvalued[0]222d[1]0016d[2]1115A (2×3)Bᵀ (B: 3×2)D = A·B (2×2)

carp: 3·5 = 15 → d[2] = (1, 1, 15).

rowcolvaluea[0]233a[1]001a[2]022a[3]113rowcolvaluebt[0]233bt[1]004bt[2]026bt[3]115rowcolvalued[0]222d[1]0016d[2]1115A (2×3)Bᵀ (B: 3×2)D = A·B (2×2)

Başlık yazılır: D 2×2, 2 terim. D = [[16, 0], [0, 15]].

void smult(term a[], term bt[], term d[]) {
  int ra, ea, rb, eb, nd = 0;
  for (ra = 1; ra <= a[0].value; ra = ea) {
    ea = son(a, ra);           /* A satırı [ra,ea) */
    for (rb = 1; rb <= bt[0].value; rb = eb) {
      eb = son(bt, rb);        /* B sütunu [rb,eb) */
      sakla(d, &nd, a[ra].row, bt[rb].row,
            carp(a, ra, ea, bt, rb, eb));
    }
  }
  d[0].row = a[0].row;  d[0].col = bt[0].row;
  d[0].value = nd;
}

Seyrek Matris Çarpımı · Horowitz 2.5

Seyrek çarpımın karmaşıklığı

  • Transpoz: O(colsB + tB).
  • A'nın her satırı i için Bᵀ baştan sona gezilir: Σi tB = rowsA·tB.
  • Her (i, j) çifti için A satırının terimleri de gezilir: Σj tA = colsB·tA.
  • Toplam: O(colsB·tA + rowsA·tB).
Klasik çarpım

O(rowsA·colsA·colsB). Matrisler doluysa (tA = rowsA·colsA) seyrek sürüm de aynı dereceye çıkar, sabiti daha büyüktür.

Ne zaman değer?

tA ≪ rows·cols ve tB ≪ rows·cols iken. Sonuç D'nin seyrek olacağı garanti değildir: MAX_TERMS aşılabilir.

Diğer Gösterimler

Sıkıştırılmış satır (CSR) gösterimi

val0151222-15311435-6691728col_ind03512302row_ptr03566781. satır: val[3..4]

Aynı 6×6 matris: 8 + 8 + 7 = 23 tamsayı (üçlülerde 27). i. satır: val[row_ptr[i] .. row_ptr[i+1]−1]; boş satır 3 → art arda eşit iki row_ptr.

/* y = M * x  (matris-vektör çarpımı) */
for (i = 0; i < rows; i++) {
  y[i] = 0;
  for (k = row_ptr[i]; k < row_ptr[i+1]; k++)
    y[i] += val[k] * x[col_ind[k]];
}
Kullanım

Bilimsel hesapta en yaygın biçimlerden; M·x O(rows + terms). Sütun sürümü: CSC.

Bağ

row_ptr, fast_transpose'taki start_pos ile aynı fikir (önek toplamları).

Diğer Gösterimler

Seyrek matrisin bağlı listeyle gösterimi

  • Üçlü dizide ekleme/silme kaydırma ister ve MAX_TERMS sınırı vardır.
  • Bağlı gösterimde her dolu öğe bir düğümdür; aynı satırdakiler ve aynı sütundakiler döngülü listelerle bağlanır; satır/sütun başlık düğümleri vardır (Horowitz 4.7).
  • Ekleme/silme O(1) bağ değişikliği (yer bulunduktan sonra); satıra ve sütuna göre gezinme kolay.
  • Bedeli: öğe başına iki işaretçi ek bellek.
Hocadan “Ödev kâğıdı benim kâğıdımla üst üste getirilip ışığa tutulduğunda… (fark görülmemeli)”
Nerede?

Seyrek matrisin bağlı gerçeklemesi, bağlı listelerin doğrudan bir uygulamasıdır.

Kapanış

Karmaşıklık Özeti

İşlemZamanNot
padd (n, m terim)O(n + m)sıralı terimler + birleştirme
pmult (dizi, art arda padd)O(n²m)küçük polinom dışta: O(min²·max)
PolCarp (bağlı liste)O(n²m) en kötübas ile arama kısalır
transposeO(cols·terms)dolu matriste O(cols²·rows)
fast_transposeO(cols + terms)O(cols) ek bellek
Seyrek çarpım (smult / mmult)O(colsB·tA + rowsA·tB)önce Bᵀ
CSR ile M·xO(rows + terms)satır başı dizisi

Kapanış

Sık Yapılan Hatalar

1Terimleri sırasız tutup padd'i yine de iki işaretçiyle yazmak.
2Toplamı 0 olan terimi sonuca eklemek ya da listeden silmemek.
3attach'ta float katsayıyı int parametreyle kırpmak; avail taşmasını denetlememek.
4Transpozda yalnız row/col değiştirip sıralamayı unutmak; M[0] başlığını terim sanmak.
5start_pos[M[i].col]++ ifadesinde ön ek/son ek farkını karıştırmak.
6Seyrek çarpımda A'nın transpozunu almak; sonucun seyrek kalacağını varsaymak.

Kapanış

Alıştırmalar

  1. A = 2x⁴ + 3x² − 1 ve B = −3x² + x + 1 için padd'i izleyin. D'de kaç terim olur?
  2. P dizisi gösteriminde 10 terimli iki polinomun toplamı en çok kaç attach çağrısı yapar?
  3. 5×5 matriste kaç sıfırdan fazla öğe olunca üçlü gösterim 2B diziden fazla yer kaplar?
  4. Terimleri (0,1,5), (1,0,2), (1,3,4), (2,1,1) olan 3×4 matrisin row_terms ve start_pos dizileri nedir?
  5. fast_transpose tek diziyle nasıl yazılır? Hangi sıralama algoritmasına benzer?
  6. (x² + 1)(x² − 1) çarpımında PolCarp hangi terimi siler?
İpucu / cevaplar
  1. D = 2x⁴ + x (3x² − 3x² = 0 ve −1 + 1 = 0 eklenmez): 2 terim.
  2. Hiç ortak üs yoksa 20.
  3. 3(t + 1) > 25 → t ≥ 8: 8 ve üstü dolu öğede.
  4. Sütunlar 1, 0, 3, 1 → row_terms = [1, 2, 0, 1], start_pos = [1, 2, 4, 4].
  5. row_terms'ü yerinde başlangıç konumlarına çevirip aynı diziyi artırarak; sayarak sıralama (counting sort).
  6. x² · (−1) = −x² ile 1 · x² = +x² toplanıp 0 olur: x² terimi silinir → x⁴ − 1.

Kapanış

Bölüm Özeti

Gösterim önemli

Katsayı dizisi seyrek polinomda israf; terim dizisi yalnız anlamlı terimleri tutar.

Sıralılık + birleştirme

padd O(n + m): sıralı iki diziyi iki işaretçiyle yürümek.

Bağlı liste

Terim sayısı bilinmiyorsa esnek; PolCarp aynı üsleri birleştirir, sıfırları siler.

Üçlüler

⟨row, col, value⟩ + başlık M[0]; t < rows·cols/3 iken kazançlı.

Transpoz

transpose O(cols·t); fast_transpose say–yerleştir ile O(cols + t).

Çarpım

Önce Bᵀ, sonra satır·satır birleştirme; CSR aynı “satır başı” fikrinin standart biçimi.

Kapanış

Kaynaklar ve Devam

  • E. Horowitz, S. Sahni, S. Anderson-Freed, Fundamentals of Data Structures in C, 2. bs., 2.4–2.5 (polinom, seyrek matris), 4.4 ve 4.7 (bağlı polinom ve seyrek matris).
  • Y. Saad, Iterative Methods for Sparse Linear Systems, 3.4 (CSR/CSC biçimleri).

Son

Bölüm 5: Polinomlar ve Seyrek Matrisler
Sonraki bölüm: Bölüm 6: Yığınlar · Ders sayfası
1 / 1 C ile Veri Yapıları