Kolekcije u Javi: HashMap, HashSet i TreeMap

Lekcija 15 od 16 · Java

Mapa (rečnik) čuva parove ključ → vrednost i omogućava brzo pronalaženje vrednosti po ključu. Skup čuva elemente bez ponavljanja. To su strukture koje se najčešće koriste za brojanje, grupisanje i proveru „da li smo ovo već videli“.

Pre nego što počneš: potrebno predznanje – liste, stringovi.
Kako da učiš: svaki primer prekucaj i pokreni, a zadatke na kraju prvo reši sam/a; uputstvo i rešenje otvori tek posle toga. Svi programi su u Javi (Java 17 ili novija); svaki je preveden i pokrenut i daje navedeni izlaz.

HashMap: ključ i vrednost

import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;

public class Main {
    public static void main(String[] args) {
        Map<String, Integer> stanovnici = new HashMap<>();
        stanovnici.put("Beograd", 1_681_405);    // dodavanje para
        stanovnici.put("Novi Sad", 368_967);
        stanovnici.put("Nis", 249_501);
        stanovnici.put("Nis", 249_816);          // isti ključ: vrednost se zamenjuje

        System.out.println(stanovnici.get("Novi Sad"));
        System.out.println(stanovnici.get("Kragujevac"));          // nema ključa: null
        System.out.println(stanovnici.getOrDefault("Kragujevac", 0));
        System.out.println("Ima Nis? " + stanovnici.containsKey("Nis") + ", parova: " + stanovnici.size());

        stanovnici.remove("Beograd");
        // ispis abecedno po ključu: kopija u TreeMap (HashMap nema određen redosled)
        for (Map.Entry<String, Integer> e : new TreeMap<>(stanovnici).entrySet())
            System.out.println(e.getKey() + " -> " + e.getValue());
    }
}

HashMap ne čuva nikakav određen redosled ključeva. Ako treba sortiran redosled, koristi se TreeMap, a ako treba redosled dodavanja – LinkedHashMap.

Brojanje pojavljivanja

Najčešća upotreba mape: koliko se puta pojavio svaki element. Metoda merge(kljuc, 1, Integer::sum) dodaje 1 na postojeću vrednost ili postavlja 1 za nov ključ.

import java.util.Map;
import java.util.Scanner;
import java.util.TreeMap;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        String tekst = sc.nextLine().toLowerCase();
        Map<String, Integer> broj = new TreeMap<>();      // ključevi abecedno
        for (String rec : tekst.split("[^a-z]+")) {
            if (rec.isEmpty()) continue;
            broj.merge(rec, 1, Integer::sum);             // isto: broj.put(rec, broj.getOrDefault(rec, 0) + 1)
        }
        System.out.println(broj);

        // najčešća reč
        String najcesca = null;
        for (String r : broj.keySet())
            if (najcesca == null || broj.get(r) > broj.get(najcesca))
                najcesca = r;
        System.out.println("Najcesca: " + najcesca + " (" + broj.get(najcesca) + ")");
    }
}

HashSet i TreeSet: skup bez ponavljanja

import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

public class Main {
    public static void main(String[] args) {
        Set<Integer> a = new TreeSet<>(List.of(5, 1, 3, 9, 7, 3, 1));   // duplikati nestaju, sortirano
        Set<Integer> b = new TreeSet<>(List.of(3, 4, 5, 6));
        System.out.println("a = " + a + ", b = " + b);

        Set<Integer> presek = new TreeSet<>(a);
        presek.retainAll(b);                        // zadrži samo zajedničke
        Set<Integer> unija = new TreeSet<>(a);
        unija.addAll(b);
        Set<Integer> razlika = new TreeSet<>(a);
        razlika.removeAll(b);
        System.out.println("presek " + presek + ", unija " + unija + ", a\\b " + razlika);

        // add vraća false ako element već postoji – prvo ponavljanje u nizu
        Set<String> videno = new HashSet<>();
        for (String s : new String[]{"crvena", "plava", "zuta", "plava", "crvena"})
            if (!videno.add(s)) {
                System.out.println("Prvo ponavljanje: " + s);
                break;
            }
    }
}

Provera contains je u HashSet-u i HashMap-u praktično trenutna, bez obzira na broj elemenata, dok lista mora da pregleda element po element.

TreeMap: sortirani ključevi i pretraga

import java.util.TreeMap;

public class Main {
    public static void main(String[] args) {
        // granica bodova -> ocena
        TreeMap<Integer, Integer> skala = new TreeMap<>();
        skala.put(0, 1);
        skala.put(50, 2);
        skala.put(65, 3);
        skala.put(80, 4);
        skala.put(90, 5);

        int[] bodovi = {45, 50, 72, 89, 100};
        for (int b : bodovi) {
            // floorKey: najveći ključ koji je <= b
            int ocena = skala.get(skala.floorKey(b));
            System.out.println(b + " bodova -> ocena " + ocena);
        }
        System.out.println("prva granica " + skala.firstKey() + ", poslednja " + skala.lastKey());
    }
}
StrukturaRedosledKada je koristiti
HashMap / HashSetnikakav određennajbrža; kada redosled nije bitan
LinkedHashMap / LinkedHashSetredosled dodavanjakada treba zapamtiti redosled
TreeMap / TreeSetsortiransortiran ispis, floorKey, ceilingKey

Mapa sa listom: grupisanje

Vrednost u mapi može biti i lista. Metoda computeIfAbsent pravi praznu listu kada se ključ pojavi prvi put:

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.TreeMap;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        Map<String, List<String>> poOdeljenju = new TreeMap<>();
        for (int i = 0; i < n; i++) {
            String ime = sc.next(), odeljenje = sc.next();
            poOdeljenju.computeIfAbsent(odeljenje, k -> new ArrayList<>()).add(ime);
        }
        for (var e : poOdeljenju.entrySet())          // var: tip zaključuje prevodilac
            System.out.println(e.getKey() + ": " + String.join(", ", e.getValue()));
    }
}

Zadaci za vežbu

Za svaki zadatak postoji primer, uputstvo i rešenje. Rešenja su provereno tačna na primerima iz teksta.

ZADATAK 01Težina: srednje

Anagrami

Dve reči su anagrami ako imaju ista slova u drugačijem redosledu (pas i sap). Učitava se n, pa n reči od malih slova. Ispisati grupe anagrama (samo grupe sa bar dve reči), svaku u svom redu, reči unutar grupe abecedno, a grupe po prvoj reči abecedno.

Primer

Ulaz

7
sap
list
pas
rak
slit
aps
kar

Izlaz

aps pas sap
kar rak
list slit
ZADATAK 02Težina: srednje

Dva broja sa zbirom

Učitavaju se n i k, pa n celih brojeva (n ≤ 200 000). Ispisati broj parova pozicija i < j za koje je a[i] + a[j] = k. Rešenje sa dve petlje je presporo – treba jedan prolaz.

Primer 1

Ulaz

6 7
1 6 3 4 5 2

Izlaz

3

Primer 2

Ulaz

5 4
2 2 2 1 3

Izlaz

4
Česte greške i saveti
  • get za ključ koji ne postoji vraća null; ako vrednost pretvaraš u int, to izaziva NullPointerException. Koristi getOrDefault.
  • Ne oslanjaj se na redosled ispisa HashMap-e – za sortiran ispis koristi TreeMap.
  • Ključ u mapi i element skupa ne smeju se menjati dok su u kolekciji (posebno kod sopstvenih klasa).
  • Za sopstvenu klasu kao ključ moraju postojati metode equals i hashCode; record ih ima automatski.

Proveri znanje

Otvori kviz

Mape i skupovi

1. Šta se dešava kada se put pozove sa ključem koji već postoji?




2. Koja struktura čuva ključeve sortirane?




3. Šta vraća set.add(x) ako x već postoji u skupu?




4. Za šta je mapa najpogodnija?