Java programozás 17. – Többdimenziós tömbök

Többdimenziós tömbök, avagy tömbök egy csokorban

A tömb, mint összetett adattípus az előző anyagokból már ismerős lehet. Míg a tömbök egy adatsort tartalmaznak, a többdimenziós tömbök pedig többet. A többdimenziós tömbök valójában tömbök tömbjei. A dimenziók száma elméletileg nincs korlátozva, gyakorlatilag 3 dimenziónál többel dolgozni nem feltétlenül praktikus.

Egy általános tömb deklarációja a következőképp néz ki:

// deklarálás és inicializálás, ami csak 0 értékekkel tölti fel a tömböt
int[] tomb = new int[10];

// deklarálás és azonnali kezdőérték adás
int[] tomb = {1,2,3,4,5,6,7,8,9,10};

// adott indexű elem kiválasztása
tomb[5]

Ez a többdimenziós tömbök esetén is hasonló, de mivel ezek tömbök tömbjei, ezért ezt formailag is jelezni kell.

Kétdimenziós tömbök

// kétdimenziós tömb deklarálása és inicializálása
int[][] tomb = new int[2][3];

// kétdimenziós tömb adott elemének kiválasztása
tomb[1][2]

Az előző deklarálás azt jelenti, hogy létrehozunk egy 2 sorból és 3 oszlopból álló kétdimenziós tömböt. A sorok és oszlopok sorszámozása (indexelése) itt is 0-val indul, mint általában a tömbök esetén. Mint már említettem, a többdimenziós tömb valójában tömbök tömbje, de formailag ez hogy néz ki? Nézzük meg egy konkrét példán keresztül:

int[][] tomb = { { 2,4,6 }, { 3,7,8 } };

Mit is jelent ez? Adott két sor (a két kicsi tömb darabszáma, ami a számokat tartalmazza) és adott 3 oszlop (ami a kis tömbökben lévő számok darabszámát jelenti). Láthatod, hogy a kis tömbökben lévő számok darabszáma megegyezik, ez nem véletlen. Valójában ez a szám az oszlopok száma. Hogy jobban látható legyen, átrendezem az előző példában szereplő tömb szerkezetét:

int[][] tomb = {
                 { 2,4,6 },
                 { 3,7,8 }
               };

Így már egyértelműbb, hogy mit jelent a sorok és oszlopok száma. A két kis belső tömb jelenti a sorokat, egymás alá írva őket, valóban sorokat alkotnak. A bennük lévő elemek száma pedig kötött, mert ez jelenti az oszlopok számát. A két sort összefogó külső tömb határolóit direkt külön sorba írtam, hogy az ne zavarjon, de az is a struktúra része. Amikor hivatkozunk egy elemre (tomb[1][2]), akkor azt mondjuk meg, hogy az 1-es indexű kis tömbnek (a másodiknak) a 2-es oszlopában (a harmadikban) lévő 8-as elemre gondolunk. Ne feledd, a sor és oszlop indexelése is 0-val kezdődik.

A kétdimenziós tömbök kezeléséhez szinte minden esetben két egymásba ágyazott ciklusra van szükség, olyanokra, mint amilyeneket a rendezéseknél is láthattál. A külső ciklus a sorszámot, a belső az oszlopszámot lépteti. Nézzük meg, hogy néz ez ki:

for( int i = 0; i < tomb.length; i++ )
{
  for( int j = 0; j < tomb[i].length; j++ )
  {
    tomb[i][j] = (int)(Math.random()*10);
  }
}

Ez a példa végigmegy a tömb összes elemén, és mindegyiket egy [0;9] intervallumból sorsolt számmal tölt fel. Láthatod, hogy van egy tömbelem kiválasztás (tomb[i][j]), ami az előzőleg ismertetett módon [sor][oszlop] választja ki az adott elemet. Mivel a későbbiekben nagy valószínűséggel mindig ugyanolyan nevű változókat használsz, ezért jó ha megjegyzed, hogy az i változóval jelölöd a sorokat, és j-vel az oszlopokat. Ez a későbbiekben fontos lesz, hogy tudd, melyik melyik.
A kiemelt sorban van igazából az érdekesség, ami elsőre furcsa lehet. A tömb i indexű elemének tömbmérete? Kétdimenziós tömbben a tömb deklarálása után az inicializáláskor meg kell határozni a tömb méretét. Így van ez az alap tömbök esetén is, és így van ez itt is. A különbség az, hogy itt külön kell beállítani a sorok és oszlopok számát. Először a sorok, utána az oszlopok számát. De akkor a tomb.length melyiket adja meg a kettő közül, és hogy kapjuk meg a másikat? Tisztázzunk akkor pár sarokpontot az ilyen tömbök kezelésével kapcsolatban

tomb.length;    // sorok száma (a kis tömbök darabszáma)
tomb[1];        // az 1-es indexű sor elemei (2. sor tömbje)
tomb[i].length; // oszlopok száma (az i indexű tömbben lévő elemek száma)
tomb[3][2];     // tömbben tárolt elem, ami a 4. sor 3. oszlopában van

Az oszlopok számát miért egy i indexű sor méretéből kapjuk meg, miért nem fixen a 0 indexű sor méretéből? Azért, mert létezik egy speciális többdimenziós tömbtípus, melyet nagyon ritkán használunk, és ott eltérhet az egyes sorok (kis tömbök) mérete, így mindig az aktuális sor méretével dolgozzunk.

Kétdimenziós tömbök bejárása

  1. az összes elem bejárása:
    Ebben az esetben két ciklusra van szükség, amire már láttál példát a tömb feltöltésénél.
  2. egy sor bejárása:
    Ekkor elég csak egy konkrét soron végigmenni egyetlen ciklussal. Ebben a példában a 3. sor összes elemét írjuk ki egymás mellé. Ez a következőképp néz ki:

    for( int j = 0; j < tomb[2].length; j++ )
    {
      System.out.print(tomb[2][j]+" ");
    }
    

    Ebben az esetben láthatjuk, hogy a tomb[2]-re hivatkozok fixen, több helyen is. Először a ciklus fejében, ahol a 2-es indexű (3.) sor elemeit akarom kiírni. Valamint a konkrét elem kiválasztásánál is látszik, hogy csak a 2-es indexű sor szerepel, de azon belül a j-vel végiglépkedek a sor összes elemén (oszlopán). Technikailag a j helyett itt i is lehetne ciklusváltozó, a program akkor is tökéletesen működne. Logikailag azért szoktam javasolni, hogy j legyen, mert akkor jobban rögzül, hogy a j az oszlopokat jelenti, és most csak az oszlop változik, a sor kötött.

  3. egy oszlop bejárása:
    Az előzőhöz hasonlóan itt is elég egyetlen ciklus, hiszen egyetlen oszlopon kell csak végigmenni. Ekkor az oszlop száma kötött és csak a sorszám változik. Ez így néz ki:

    for( int i = 0; i < tomb.length; i++ )
    {
      System.out.println(tomb[i][4]);
    }
    

Láthatod, hogy a ciklus fejében máshogy szerepel a futási feltétel, csak tomb.length szerepel, ami a sorok számát jelenti. A ciklusmagban pedig az adott elem kiválasztásakor a oszlopszám fix (jelen esetben a 4-es indexű 5. sor) és az sorszám az, ami változik, ezért használtam i ciklusváltozót.

Most már tetszőleges kétdimenziós tömböt be tudunk járni, jöhetnek az ezzel kapcsolatos feladatok. Az első feladat a tömb feltöltése, a többi feladatban pedig ezzel a tömbbel dolgoznánk.

Gyakorló feladatok

  1. Tölts fel egy 3×5-ös kétdimenziós tömböt a [-10;30] intervallumból:
    int[][] tomb = new int[3][5];
    
    for( int i = 0; i < tomb.length; i++ )
    {
      for( int j = 0; j < tomb[i].length; j++ )
      {
        tomb[i][j] = (int)(Math.random()*41)-10;
      }
    }
    
  2. Írd ki a tömböt sorokba és oszlopokba rendezve:
    int[][] tomb = new int[3][5];
    
    for( int i = 0; i < tomb.length; i++ )
    {
      for( int j = 0; j < tomb[i].length; j++ )
      {
    // egymás mellé írom ki egy sor elemeit
        System.out.print(tomb[i][j]+" ");
      }
    // ha végeztem egy sor kiírásával, akkor új sort kezdek
      System.out.println();
    }
    
  3. Írd ki a tömbben szereplő számok összegét:
    int osszeg = 0;
    for( int i = 0; i < tomb.length; i++ )
    {
      for( int j = 0; j < tomb[i].length; j++ )
      {
        osszeg = osszeg + tomb[i][j];
    // vagy osszeg += tomb[i][j];
      }
    }
    System.out.println("A tomb elemeinek osszege"+osszeg);
    
  4. Írd ki a 2. sor összegét:
    int osszeg = 0;
    for( int j = 0; j < tomb[1].length; j++ )
    {
      osszeg = osszeg + tomb[1][j];
    // vagy osszeg += tomb[1][j];
    }
    System.out.println("A 2. sor osszege"+osszeg);
    
  5. Számold meg, hány negatív szám szerepel a tömbben:
    int db = 0;
    for( int i = 0; i < tomb.length; i++ )
    {
      for( int j = 0; j < tomb[i].length; j++ )
      {
        if( tomb[i][j] < 0 )
        {
          db++;
        }
      }
    }
    System.out.println("A tombben "+db+" negativ szam van.");
    
  6. Számold meg, hány páros szám található a 3. oszlopban:
    int db = 0;
    for( int i = 0; i < tomb.length; i++ )
    {
      if( tomb[i][2] % 2 == 0 )
      {
        db++;
      }
    }
    System.out.println("A tomb 3. oszlopaban "+db+" paros szam van.");
    
  7. Írd ki, melyik a legkisebb elem a tömbben:Ez a feladat nem teljesen ugyanaz, mint amit a minimumkeresésnél láthattál. Arra remélem emlékszel, hogy a minimumnak a helyét, és nem az értékét tároljuk, mert a helyéből két dologra is válaszolhatunk, ezt most nem írnám le újra. De itt a hely nem egy index, hanem kettő: [oszlop][sor] Két dolgot tehetsz. Vagy két változót használsz a hely tárolására (egyet a sornak, egyet az oszlopnak), vagy egy két elemű tömbben tárolod, valahogy így:

    int[] min = new int[2];
    min[0] = sor;
    min[1] = oszlop;
    

    Vagy tárolhatod két változóban is:

    int minI = sor;
    int minJ = oszlop;
    

    Rád bízom melyiket használod, a lényeg, hogy helyesen tedd. Lássunk akkor példát a bonyolultabbra:

    int[] min = new int[2];
    // ebben a két sorban állítom be, hogy az első elem az első minimum
    min[0] = 0;
    min[1] = 0;
    
    for( int i = 0; i < tomb.length; i++ )
    {
      for( int j = 0; j < tomb[i].length; j++ )
      {
    // ha a tömb aktuális eleme kisebb, mint az eddigi minimum
    // ahol a minimum elem sora min[0], oszlopa min[1]
        if( tomb[i][j] < tomb[ min[0] ][ min[1] ] )
        {
          min[0] = i;
          min[1] = j;
        }
      }
    }
    // na itt ne keverd össze a [ ] jeleket...
    System.out.println("A tomb legkisebb eleme: "+tomb[min[0]][min[1]]);
    

    Azért hasonlítsuk ezt össze azzal, ha két külön változóban tárolod a minimum elem sorát és oszlopát:

    // ebben a két sorban állítom be, hogy az első elem az első minimum
    int minI = 0;
    int minJ = 0;
    
    for( int i = 0; i < tomb.length; i++ )
    {
      for( int j = 0; j < tomb[i].length; j++ )
      {
    // ha a tömb aktuális eleme kisebb, mint az eddigi minimum
    // ahol a minimum elem sora min[0], oszlopa min[1]
        if( tomb[i][j] < tomb[minI][minJ] )
        {
          minI = i;
          minJ = j;
        }
      }
    }
    // na itt ne keverd össze a [ ] jeleket...
    System.out.println("A tomb legkisebb eleme: "+tomb[minI][minJ]);
    

    Talán a két külön változó kicsit barátságosabb.

    Ha igazán figyeltél az eddigiekben, kiszúrhattad, hogy más különbség is van a minimumkereséshez képest, azon kívül, hogy itt a minimum helyét értelemszerűen két számként tároljuk. Figyeld meg, honnan indulnak itt a ciklusok. Nem 1-től! A minimumkeresésnél emlékezhetsz, hogy az első (0 indexű) elem a legkisebb, ezért a ciklus 1-es indextől kezdődik, hogy önmagával már ne hasonlítsuk össze. Itt ezt nem tehetjük meg. Miért?

    Ha a külső ciklusban az i változó 1-től indulna, akkor az első (0 indexű) sor teljesen kimaradna a vizsgálatból. Ha a belső ciklusban a j változó indulna 1-től, akkor pedig minden sor első eleme, vagyis a teljes első (0. indexű) oszlop maradna ki. Itt kénytelenek vagyunk az első minimumot önmagával is összehasonlítani, ami azért valljuk be, nem túl nagy veszteség. De ha nem így oldod meg, akkor súlyos hiba.

Háromdimenziós tömbök

Többdimenziós tömböket 3 dimenzió felett nem igazán használunk. A 3. dimenzióval még van értelme dolgozni, mondjuk térbeli koordináták, vagy képfeldolgozás esetén mondjuk egy RGB kód tárolása esetén. Ebben az esetben formailag így néz ki a tömbünk:

// háromdimenziós tömb deklarálása és inicializálása
int[][][] tomb = new int[4][5][2];

// háromdimenziós tömb adott elemének kiválasztása
tomb[1][2][1]

Itt a 3. dimenzió mondjuk mint egyfajta magasság értelmezhető térbeli pontok tárolása esetén. Színkódoknál pedig a 3 színkomponens értékét tárolhatjuk a tömbben. Ezekben az esetekben a tömb teljes bejárása értelemszerűen 3 ciklust jelent, de csak az első sorbeli magasságadatok bejárása is két ciklust igényel. Akkor van szükség egy ciklusra, ha a 3 dimenzióból 2 rögzített. Például az első sor második eleméhez tartozó pontok (a tomb[0][1] magasságoszlopa) bejárása esetén.

Fűrészfogas tömbök

Láthattad, hogy a kétdimenziós tömbök esetén az oszlopok száma minden esetben megegyezik. Ez azonban nem mindig van így. Megadható az is, hogy az egyes sorok változó (de megadásuk után fix) hosszúak legyenek. Ezt a szerkezetet fűrészfogas tömbnek is szokás nevezni. Ilyen szerkezetet nagyon speciális esetekben használunk, de a kezelése a fentiek alapján meglehetősen egyszerű. Lássuk hogyan deklaráljuk ezt:

// először csak a sorok számát adjuk meg
int[][] tomb = new int[3][];

// ezután használat előtt egyenként adjuk meg a sorok méreteit

tomb[0] = new int[5];
tomb[1] = new int[7];
tomb[2] = new int[3];

// töltsük fel a tömböt a [0;9] intervallumból
for( int i = 0; i < tomb.length; i++ )
{
  for( int j = 0; j < tomb[i].length; j++ )
  {
    tomb[i][j] = (int)(Math.random()*10);
  }
}

// írjuk ki a tömböt
for( int i = 0; i < tomb.length; i++ )
{
  for( int j = 0; j < tomb[i].length; j++ )
  {
    System.out.print(tomb[i][j]+" ");
  }
  System.out.println();
}

Láthatod, hogy a tömb sorai nem egyforma hosszúak. Itt csak arra kell vigyázni, hogy direkt hozzáféréssel soha ne hivatkozz olyan indexű elemre, ami nem létezik. Nem értelmezhető például a 4. oszlop, mivel a 3. sorban csak 3 oszlop található. De a tömb bejárása, minimum/maximum keresés, sorösszeg, tömb összeg, megszámlálások, gond nélkül kivitelezhetők a fent kidolgozott példák alapján, de csak akkor, ha ezek nem egy oszlopra korlátozódnak. Csak akkor kell nagyon figyelni, ha csak oszlopban akarunk mozogni, mert tisztázni kell előre, létezik-e teljesen az adott oszlop, vagy valamelyik sorban lyukas. Ez a fűrészfogas szerkezet 3 és több dimenzióra is létrehozható, de azzal már szinte csak elméleti síkon kell számolni.

Következő lecke: Adatbekérés

4 Replies to “Java programozás 17. – Többdimenziós tömbök”

  1. Pingback: Java programozás 16. – Alap algoritmusok

  2. Kedves Tanár Úr!
    Elnézését kérem, hogy ilyennel zavarom, de mástól nem tudok kérdezni.
    A 7. feladathoz – melyik a legkisebb elem a tömbben-, magamtól az alábbi megoldást írtam, ami működött, mert ki tudtam íratni a legkisebb számot.Ezzel a megoldással csak annyi a probléma, hogy csak az értékét tudjuk a legkisebb számnak, a helyét nem, vagy esetleg más gondot is okozhat az alkalmazása, ami az egyszerű próba során nem feltétlen jelenik meg? Előre is köszönöm szépen a válaszát.
    Üdvözlettel
    Nagy Katalin
    int min=tomb[0][0];
    for (int i = 0; i < tomb.length; i++)
    {
    for (int j=0; j<tomb[i].length; j++)
    {
    if(tomb[i][j]<min)
    {
    min=tomb[i][j];
    }
    }
    }

    • Gyakorlatilag csak az a kérdés, hogy valóban szükség van a helyére, vagy csak a legkisebb értékre van szükségem. Ha a helye nem kell, teljesen jó ez a megoldás. Ha a helye is kell, akkor célszerű az i és j értékeit eltárolni akkor, amikor a két for ciklusban a feltétel teljesül (vagyis találtunk kisebbet). Akkor pontosan meg tudjuk mondani, melyik az az elem.

Vélemény, hozzászólás?

Az e-mail címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöltük

*

Ez az oldal az Akismet szolgáltatást használja a spam csökkentésére. Ismerje meg a hozzászólás adatainak feldolgozását .