Datové struktury a stromy v Javě

MarSik

Re:Datové struktury a stromy v Javě
« Odpověď #45 kdy: 22. 02. 2018, 17:23:23 »
Já to chápu tak, že vyhledávání bude logn pokud koliduje hash32 % size a lineární pokud kolidují přímo hashe. Ale Javu neznám.

Já asi nechápu místní význam pojmu vyhledávání.

Přístup k náhodnému prvku HashMapy pomocí klíče bude O(1) = konstantní, nebo při kolizi nejhůře O(log n). Ve starších verzích nejhůře O(n) = lineární. V obou případech je N počet kolidujících prvků se stejným hash (nebo hash % bucket_count). Navíc dojde k přepočítání té hash struktury, když začne být příliš plná.

Pokud hledám podle hodnoty, tak je složitost HashMapy O(n), protože ty prvky musím projít všechny. S tím může pomoci LinkedHashMap (pořadí podle času vložení) nebo spíše jedna ze SortedMap implementací. Ty už ovšem interně používají strom a náhodný přístup pomocí klíče je O(log n). Na druhou stranu hledání podle hodnoty je také O(log n), protože můžu použít půlení intervalu.

Další možností je mít mapy dvě - podle klíče a podle hodnoty. Cena za to je větší spotřeba paměti. Takovou strukturu najdete třeba v Guavě: https://github.com/google/guava/wiki/NewCollectionTypesExplained#bimap

Nic z toho není specifické pro Javu a podobné struktury najdete i v jiných jazycích. Všechny ty časové složitosti jsou pořád stejné jako nás kdysi učili na škole a není na nich nic magického. Prostě hledání klíče v "hash poli" je O(1), ve stromu O(log n) a v poli O(n). Všechny ty struktury co tu popisujete jsou jen kombinací těchto tří.


Jenda

Re:Datové struktury a stromy v Javě
« Odpověď #46 kdy: 22. 02. 2018, 18:44:53 »
Přístup k náhodnému prvku HashMapy pomocí klíče bude O(1) = konstantní, nebo při kolizi nejhůře O(log n). Ve starších verzích nejhůře O(n) = lineární. V obou případech je N počet kolidujících prvků se stejným hash (nebo hash % bucket_count).

Jak se udělá logaritmický přístup k prvku, když mají všechny prvky stejný hash? Předpokládám, že je podle ničeho jiného nemůžeš porovnávat, máš jenom hash a equals().

borekz

  • ****
  • 493
    • Zobrazit profil
    • E-mail
Re:Datové struktury a stromy v Javě
« Odpověď #47 kdy: 22. 02. 2018, 19:24:05 »
Pokud je k dispozici jen hash a equal, pak je nejvyšší složitost lineární. Pokud jdou objekty porovnávat, je na hash-slotu strom.

MarSik

Re:Datové struktury a stromy v Javě
« Odpověď #48 kdy: 23. 02. 2018, 11:08:19 »
Máte oba samozřejmě pravdu, nicméně zrovna v Javě u Map (narozdíl od typického použití Setu) jsou klíče takřka vždy porovnatelné. Nebo alespoň dostatečně často, abychom si mohli dovolit zjednodušení v diskusi. V Javě má stejně smysl mluvit jen o amortizovaném O(1), protože to N je malé a ta kolekce se sama přepočítá jakmile to začne být výhodné.

Já jsem hlavně reagoval na to, že tu spousta lidí hází pojmy jako vyhledávat a lineární a vůbec nevnímají tu konstantní složitost pro náhodný přístup pomocí hashe. Vůbec si nepředstaví ten algoritmus a cykly v něm (a nebo netuší co je principem hash tabulky...).

10xCoder

Re:Datové struktury a stromy v Javě
« Odpověď #49 kdy: 23. 02. 2018, 11:53:33 »
Máte oba samozřejmě pravdu, nicméně zrovna v Javě u Map (narozdíl od typického použití Setu) jsou klíče takřka vždy porovnatelné. Nebo alespoň dostatečně často, abychom si mohli dovolit zjednodušení v diskusi. V Javě má stejně smysl mluvit jen o amortizovaném O(1), protože to N je malé a ta kolekce se sama přepočítá jakmile to začne být výhodné.

Já jsem hlavně reagoval na to, že tu spousta lidí hází pojmy jako vyhledávat a lineární a vůbec nevnímají tu konstantní složitost pro náhodný přístup pomocí hashe. Vůbec si nepředstaví ten algoritmus a cykly v něm (a nebo netuší co je principem hash tabulky...).
Někteří jen poukazovali na chybné tvrzení, že medián v hashmapě jde najít jinak než sekvenčně. To s klíči vůbec nesouvisí, nalezení i-tého prvku v nesetříděné množině pro libovolné i je lineární.


Re:Datové struktury a stromy v Javě
« Odpověď #50 kdy: 23. 02. 2018, 12:15:05 »
Někteří jen poukazovali na chybné tvrzení, že medián v hashmapě jde najít jinak než sekvenčně. To s klíči vůbec nesouvisí, nalezení i-tého prvku v nesetříděné množině pro libovolné i je lineární.
A na to právě reagoval MarSik, protože nalezení i-tého prvku v nesetříděné množině je prostě nesmysl. Když je ta množina nesetříděná, nemají prvky v ní žádné pořadí a neexistuje žádný i-tý prvek. Pokud je ale řeč o poli, tam jsou prvky seřazené podle svého pořadí v tom poli – a nalezení i-tého prvku se provede v konstantním čase, protože je to jen jednoduché přičtení adresy.

Takže v hashmapě se hledá tak, že se vypočítá hash klíče a tento hash modulo velikost hashovací tabulky je přímo index v dané tabulce (přístup je tedy konstantní). Pokud je pod tímto hashem jediný klíč, mám výsledek, pokud dojde ke kolizi a pod stejným hashem je uloženo víc klíčů, je nutné ještě najít ten správný klíč. Obecně je pak v tabulce uložen neseřazený seznam klíčů a pak nezbývá než porovnat klíče jeden po druhém, dokud se nenajde shoda (tedy složitost je lineární). Pokud je možné klíče seřadit, lze to optimalizovat a v případě kolize klíčů použít nějakou strukturu, ve které se lépe vyhledává, než neseřazený seznam.

10xCoder

Re:Datové struktury a stromy v Javě
« Odpověď #51 kdy: 23. 02. 2018, 12:21:08 »
Někteří jen poukazovali na chybné tvrzení, že medián v hashmapě jde najít jinak než sekvenčně. To s klíči vůbec nesouvisí, nalezení i-tého prvku v nesetříděné množině pro libovolné i je lineární.
A na to právě reagoval MarSik, protože nalezení i-tého prvku v nesetříděné množině je prostě nesmysl. Když je ta množina nesetříděná, nemají prvky v ní žádné pořadí a neexistuje žádný i-tý prvek. Pokud je ale řeč o poli, tam jsou prvky seřazené podle svého pořadí v tom poli – a nalezení i-tého prvku se provede v konstantním čase, protože je to jen jednoduché přičtení adresy.

Takže v hashmapě se hledá tak, že se vypočítá hash klíče a tento hash modulo velikost hashovací tabulky je přímo index v dané tabulce (přístup je tedy konstantní). Pokud je pod tímto hashem jediný klíč, mám výsledek, pokud dojde ke kolizi a pod stejným hashem je uloženo víc klíčů, je nutné ještě najít ten správný klíč. Obecně je pak v tabulce uložen neseřazený seznam klíčů a pak nezbývá než porovnat klíče jeden po druhém, dokud se nenajde shoda (tedy složitost je lineární). Pokud je možné klíče seřadit, lze to optimalizovat a v případě kolize klíčů použít nějakou strukturu, ve které se lépe vyhledává, než neseřazený seznam.
Nechápeš, pročež píšeš nesmysly. Představ si pole čísel v náhodném pořadí. Nalezení i-tého největšího prvku je lineární. A stejně lineární je to i v hashmapě, když jsou hodnotami čísla (na klíči nesejde). Nalezení mediánu je jen speciální případ nalezení i-tého největšího prvku.

MarSik

Re:Datové struktury a stromy v Javě
« Odpověď #52 kdy: 23. 02. 2018, 13:01:18 »
Nechápeš, pročež píšeš nesmysly. Představ si pole čísel v náhodném pořadí. Nalezení i-tého největšího prvku je lineární. A stejně lineární je to i v hashmapě, když jsou hodnotami čísla (na klíči nesejde). Nalezení mediánu je jen speciální případ nalezení i-tého největšího prvku.

Co má nalezení i-tého prvku společného s HashMapu? Máte samozřejmě pravdu, pokud se bavíme o hledání hodnoty v poli nebo o procházení všech prvků. Jenže to u mapy nedává smysl. Mapa slouží na získání hodnoty podle klíče a žádné hledání se tam nekoná. Hash se použije jako přímý offset do pole.

MarSik

Re:Datové struktury a stromy v Javě
« Odpověď #53 kdy: 23. 02. 2018, 13:13:04 »
Jak se udělá logaritmický přístup k prvku, když mají všechny prvky stejný hash? Předpokládám, že je podle ničeho jiného nemůžeš porovnávat, máš jenom hash a equals().

Jen taková technická poznámka, nakonec nemáte totiž úplně pravdu:

Pokud je bucketů méně než možných hashů (tj. vždy), tak v případě kolize nemají všechny prvky stejný hash (kolize je na úrovni indexu bucketu). Takže se dá porovnávat ten hash samotný.

Navíc, pokud mají dva prvky stejný hash klíče, tak se z hlediska kolekcí jedná o ten samý prvek. Tj dojde k přepsání hodnoty pro ten klíč a žádná kolize nenastane.

Labrat

Re:Datové struktury a stromy v Javě
« Odpověď #54 kdy: 23. 02. 2018, 13:16:52 »
Nechápeš, pročež píšeš nesmysly. Představ si pole čísel v náhodném pořadí. Nalezení i-tého největšího prvku je lineární. A stejně lineární je to i v hashmapě, když jsou hodnotami čísla (na klíči nesejde). Nalezení mediánu je jen speciální případ nalezení i-tého největšího prvku.

Co má nalezení i-tého prvku společného s HashMapu? Máte samozřejmě pravdu, pokud se bavíme o hledání hodnoty v poli nebo o procházení všech prvků. Jenže to u mapy nedává smysl. Mapa slouží na získání hodnoty podle klíče a žádné hledání se tam nekoná. Hash se použije jako přímý offset do pole.
To se zeptejte javamana. On každou diskusi převrátí na hlavu. Asi nevěděl pořádně, co je hashmapa, a zamotal se.

Re:Datové struktury a stromy v Javě
« Odpověď #55 kdy: 23. 02. 2018, 13:21:30 »

Navíc, pokud mají dva prvky stejný hash klíče, tak se z hlediska kolekcí jedná o ten samý prvek. Tj dojde k přepsání hodnoty pro ten klíč a žádná kolize nenastane.

Ne.

Re:Datové struktury a stromy v Javě
« Odpověď #56 kdy: 23. 02. 2018, 13:30:27 »
Nechápeš, pročež píšeš nesmysly. Představ si pole čísel v náhodném pořadí. Nalezení i-tého největšího prvku je lineární. A stejně lineární je to i v hashmapě, když jsou hodnotami čísla (na klíči nesejde). Nalezení mediánu je jen speciální případ nalezení i-tého největšího prvku.
Nesmysly tu píše spousta lidí, ale já ani MarSik to nejsme. i-tý největší prvek pole je úplně něco jiného, než i-tý prvek pole. i-tý největší prvek se vztahuje k hodnotám těch prvků, a mimo jiné předpokládá, že ty prvky umím porovnat a tedy seřadit. i-tý prvek se vztahuje k pořadí těch prvků v poli, v programátorském zápisu je to prostě pole.

V hashmapě to „stejně lineární“ není – a bylo by dobré, kdybyste si konečně zjistil, jak hashmapa funguje. Začněme tím, že v hashmapě jsou dvě struktury, ve kterých se prvek hledá. První struktura je hashtabulka, klíčová struktura pro hashmapu – je to pole, ve kterém index pole přímo odpovídá hashi klíče. Mám klíč, z něj vypočítám hash, ten hash převedu do rozsahu velikosti pole (např. hash modulo velikost pole), a tahle hodnota se použije přímo jako index té hashtabulky. Vyhledávání v té tabulce tedy není v lineárním čase, ale v konstantním – je to prostě přístup do pole podle indexu. No a protože může dojít ke kolizi hashů, tj. víc klíčů má stejný hash a mělo by být umístěných ve jedné položce té hashtabulky, musím mít možnost uložit na tu pozici ne jeden klíč, ale jejich množinu. Nejjednodušší je udělat tam spojový seznam, v něm se pak konkrétní hodnota opravdu hledá v lineárním čase, ale pokud klíče umím setřídit, můžu tam použít i jinou strukturu, ve které půjde rychleji hledat – třeba seřazený seznam nebo binární vyhledávací strom.

Představte si to třeba pro hashtabulku o velikosti čtyř prvků. Prázdná vypadá takhle:

Kód: [Vybrat]
hashtable[0] = ();
hashtable[1] = ();
hashtable[2] = ();
hashtable[3] = ();

Budete tam chtít vložit klíč 'a', vypočítáte jeho hash, třeba 32745, a ten převedete do rozsahu hashtabulky, třeba modulo 4, takže vám vyjde index do hashovací tabulky 1. Takže klíč uložíte:

Kód: [Vybrat]
hashtable[0] = ();
hashtable[1] = ('a');
hashtable[2] = ();
hashtable[3] = ();

Pak budete chtít vložit klíč 'b', hash 126, index do hashovací tabulky 2:

Kód: [Vybrat]
hashtable[0] = ();
hashtable[1] = ('a');
hashtable[2] = ('b');
hashtable[3] = ();

Klíč 'c', hash 8293121, index do hashovací tabulky 1:

Kód: [Vybrat]
hashtable[0] = ();
hashtable[1] = ('a', 'c');
hashtable[2] = ('b');
hashtable[3] = ();

Vidíte, že tam došlo ke kolizi hashů (respektive až těch indexů v hashtabulce), takže pro hash 1 mám uložené dva různé klíče.

Když teď budu chtít v mapě najít klíč 'd', určím jeho hash 172340, index je 0, takže si v konstantním čase v hashtabulce najdu řádek s indexem 0:
Kód: [Vybrat]
hashtable[0] = (); //nalezený řádek v tabulce
hashtable[1] = ('a', 'c');
hashtable[2] = ('b');
hashtable[3] = ();
Vidím, že seznam klíčů je prázdný, tedy klíč 'd' v mapě není. Kdyby mi vyšel index 1, musím klíč hledat v té množině ('a', 'c'), a teprve pokud by tohle byl nesetříděný seznam, bude mít toto hledání lineární složitost. Tohle už je ale implementační detail, který může být v různých implementacích řešený různě. Podstatný pro hashovací tabulku je ten „trik“ že hash je přímo indexem pole a tudíž jeho vyhledání má konstantní časovou složitost.

Re:Datové struktury a stromy v Javě
« Odpověď #57 kdy: 23. 02. 2018, 13:39:54 »
Pokud je bucketů méně než možných hashů (tj. vždy), tak v případě kolize nemají všechny prvky stejný hash (kolize je na úrovni indexu bucketu). Takže se dá porovnávat ten hash samotný.

Navíc, pokud mají dva prvky stejný hash klíče, tak se z hlediska kolekcí jedná o ten samý prvek. Tj dojde k přepsání hodnoty pro ten klíč a žádná kolize nenastane.
Nikoli, dva prvky se stejným hashem jsou i z pohledu kolekcí pořád dva různé prvky. To, že může dojít ke kolizi, je základní princip hashe – a pro ukládání dat do hashtabulky se nepoužívají kryptograficky silné hashovací funkce, takže k těm kolizím reálně dochází a počítá se s nimi.

Máte pravdu, že v praxi může dojít ke dvěma typům kolizí – kolizi hashů, a kolizi indexů bucketu. To už je ale spíš otázka praktické implementace. V teorii je výpočet hashe záležitostí té hashtabulky, protože to ona ten hash potřebuje a objekt ukládaný do hashtabulky teoreticky nemusí nic o nějakém hashi vědět. Tím pádem si hashtabulka spočítá už rovnou hash o správné velikosti, aby šel rovnou použít jako index do té hashtabulky. Proto se v případě hashtabulky tomu indexu říká i hash. Ale třeba v Javě je to implementováno tak, jak popisujete – tj. výpočet hashe je (objektově nečistě) implementován přímo v každém objektu, tj. výpočet hashe vrací hash o nějaké univerzální velikosti (konkrétně integeru), a HashMap nebo HashSet si tenhle univerzální hash musí následně převést do rozsahu odpovídajícího aktuální velikosti hashtabulky (pole).

MarSik

Re:Datové struktury a stromy v Javě
« Odpověď #58 kdy: 23. 02. 2018, 13:40:15 »
Navíc, pokud mají dva prvky stejný hash klíče, tak se z hlediska kolekcí jedná o ten samý prvek.
Ne.

Mno dobře, existuje speciální případ, kdy dvě různé hodnoty podle equals mají stejný hashCode. Nebyl jsem úplně přesný.

  • If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.
  • It is not required that if two objects are unequal according to the equals(java.lang.Object) method, then calling the hashCode method on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hash tables.

Labrat

Re:Datové struktury a stromy v Javě
« Odpověď #59 kdy: 23. 02. 2018, 13:44:06 »
Nechápeš, pročež píšeš nesmysly. Představ si pole čísel v náhodném pořadí. Nalezení i-tého největšího prvku je lineární. A stejně lineární je to i v hashmapě, když jsou hodnotami čísla (na klíči nesejde). Nalezení mediánu je jen speciální případ nalezení i-tého největšího prvku.
Nesmysly tu píše spousta lidí, ale já ani MarSik to nejsme. i-tý největší prvek pole je úplně něco jiného, než i-tý prvek pole. i-tý největší prvek se vztahuje k hodnotám těch prvků, a mimo jiné předpokládá, že ty prvky umím porovnat a tedy seřadit. i-tý prvek se vztahuje k pořadí těch prvků v poli, v programátorském zápisu je to prostě pole.

V hashmapě to „stejně lineární“ není – a bylo by dobré, kdybyste si konečně zjistil, jak hashmapa funguje. Začněme tím, že v hashmapě jsou dvě struktury, ve kterých se prvek hledá. První struktura je hashtabulka, klíčová struktura pro hashmapu – je to pole, ve kterém index pole přímo odpovídá hashi klíče. Mám klíč, z něj vypočítám hash, ten hash převedu do rozsahu velikosti pole (např. hash modulo velikost pole), a tahle hodnota se použije přímo jako index té hashtabulky. Vyhledávání v té tabulce tedy není v lineárním čase, ale v konstantním – je to prostě přístup do pole podle indexu. No a protože může dojít ke kolizi hashů, tj. víc klíčů má stejný hash a mělo by být umístěných ve jedné položce té hashtabulky, musím mít možnost uložit na tu pozici ne jeden klíč, ale jejich množinu. Nejjednodušší je udělat tam spojový seznam, v něm se pak konkrétní hodnota opravdu hledá v lineárním čase, ale pokud klíče umím setřídit, můžu tam použít i jinou strukturu, ve které půjde rychleji hledat – třeba seřazený seznam nebo binární vyhledávací strom.

Představte si to třeba pro hashtabulku o velikosti čtyř prvků. Prázdná vypadá takhle:

Kód: [Vybrat]
hashtable[0] = ();
hashtable[1] = ();
hashtable[2] = ();
hashtable[3] = ();

Budete tam chtít vložit klíč 'a', vypočítáte jeho hash, třeba 32745, a ten převedete do rozsahu hashtabulky, třeba modulo 4, takže vám vyjde index do hashovací tabulky 1. Takže klíč uložíte:

Kód: [Vybrat]
hashtable[0] = ();
hashtable[1] = ('a');
hashtable[2] = ();
hashtable[3] = ();

Pak budete chtít vložit klíč 'b', hash 126, index do hashovací tabulky 2:

Kód: [Vybrat]
hashtable[0] = ();
hashtable[1] = ('a');
hashtable[2] = ('b');
hashtable[3] = ();

Klíč 'c', hash 8293121, index do hashovací tabulky 1:

Kód: [Vybrat]
hashtable[0] = ();
hashtable[1] = ('a', 'c');
hashtable[2] = ('b');
hashtable[3] = ();

Vidíte, že tam došlo ke kolizi hashů (respektive až těch indexů v hashtabulce), takže pro hash 1 mám uložené dva různé klíče.

Když teď budu chtít v mapě najít klíč 'd', určím jeho hash 172340, index je 0, takže si v konstantním čase v hashtabulce najdu řádek s indexem 0:
Kód: [Vybrat]
hashtable[0] = (); //nalezený řádek v tabulce
hashtable[1] = ('a', 'c');
hashtable[2] = ('b');
hashtable[3] = ();
Vidím, že seznam klíčů je prázdný, tedy klíč 'd' v mapě není. Kdyby mi vyšel index 1, musím klíč hledat v té množině ('a', 'c'), a teprve pokud by tohle byl nesetříděný seznam, bude mít toto hledání lineární složitost. Tohle už je ale implementační detail, který může být v různých implementacích řešený různě. Podstatný pro hashovací tabulku je ten „trik“ že hash je přímo indexem pole a tudíž jeho vyhledání má konstantní časovou složitost.
MarSik chápe, co je medián a o co javablbovi šlo.