Discussion:
Datenstruktur gesucht für A*
(zu alt für eine Antwort)
Malte Schneider
2005-08-29 07:09:27 UTC
Permalink
Moin Jungs,

ich hab mich an der Implementierung des A*-Algorithmus versucht.
Funktioniert auch erstmal soweit. Leider hab ich vergeblich nach einer
Datenstruktur gesucht, in der ich die noch zu bearbeitenden Knoten
speichere. Diese muss ja folgende Eigenschaften haben:
1) contains, add, remove sollten in O(1) zu erledigen sein
2) der Knoten mit den geringsten Kosten sollte in möglichst kurzer Zeit
gefunden werden können.

Das erste deutet auf jeden Fall in Richtung Set. Das 2. eher in Richtung
einer sortierten/sortierbaren Collection. Leider schließt sich das
gegenseitig irgendwie aus. Wenn man aber genauer überlegt, benötigt man
ja keine komplette Sortierung sondern immer nur genau 1 Element.

Was würdet ihr vorschlagen ?

Malte
Eric Bodden
2005-08-29 07:19:19 UTC
Permalink
Post by Malte Schneider
Das erste deutet auf jeden Fall in Richtung Set. Das 2. eher in Richtung
einer sortierten/sortierbaren Collection. Leider schließt sich das
gegenseitig irgendwie aus. Wenn man aber genauer überlegt, benötigt man
ja keine komplette Sortierung sondern immer nur genau 1 Element.
Was würdet ihr vorschlagen ?
Ein Set / ein Map verwenden und das kleinste Element jeweils extra
referenzieren. Das ganze in einer Klasse kapseln.

Oder: Schau Dir mal die Treemap an. Ich kenn sie nicht genau, weiß aber daß
sie Sortierung und recht schnellen Zugriff bietet.

Eric
--
Eric Bodden, ICQ: 12656220, http://www.bodden.de, PGP: BB465582
PHPHiglighter: highlight whatever you want, maintaining HTML compliance
http://bodden.de/projects/php/
Malte Schneider
2005-08-29 08:20:15 UTC
Permalink
Post by Eric Bodden
Ein Set / ein Map verwenden und das kleinste Element jeweils extra
referenzieren. Das ganze in einer Klasse kapseln.
Oder: Schau Dir mal die Treemap an. Ich kenn sie nicht genau, weiß aber daß
sie Sortierung und recht schnellen Zugriff bietet.
Eric
An eine Set hatte ich auch schon gedacht. Man kann beim add sein
"kleinstes Element" ja überprüfen und ggf. aktualisieren. Aber was macht
man bei remove ? Obendrein können sich die Kosten eines Knotens auch
nochmal ändern, auch wenn er schon in der "openList" ist. Das "kleines
Element" müßte dann auch neu bestimmt werden.

Der zweite Vorschlag erscheint auch auf den ersten Blick logisch, hat
aber einen Pferdefuss: TreeSet/Map kann man nur für Ordnungen (im
mathematischen Sinne) nutzen. Das heißt, man könnte keine Knoten
speichern, die gleiche Kosten haben.

Malte
Peter Büttner
2005-08-29 10:23:57 UTC
Permalink
Post by Malte Schneider
Post by Eric Bodden
Ein Set / ein Map verwenden und das kleinste Element jeweils extra
referenzieren. Das ganze in einer Klasse kapseln.
Oder: Schau Dir mal die Treemap an. Ich kenn sie nicht genau, weiß aber daß
sie Sortierung und recht schnellen Zugriff bietet.
An eine Set hatte ich auch schon gedacht. Man kann beim add sein
"kleinstes Element" ja überprüfen und ggf. aktualisieren. Aber was macht
man bei remove ? Obendrein können sich die Kosten eines Knotens auch
nochmal ändern, auch wenn er schon in der "openList" ist. Das "kleines
Element" müßte dann auch neu bestimmt werden.
Der zweite Vorschlag erscheint auch auf den ersten Blick logisch, hat
aber einen Pferdefuss: TreeSet/Map kann man nur für Ordnungen (im
mathematischen Sinne) nutzen. Das heißt, man könnte keine Knoten
speichern, die gleiche Kosten haben.
TreeSet
Du kannst einen Comparator der cost+'Wert' einbezieht verwenden.

LinkedHashList
hat O(1) liefert die Daten in 'definierter'
Reihenfolge, so wie sie hinzugefügt wurden, kannst du also nicht
beeinflussen, schade.

Bei beiden: wenn sich cost ändert mußt du neu sortieren, das verkraften
wohl beide Strukturen nicht, du müsstest dann eh' alle Elemente in eine
neue Collection stecken.


Anderes: nimm eine LinkedList + ein HashSet, ersteres für die
Ordnung, letzteres für die schnellen Zugriffe. Versteck beides
in einer Klasse. So hast du zwar doppelt so viele /Objektpointer/
und ein wenig add/remove overhead, dürfte aber nix machen.
Wenn sich cost ändert die List neu sortieren.



Grüße
Peter
--
Shell&Jar : Individual icons for jars
jMineSweeper : extended
www.PeterBuettner.de
Malte Schneider
2005-08-29 11:51:24 UTC
Permalink
Post by Malte Schneider
TreeSet
Du kannst einen Comparator der cost+'Wert' einbezieht verwenden.
Das hat den schon erwähnten nachteil, dass man Nodes mit gleichen Kosten
nicht speichern kann.
Post by Malte Schneider
LinkedHashList
hat O(1) liefert die Daten in 'definierter'
Reihenfolge, so wie sie hinzugefügt wurden, kannst du also nicht
beeinflussen, schade.
Kam genau aus diesem Grund nicht in Frage.
Post by Malte Schneider
Bei beiden: wenn sich cost ändert mußt du neu sortieren, das verkraften
wohl beide Strukturen nicht, du müsstest dann eh' alle Elemente in eine
neue Collection stecken.
Das stimmt nicht ganz, weil TreeSet nach #compareTo sortiert und zwar
schon beim Einfügen, was die Operation auch O(log(n)) zeit kosten läßt.
Post by Malte Schneider
Anderes: nimm eine LinkedList + ein HashSet, ersteres für die
Ordnung, letzteres für die schnellen Zugriffe. Versteck beides
in einer Klasse. So hast du zwar doppelt so viele /Objektpointer/
und ein wenig add/remove overhead, dürfte aber nix machen.
Wenn sich cost ändert die List neu sortieren.
So habs ich es im Moment implementiert. Leider muss man bei add und beim
Ändern der Kosten eines bereits eingefügten Nodes neu sortieren. Das
kostet mehr zeit als nötig, weil man ja eigentlich nur den Node mit den
kleinsten Kosten braucht und nicht den zweitkleinsten und drittkleinsten
auch noch, zumindest wenn zwischendurch wieder Nodes hinzukommen. Wenn
man immer abwechselnd einen Node added und liest, dann würde serielle
Suche schneller sein als jedes Mal zu sortieren. -> Frage an unsere
Akademiker: ab wann ist Sortieren günstiger ?

Malte
Peter Büttner
2005-08-29 12:22:15 UTC
Permalink
Post by Malte Schneider
Post by Malte Schneider
TreeSet
Du kannst einen Comparator der cost+'Wert' einbezieht verwenden.
Das hat den schon erwähnten nachteil, dass man Nodes mit gleichen Kosten
nicht speichern kann.
Wieso? (alles mit Namen die ungefähr so sind:-)

Wrapper(int cost, Comparable data)

...
Comparator

compareTo(){
if a.cost < b.cost return -1;
if a.cost > b.cost return 1;

return a.data.compareTo(b.data)
Post by Malte Schneider
Post by Malte Schneider
LinkedHashList
hat O(1) liefert die Daten in 'definierter'
Reihenfolge, so wie sie hinzugefügt wurden, kannst du also nicht
beeinflussen, schade.
Kam genau aus diesem Grund nicht in Frage.
Post by Malte Schneider
Bei beiden: wenn sich cost ändert mußt du neu sortieren, das verkraften
wohl beide Strukturen nicht, du müsstest dann eh' alle Elemente in eine
neue Collection stecken.
Das stimmt nicht ganz, weil TreeSet nach #compareTo sortiert und zwar
schon beim Einfügen, was die Operation auch O(log(n)) zeit kosten läßt.
Wenn du neu bewertest sortiert TreeSet aber nicht neu?! Das nimmt doch
eine unveränderliche Ordnung an.
Post by Malte Schneider
Post by Malte Schneider
Anderes: nimm eine LinkedList + ein HashSet, ersteres für die
Ordnung, letzteres für die schnellen Zugriffe. Versteck beides
in einer Klasse. So hast du zwar doppelt so viele /Objektpointer/
und ein wenig add/remove overhead, dürfte aber nix machen.
Wenn sich cost ändert die List neu sortieren.
So habs ich es im Moment implementiert. Leider muss man bei add und beim
Ändern der Kosten eines bereits eingefügten Nodes neu sortieren. Das
Wieso? binarySearch und insert. Ok: binarySearch ist auf linkedList
nicht schön. Vielleicht was anderes.
Post by Malte Schneider
kostet mehr zeit als nötig, weil man ja eigentlich nur den Node mit den
kleinsten Kosten braucht und nicht den zweitkleinsten und drittkleinsten
auch noch, zumindest wenn zwischendurch wieder Nodes hinzukommen. Wenn
man immer abwechselnd einen Node added und liest, dann würde serielle
Liste Sortieren, neue in zweite Liste, beim getSmalest() in der
sortierten das erste, wenn in der 2ten was kleineres halt dieses.
wenn 2te zu groß einfügen und neu sortieren? Klingt wie ein Tree in
klein:-) Also doch TreeSet mit einem kompletten refresh beim costChange

Kommt halt drauf an wie das so ist mit add/remove
Post by Malte Schneider
Suche schneller sein als jedes Mal zu sortieren. -> Frage an unsere
Akademiker: ab wann ist Sortieren günstiger ?
Ist die Performance denn überhaupt wichtig?
(Ich weis nicht was A* ist:-)



Grüße
Peter
--
Shell&Jar : Individual icons for jars
jMineSweeper : extended
www.PeterBuettner.de
Malte Schneider
2005-08-29 14:22:37 UTC
Permalink
Post by Peter Büttner
Wieso? (alles mit Namen die ungefähr so sind:-)
Wrapper(int cost, Comparable data)
...
Comparator
compareTo(){
if a.cost < b.cost return -1;
if a.cost > b.cost return 1;
return a.data.compareTo(b.data)
Was soll in dem Falle data sein ? Die Knoten enthalten an sich nur die
Kosten. Wobei:
1) node1.equals(node2) wenn die Positionen in Graphen gleich sind
2) node1<node2 wenn node1.cost<node2.cost
Post by Peter Büttner
Wenn du neu bewertest sortiert TreeSet aber nicht neu?! Das nimmt doch
eine unveränderliche Ordnung an.
Aber mit einem remove gefolgt von einem add erreiche ich ein "resort".
Kosten sind dann auch nur O(2*log(n))
Post by Peter Büttner
Wieso? binarySearch und insert. Ok: binarySearch ist auf linkedList
nicht schön. Vielleicht was anderes.
Eben.
Post by Peter Büttner
Liste Sortieren, neue in zweite Liste, beim getSmalest() in der
sortierten das erste, wenn in der 2ten was kleineres halt dieses.
wenn 2te zu groß einfügen und neu sortieren? Klingt wie ein Tree in
klein:-) Also doch TreeSet mit einem kompletten refresh beim costChange
Ich kann dir irgendwie nicht folgen...
Post by Peter Büttner
Kommt halt drauf an wie das so ist mit add/remove
Ein TreeSet/Map ist in Java als Rot-Schwarz-Baum implementiert, das
steht auch in der API. Das heißt ein Element wird beim add an die Stelle
eingefügt, an die es lauf Ordnung gehört. remove macht dann eine binäre
Suche auf dem Baum um das Element zu finden, dass er löschen soll.
Post by Peter Büttner
Ist die Performance denn überhaupt wichtig?
(Ich weis nicht was A* ist:-)
Oh ja, die ist entscheidend. Die Performance der "openList" ist der
Flaschenhals beim A*. Schau einfach mal hier
http://www.policyalmanac.org/games/aStarTutorial.htm

Malte
Peter Büttner
2005-08-29 15:05:09 UTC
Permalink
Post by Malte Schneider
Post by Peter Büttner
Wieso? (alles mit Namen die ungefähr so sind:-)
Wrapper(int cost, Comparable data)
...
Comparator
compareTo(){
if a.cost < b.cost return -1;
if a.cost > b.cost return 1;
return a.data.compareTo(b.data)
Was soll in dem Falle data sein ? Die Knoten enthalten an sich nur die
Nun, du willst doch worauf verweisen (in unten angegebenem Link auf
'Quadrate', die nodes also) Wenn deine nodes comparable implementieren
und du nur auf cost vergleichst sind zwei knoten gleich sobald sie dieselben
cost haben, und das wolltest du doch verhindern, da du sonst keine 2 mit
denselben cost in eine TreeMap bekommst, also musst du irgendein weiteres
Kriterium nehmen.
Post by Malte Schneider
1) node1.equals(node2) wenn die Positionen in Graphen gleich sind
2) node1<node2 wenn node1.cost<node2.cost
Post by Peter Büttner
Wenn du neu bewertest sortiert TreeSet aber nicht neu?! Das nimmt doch
eine unveränderliche Ordnung an.
Aber mit einem remove gefolgt von einem add erreiche ich ein "resort".
Kosten sind dann auch nur O(2*log(n))
Kommt das damit klar das? Nimmt es /sicher/ nicht an das die bisherigen
Daten sortiert waren? Das steht so irgendwie nicht in der API
Post by Malte Schneider
Post by Peter Büttner
Liste Sortieren, neue in zweite Liste, beim getSmalest() in der
sortierten das erste, wenn in der 2ten was kleineres halt dieses.
wenn 2te zu groß einfügen und neu sortieren? Klingt wie ein Tree in
klein:-) Also doch TreeSet mit einem kompletten refresh beim costChange
Ich kann dir irgendwie nicht folgen...
Du willst selten sortieren. Wenn z.B. selten neue elemente
hinzukommen, und die vielleicht gerade jene sind die auch schnell
wieder rauskommen, dann lohnt es sich die 'große' Liste mit den 1000
Elementen selten zu ändern und auf eine kleine Liste
mit z.B. 20 Elementen
Post by Malte Schneider
Post by Peter Büttner
Kommt halt drauf an wie das so ist mit add/remove
Ein TreeSet/Map ist in Java als Rot-Schwarz-Baum implementiert, das
steht auch in der API. Das heißt ein Element wird beim add an die Stelle
eingefügt, an die es lauf Ordnung gehört. remove macht dann eine binäre
Suche auf dem Baum um das Element zu finden, dass er löschen soll.
Post by Peter Büttner
Ist die Performance denn überhaupt wichtig?
(Ich weis nicht was A* ist:-)
Oh ja, die ist entscheidend. Die Performance der "openList" ist der
Flaschenhals beim A*. Schau einfach mal hier
http://www.policyalmanac.org/games/aStarTutorial.htm
Malte
--
Grüße
Peter
--
Shell&Jar : Individual icons for jars
jMineSweeper : extended
www.PeterBuettner.de
Malte Schneider
2005-08-29 15:11:27 UTC
Permalink
Post by Peter Büttner
Nun, du willst doch worauf verweisen (in unten angegebenem Link auf
'Quadrate', die nodes also) Wenn deine nodes comparable implementieren
und du nur auf cost vergleichst sind zwei knoten gleich sobald sie dieselben
cost haben, und das wolltest du doch verhindern, da du sonst keine 2 mit
denselben cost in eine TreeMap bekommst, also musst du irgendein weiteres
Kriterium nehmen.
Diese Nodes gehören einfach keiner mathematischen Ordnung an. Mir sind
nun auch nicht alle Kriterien für so eine Ordnung bekannt (dafür ist das
Studium zu lange her, die natürlichen Zahlen sind so eine Ordnung) aber
es dürfen keine 2 Elemente der Ordnung den gleichen Rang haben, d.h.
gleich sein.
Post by Peter Büttner
Kommt das damit klar das? Nimmt es /sicher/ nicht an das die bisherigen
Daten sortiert waren? Das steht so irgendwie nicht in der API
Mit dem Verweis auf Rot-Schwarz-Bäume ist die Vorgehensweise für add und
remove eigentlich schon erklärt.
Post by Peter Büttner
Du willst selten sortieren. Wenn z.B. selten neue elemente
hinzukommen, und die vielleicht gerade jene sind die auch schnell
wieder rauskommen, dann lohnt es sich die 'große' Liste mit den 1000
Elementen selten zu ändern und auf eine kleine Liste
mit z.B. 20 Elementen
Anfangs wird oft hinzugefügt und wenig removed, später genau umgekehrt.
Das hängt aber immer von der Anzahl zu prüfender Nodes ab und von Weg
den man finden will.

Malte
Peter Büttner
2005-08-29 15:16:49 UTC
Permalink
Post by Peter Büttner
Nun, du willst doch worauf verweisen (in unten angegebenem Link auf
'Quadrate', die nodes also) Wenn deine nodes comparable implementieren
Nur zur Info: ich kam in meinem NewsClient auf falsche Tasten und habe zu
früh abgeschickt, das war noch nicht zuende, hab' gecancelt und fertig
geschrieben.


Grüße
Peter
Peter Büttner
2005-08-29 15:14:57 UTC
Permalink
Post by Malte Schneider
Post by Peter Büttner
Wieso? (alles mit Namen die ungefähr so sind:-)
Wrapper(int cost, Comparable data)
...
Comparator
compareTo(){
if a.cost < b.cost return -1;
if a.cost > b.cost return 1;
return a.data.compareTo(b.data)
Was soll in dem Falle data sein ? Die Knoten enthalten an sich nur die
Nun, du willst doch worauf verweisen (in unten angegebenem Link auf
'Quadrate', die nodes also) Wenn deine nodes comparable implementieren
und du nur auf cost vergleichst sind zwei knoten gleich sobald sie dieselben
cost haben, und das wolltest du doch verhindern, da du sonst keine 2 mit
denselben cost in eine TreeMap bekommst, also musst du irgendein weiteres
Kriterium nehmen.

Geht wohl auch ohne wrapper, mit geeignetem Comparator.
Post by Malte Schneider
1) node1.equals(node2) wenn die Positionen in Graphen gleich sind
2) node1<node2 wenn node1.cost<node2.cost
Post by Peter Büttner
Wenn du neu bewertest sortiert TreeSet aber nicht neu?! Das nimmt doch
eine unveränderliche Ordnung an.
Aber mit einem remove gefolgt von einem add erreiche ich ein "resort".
Kosten sind dann auch nur O(2*log(n))
Kommt das damit klar wenn du bei bestehenden Elementen die Ordnung
änderst? Nimmt es /sicher/ nicht an das die bisherigen Daten sortiert
waren? Das steht so irgendwie nicht in der API.

Oder meinst du du willst jene Objekte deren cost sich ändert entfernen
und neu hinzufügen? Das ginge natürlich.
Post by Malte Schneider
Post by Peter Büttner
Liste Sortieren, neue in zweite Liste, beim getSmalest() in der
sortierten das erste, wenn in der 2ten was kleineres halt dieses.
wenn 2te zu groß einfügen und neu sortieren? Klingt wie ein Tree in
klein:-) Also doch TreeSet mit einem kompletten refresh beim costChange
Ich kann dir irgendwie nicht folgen...
Du willst selten sortieren. Wenn z.B. selten neue Elemente
hinzukommen, und die _vielleicht_ gerade jene sind die auch alsbald
wieder rauskommen, dann lohnt es sich die 'große' Liste mit den 1000
Elementen selten zu ändern und auf eine kleine Liste
mit z.B. 20 Elementen zurückzugreifen. Spinnt man das weiter und
unterteilt jeweils tiefer, so bekommt man wohl einen tree.
Post by Malte Schneider
Post by Peter Büttner
Kommt halt drauf an wie das so ist mit add/remove
Ein TreeSet/Map ist in Java als Rot-Schwarz-Baum implementiert, das
steht auch in der API.
Ich bin nicht doof:-) Es ist natürlich gemeint wie die Elemente in deine
Struktur kommen. Also z.B. wie im letzten Absatz angenommen: die zuletzt
reinkamen kommen zuerst wieder raus. Es könnte auch eher random sein,
oder die ältesten kommen zuerst raus. (Ich meine das nicht so hart wie
Queue oder Stack, mehr 'fuzzy') sowas halt.

Ich denke mich jetzt nicht in deinen A* rein, aber du solltest wissen
wie die Dinger so rein und rauskommen.

[...]
Post by Malte Schneider
Post by Peter Büttner
Ist die Performance denn überhaupt wichtig?
(Ich weis nicht was A* ist:-)
Oh ja, die ist entscheidend. Die Performance der "openList" ist der
Flaschenhals beim A*. Schau einfach mal hier
http://www.policyalmanac.org/games/aStarTutorial.htm
Werd' ich mal irgendwann genauer ansehen...

Wobei ich das eher im großen meinte. Wenn dein User vorm Monitor
nur 100 statt 300ms wartet ist es Wurscht.



Grüße
Peter
--
Shell&Jar : Individual icons for jars
jMineSweeper : extended
www.PeterBuettner.de
Malte Schneider
2005-08-29 15:31:00 UTC
Permalink
[snip]
Post by Peter Büttner
Ich bin nicht doof:-) Es ist natürlich gemeint wie die Elemente in deine
Struktur kommen. Also z.B. wie im letzten Absatz angenommen: die zuletzt
reinkamen kommen zuerst wieder raus. Es könnte auch eher random sein,
oder die ältesten kommen zuerst raus. (Ich meine das nicht so hart wie
Queue oder Stack, mehr 'fuzzy') sowas halt.
Ich denke mich jetzt nicht in deinen A* rein, aber du solltest wissen
wie die Dinger so rein und rauskommen.
Man kann nicht voraussetzen, dass jeder Rot-Schwarz-Bäume kennt.
Deswegen hab ichs nochmal erwähnt.
Ein TreeSet ist ja genau dafür da, die Reihenfolge der Elemente ihrer
mathematischen Ordnung anzupassen.
Post by Peter Büttner
Post by Malte Schneider
Oh ja, die ist entscheidend. Die Performance der "openList" ist der
Flaschenhals beim A*. Schau einfach mal hier
http://www.policyalmanac.org/games/aStarTutorial.htm
Werd' ich mal irgendwann genauer ansehen...
Wobei ich das eher im großen meinte. Wenn dein User vorm Monitor
nur 100 statt 300ms wartet ist es Wurscht.
Das Problem ist, dass es schon mal 0.5 Sek dauert, den Web durch ein
50x50 Raster zu finden. Das ist eigentlich zu lang für das was ich
machen will.

Malte
Peter Büttner
2005-08-29 19:12:41 UTC
Permalink
Post by Malte Schneider
[snip]
Post by Peter Büttner
Ich bin nicht doof:-) Es ist natürlich gemeint wie die Elemente in deine
Struktur kommen. Also z.B. wie im letzten Absatz angenommen: die zuletzt
reinkamen kommen zuerst wieder raus. Es könnte auch eher random sein,
oder die ältesten kommen zuerst raus. (Ich meine das nicht so hart wie
Queue oder Stack, mehr 'fuzzy') sowas halt.
Ich denke mich jetzt nicht in deinen A* rein, aber du solltest wissen
wie die Dinger so rein und rauskommen.
Man kann nicht voraussetzen, dass jeder Rot-Schwarz-Bäume kennt.
Aber A*?
Post by Malte Schneider
Deswegen hab ichs nochmal erwähnt.
Ein TreeSet ist ja genau dafür da, die Reihenfolge der Elemente ihrer
mathematischen Ordnung anzupassen.
Post by Peter Büttner
Post by Malte Schneider
Oh ja, die ist entscheidend. Die Performance der "openList" ist der
Flaschenhals beim A*. Schau einfach mal hier
http://www.policyalmanac.org/games/aStarTutorial.htm
Werd' ich mal irgendwann genauer ansehen...
Wobei ich das eher im großen meinte. Wenn dein User vorm Monitor
nur 100 statt 300ms wartet ist es Wurscht.
Das Problem ist, dass es schon mal 0.5 Sek dauert, den Web durch ein
50x50 Raster zu finden. Das ist eigentlich zu lang für das was ich
machen will.
50x50 scheint handhabbar. Du könntest hier banal optimieren
indem du ja maximal 2500 elemente in deinen strukturen hast.
statt irgendein hashCode die nummer des elements.


wie ist dein cost verteilt? also integer 1..100?
aber egal, kategorien bauen, und statt die elemente in
ein TreeSet zu tun könntest du dir sowas wie ein BucketSet
bauen, oder eher Set[] buckets, feste Anzahl, Zugriff mit o(1).
Immer alles elemente mit gleichem cost in ein Bucket, auch zugriff
o(1). Oder diese kategorien intern sortieren.



Eine Bemerkung:
Warum verdammt nochmal gibst du keine weiteren Informationen raus,
bzw. nicht gleich. Wie soll man was vernünftiges raten (nicht im Sinne
von Lotto, sondern Ratschlag) wenn man nix weis.
Warum baust du nicht ein kleines Päckchen mit Code der dein Problem
_ausführbar_ zur Verfügung stellt? So hätten wir vielleicht etwas
Spaß am optimieren, würden dir nicht per _Einbahnstrasse_ know how
im Wert eines gebrauchten Kleinwagens schenken. Wir würden vielleicht
auch etwas über A* lernen. Das wäre etwas ausgleichender.

Früher gab's mal jpec mit lustigem optimieren, da wurden aber nicht
soviele Informationen vom _Hilfesuchenden_ zurückgehalten.




Grüße
Peter
--
Shell&Jar : Individual icons for jars
jMineSweeper : extended
www.PeterBuettner.de
Malte Schneider
2005-08-30 06:22:13 UTC
Permalink
Post by Peter Büttner
Aber A*?
Nein, den auch nicht, da hast du recht.
Post by Peter Büttner
50x50 scheint handhabbar. Du könntest hier banal optimieren
indem du ja maximal 2500 elemente in deinen strukturen hast.
statt irgendein hashCode die nummer des elements.
Stimmt, das sollte einige equals-Aufrufe einsparan.
Post by Peter Büttner
wie ist dein cost verteilt? also integer 1..100?
aber egal, kategorien bauen, und statt die elemente in
ein TreeSet zu tun könntest du dir sowas wie ein BucketSet
bauen, oder eher Set[] buckets, feste Anzahl, Zugriff mit o(1).
Immer alles elemente mit gleichem cost in ein Bucket, auch zugriff
o(1). Oder diese kategorien intern sortieren.
Die Kosten um von einem Node zu einem anderen zu gelangen sind zwischen
1 und 100. Die Einordung in solche Buckets wird kaum was bringen, da
sicher 2400 der 2500 die gleichen Kosten haben. Während A* spielen
ausserdem eher die kumulativen Kosten eine Rolle.
Post by Peter Büttner
Warum verdammt nochmal gibst du keine weiteren Informationen raus,
bzw. nicht gleich. Wie soll man was vernünftiges raten (nicht im Sinne
von Lotto, sondern Ratschlag) wenn man nix weis.
Weil ich dafür erstmal A* erklären müßte. Aber
http://www.policyalmanac.org/games/aStarTutorial.htm erlärt das viel
besser als ich das könnte.
Post by Peter Büttner
Warum baust du nicht ein kleines Päckchen mit Code der dein Problem
_ausführbar_ zur Verfügung stellt? So hätten wir vielleicht etwas
Spaß am optimieren, würden dir nicht per _Einbahnstrasse_ know how
im Wert eines gebrauchten Kleinwagens schenken. Wir würden vielleicht
auch etwas über A* lernen. Das wäre etwas ausgleichender.
Daran hatte ich auch schon gedacht, nur wird das nicht einfach etwas
"kleines" zusammenzubasteln, was euch weiterhelfen würde. Den kompletten
Code wollte ich euch auch nicht antun.

Malte
Peter Büttner
2005-08-30 11:37:09 UTC
Permalink
Post by Malte Schneider
Post by Peter Büttner
50x50 scheint handhabbar. Du könntest hier banal optimieren
indem du ja maximal 2500 elemente in deinen strukturen hast.
statt irgendein hashCode die nummer des elements.
Stimmt, das sollte einige equals-Aufrufe einsparan.
Ich meint auch, vergaß es zu schreiben, das man damit was machen
könnte im Sinne von matrix nehmen und dort was ablegen, nicht weiter
gedacht, brauch ich auch nicht:-)
Post by Malte Schneider
Post by Peter Büttner
wie ist dein cost verteilt? also integer 1..100?
aber egal, kategorien bauen, und statt die elemente in
ein TreeSet zu tun könntest du dir sowas wie ein BucketSet
bauen, oder eher Set[] buckets, feste Anzahl, Zugriff mit o(1).
Immer alles elemente mit gleichem cost in ein Bucket, auch zugriff
o(1). Oder diese kategorien intern sortieren.
Die Kosten um von einem Node zu einem anderen zu gelangen sind zwischen
1 und 100. Die Einordung in solche Buckets wird kaum was bringen, da
sicher 2400 der 2500 die gleichen Kosten haben. Während A* spielen
Na das ist doch prima! alle in einem Bucket, Zugriff o(1), welches du von
den
Post by Malte Schneider
ausserdem eher die kumulativen Kosten eine Rolle.
Post by Peter Büttner
Warum verdammt nochmal gibst du keine weiteren Informationen raus,
bzw. nicht gleich. Wie soll man was vernünftiges raten (nicht im Sinne
von Lotto, sondern Ratschlag) wenn man nix weis.
Weil ich dafür erstmal A* erklären müßte. Aber
http://www.policyalmanac.org/games/aStarTutorial.htm erlärt das viel
besser als ich das könnte.
Das ist langatmig:-)
Mir ging es such eher drum das du Erkenntnisse beisteuerst die die
Lösungsmenge einschränken. Das ist im ersten Posting evtl. etwas
schwierig da man noch gar nicht weis was so wichtig ist, aber
alsbald an einer Stelle nachlegen:
50x50 Gitter, cost in [1..100], fast alle cost gleich,
insert/remove verhalten der openList.
_Das_ ist der extrakt der dein Problem lösen hilft, nicht 'einmal
schnelle Datenstruktur'. Ohne die extrahierten Infos sprichst du halt
eher Leute an die das schon mal gelöst haben, die Zielgruppe dürfte
deutlich schrumpfen

Ich dacht ich google mal nach einer java Implementierung (hab aber nur
1min investiert, Ergebnisse sind also evtl. nicht 1A):

http://www.cuspy.com/software/pathfinder/
Die ist ohne source, aber recht nett gemacht als Applet (nur das
setzen der Klötze ist umständlich)

Hier ist source:
http://www.gameai.com/javastar.html
aber nicht ausprobiert, 260 Zeilen
Post by Malte Schneider
Post by Peter Büttner
Warum baust du nicht ein kleines Päckchen mit Code der dein Problem
_ausführbar_ zur Verfügung stellt? So hätten wir vielleicht etwas
Spaß am optimieren, würden dir nicht per _Einbahnstrasse_ know how
im Wert eines gebrauchten Kleinwagens schenken. Wir würden vielleicht
auch etwas über A* lernen. Das wäre etwas ausgleichender.
Daran hatte ich auch schon gedacht, nur wird das nicht einfach etwas
"kleines" zusammenzubasteln, was euch weiterhelfen würde. Den kompletten
Code wollte ich euch auch nicht antun.
Den kann man irgendwo hinlegen.



Grüße
Peter
--
Shell&Jar : Individual icons for jars
jMineSweeper : extended
www.PeterBuettner.de
Peter Büttner
2005-08-30 11:42:20 UTC
Permalink
Post by Malte Schneider
Post by Peter Büttner
50x50 scheint handhabbar. Du könntest hier banal optimieren
indem du ja maximal 2500 elemente in deinen strukturen hast.
statt irgendein hashCode die nummer des elements.
Stimmt, das sollte einige equals-Aufrufe einsparan.
Ich meint auch - vergaß es zu schreiben - das man damit was machen
könnte im Sinne von matrix nehmen und dort was ablegen, noch nicht
weiter gedacht...
Post by Malte Schneider
Post by Peter Büttner
wie ist dein cost verteilt? also integer 1..100?
aber egal, kategorien bauen, und statt die elemente in
ein TreeSet zu tun könntest du dir sowas wie ein BucketSet
bauen, oder eher Set[] buckets, feste Anzahl, Zugriff mit o(1).
Immer alles elemente mit gleichem cost in ein Bucket, auch zugriff
o(1). Oder diese kategorien intern sortieren.
Die Kosten um von einem Node zu einem anderen zu gelangen sind zwischen
1 und 100. Die Einordung in solche Buckets wird kaum was bringen, da
sicher 2400 der 2500 die gleichen Kosten haben. Während A* spielen
Na das ist doch prima! alle in einem Bucket, Zugriff o(1), welches du von
den 'gleich teuren' nimmst ist doch egal. Evtl. willst du deine cost
Funktion etwas feiner machen?
Post by Malte Schneider
ausserdem eher die kumulativen Kosten eine Rolle.
Post by Peter Büttner
Warum verdammt nochmal gibst du keine weiteren Informationen raus,
bzw. nicht gleich. Wie soll man was vernünftiges raten (nicht im Sinne
von Lotto, sondern Ratschlag) wenn man nix weis.
Weil ich dafür erstmal A* erklären müßte. Aber
http://www.policyalmanac.org/games/aStarTutorial.htm erlärt das viel
besser als ich das könnte.
Das ist langatmig:-)

Mir ging es such eher drum das du Erkenntnisse beisteuerst die die
Lösungsmenge einschränken. Das ist im ersten Posting evtl. etwas
schwierig da man noch gar nicht weis was so wichtig ist, aber
alsbald an einer Stelle nachlegen:
50x50 Gitter, cost in [1..100], fast alle cost gleich,
insert/remove verhalten der openList, ...
_Das_ ist der Extrakt der dein Problem lösen hilft, nicht 'einmal
schnelle Datenstruktur'. Ohne die extrahierten Infos sprichst du halt
eher Leute an die das schon mal gelöst haben, die Zielgruppe dürfte
deutlich schrumpfen.


Ich dacht' ich google mal nach einer java Implementierung (hab aber nur
1min investiert, Ergebnisse sind also evtl. nicht 1A):

http://www.cuspy.com/software/pathfinder/
Die ist ohne source, aber recht nett gemacht als Applet (nur das
setzen der Klötze ist umständlich)

Hier ist source:
http://www.gameai.com/javastar.html
aber nicht ausprobiert, 260 Zeilen
Post by Malte Schneider
Post by Peter Büttner
Warum baust du nicht ein kleines Päckchen mit Code der dein Problem
_ausführbar_ zur Verfügung stellt? So hätten wir vielleicht etwas
Spaß am optimieren, würden dir nicht per _Einbahnstrasse_ know how
im Wert eines gebrauchten Kleinwagens schenken. Wir würden vielleicht
auch etwas über A* lernen. Das wäre etwas ausgleichender.
Daran hatte ich auch schon gedacht, nur wird das nicht einfach etwas
"kleines" zusammenzubasteln, was euch weiterhelfen würde. Den kompletten
Code wollte ich euch auch nicht antun.
Den kann man irgendwo hinlegen.
Wenn du es anderen nett und bequem machst kriegst du auch eher geholfen,
bzw. findest eher Leute die ein Problem reizt als wenn sich alle auch
noch um dich bemühen müssen. (Nicht das ich als Fragesteller auch mal
daneben liege, das Recht will ich auch mir zugestehen:-)





Grüße
Peter
--
Shell&Jar : Individual icons for jars
jMineSweeper : extended
www.PeterBuettner.de
Malte Schneider
2005-08-31 06:20:15 UTC
Permalink
Post by Peter Büttner
Das ist langatmig:-)
Etwa in der Mitte des Turorials findet sich eine kurze Zusammenfassung ..
Post by Peter Büttner
Mir ging es such eher drum das du Erkenntnisse beisteuerst die die
Lösungsmenge einschränken. Das ist im ersten Posting evtl. etwas
schwierig da man noch gar nicht weis was so wichtig ist, aber
50x50 Gitter, cost in [1..100], fast alle cost gleich,
insert/remove verhalten der openList, ...
_Das_ ist der Extrakt der dein Problem lösen hilft, nicht 'einmal
schnelle Datenstruktur'. Ohne die extrahierten Infos sprichst du halt
eher Leute an die das schon mal gelöst haben, die Zielgruppe dürfte
deutlich schrumpfen.
Ich hatte irgendwie angenommen, dass mehr Leute A* kennen.
Post by Peter Büttner
Ich dacht' ich google mal nach einer java Implementierung (hab aber nur
http://www.cuspy.com/software/pathfinder/
Die ist ohne source, aber recht nett gemacht als Applet (nur das
setzen der Klötze ist umständlich)
Ohne Code leider wenig nützlich.
Post by Peter Büttner
http://www.gameai.com/javastar.html
aber nicht ausprobiert, 260 Zeilen
Diese Impl erscheint mir ziemlich mies.
Post by Peter Büttner
Den kann man irgendwo hinlegen.
Wenn du es anderen nett und bequem machst kriegst du auch eher geholfen,
bzw. findest eher Leute die ein Problem reizt als wenn sich alle auch
noch um dich bemühen müssen. (Nicht das ich als Fragesteller auch mal
daneben liege, das Recht will ich auch mir zugestehen:-)
Mir fällt momentan kein WebSpace ein, wo ich das hinpacken könnte. Wenn
man ein SourceForge-Projekt draus wird, sieht das wieder anders aus.

Malte
Ingo R. Homann
2005-08-31 09:11:26 UTC
Permalink
Hi,
Post by Malte Schneider
Ich hatte irgendwie angenommen, dass mehr Leute A* kennen.
Du studierst nicht zufällig NWI an der Uni Bielefeld?

Ciao,
Ingo
Malte Schneider
2005-08-31 14:43:01 UTC
Permalink
Post by Ingo R. Homann
Du studierst nicht zufällig NWI an der Uni Bielefeld?
Ciao,
Ingo
Ne, wie kommst du darauf ?

Malte
Ingo R. Homann
2005-08-31 15:07:45 UTC
Permalink
Hi,
Post by Malte Schneider
Post by Ingo R. Homann
Du studierst nicht zufällig NWI an der Uni Bielefeld?
Ne, wie kommst du darauf ?
Weil (u.a.) der da durchgekaut wird.

Und (vielleicht, ich weiss es nicht) A* nicht so ein
"Standard-Algorithmus" ist, wie die diversen Sortier-Verfahren, die ja
an jeder Uni durchgekaut werden, wohingegen A* ja (zumindest hier)
scheinbar eher unbekannt ist.

Ciao,
Ingo
Malte Schneider
2005-09-01 12:03:56 UTC
Permalink
Post by Ingo R. Homann
Weil (u.a.) der da durchgekaut wird.
Und (vielleicht, ich weiss es nicht) A* nicht so ein
"Standard-Algorithmus" ist, wie die diversen Sortier-Verfahren, die ja
an jeder Uni durchgekaut werden, wohingegen A* ja (zumindest hier)
scheinbar eher unbekannt ist.
Ich kann mich nicht mehr so recht erinnern, ob ich damit auch gequält
wurde (FSU Jena). Aber ich hab A* anderswo wiederentdeckt. Kennst du
dich denn mit A* aus ? Wäre interessant bessere Heuristiken zu finden
als die "MahattanMethod" oder gar einfach 0.

Malte
Ingo R. Homann
2005-09-01 12:29:08 UTC
Permalink
Hi,
Post by Malte Schneider
Post by Ingo R. Homann
Weil (u.a.) der da durchgekaut wird.
Und (vielleicht, ich weiss es nicht) A* nicht so ein
"Standard-Algorithmus" ist, wie die diversen Sortier-Verfahren, die ja
an jeder Uni durchgekaut werden, wohingegen A* ja (zumindest hier)
scheinbar eher unbekannt ist.
Ich kann mich nicht mehr so recht erinnern, ob ich damit auch gequält
wurde (FSU Jena). Aber ich hab A* anderswo wiederentdeckt. Kennst du
dich denn mit A* aus ?
Ist lange her und ich habe nie produktiv mit gearbeitet...
Post by Malte Schneider
Wäre interessant bessere Heuristiken zu finden
als die "MahattanMethod" oder gar einfach 0.
"Heuristiken" oder "Metriken"?

Anyway: Ich habe deine Quelle nicht noch mal durchgelesen, und
vielleicht ist meine Idee ja schon alt, und ich habe nur den
"Standard-A*" kennengelernt.

Aber mich hat immer schon mal interessiert (bin nur nie zu gekommen, es
selbst auszuprobieren), ob es nicht einen *ziemlichen*
Performance-Gewinn gibt, wenn man die einzelnen Zellen baumartig
zusammenfasst...


a b c d e f g h
i j k l m n o p
q r s t u v w x
y z 1 2 3 4 5 6

zu

AA CC EE GG
AA CC EE GG
QQ SS UU WW
QQ SS UU WW

zu

A'A'A'A' E'E'E'E'
A'A'A'A' E'E'E'E'
A'A'A'A' E'E'E'E'
A'A'A'A' E'E'E'E'

... und den Algorithmus so modifiziert, dass er erstmal auf den größeren
(und dafür deutlich weniger) Zellen arbeitet, und nur dann in die Tiefe
geht (die Auflösung verbessert), wenn eine bestimmte Zelle partiell
besetzt ist anstatt dass sie komplett besetzt ist oder gar nicht besetzt
ist.

Ist etwas schwer zu beschreiben. Ich hoffe, Du verstehst, was ich meine.
Für die Bildverarbeitung (die an der uni Bielefeld übrigens auch 'in'
ist), gibts dieses Verfahren zum Packen von Schwarz-Weiss-Bildern, und
heisst glaube ich Quadtree. Wenn Du mal danach googelst, findest Du
sicher was.

Jedenfalls könnte das im Optimalfall eine Reduzierung des Aufwandes von
Faktor n auf Faktor log(n) bringen. Ich kann mir eigentlich kaum
vorstellen, dass nicht schon jemand anders auf die Idee gekommen ist...

Ciao,
Ingo
Malte Schneider
2005-09-01 13:55:30 UTC
Permalink
Post by Ingo R. Homann
"Heuristiken" oder "Metriken"?
Laut meiner Quelle "heuristic".
Post by Ingo R. Homann
Anyway: Ich habe deine Quelle nicht noch mal durchgelesen, und
vielleicht ist meine Idee ja schon alt, und ich habe nur den
"Standard-A*" kennengelernt.
Aber mich hat immer schon mal interessiert (bin nur nie zu gekommen, es
selbst auszuprobieren), ob es nicht einen *ziemlichen*
Performance-Gewinn gibt, wenn man die einzelnen Zellen baumartig
zusammenfasst...
a b c d e f g h
i j k l m n o p
q r s t u v w x
y z 1 2 3 4 5 6
zu
AA CC EE GG
AA CC EE GG
QQ SS UU WW
QQ SS UU WW
zu
A'A'A'A' E'E'E'E'
A'A'A'A' E'E'E'E'
A'A'A'A' E'E'E'E'
A'A'A'A' E'E'E'E'
... und den Algorithmus so modifiziert, dass er erstmal auf den größeren
(und dafür deutlich weniger) Zellen arbeitet, und nur dann in die Tiefe
geht (die Auflösung verbessert), wenn eine bestimmte Zelle partiell
besetzt ist anstatt dass sie komplett besetzt ist oder gar nicht besetzt
ist.
An sich eine clevere Idee. Es dürfte aber nur dann etwas bringen, wenn
die meisten Knoten nicht belegt sind. Ansonsten wird der Aufwand im
Mittel wahrscheinlich höher. Aber es war noch nie meine Stärke
Aufwandsabschätzungen zu machen, die Wahrscheinlichkeiten einbeziehen.
Post by Ingo R. Homann
Ist etwas schwer zu beschreiben. Ich hoffe, Du verstehst, was ich meine.
Für die Bildverarbeitung (die an der uni Bielefeld übrigens auch 'in'
ist), gibts dieses Verfahren zum Packen von Schwarz-Weiss-Bildern, und
heisst glaube ich Quadtree. Wenn Du mal danach googelst, findest Du
sicher was.
Dieses Verfahren ist mir auch geläufig.
Post by Ingo R. Homann
Jedenfalls könnte das im Optimalfall eine Reduzierung des Aufwandes von
Faktor n auf Faktor log(n) bringen. Ich kann mir eigentlich kaum
vorstellen, dass nicht schon jemand anders auf die Idee gekommen ist...
Doch bestimmt. Man sollte eben irgendwie messen können, ob viele oder
wenige Zellen besetzt sind. Anhand eines Schwellwertes wählt man dann
die eine oder andere Strategie aus. So wie Collections.sort() für das
Sortierverfahren abhängig von der Größe der List auswählt.

Malte
Ingo R. Homann
2005-09-01 14:27:03 UTC
Permalink
Hi,
Post by Malte Schneider
An sich eine clevere Idee. Es dürfte aber nur dann etwas bringen, wenn
die meisten Knoten nicht belegt sind.
Oder, wenn die meisten Knoten belegt sind. Weniger gut ist es nur in den
Fällen, wo eine starke "Vermischung" von belegten und nicht-belegten
Knoten vorkommt (also z.B. an Rändern).
Post by Malte Schneider
Ansonsten wird der Aufwand im
Mittel wahrscheinlich höher.
IMHO aber "nur" durch einen konstanten Faktor, also O(1). (Was natürlich
auch entscheidend sein kann!)
Post by Malte Schneider
Aber es war noch nie meine Stärke
Aufwandsabschätzungen zu machen, die Wahrscheinlichkeiten einbeziehen.
Im Zweifelsfall: Testen!
Post by Malte Schneider
Doch bestimmt. Man sollte eben irgendwie messen können, ob viele oder
wenige Zellen besetzt sind. Anhand eines Schwellwertes wählt man dann
die eine oder andere Strategie aus.
Ja, z.B. :-)

Ciao,
Ingo
Malte Schneider
2005-09-01 14:30:40 UTC
Permalink
Post by Ingo R. Homann
Hi,
Post by Malte Schneider
An sich eine clevere Idee. Es dürfte aber nur dann etwas bringen, wenn
die meisten Knoten nicht belegt sind.
Oder, wenn die meisten Knoten belegt sind. Weniger gut ist es nur in den
Fällen, wo eine starke "Vermischung" von belegten und nicht-belegten
Knoten vorkommt (also z.B. an Rändern).
Post by Malte Schneider
Ansonsten wird der Aufwand im
Mittel wahrscheinlich höher.
IMHO aber "nur" durch einen konstanten Faktor, also O(1). (Was natürlich
auch entscheidend sein kann!)
Post by Malte Schneider
Aber es war noch nie meine Stärke
Aufwandsabschätzungen zu machen, die Wahrscheinlichkeiten einbeziehen.
Im Zweifelsfall: Testen!
Post by Malte Schneider
Doch bestimmt. Man sollte eben irgendwie messen können, ob viele oder
wenige Zellen besetzt sind. Anhand eines Schwellwertes wählt man dann
die eine oder andere Strategie aus.
Ja, z.B. :-)
Ciao,
Ingo
Vieleicht sollte ich dir mal meinen bisherigen Code verfügbar machen. Da
kannst du leichter experimentieren. Eine einfache Visualisierung von
A* ist enthalten ;-)

Malte
Paul Ebermann
2005-09-01 23:50:37 UTC
Permalink
Post by Ingo R. Homann
Post by Malte Schneider
Wäre interessant bessere Heuristiken zu finden
als die "MahattanMethod" oder gar einfach 0.
"Heuristiken" oder "Metriken"?
Wir wollen eine Heuristik für die Metrik - die Heuristik
selbst muss keine Metrik sein (die Manhattan-Metrik ist
eine), sollte aber nie größer als die echte Metrik
(die ja der Algorithmus ja ausrechnen soll) sein - das ist
bei der Manhattan-Metrik nicht gegeben. Daher kann die
Manhattan-Metrik in Extemfällen zu einem falschem (zu
langen) Weg führen.

Vielleicht könnte man etwas wie

dx + dy - 2/3 min(dx,dy)

nehmen, anstatt einfach nur

dx + dy.

(dx := |x1 - x2|, dy := |y1 - y2|)

(Am besten ist wohl sqrt(dx^2 + dy^2), aber das
ist eben aufwendiger auszurechnen.)
Post by Ingo R. Homann
Anyway: Ich habe deine Quelle nicht noch mal durchgelesen, und
vielleicht ist meine Idee ja schon alt, und ich habe nur den
"Standard-A*" kennengelernt.
Aber mich hat immer schon mal interessiert (bin nur nie zu gekommen, es
selbst auszuprobieren), ob es nicht einen *ziemlichen*
Performance-Gewinn gibt, wenn man die einzelnen Zellen baumartig
zusammenfasst...
[...]
Post by Ingo R. Homann
... und den Algorithmus so modifiziert, dass er erstmal auf den größeren
(und dafür deutlich weniger) Zellen arbeitet, und nur dann in die Tiefe
geht (die Auflösung verbessert), wenn eine bestimmte Zelle partiell
besetzt ist anstatt dass sie komplett besetzt ist oder gar nicht besetzt
ist.
Der allgemeine A*-Algorithmus arbeitet ja auf einem gewichtetem
Graphen - dass man da bei Landkarten üblicherweise eine Quadrataufteilung
nimmt, hat nur praktische Gründe. Natürlich kannst du beliebig
aufteilen ...

Im Ergebnis könnte das zu einem leicht längeren Weg führen,
wenn man jetzt immer zwischen den Mitten der großen Zellen läuft,
anstatt den direkten Weg zu nehmen. Aber vielleicht kann man das
beheben, indem man den Ergebnis-Weg noch einmal glättet.


Paul
--
The Java Language Specification: http://java.sun.com/docs/books/jls/index.htmls
Mit Änderungen durch 5.0:
http://jcp.org/aboutJava/communityprocess/pfd/jsr014/index2.html
(18 PDFs in einem Zip in einem anderen Zip - wer kennt eine bessere Version?)
Malte Schneider
2005-09-02 09:17:34 UTC
Permalink
Post by Paul Ebermann
Wir wollen eine Heuristik für die Metrik - die Heuristik
selbst muss keine Metrik sein (die Manhattan-Metrik ist
eine), sollte aber nie größer als die echte Metrik
(die ja der Algorithmus ja ausrechnen soll) sein - das ist
bei der Manhattan-Metrik nicht gegeben. Daher kann die
Manhattan-Metrik in Extemfällen zu einem falschem (zu
langen) Weg führen.
Vielleicht könnte man etwas wie
dx + dy - 2/3 min(dx,dy)
nehmen, anstatt einfach nur
wie kommst du gerade auf diese Berechnungsvorschrift ?
Post by Paul Ebermann
dx + dy.
(dx := |x1 - x2|, dy := |y1 - y2|)
Klar.
Post by Paul Ebermann
(Am besten ist wohl sqrt(dx^2 + dy^2), aber das
ist eben aufwendiger auszurechnen.)
Ja leider. Man könnte ja auch für diagonale Schritte über den Pytagoras
höhere Kosten berechnen. Aber auch das ist aufwendig, weshalb ich mich
dagegen entschieden hab. Ich nutze für die Kosten ohnehin nur int.
Post by Paul Ebermann
Der allgemeine A*-Algorithmus arbeitet ja auf einem gewichtetem
Graphen - dass man da bei Landkarten üblicherweise eine Quadrataufteilung
nimmt, hat nur praktische Gründe. Natürlich kannst du beliebig
aufteilen ...
natürlich.
Post by Paul Ebermann
Im Ergebnis könnte das zu einem leicht längeren Weg führen,
wenn man jetzt immer zwischen den Mitten der großen Zellen läuft,
anstatt den direkten Weg zu nehmen. Aber vielleicht kann man das
beheben, indem man den Ergebnis-Weg noch einmal glättet.
Vorausgesetzt man verschleudert beim Glätten nicht die gesamte
eingesparte Zeit wieder.

Mein Anwendungsfall erfordert es ohnehin nicht, immer den kürzesten Weg
zu finden. Wichtiger ist eher, dass sich ein annehmbar kurzer Weg
schnell berechnen läßt.

Wollte man immer den kürzesten Weg, könnte man auch eine konstante
Heuristik von 0 annehmen.

Malte
Paul Ebermann
2005-09-02 11:06:56 UTC
Permalink
Post by Malte Schneider
Post by Paul Ebermann
Wir wollen eine Heuristik für die Metrik - die Heuristik
selbst muss keine Metrik sein (die Manhattan-Metrik ist
eine), sollte aber nie größer als die echte Metrik
(die ja der Algorithmus ja ausrechnen soll) sein - das ist
bei der Manhattan-Metrik nicht gegeben. Daher kann die
Manhattan-Metrik in Extemfällen zu einem falschem (zu
langen) Weg führen.
Vielleicht könnte man etwas wie
dx + dy - 2/3 min(dx,dy)
nehmen, anstatt einfach nur
wie kommst du gerade auf diese Berechnungsvorschrift ?
Der Fall, in dem die Manhattan-Metrik am schlechtesten (=am
meisten falsch) ist, ist ja der, dass man einfach diagonal
gehen kann, also dx = dy. Dann ist der Weg sqrt(2)*dx (~= 1.41*dx).
Meine Metrik gibt dann 1.33*dx, also etwas darunter (=gut!).
Die Manhattan-Metrik würde 2 ergeben, das ist zu viel (schlecht!).

Das andere Extrem ist dy = 0, dann ergeben beide Metriken
genau das Optimum. In den Fällen dazwischen berechnet meine
Metrik den Weg zuerst orthogonal, danach diagonal, und dann
noch einen Abschlag, um den Umweg auszugleichen ...

Ich fürchte, das kann auch noch daneben- (= darüber-)liegen
hoffentlich nicht so stark. (Ich habe keine Lust, das jetzt
detailliert auszurechnen.

-------------------
'-. \
'-. \
'-. \
'-. \
'-. \
'-. \
'-. \
'-. \
'-\
Post by Malte Schneider
Post by Paul Ebermann
dx + dy.
(dx := |x1 - x2|, dy := |y1 - y2|)
Klar.
Post by Paul Ebermann
(Am besten ist wohl sqrt(dx^2 + dy^2), aber das
ist eben aufwendiger auszurechnen.)
Ja leider. Man könnte ja auch für diagonale Schritte über den Pytagoras
höhere Kosten berechnen. Aber auch das ist aufwendig, weshalb ich mich
dagegen entschieden hab. Ich nutze für die Kosten ohnehin nur int.
Ähm ... wenn du für diagonale Schritte nur 1-Kosten berechnest (die
Kosten in Wirklichkeit aber höher sind), dann wird dein Algorithmus
häufiger mal falsche Ergebnisse ausspucken.

In dem Tutorial wird ja mit Kosten von 14 für diagonale und 10 für
orthogonale Schritte gerechnet - das kommt schon viel besser hin.

Auf jeden Fall sollte die Heuristik irgendwie dazupassen.
Post by Malte Schneider
Post by Paul Ebermann
Im Ergebnis könnte das zu einem leicht längeren Weg führen,
wenn man jetzt immer zwischen den Mitten der großen Zellen läuft,
anstatt den direkten Weg zu nehmen. Aber vielleicht kann man das
beheben, indem man den Ergebnis-Weg noch einmal glättet.
Vorausgesetzt man verschleudert beim Glätten nicht die gesamte
eingesparte Zeit wieder.
Das hängt auch ein bisschen davon ab, was für Wege am Ende nutzbar sind.
Ist ein direkter Weg im Winkel von 70° wirklich schneller zu laufen als
einer, der zuerst ein Stück 90° und dann 45° nimmt?

[Damit waren jetzt Computerspiele gemeint, wo die Einheiten eben
sowieso nur auf einem Raster in 90/45°-Winkeln laufen ...]


Paul
--
Zitieren im Usenet: http://einklich.net/usenet/zitier.htm
Warum Realnamen: http://www.wschmidhuber.de/realname/
Ralf Ullrich
2005-09-02 12:09:36 UTC
Permalink
Post by Paul Ebermann
Post by Malte Schneider
Post by Paul Ebermann
Vielleicht könnte man etwas wie
dx + dy - 2/3 min(dx,dy)
Der Fall, in dem die Manhattan-Metrik am schlechtesten (=am
meisten falsch) ist, ist ja der, dass man einfach diagonal
gehen kann, also dx = dy. Dann ist der Weg sqrt(2)*dx (~= 1.41*dx).
Meine Metrik gibt dann 1.33*dx, also etwas darunter (=gut!).
Die Manhattan-Metrik würde 2 ergeben, das ist zu viel (schlecht!).
[...]
Post by Paul Ebermann
Post by Malte Schneider
Post by Paul Ebermann
(Am besten ist wohl sqrt(dx^2 + dy^2), aber das
ist eben aufwendiger auszurechnen.)
Ja leider. Man könnte ja auch für diagonale Schritte über den Pytagoras
höhere Kosten berechnen. Aber auch das ist aufwendig, weshalb ich mich
dagegen entschieden hab. Ich nutze für die Kosten ohnehin nur int.
Ähm ... wenn du für diagonale Schritte nur 1-Kosten berechnest (die
Kosten in Wirklichkeit aber höher sind), dann wird dein Algorithmus
häufiger mal falsche Ergebnisse ausspucken.
In dem Tutorial wird ja mit Kosten von 14 für diagonale und 10 für
orthogonale Schritte gerechnet - das kommt schon viel besser hin.
Auf jeden Fall sollte die Heuristik irgendwie dazupassen.
Also Paul, ich habe mal schnell deine "Mutmaßungen" durch einen kleinen
Test auf einem 15x9 Feld überprüft. Unten die detaillierten Ergebnisse.

Deine Mutmaßungen sind im großen und ganzen Falsch.

Sei h(dx,dy) die heuristische Metrik für den noch zu gehenden Weg. Dann ist

1. h(dx,dy) = 0 äquivalent zum originalen Algorithmus von Dijkstra, der
garantiert einen kürzesten Weg findet. Im Test macht er das nach 67
Iterationen. (Ergebnis Dijkstra)

2. h(dx,dy) = dx + dy äquivalent zum "Standard" A* mit
"Manhattan-Metrik". Auch diese findet AFAIK garantiert einen kürzesten
Weg. Im Test erfolgt das bereits nach 14 Iterationen. (Ergebnis Astar)

3. h(dx,dy) = dx + dy - 2/3*min(dx,dy), also deinem Vorschlag für eine
"Verbesserung". Diese ist aber eine Verschlechterung. Der Weg wird erst
nach 20 Iterationen gefunden. (Ergebnis Ebermann)

4. h(dx,dy) = sqrt(dx*dx + dy*dy), also deinem mutmaßlichen "Optimum".
Was aber eine noch weitergehende Verschlechterung ist, der Weg wird
nämlich erst nach 21 Iterationen gefunden. (Ergebnis Pythagoras)

Was sagst du nun?

cu

Legende zu den Ergebnissen:

W = Wand
_ = nicht untersuchtes begehbares Feld
1-9 = erste neun Felder der Open-List
o = weitere Felder der Open-List
C = Feld ist auf der Closed-List
S = Start-Punkt
* = Weg-Punkte (und Ziele, es kann bei meiner Implementation mehrere geben!)

Das Feld sieht am Amfang immer so aus:

WWWWWWWWWWWWWWW
W_____________W
W_____________W
W______W______W
W___S__W__*___W
W______W______W
W_____________W
W_____________W
WWWWWWWWWWWWWWW



=== #67 open: 7
WWWWWWWWWWWWWWW
WCCCCCCCCCC6__W
WCCCCCCCCCC2__W
WCCCCCCWCCC3__W
WCCCS*CWCC*5__W
WCCCCC*WC*C4__W
WCCCCCC**CC1__W
WCCCCCCCCCC7__W
WWWWWWWWWWWWWWW
Dijkstra: 67 iterations / path lentgh: 7
=== #14 open: 25
WWWWWWWWWWWWWWW
W_____________W
W__o795o______W
W_o4CCCWoo6___W
W_oCS*CW2**___W
W_ooCC*W*1o___W
W__ooo3*oo____W
W_____oo8_____W
WWWWWWWWWWWWWWW
AStar: 14 iterations / path lentgh: 7
=== #20 open: 27
WWWWWWWWWWWWWWW
W____oo8oo____W
W__oo5C**oo___W
W__oCC*WC*4___W
W__3S*CWo1*___W
W__oCCCWCC7___W
W__oo2CCC6o___W
W____oo9oo____W
WWWWWWWWWWWWWWW
Ebermann: 20 iterations / path lentgh: 7
=== #21 open: 26
WWWWWWWWWWWWWWW
W____oo8oo____W
W__o43CCCoo___W
W__9CCCWCCo___W
W__6S*CW5C*___W
W__oCC*W**7___W
W__oo1C*C2o___W
W____ooooo____W
WWWWWWWWWWWWWWW
Pythagoras: 21 iterations / path lentgh: 7
Paul Ebermann
2005-09-03 06:25:07 UTC
Permalink
Post by Ralf Ullrich
Post by Paul Ebermann
Vielleicht könnte man etwas wie
dx + dy - 2/3 min(dx,dy)
[...]
Post by Ralf Ullrich
Post by Paul Ebermann
(Am besten ist wohl sqrt(dx^2 + dy^2), aber das
ist eben aufwendiger auszurechnen.)
Also Paul, ich habe mal schnell deine "Mutmaßungen" durch einen kleinen
Test auf einem 15x9 Feld überprüft. Unten die detaillierten Ergebnisse.
Danke, dass du dir die Mühe gemacht hast.
(Mir fehlte dazu die Zeit.)
Post by Ralf Ullrich
Deine Mutmaßungen sind im großen und ganzen Falsch.
Aha.
Post by Ralf Ullrich
Sei h(dx,dy) die heuristische Metrik für den noch zu gehenden Weg. Dann ist
1. h(dx,dy) = 0 äquivalent zum originalen Algorithmus von Dijkstra, der
garantiert einen kürzesten Weg findet. Im Test macht er das nach 67
Iterationen. (Ergebnis Dijkstra)
2. h(dx,dy) = dx + dy äquivalent zum "Standard" A* mit
"Manhattan-Metrik". Auch diese findet AFAIK garantiert einen kürzesten
Weg. Im Test erfolgt das bereits nach 14 Iterationen. (Ergebnis Astar)
Zur "Garantie" siehe unten im Beispiel.
Post by Ralf Ullrich
3. h(dx,dy) = dx + dy - 2/3*min(dx,dy), also deinem Vorschlag für eine
"Verbesserung". Diese ist aber eine Verschlechterung. Der Weg wird erst
nach 20 Iterationen gefunden. (Ergebnis Ebermann)
4. h(dx,dy) = sqrt(dx*dx + dy*dy), also deinem mutmaßlichen "Optimum".
Was aber eine noch weitergehende Verschlechterung ist, der Weg wird
nämlich erst nach 21 Iterationen gefunden. (Ergebnis Pythagoras)
Was sagst du nun?
Wenigstens finden alle den richtigen Weg, ist doch
schon mal positiv :-)

Die Manhattan-Metrik scheint also doch besser zu sein,
als ich dachte. Wenn damit der richtige Weg gefunden wird,
dann richtig schnell :-)

Ich habe mal eine Karte gebastelt, wo man den Unterschied
sieht:

WWWWWWWWWWWWWWWWWWWWWWWWWWWW
WWWWWWWWWWWWWWWWW_WWWWWWWWWW
WWWWWWWWWWWWWWWW_W_WW_WWWWWW
WWWWWWWWWWWWWWWW_W_W_W_WWWWW
WWWWWWWWWWWWWWWW_W_W_W_WWWWW
WWWWWWWWWWWWWWWW_W_W_WW___*W
WS_______________WW_WWWWW_WW
WW_WWWWWWWWWWWWWWWWWWWWW_WWW
WWW_WWWWWWWWWWWWWWWWWWW_WWWW
WWWW_WWWWWWWWWWWWWWWWW_WWWWW
WWWWW_WWWWWWWWWWWWWWW_WWWWWW
WWWWWW_WWWWWWWWWWWWW_WWWWWWW
WWWWWWW_WWWWWWWWWWW_WWWWWWWW
WWWWWWWW_WWWWWWWWW_WWWWWWWWW
WWWWWWWWW_WWWWWWW_WWWWWWWWWW
WWWWWWWWWW_WWWWW_WWWWWWWWWWW
WWWWWWWWWWW_WWW_WWWWWWWWWWWW
WWWWWWWWWWWW___WWWWWWWWWWWWW
WWWWWWWWWWWWWWWWWWWWWWWWWWWW

Es gibt hier offensichtlich genau zwei Wege
zum Ziel. Einer der beiden ist länger ... Du
darfst jetzt mal raten, welchen der beiden
Wege die einzelnen Heuristiken finden :-)

(Ich habe die Karte etwas vereinfacht, um
das Suchen zu beschleunigen - in meiner
Original-Karte gab es links noch ein
riesiges freies Feld, welches erst noch
mit abgesucht wurde ...)

Wenn man sich die Kosten des gefundenen Weges ausgeben
lässt (ich habe das mal normiert auf 100 bzw. 141 pro
Wegstück), erhält man für den nördlichen Weg 3728, für
den südlichen Weg 3443.

Die Manhattan-Metrik findet den nördlichen Weg, die
anderen Heuristiken den südlichen.

Die Manhattan-Heuristik ist übrigens auch hier schneller
als die anderen (40 Schritte statt 53 bzw. 60), aber
dafür ein falsches Ergebnis :-(

"Die Manhattan-Metrik findet häufiger mal den falschen
Weg" ist leicht übertrieben, aber es gibt eben Fälle,
in denen sie es tut.


Paul

PS:

(Die Karte ist 27 x 19 groß, so groß sollten dann
auch die Pathfinder sein.)

Hier die passenden Methoden ...
---
private static void demoPathfinder(Pathfinder pf) {
pf.parseMap(
"WWWWWWWWWWWWWWWWWWWWWWWWWWWW",
"WWWWWWWWWWWWWWWWW_WWWWWWWWWW",
"WWWWWWWWWWWWWWWW_W_WW_WWWWWW",
"WWWWWWWWWWWWWWWW_W_W_W_WWWWW",
"WWWWWWWWWWWWWWWW_W_W_W_WWWWW",
"WWWWWWWWWWWWWWWW_W_W_WW___*W",
"WS_______________WW_WWWWW_WW",
"WW_WWWWWWWWWWWWWWWWWWWWW_WWW",
"WWW_WWWWWWWWWWWWWWWWWWW_WWWW",
"WWWW_WWWWWWWWWWWWWWWWW_WWWWW",
"WWWWW_WWWWWWWWWWWWWWW_WWWWWW",
"WWWWWW_WWWWWWWWWWWWW_WWWWWWW",
"WWWWWWW_WWWWWWWWWWW_WWWWWWWW",
"WWWWWWWW_WWWWWWWWW_WWWWWWWWW",
"WWWWWWWWW_WWWWWWW_WWWWWWWWWW",
"WWWWWWWWWW_WWWWW_WWWWWWWWWWW",
"WWWWWWWWWWW_WWW_WWWWWWWWWWWW",
"WWWWWWWWWWWW___WWWWWWWWWWWWW",
"WWWWWWWWWWWWWWWWWWWWWWWWWWWW"
);
pf.findPath();
System.out.println(pf.getClass().getSimpleName()+": " + pf.steps+
" iterations / path length: "+pf.size());
for (int i = 0; i < pf.size(); i++) {
System.out.println(i + ": " + pf.get(i).getX() + "/"
+ pf.get(i).getY());
}
}

private void parseMap(String... map)
{
for (int y = 0; y < this.height; y++)
{
for (int x = 0; x < this.width; x++)
{
switch (map[y].charAt(x))
{
case 'W':
this.addWall(x,y);
break;
case 'S':
this.setStart(x,y);
break;
case '*':
this.addTarget(x,y);
break;
case '_':
break;
}
}
}
}
---
--
Die Homepage von de.comp.lang.java: http://www.dclj.de
Pauls Package-Sammlung: http://purl.org/NET/ePaul/#pps
Ralf Ullrich
2005-09-03 11:56:16 UTC
Permalink
Post by Paul Ebermann
Danke, dass du dir die Mühe gemacht hast.
(Mir fehlte dazu die Zeit.)
Dito. wegen der ausführlichen Analyse. Also gut ich ziehe das "AFAIK
immer den kürzesten für Manhatten" zurück.

Einigen wir uns mal darauf, das für "praktikable" Fälle (z.B. eines
Computer-Spiels, dass sich nicht in einem Labyrinth abspielt) Manhatten
durchaus seine Berechtigung hat.

cu
Paul Ebermann
2005-09-03 13:41:06 UTC
Permalink
Post by Ralf Ullrich
Post by Paul Ebermann
Danke, dass du dir die Mühe gemacht hast.
(Mir fehlte dazu die Zeit.)
Dito. wegen der ausführlichen Analyse. Also gut ich ziehe das "AFAIK
immer den kürzesten für Manhatten" zurück.
Einigen wir uns mal darauf, das für "praktikable" Fälle (z.B. eines
Computer-Spiels, dass sich nicht in einem Labyrinth abspielt) Manhatten
durchaus seine Berechtigung hat.
OK. (Wobei in einem "echten" Labyrinth, wo es keine
diagonalen Gänge gibt, die Manhattan-Methode schon
optimal ist.)

Ich überlege noch, ob man das nicht etwas verbessern
könnte - meine Variante taugte offensichtlich nichts.
Vielleicht ein Faktor 0.7 auf das Manhattan-Ergebnis?

(Dann kommt sie auch mit meinem Labyrinth klar - aber
ich nehme an, dass sie nicht mehr ganz so schnell ist.)


Paul
--
Ich beantworte Java-Fragen per E-Mail nur gegen Bezahlung. Kostenpunkt
100 Euro pro angefangene Stunde. Bitte erwähne in der Anfrage, dass du
das akzeptierst, da ich sonst die E-Mail einfach ignoriere.
(In der Newsgroup fragen ist billiger und oft auch schneller.)
Paul Ebermann
2005-08-31 01:17:58 UTC
Permalink
Post by Malte Schneider
Post by Peter Büttner
Warum verdammt nochmal gibst du keine weiteren Informationen raus,
bzw. nicht gleich. Wie soll man was vernünftiges raten (nicht im Sinne
von Lotto, sondern Ratschlag) wenn man nix weis.
Weil ich dafür erstmal A* erklären müßte. Aber
http://www.policyalmanac.org/games/aStarTutorial.htm erlärt das viel
besser als ich das könnte.
Punkt 7 bei den "Implementation Notes" sagt doch alles - du brauchst
einen Heap. Der Autor schlägt einen Binär-Heap vor, eventuell ist aber
ein Binomial-Heap oder ein Fibinacci-Heap geeigneter.

http://de.wikipedia.org/wiki/Heap_(Datenstruktur)


Paul
--
Zitieren im Usenet: http://einklich.net/usenet/zitier.htm
Warum Realnamen: http://www.wschmidhuber.de/realname/
Ingo R. Homann
2005-08-29 13:55:56 UTC
Permalink
Hi,
Post by Malte Schneider
So habs ich es im Moment implementiert. Leider muss man bei add und beim
Ändern der Kosten eines bereits eingefügten Nodes neu sortieren. Das
kostet mehr zeit als nötig, weil man ja eigentlich nur den Node mit den
kleinsten Kosten braucht und nicht den zweitkleinsten und drittkleinsten
auch noch, zumindest wenn zwischendurch wieder Nodes hinzukommen. Wenn
man immer abwechselnd einen Node added und liest, dann würde serielle
Suche schneller sein als jedes Mal zu sortieren. -> Frage an unsere
Akademiker: ab wann ist Sortieren günstiger ?
Um es mal zusammenzufassen: Eine SortedMap (also TreeSet) kommt nicht in
Frage, weil die Kosten sich ändern. Das einzige, was deine
O(1)-Anforderungen erfüllt, ist eine HashSet. Die solltest Du
kombinieren mit dem Zwischenspeichern des kleinsten Elemenets und das
ganze kapseln. Um mit removes klarzukommen, solltest Du das in etwa wie
unten dargestellt machen.

Das einzige Problem: Wenn sehr oft remove (oder update() in bestimmten
Konstellationen) und dann getSmallest() aufgerufen wird, muss bei
letzterem immer neu gesucht werden, was natürlich teuer ist.

Das ganze könntest Du nur dadurch umgehen, dass Du doch eine TreeMap
selbst implementierst, die auch mit updates klar kommt. Den
callback-Mechanismus kannst Du dir ja von meinem Beispiel unten
abgucken. Das ganze ist allerdings programmtechnisch recht aufwändig,
und deine O(1)-Anforderungen erfüllt es auch nicht.

Kurz: Der folgende Ansatz ist IMHO der beste (natürlich ungetestet):

Ciao,
Ingo



class Entry {
private int id;
private int cost;
private MySet set;
public int setCost(int cost) {
this.cost=cost;
set.update(this);
}
public boolean equals(Entry e) {
return id==e.id;
}
}

class MySet {

private Entry smallest;

private HashSet<Entry> entries=new HashSet<Entry>();

public void add(Entry e) {
if(smallest==null||e.cost<smallest.cost) {
smallest=e;
}
e.set=this;
add(e);
}

public void update(Entry e) {
if(e.cost<smallest.cost) {
smallest=e;
} else if(e.equals(smallest)) {
smallest=null;
}
}

public void remove(Entry e) {
if(e.equals(smallest)) {
smallest=null;
}
entries.remove(e);
}

public Entry getSmallest() {
if(smallest==null) {
smallest=searchSmallest(); // O(N)
}
return smallest;
}

}
Malte Schneider
2005-08-29 15:35:33 UTC
Permalink
Post by Ingo R. Homann
Hi,
Post by Malte Schneider
So habs ich es im Moment implementiert. Leider muss man bei add und
beim Ändern der Kosten eines bereits eingefügten Nodes neu sortieren.
Das kostet mehr zeit als nötig, weil man ja eigentlich nur den Node
mit den kleinsten Kosten braucht und nicht den zweitkleinsten und
drittkleinsten auch noch, zumindest wenn zwischendurch wieder Nodes
hinzukommen. Wenn man immer abwechselnd einen Node added und liest,
dann würde serielle Suche schneller sein als jedes Mal zu sortieren.
-> Frage an unsere Akademiker: ab wann ist Sortieren günstiger ?
Um es mal zusammenzufassen: Eine SortedMap (also TreeSet) kommt nicht in
Frage, weil die Kosten sich ändern. Das einzige, was deine
O(1)-Anforderungen erfüllt, ist eine HashSet. Die solltest Du
kombinieren mit dem Zwischenspeichern des kleinsten Elemenets und das
ganze kapseln. Um mit removes klarzukommen, solltest Du das in etwa wie
unten dargestellt machen.
Das einzige Problem: Wenn sehr oft remove (oder update() in bestimmten
Konstellationen) und dann getSmallest() aufgerufen wird, muss bei
letzterem immer neu gesucht werden, was natürlich teuer ist.
Das ganze könntest Du nur dadurch umgehen, dass Du doch eine TreeMap
selbst implementierst, die auch mit updates klar kommt. Den
callback-Mechanismus kannst Du dir ja von meinem Beispiel unten
abgucken. Das ganze ist allerdings programmtechnisch recht aufwändig,
und deine O(1)-Anforderungen erfüllt es auch nicht.
Ciao,
Ingo
class Entry {
private int id;
private int cost;
private MySet set;
public int setCost(int cost) {
this.cost=cost;
set.update(this);
}
public boolean equals(Entry e) {
return id==e.id;
}
}
class MySet {
private Entry smallest;
private HashSet<Entry> entries=new HashSet<Entry>();
public void add(Entry e) {
if(smallest==null||e.cost<smallest.cost) {
smallest=e;
}
e.set=this;
add(e);
}
public void update(Entry e) {
if(e.cost<smallest.cost) {
smallest=e;
} else if(e.equals(smallest)) {
smallest=null;
}
}
public void remove(Entry e) {
if(e.equals(smallest)) {
smallest=null;
}
entries.remove(e);
}
public Entry getSmallest() {
if(smallest==null) {
smallest=searchSmallest(); // O(N)
}
return smallest;
}
}
An sowas in der Richtung hatte ich auch schon gedacht. Nur leider stört
mich das O(n) noch etwas. Gerade zum Ende hin dürfte es häufig
vorkommen, dass oft remove ausgeführt wird, was dann zu einem
searchSmallest() führt.

Malte
Ingo R. Homann
2005-08-29 15:49:45 UTC
Permalink
Hi,
Post by Malte Schneider
An sowas in der Richtung hatte ich auch schon gedacht. Nur leider stört
mich das O(n) noch etwas. Gerade zum Ende hin dürfte es häufig
vorkommen, dass oft remove ausgeführt wird, was dann zu einem
searchSmallest() führt.
Post by Ingo R. Homann
Das einzige Problem: Wenn sehr oft remove (oder update() in bestimmten
Konstellationen) und dann getSmallest() aufgerufen wird, muss bei
letzterem immer neu gesucht werden, was natürlich teuer ist.
Das ganze könntest Du nur dadurch umgehen, dass Du doch eine TreeMap
selbst implementierst, die auch mit updates klar kommt. Den
callback-Mechanismus kannst Du dir ja von meinem Beispiel unten
abgucken. Das ganze ist allerdings programmtechnisch recht aufwändig,
und deine O(1)-Anforderungen erfüllt es auch nicht.
Hab' aber gerade keine Lust, das auch noch für dich zu implementieren... ;-)

Ciao,
Ingo
Malte Schneider
2005-08-29 17:58:18 UTC
Permalink
[snip]
Post by Ingo R. Homann
Hab' aber gerade keine Lust, das auch noch für dich zu implementieren... ;-)
Ciao,
Ingo
Danke, hat schon mal einiges gebracht. Vorallem >>schwierige<< Wege
werden schneller gefunden.

Malte
Marco Lange
2005-08-29 12:47:00 UTC
Permalink
Hi,
Post by Malte Schneider
ich hab mich an der Implementierung des A*-Algorithmus versucht.
Funktioniert auch erstmal soweit. Leider hab ich vergeblich nach einer
Datenstruktur gesucht, in der ich die noch zu bearbeitenden Knoten
1) contains, add, remove sollten in O(1) zu erledigen sein
2) der Knoten mit den geringsten Kosten sollte in möglichst kurzer Zeit
gefunden werden können.
Das erste deutet auf jeden Fall in Richtung Set. Das 2. eher in Richtung
einer sortierten/sortierbaren Collection. Leider schließt sich das
gegenseitig irgendwie aus. Wenn man aber genauer überlegt, benötigt man
ja keine komplette Sortierung sondern immer nur genau 1 Element.
Was würdet ihr vorschlagen ?
Ich bezweifle, dass es eine Datenstruktur mit O(1) für alles, was Du
haben möchstest, gibt.

Viele Grüße,
Marco
Malte Schneider
2005-08-29 14:30:48 UTC
Permalink
Post by Marco Lange
Ich bezweifle, dass es eine Datenstruktur mit O(1) für alles, was Du
haben möchstest, gibt.
Viele Grüße,
Marco
Irgendwie scheint das etwas falsch angekommen zu sein: ich fordere nicht
O(1) für alles max. sondern O(log(n)). Schneller kanns kaum gehen, da ja
eine Suche in einem Binärbaum schon O(log(n)) braucht und wir ja
wissen, dass das nicht schneller geht.

Malte
Michael Klemm
2005-08-29 16:10:22 UTC
Permalink
Post by Malte Schneider
Post by Marco Lange
Ich bezweifle, dass es eine Datenstruktur mit O(1) für alles, was Du
haben möchstest, gibt.
Viele Grüße,
Marco
Irgendwie scheint das etwas falsch angekommen zu sein: ich fordere nicht
O(1) für alles max. sondern O(log(n)). Schneller kanns kaum gehen, da ja
eine Suche in einem Binärbaum schon O(log(n)) braucht und wir ja
wissen, dass das nicht schneller geht.
Warum nimmst Du dann für die Speicherung aller Daten dann ein HashSet,
welches Dir schnelles Einfügen, Element-Test usw. bietet. Das ganze
würde ich dann noch mit einem Heap verheiraten, dann hast Du das
kleinste/größte Element immer oben. Einfügen dort braucht O(log n),
Löschen ebenso. Insgesamt hast Du dann:

- Element-Test: nahe O(1)
- Add: O(log n)
- Remove: O(log n)

Viele Grüße
-michael

--
Post by Malte Schneider
Nenne mir ein Wort und ich erkläre Dir, daß es griechischen Ursprungs
ist
Na dann: Semmelknödel und Wolpertinger
(Anastasios Tsitlakidis und Michael Rauscher in d.c.l.j)
Malte Schneider
2005-08-29 16:57:25 UTC
Permalink
Post by Michael Klemm
Warum nimmst Du dann für die Speicherung aller Daten dann ein HashSet,
welches Dir schnelles Einfügen, Element-Test usw. bietet. Das ganze
würde ich dann noch mit einem Heap verheiraten, dann hast Du das
kleinste/größte Element immer oben. Einfügen dort braucht O(log n),
- Element-Test: nahe O(1)
- Add: O(log n)
- Remove: O(log n)
Das Problem ist, dass die Elemente zwar sortierbar sind, aber keiner
Ordnung im mathematischen Sinne entsprechen. D.h. man kann die Frage
welches das Kleinste ist nicht immer eindeutig beantworten, weil 2
Elemente gleich klein/gross sind.
Daraus eine Ordnung zu machen, indem man noch etwas hinzufügt, wäre wie
0,5 zu den natürlichen Zahlen zu zählen ..

Malte
Jochen Theodorou
2005-08-29 17:22:17 UTC
Permalink
Post by Malte Schneider
Post by Michael Klemm
Warum nimmst Du dann für die Speicherung aller Daten dann ein HashSet,
welches Dir schnelles Einfügen, Element-Test usw. bietet. Das ganze
würde ich dann noch mit einem Heap verheiraten, dann hast Du das
kleinste/größte Element immer oben. Einfügen dort braucht O(log n),
- Element-Test: nahe O(1)
- Add: O(log n)
- Remove: O(log n)
Das Problem ist, dass die Elemente zwar sortierbar sind, aber keiner
Ordnung im mathematischen Sinne entsprechen. D.h. man kann die Frage
welches das Kleinste ist nicht immer eindeutig beantworten, weil 2
Elemente gleich klein/gross sind.
Daraus eine Ordnung zu machen, indem man noch etwas hinzufügt, wäre wie
0,5 zu den natürlichen Zahlen zu zählen ..
Es mag ja sein dass ich da was flasch in Erinnerung habe, aber kann man
mit <= nicht eine Kette von Elementen bauen und unabhängig davon ob das
erste das einzigste kleinste Element ist kann ich doch einfach eines der
kleinsten nehmen und es als das kleinste definieren, oder? Es mag also
nicht DAS kleinste Element geben, aber es gibt ein kleinstes Element.

Gruss theo
Malte Schneider
2005-08-29 18:15:22 UTC
Permalink
Post by Jochen Theodorou
Post by Malte Schneider
Post by Michael Klemm
Warum nimmst Du dann für die Speicherung aller Daten dann ein HashSet,
welches Dir schnelles Einfügen, Element-Test usw. bietet. Das ganze
würde ich dann noch mit einem Heap verheiraten, dann hast Du das
kleinste/größte Element immer oben. Einfügen dort braucht O(log n),
- Element-Test: nahe O(1)
- Add: O(log n)
- Remove: O(log n)
Das Problem ist, dass die Elemente zwar sortierbar sind, aber keiner
Ordnung im mathematischen Sinne entsprechen. D.h. man kann die Frage
welches das Kleinste ist nicht immer eindeutig beantworten, weil 2
Elemente gleich klein/gross sind.
Daraus eine Ordnung zu machen, indem man noch etwas hinzufügt, wäre
wie 0,5 zu den natürlichen Zahlen zu zählen ..
Es mag ja sein dass ich da was flasch in Erinnerung habe, aber kann man
mit <= nicht eine Kette von Elementen bauen und unabhängig davon ob das
erste das einzigste kleinste Element ist kann ich doch einfach eines der
kleinsten nehmen und es als das kleinste definieren, oder? Es mag also
nicht DAS kleinste Element geben, aber es gibt ein kleinstes Element.
Gruss theo
Ja klar, aber du kannst derart definierte Elemente nicht in einen
Binärbaum einordnen.

Malte
Jochen Theodorou
2005-08-29 19:01:58 UTC
Permalink
[...]
Post by Malte Schneider
Post by Jochen Theodorou
Es mag ja sein dass ich da was flasch in Erinnerung habe, aber kann
man mit <= nicht eine Kette von Elementen bauen und unabhängig davon
ob das erste das einzigste kleinste Element ist kann ich doch einfach
eines der kleinsten nehmen und es als das kleinste definieren, oder?
Es mag also nicht DAS kleinste Element geben, aber es gibt ein
kleinstes Element.
Ja klar, aber du kannst derart definierte Elemente nicht in einen
Binärbaum einordnen.
Klar doch, wenn ich sage Links ist <= und Rechts ist >
Oder wenn ich zu dem Wert die Anzahl speichere, bzw. die Menge (z.B. als
Liste) speichere.

Gruss theo
Peter Büttner
2005-08-29 19:17:19 UTC
Permalink
Post by Malte Schneider
Post by Michael Klemm
Warum nimmst Du dann für die Speicherung aller Daten dann ein HashSet,
welches Dir schnelles Einfügen, Element-Test usw. bietet. Das ganze
würde ich dann noch mit einem Heap verheiraten, dann hast Du das
kleinste/größte Element immer oben. Einfügen dort braucht O(log n),
- Element-Test: nahe O(1)
- Add: O(log n)
- Remove: O(log n)
Das Problem ist, dass die Elemente zwar sortierbar sind, aber keiner
Ordnung im mathematischen Sinne entsprechen. D.h. man kann die Frage
Das sind deine Objekte.
Du bist Gott.
Du gibst ihnen einfach eine Ordnung.


System.identityHashCode() sollte eine eindeutige nummer liefern, das
ist aber ein hack, steht da nicht.

Andernfalls einfach durchnummerieren. bei deinem 50x50 quadrat ist das doch
banal, einfach (x<<16 + y)der koordinaten
Post by Malte Schneider
welches das Kleinste ist nicht immer eindeutig beantworten, weil 2
Elemente gleich klein/gross sind.
Daraus eine Ordnung zu machen, indem man noch etwas hinzufügt, wäre wie
0,5 zu den natürlichen Zahlen zu zählen ..
Es geht doch nur drum elemente mit gleichem cost in ein TreeSet zu
zwängen.



Grüße
Peter
--
Shell&Jar : Individual icons for jars
jMineSweeper : extended
www.PeterBuettner.de
Malte Schneider
2005-08-30 07:15:29 UTC
Permalink
Post by Peter Büttner
Das sind deine Objekte.
Du bist Gott.
Du gibst ihnen einfach eine Ordnung.
Schlag doch mal bitte eine Variante vor, die keinen Pferdefuß hat. Man
hat halt Nodes mit gleichen Kosten aber verschiedenen Koordinaten.
Welchen der Nodes bevorzugt man, indem man ihm einen höheren Rang in der
Ordnung gibt ?
Post by Peter Büttner
System.identityHashCode() sollte eine eindeutige nummer liefern, das
ist aber ein hack, steht da nicht.
die equals und hashCode Methoden sind nicht das Problem. Aber danach
wird ein Element nicht in ein TreeSet/TreeMap eingefügt.

class PathNode {

public boolean equals (Object o){
if (this!=o) return false;
if (o==null) return false;
if (!(o instanceof PathNode )) return false; // beinhaltet auch (o==null)
return this.equals(((PathNode)o).getLocation());
}
public int hashCode (){
return location.hashCode();
}
public int compareTo(Object o){
return this.cost-((PathNode)o).getCost();
}
}
Post by Peter Büttner
Andernfalls einfach durchnummerieren. bei deinem 50x50 quadrat ist das doch
banal, einfach (x<<16 + y)der koordinaten
Es geht doch nur drum elemente mit gleichem cost in ein TreeSet zu
zwängen.
Ja eben. TreeSet ordnet die Elemente entsprechend der Ergebnis von
compareTo. Liefert sie 0 dann gelten Elemente als gleich und würden den
gleichen Platz im Baum einnehmen.

Malte
Ingo R. Homann
2005-08-30 07:42:33 UTC
Permalink
Hi,
Post by Malte Schneider
Post by Peter Büttner
Du gibst ihnen einfach eine
Ordnung.
Schlag doch mal bitte eine Variante vor, die keinen Pferdefuß hat. Man
hat halt Nodes mit gleichen Kosten aber verschiedenen Koordinaten.
Welchen der Nodes bevorzugt man, indem man ihm einen höheren Rang in der
Ordnung gibt ?
Das mit der höheren x-Koordinate. Wenn die x-Koordinaten gleich sind,
dann das mit der höheren y-Koordinate. Wenn die auch gleich sind, dann
sind die Objekte gleich. (Natürlich geht es dann nicht mehr, dass equals
zwei Objekte als gleich ansieht, nur anhand ihrer Koordinaten ohne
Berücksichtigung ihrer Kosten. Ob Du sowas benötigst, musst Du selbst
wissen.)

Ich glaube, das wurde aber schon ein paar mal vorgeschlagen.

Ciao,
Ingo
Malte Schneider
2005-08-30 08:31:24 UTC
Permalink
Post by Ingo R. Homann
Das mit der höheren x-Koordinate. Wenn die x-Koordinaten gleich sind,
dann das mit der höheren y-Koordinate. Wenn die auch gleich sind, dann
sind die Objekte gleich.
Sollte gehen.
Post by Ingo R. Homann
(Natürlich geht es dann nicht mehr, dass equals
zwei Objekte als gleich ansieht, nur anhand ihrer Koordinaten ohne
Berücksichtigung ihrer Kosten. Ob Du sowas benötigst, musst Du selbst
wissen.)
Na doch, denn wenn ich dann weiss, dass wirklich der gleiche node
gemeint ist, dann ist es ja auch richtig, dass compareTo eine 0 liefert.
Post by Ingo R. Homann
Ich glaube, das wurde aber schon ein paar mal vorgeschlagen.
Schon möglich, denn langsam steh ich im Wald ..

Malte
Ingo R. Homann
2005-08-30 09:54:48 UTC
Permalink
Hi,
Post by Malte Schneider
Post by Ingo R. Homann
Das mit der höheren x-Koordinate. Wenn die x-Koordinaten gleich sind,
dann das mit der höheren y-Koordinate. Wenn die auch gleich sind, dann
sind die Objekte gleich.
Sollte gehen.
Post by Ingo R. Homann
(Natürlich geht es dann nicht mehr, dass equals
zwei Objekte als gleich ansieht, nur anhand ihrer Koordinaten ohne
Berücksichtigung ihrer Kosten.)
Na doch, denn wenn ich dann weiss, dass wirklich der gleiche node
gemeint ist, dann ist es ja auch richtig, dass compareTo eine 0 liefert.
Nein, nur, wenn auch die Kosten gleich sind:

equals und compareTo (und hashCode) müssen schon konsistent
implementiert sein, wenn Du die Objekte in eine HashMap, TreeMap etc
stecken willst. Und die natürlich Ordnung hat ja auch einige
Eigenschaften, die nicht verletzt sein dürfen (Reflexivität,
Transitivität, etc.)

Ciao,
Ingo
Malte Schneider
2005-08-30 10:02:42 UTC
Permalink
Post by Ingo R. Homann
Hi,
Post by Malte Schneider
Post by Ingo R. Homann
Das mit der höheren x-Koordinate. Wenn die x-Koordinaten gleich sind,
dann das mit der höheren y-Koordinate. Wenn die auch gleich sind,
dann sind die Objekte gleich.
Sollte gehen.
Post by Ingo R. Homann
(Natürlich geht es dann nicht mehr, dass equals
zwei Objekte als gleich ansieht, nur anhand ihrer Koordinaten ohne
Berücksichtigung ihrer Kosten.)
Na doch, denn wenn ich dann weiss, dass wirklich der gleiche node
gemeint ist, dann ist es ja auch richtig, dass compareTo eine 0 liefert.
equals und compareTo (und hashCode) müssen schon konsistent
implementiert sein, wenn Du die Objekte in eine HashMap, TreeMap etc
stecken willst. Und die natürlich Ordnung hat ja auch einige
Eigenschaften, die nicht verletzt sein dürfen (Reflexivität,
Transitivität, etc.)
Ciao,
Ingo
mein ich doch:

public int compareTo(Object o){
PathNode otherNode = (PathNode)o;
int c = this.cost-otherNode.getCost();
if (c!=0) return c;
c = this.location.getX()-otherNode.getLocation().getX();
if (c!=0) return c;
c = this.location.getY()-otherNode.getLocation().getY();
//if (c!=0) // x und y identisch->identische Nodes
return c;
}

Malte
Peter Büttner
2005-08-30 10:40:51 UTC
Permalink
Post by Ingo R. Homann
Hi,
Post by Malte Schneider
Post by Ingo R. Homann
Das mit der höheren x-Koordinate. Wenn die x-Koordinaten gleich sind,
dann das mit der höheren y-Koordinate. Wenn die auch gleich sind, dann
sind die Objekte gleich.
Sollte gehen.
Post by Ingo R. Homann
(Natürlich geht es dann nicht mehr, dass equals
zwei Objekte als gleich ansieht, nur anhand ihrer Koordinaten ohne
Berücksichtigung ihrer Kosten.)
Na doch, denn wenn ich dann weiss, dass wirklich der gleiche node
gemeint ist, dann ist es ja auch richtig, dass compareTo eine 0 liefert.
equals und compareTo (und hashCode) müssen schon konsistent
implementiert sein, wenn Du die Objekte in eine HashMap, TreeMap etc
Wenn man die Doku zu TreeSet liest steht da das equals nicht verwendet
wird, darf also inkonsistent sein.

Wenn man allerdings dies PathNodes (schnell) wieder aus dem TreeSet
rauskriegen will muß man, falls die Ordnung durch (cost,x,y) definiert
ist den Knoten eigentlich schon in der Hand haben oder cost kennen (x,y)
reicht nicht.

Und denselben (~x,y) Knoten mit verschiedenen cost 2x im Set haben
ist evtl. auch ungesund.
Post by Ingo R. Homann
stecken willst. Und die natürlich Ordnung hat ja auch einige
Eigenschaften, die nicht verletzt sein dürfen (Reflexivität,
Transitivität, etc.)
Das ist kein Problem aufrechtzuerhalten. Man könnte die PathNode auch
einfach nummerieren:
PathNode{
static int max;
int nr;
PathNode(){nr = max++;}
...

Das reicht auch als Ordnung (je nachdem was man mit dem Set noch so
alles machen will)

Grüße
Peter
--
Shell&Jar : Individual icons for jars
jMineSweeper : extended
www.PeterBuettner.de
Malte Schneider
2005-08-30 11:22:08 UTC
Permalink
Post by Peter Büttner
Post by Ingo R. Homann
Hi,
Post by Malte Schneider
Post by Ingo R. Homann
Das mit der höheren x-Koordinate. Wenn die x-Koordinaten gleich sind,
dann das mit der höheren y-Koordinate. Wenn die auch gleich sind, dann
sind die Objekte gleich.
Sollte gehen.
Post by Ingo R. Homann
(Natürlich geht es dann nicht mehr, dass equals
zwei Objekte als gleich ansieht, nur anhand ihrer Koordinaten ohne
Berücksichtigung ihrer Kosten.)
Na doch, denn wenn ich dann weiss, dass wirklich der gleiche node
gemeint ist, dann ist es ja auch richtig, dass compareTo eine 0 liefert.
equals und compareTo (und hashCode) müssen schon konsistent
implementiert sein, wenn Du die Objekte in eine HashMap, TreeMap etc
Wenn man die Doku zu TreeSet liest steht da das equals nicht verwendet
wird, darf also inkonsistent sein.
Wenn man allerdings dies PathNodes (schnell) wieder aus dem TreeSet
rauskriegen will muß man, falls die Ordnung durch (cost,x,y) definiert
ist den Knoten eigentlich schon in der Hand haben oder cost kennen (x,y)
reicht nicht.
Und denselben (~x,y) Knoten mit verschiedenen cost 2x im Set haben
ist evtl. auch ungesund.
Ja, das hast du recht. man wird nicht umhinkommen, ein zweites Set zu
benutzen, das Idendität über hashCode und equals feststellt.
Post by Peter Büttner
Post by Ingo R. Homann
stecken willst. Und die natürlich Ordnung hat ja auch einige
Eigenschaften, die nicht verletzt sein dürfen (Reflexivität,
Transitivität, etc.)
Das ist kein Problem aufrechtzuerhalten. Man könnte die PathNode auch
PathNode{
static int max;
int nr;
PathNode(){nr = max++;}
...
Das reicht auch als Ordnung (je nachdem was man mit dem Set noch so
alles machen will)
Es geht ja gerade darum, auf Basis der Kosten zu sortieren.

Malte
Ingo R. Homann
2005-08-30 11:54:14 UTC
Permalink
Hi,
Post by Peter Büttner
Post by Ingo R. Homann
equals und compareTo (und hashCode) müssen schon konsistent
implementiert sein, wenn Du die Objekte in eine HashMap, TreeMap etc
Wenn man die Doku zu TreeSet liest steht da das equals nicht verwendet
wird, darf also inkonsistent sein.
Wenn man allerdings dies PathNodes (schnell) wieder aus dem TreeSet
rauskriegen will muß man, falls die Ordnung durch (cost,x,y) definiert
ist den Knoten eigentlich schon in der Hand haben oder cost kennen (x,y)
reicht nicht.
ACK.
Post by Peter Büttner
Und denselben (~x,y) Knoten mit verschiedenen cost 2x im Set haben
ist evtl. auch ungesund.
Für den A*-Algorithmus? Ja. Der Set macht das natürlich erstmal nichts
aus (sofern die entsprechenden compareto() etc implementiert sind.)
Post by Peter Büttner
Post by Ingo R. Homann
stecken willst. Und die natürlich Ordnung hat ja auch einige
Eigenschaften, die nicht verletzt sein dürfen (Reflexivität,
Transitivität, etc.)
Das ist kein Problem aufrechtzuerhalten. Man könnte die PathNode auch
PathNode{
static int max;
int nr;
PathNode(){nr = max++;}
...
Richtig. Das schrieb ich ja auch schon. Malte hat ja auch mittlerweile
gepostet, wie es compareTo implementieren will, und das ist IMHO auch
korrekt so.

Was ich meinte, war, dass man folgendes tunlichst bleiben lassen sollte:

// EVIL:
public int compareTo(Object o){
PathNode otherNode = (PathNode)o;
int c = this.id-otherNode.id;
if (c==0) return 0;
c = this.location.getX()-otherNode.getLocation().getX();
if (c!=0) return c;
c = this.location.getY()-otherNode.getLocation().getY();
if (c!=0) return c;
return this.id-otherNode.id;
}

Ciao,
Ingo
Peter Büttner
2005-08-30 10:35:36 UTC
Permalink
Post by Malte Schneider
Post by Peter Büttner
Das sind deine Objekte.
Du bist Gott.
Du gibst ihnen einfach eine Ordnung.
Schlag doch mal bitte eine Variante vor, die keinen Pferdefuß hat. Man
hat halt Nodes mit gleichen Kosten aber verschiedenen Koordinaten.
Welchen der Nodes bevorzugt man, indem man ihm einen höheren Rang in der
Ordnung gibt ?
Post by Peter Büttner
System.identityHashCode() sollte eine eindeutige nummer liefern, das
ist aber ein hack, steht da nicht.
die equals und hashCode Methoden sind nicht das Problem. Aber danach
wird ein Element nicht in ein TreeSet/TreeMap eingefügt.
Darum (equals/hashCode) geht's doch nicht.
System.identityHashCode() liefert die eine sich nicht ändernde eindeutige
Nummer um eine zusätzliche Ordnung hinzuzufügen.
Post by Malte Schneider
class PathNode {
[...]
Post by Malte Schneider
public int compareTo(Object o){
return this.cost-((PathNode)o).getCost();
und hier musst du die location hinzufügen
Post by Malte Schneider
Post by Peter Büttner
Andernfalls einfach durchnummerieren. bei deinem 50x50 quadrat ist das doch
banal, einfach (x<<16 + y)der koordinaten
das ist location (zu verwenden anstatt identityHashCode
Post by Malte Schneider
Post by Peter Büttner
Es geht doch nur drum elemente mit gleichem cost in ein TreeSet zu
zwängen.
Ja eben. TreeSet ordnet die Elemente entsprechend der Ergebnis von
compareTo. Liefert sie 0 dann gelten Elemente als gleich und würden den
gleichen Platz im Baum einnehmen.
public class Trees {
public static void main(String[] args) {
TreeSet s = new TreeSet();
s.add( new Wrap(1,"A"));
s.add( new Wrap(1,"B"));
s.add( new Wrap(3,"D"));
s.add( new Wrap(1,"C"));
System.out.println(s);


}
static class Wrap implements Comparable{
int cost;
String data;
public Wrap(int cost, String data) {
this.cost = cost;
this.data = data;
}
public String toString() { return "[" + cost + ", " + data +"]";
}
public int compareTo(Object o) {
Wrap a= this;
Wrap b= (Wrap)o;
if (a.cost < b.cost) return -1;
if (a.cost > b.cost) return 1;
return a.data.compareTo(b.data);
}
}

}




Grüße
Peter
--
Shell&Jar : Individual icons for jars
jMineSweeper : extended
www.PeterBuettner.de
Wanja Gayk
2005-08-29 23:29:42 UTC
Permalink
Malte Schneider said...
Post by Malte Schneider
1) contains, add, remove sollten in O(1) zu erledigen sein
2) der Knoten mit den geringsten Kosten sollte in möglichst kurzer Zeit
gefunden werden können.
Das einzige, was ich mir vorstellen kann, was diese Anforderung
annähernd erfüllen könnte ist Binning. Das erfordert aber, dass dein
Sortierkriterium (nehmen wir an ein Gewicht) aus nicht all zu vielen
Werten besteht.

Ungefähr so:

public WeightedObject{
public int weight;
public Object obj;
}

public Bins{
public final MAX_WEIGHT = 1000;
private final LinkedList<WeightedObject>[] bins
= new LinkedList<WeightedObject>[MAX_WEIGHT];
private int lastLowest = SIZE;

public void add(final WeightedObject o){
bins[o.weight].addLast(o);
if(o.weight < lastLowest){lastLowest=o.weight;}
}

public boolean remove (final WeightedObject o){
return bins[o.weight].remove(o);
}

public boolean contains(final WeightedObject o){
return bins[0.weight].contains(o);
}

public WeightedObject getLowest(){
int right = lastLowest;
while(right < MAX_WEIGHT){
if(bins[right].size()>0){
lastLowest=right;
return bins[lastLowest].getFirst();
}
++right
}
return null;
}
}

Die getLowest()-Methode ist zwar nicht unbedingt O(1), sondern im worst-
case hätte er O(n), aber im average case sollte er deutlich schneller
sein und schon gut an die o(1) herankommen.

Eine Möglichkeit wäre, in jeden Bin eine TreeMap zu packen, die nach
Gewicht sortiert ist, so kannst du dann notfalls auch Float/Double als
Gewichte benutzen, auch wenn du nur Integer-Bins hast, sprich: du
schneidest alles nach dem Komma ab (float zu int casten), findest dann
in O(1) den richtigen Bin, und musst von dort aus nur noch eine geringe
Anzahl von Objekten in O(log(m)), mit m<<n, absuchen.

Gruß,
-Wanja-
--
"Gewisse Schriftsteller sagen von ihren Werken immer: 'Mein Buch, mein
Kommentar, meine Geschichte'. [..] Es wäre besser, wenn sie sagten:
'unser Buch, unser Kommentar, unsere Geschichte'; wenn man bedenkt, dass
das Gute darin mehr von anderen ist als von ihnen." [Blaise Pascal]
Ralf Ullrich
2005-08-30 01:00:01 UTC
Permalink
Post by Malte Schneider
Was würdet ihr vorschlagen ?
Ein zweifach rückverketteter Binary Heap (aka Priority Queue) (siehe
http://de.wikipedia.org/wiki/Bin%C3%A4rer_Heap )

Wobei die 1. Rückverkettung in einem Eintrag seine aktuelle Position im
Heap speichert. Diese Rückverkettung beschleunigt die für A* benötigte
Kosten-Änderung erheblich. (Ohne allzuviel Overhead beim add/remove zu
erzeugen.)

Die 2.te Rückverkettung erfolgt aus einem NxM Array auf die Einträge,
wobei NxM deine Map-Größe ist. Das Array enthält zunächst nur null. Dies
erlaubt eine äußerst schnelle Implementierung um festzustellen ob ein
Feld in der Open List (und durch zufügen eines Flags auch der Closed
List) ist.

Die Datenstrukturen sehen also etwa wie folgt aus:

Entry {
int X,Y; // Koordinaten in der map, zusammen mit parent
// ergibt sich so der gesuchte Pfad.
int cost;
Entry parent;
int heapIndex; // 1. Rückverkettung
boolean closed;

// evtl. weitere member für die Closed List
}

Entry[] binHeap = new Entry[N*M+1];
// in diesem Array wird der binäre Baum abgebildet und zwar so,
// dass für alle i gilt:
// binHeap[i].cost <= binHeap[2*i+0] und
// binHeap[i].cost <= binHeap[2*i+1]
// Der Index i=0 bleibt unbenutzt. Neue Einträge werden einfach
// hinten anghängt und solange von [i] nach [i>>1] vorgeswappt, bis
// obige Heap Bedingung wieder passt.

Entry[][] map = new Entry[N][M]; // 2. Rückverkettung


Natürlich hast du hierbei zwei Arrays so groß wie deine Map, aber das
ist IMHO vertretbar.


In jedem Fall wirst du mit selbstgeschriebenen und angepassten
Implementierungen einer Binary Heap in dieser Art schneller sein, als
mit den Standard Collection-Klasse von Java.


Und du erreichst in jedem Fall ein O(1) für contains() und O(log(n)) für
add(), removeMin() und changeCost() (letzteres ist sogar etwas besser
als O(log(n)), erst recht wenn du noch reinoptimierst, dass nur
Kosten-Änderungen nach unten vornimmst.)


Wenn es dich interessiert, einfach noch mal melden, dann krame ich
meinen alten PriorityQueue-Code raus, der zumindest IIRC schonmal die 1.
Rückverkettung enthält. Der Rest bliebe dir dann noch zur Übung.

cu
Malte Schneider
2005-08-30 07:01:58 UTC
Permalink
Post by Ralf Ullrich
Ein zweifach rückverketteter Binary Heap (aka Priority Queue) (siehe
http://de.wikipedia.org/wiki/Bin%C3%A4rer_Heap )
Wie löst du denn dann das Problem mit Knoten, die gleiche Kosten haben ?
Post by Ralf Ullrich
Wobei die 1. Rückverkettung in einem Eintrag seine aktuelle Position im
Heap speichert. Diese Rückverkettung beschleunigt die für A* benötigte
Kosten-Änderung erheblich. (Ohne allzuviel Overhead beim add/remove zu
erzeugen.)
Klar.
Post by Ralf Ullrich
Die 2.te Rückverkettung erfolgt aus einem NxM Array auf die Einträge,
wobei NxM deine Map-Größe ist. Das Array enthält zunächst nur null. Dies
erlaubt eine äußerst schnelle Implementierung um festzustellen ob ein
Feld in der Open List (und durch zufügen eines Flags auch der Closed
List) ist.
Auch logisch.
Post by Ralf Ullrich
Entry {
int X,Y; // Koordinaten in der map, zusammen mit parent
// ergibt sich so der gesuchte Pfad.
int cost;
Entry parent;
int heapIndex; // 1. Rückverkettung
boolean closed;
// evtl. weitere member für die Closed List
}
Entry[] binHeap = new Entry[N*M+1];
// in diesem Array wird der binäre Baum abgebildet und zwar so,
// binHeap[i].cost <= binHeap[2*i+0] und
// binHeap[i].cost <= binHeap[2*i+1]
// Der Index i=0 bleibt unbenutzt. Neue Einträge werden einfach
// hinten anghängt und solange von [i] nach [i>>1] vorgeswappt, bis
// obige Heap Bedingung wieder passt.
Entry[][] map = new Entry[N][M]; // 2. Rückverkettung
Natürlich hast du hierbei zwei Arrays so groß wie deine Map, aber das
ist IMHO vertretbar.
Um den Speicher mach ich mir am allerwenigsten Sorgen. Man muss nunmal
Datenredundanz erzeugen, wenn man Laufzeit sparen will.
Post by Ralf Ullrich
In jedem Fall wirst du mit selbstgeschriebenen und angepassten
Implementierungen einer Binary Heap in dieser Art schneller sein, als
mit den Standard Collection-Klasse von Java.
Natürlich, wenn dann hätte man höchstens noch mit einer Kombination aus
mehreren Collections gekapselt in einer Klasse eine Chance die Laufzeit
zu optimieren.
Post by Ralf Ullrich
Und du erreichst in jedem Fall ein O(1) für contains() und O(log(n)) für
add(), removeMin() und changeCost()
Jo.
Post by Ralf Ullrich
(letzteres ist sogar etwas besser als O(log(n)), erst recht wenn du noch
reinoptimierst, dass nur Kosten-Änderungen nach unten vornimmst.)
Jupp, ein "halbes heapify" reicht ja aus.
Post by Ralf Ullrich
Wenn es dich interessiert, einfach noch mal melden, dann krame ich
meinen alten PriorityQueue-Code raus, der zumindest IIRC schonmal die 1.
Rückverkettung enthält. Der Rest bliebe dir dann noch zur Übung.
Interessiert mich sehr und wieso das Rad neu erfinden. ;-)

Thx, Malte
Ralf Ullrich
2005-08-30 13:27:47 UTC
Permalink
Post by Malte Schneider
Post by Ralf Ullrich
Ein zweifach rückverketteter Binary Heap (aka Priority Queue) (siehe
http://de.wikipedia.org/wiki/Bin%C3%A4rer_Heap )
Wie löst du denn dann das Problem mit Knoten, die gleiche Kosten haben ?
Indem ich die Heap-Bedingung aufweiche, sprich kleiner oder gleich
genügt mir.
Post by Malte Schneider
Post by Ralf Ullrich
Wenn es dich interessiert, einfach noch mal melden, dann krame ich
meinen alten PriorityQueue-Code raus, der zumindest IIRC schonmal die
1. Rückverkettung enthält. Der Rest bliebe dir dann noch zur Übung.
Interessiert mich sehr und wieso das Rad neu erfinden. ;-)
OK, unten ist der Code zur freien Verfügung. Einige Kommentare sind
etwas unglücklich, weil ich die PQ hier speziell für einen Scheduler
geschrieben habe, daher sind auch einige Methoden-Bezeichner etwas
unglücklich. Außerdem wirst du sehen, dass die 2. Rückverkettung über
eine Map gemacht wurde. Das war hier nötig, weil ich eine allgemeine PQ
schreiben wollte, die "beliebige" Objekte aufnehmen kann. Du kannst ja
durch das zweidimensionale Array direkt aus den Koordinaten auf das
Entry-Objekt kommen, d.h. überall wo in meinem Code die Map auftaucht,
wirst du die Behandlung des zweidimensionalen Arrays einbauen müssen.
Und dort wo ich auf das zu speichernde "element" verweise, könnten bei
dir eigentlich X,Y und parent stehen.

Naja, das Wichtige steckt ja eigentlich in fixUp und fixDown.

Viel Spass damit, und wenn du noch Fragen hast gerne.

cu


import java.util.ArrayList;
import java.util.HashMap;
import java.util.NoSuchElementException;

/**
* This class represents a Priority Queue. The objects added to this queue
* are ordered by priority.
* Internally this class uses a heap, which offers log(n) performance for
* the add, removeMin and rescheduleMin operations, and constant time
* performance for the the getMin operation.
*/
public class PriorityQueue {

private static class Entry {
long priority;
int position;
Object element;
}

/**
* Priority queue represented as a balanced binary heap: the two children
* of queue[n] are queue[2*n] and queue[2*n+1]. The priority queue is
* ordered on priority: The Object with the lowest
* priority is in queue[1] (assuming the queue is nonempty). For
* each node n in the heap, and each descendant of n, d,
* n.nextExecutionTime &lt;= d.nextExecutionTime.
*/
private Entry[] queue;

/**
* The reverse Map is used to quickly find an element within the queue
* when the position is not known.
*/
private HashMap reverseMap;

/**
* The number of objects in the priority queue. (The object are stored in
* queue[1] up to queue[size]).
*/
private int size = 0;

/**
* Constructs a Priority Queue
*/
public PriorityQueue() {
reverseMap = new HashMap();
queue = new Entry[128];
}

/**
* special purpose constructor, see getAllUntil()
*/
private PriorityQueue(PriorityQueue other) {
reverseMap = null;
queue = new Entry[other.queue.length];
System.arraycopy(other.queue, 0, queue, 0, queue.length);
size = other.size;
}

/**
* Adds a new object to the priority queue.
*/
public void add(long priority, Object obj) {
// Grow backing store if necessary
if (++size == queue.length) {
Entry[] newQueue = new Entry[2 * queue.length];
System.arraycopy(queue, 0, newQueue, 0, size);
queue = newQueue;
}
Entry entry = new Entry();
entry.priority = priority;
entry.element = obj;
entry.position = size;
queue[size] = entry;
reverseMap.put(entry.element, entry);
fixUp(size);
}

/**
* Return the priority of the head of the priority queue.
*/
public long getMinPriority() {
return queue[1].priority;
}

/**
* Return the "head" of the priority queue. (The head is the
* object with the lowest getPriority().)
*/
public Object getMin() {
return queue[1].element;
}

/**
* Return all elements in the priority queue which have a lower
* priority as the specified threshold. The returned array is ordered
* by priority.
*/
public Object[] getAllUntil(long threshold, Object[] target) {
PriorityQueue tmp = new PriorityQueue(this);
ArrayList list = new ArrayList();
while (!tmp.isEmpty() && (tmp.getMinPriority() <= threshold)) {
list.add(tmp.removeMin());
}
return list.toArray(target == null ? new Object[0] : target);
}

/**
* Remove the head from the priority queue.
*/
public Object removeMin() {
Object head = queue[1].element;
if (reverseMap != null) {
reverseMap.remove(head);
}
queue[1] = queue[size];
queue[1].position = 1;
queue[size--] = null; // Drop extra reference to prevent memory leak
fixDown(1);
return head;
}

/**
* Remove the specified object from the priority queue.
* @throws NoSuchElementException if the specified object is
* not in the Queue
*/
public void remove(Object obj) {
Entry entry = (Entry) reverseMap.get(obj);
if (entry == null) {
throw new NoSuchElementException();
}
reverseMap.remove(obj);
queue[entry.position] = queue[size];
queue[entry.position].position = entry.position;
queue[size--] = null; // Drop extra reference to prevent memory leak
fixDown(entry.position);
}

/**
* Sets the priority associated with the head to the
* specified value, and adjusts priority queue accordingly.
*/
public void rescheduleMin(long priority) {
queue[1].priority = priority;
fixDown(1);
}

/**
* Sets the priority associated with the specified object to the
* specified value, and adjusts priority queue accordingly.
* @throws NoSuchElementException if the specified object is
* not in the Queue
*/
public void reschedule(long priority, Object obj) {
Entry entry = (Entry) reverseMap.get(obj);
if (entry == null) {
throw new NoSuchElementException();
}
long oldPriority = entry.priority;
entry.priority = priority;
if (priority > oldPriority) {
fixDown(entry.position);
} else {
fixUp(entry.position);
}
}

/**
* Returns true if the priority queue contains no elements.
*/
public boolean isEmpty() {
return size == 0;
}

/**
* Removes all elements from the priority queue.
*/
public void clear() {
// Null out task references to prevent memory leak
for (int i = 1; i <= size; i++) {
queue[i].element = null;
queue[i] = null;
}
reverseMap.clear();
size = 0;
}

/**
* Establishes the heap invariant (described above) assuming the heap
* satisfies the invariant except possibly for the leaf-node indexed by k
* (which may have a priority less than its parent's).
*
* This method functions by "promoting" queue[k] up the hierarchy
* (by swapping it with its parent) repeatedly until queue[k]'s
* priority is greater than or equal to that of its parent.
*/
private void fixUp(int k) {
while (k > 1) {
int j = k >> 1;
if (queue[j].priority <= queue[k].priority) {
break;
}
Entry tmp = queue[j];
queue[j] = queue[k];
queue[j].position = j;
queue[k] = tmp;
queue[k].position = k;
k = j;
}
}

/**
* Establishes the heap invariant (described above) in the subtree
* rooted at k, which is assumed to satisfy the heap invariant except
* possibly for node k itself (which may have a priority greater
* than its children's).
*
* This method functions by "demoting" queue[k] down the hierarchy
* (by swapping it with its smaller child) repeatedly until queue[k]'s
* priority is less than or equal to those of its children.
*/
private void fixDown(int k) {
int j;
while ((j = k << 1) <= size) {
if ((j < size) && (queue[j].priority > queue[j + 1].priority))
j++; // j indexes smallest kid
if (queue[k].priority <= queue[j].priority)
break;
Entry tmp = queue[j];
queue[j] = queue[k];
queue[j].position = j;
queue[k] = tmp;
queue[k].position = k;
k = j;
}
}
}
Malte Schneider
2005-08-30 15:29:29 UTC
Permalink
Post by Ralf Ullrich
Post by Malte Schneider
Post by Ralf Ullrich
Ein zweifach rückverketteter Binary Heap (aka Priority Queue) (siehe
http://de.wikipedia.org/wiki/Bin%C3%A4rer_Heap )
Wie löst du denn dann das Problem mit Knoten, die gleiche Kosten haben ?
Indem ich die Heap-Bedingung aufweiche, sprich kleiner oder gleich
genügt mir.
Post by Malte Schneider
Post by Ralf Ullrich
Wenn es dich interessiert, einfach noch mal melden, dann krame ich
meinen alten PriorityQueue-Code raus, der zumindest IIRC schonmal die
1. Rückverkettung enthält. Der Rest bliebe dir dann noch zur Übung.
Interessiert mich sehr und wieso das Rad neu erfinden. ;-)
OK, unten ist der Code zur freien Verfügung. Einige Kommentare sind
etwas unglücklich, weil ich die PQ hier speziell für einen Scheduler
geschrieben habe, daher sind auch einige Methoden-Bezeichner etwas
unglücklich. Außerdem wirst du sehen, dass die 2. Rückverkettung über
eine Map gemacht wurde. Das war hier nötig, weil ich eine allgemeine PQ
schreiben wollte, die "beliebige" Objekte aufnehmen kann. Du kannst ja
durch das zweidimensionale Array direkt aus den Koordinaten auf das
Entry-Objekt kommen, d.h. überall wo in meinem Code die Map auftaucht,
wirst du die Behandlung des zweidimensionalen Arrays einbauen müssen.
Und dort wo ich auf das zu speichernde "element" verweise, könnten bei
dir eigentlich X,Y und parent stehen.
Naja, das Wichtige steckt ja eigentlich in fixUp und fixDown.
Viel Spass damit, und wenn du noch Fragen hast gerne.
cu
import java.util.ArrayList;
import java.util.HashMap;
import java.util.NoSuchElementException;
/**
* This class represents a Priority Queue. The objects added to this queue
* are ordered by priority.
* Internally this class uses a heap, which offers log(n) performance for
* the add, removeMin and rescheduleMin operations, and constant time
* performance for the the getMin operation.
*/
public class PriorityQueue {
private static class Entry {
long priority;
int position;
Object element;
}
/**
* Priority queue represented as a balanced binary heap: the two children
* of queue[n] are queue[2*n] and queue[2*n+1]. The priority queue is
* ordered on priority: The Object with the lowest
* priority is in queue[1] (assuming the queue is nonempty). For
* each node n in the heap, and each descendant of n, d,
* n.nextExecutionTime &lt;= d.nextExecutionTime.
*/
private Entry[] queue;
/**
* The reverse Map is used to quickly find an element within the queue
* when the position is not known.
*/
private HashMap reverseMap;
/**
* The number of objects in the priority queue. (The object are stored in
* queue[1] up to queue[size]).
*/
private int size = 0;
/**
* Constructs a Priority Queue
*/
public PriorityQueue() {
reverseMap = new HashMap();
queue = new Entry[128];
}
/**
* special purpose constructor, see getAllUntil()
*/
private PriorityQueue(PriorityQueue other) {
reverseMap = null;
queue = new Entry[other.queue.length];
System.arraycopy(other.queue, 0, queue, 0, queue.length);
size = other.size;
}
/**
* Adds a new object to the priority queue.
*/
public void add(long priority, Object obj) {
// Grow backing store if necessary
if (++size == queue.length) {
Entry[] newQueue = new Entry[2 * queue.length];
System.arraycopy(queue, 0, newQueue, 0, size);
queue = newQueue;
}
Entry entry = new Entry();
entry.priority = priority;
entry.element = obj;
entry.position = size;
queue[size] = entry;
reverseMap.put(entry.element, entry);
fixUp(size);
}
/**
* Return the priority of the head of the priority queue.
*/
public long getMinPriority() {
return queue[1].priority;
}
/**
* Return the "head" of the priority queue. (The head is the
* object with the lowest getPriority().)
*/
public Object getMin() {
return queue[1].element;
}
/**
* Return all elements in the priority queue which have a lower
* priority as the specified threshold. The returned array is ordered
* by priority.
*/
public Object[] getAllUntil(long threshold, Object[] target) {
PriorityQueue tmp = new PriorityQueue(this);
ArrayList list = new ArrayList();
while (!tmp.isEmpty() && (tmp.getMinPriority() <= threshold)) {
list.add(tmp.removeMin());
}
return list.toArray(target == null ? new Object[0] : target);
}
/**
* Remove the head from the priority queue.
*/
public Object removeMin() {
Object head = queue[1].element;
if (reverseMap != null) {
reverseMap.remove(head);
}
queue[1] = queue[size];
queue[1].position = 1;
queue[size--] = null; // Drop extra reference to prevent memory leak
fixDown(1);
return head;
}
/**
* Remove the specified object from the priority queue.
* not in the Queue
*/
public void remove(Object obj) {
Entry entry = (Entry) reverseMap.get(obj);
if (entry == null) {
throw new NoSuchElementException();
}
reverseMap.remove(obj);
queue[entry.position] = queue[size];
queue[entry.position].position = entry.position;
queue[size--] = null; // Drop extra reference to prevent memory leak
fixDown(entry.position);
}
/**
* Sets the priority associated with the head to the
* specified value, and adjusts priority queue accordingly.
*/
public void rescheduleMin(long priority) {
queue[1].priority = priority;
fixDown(1);
}
/**
* Sets the priority associated with the specified object to the
* specified value, and adjusts priority queue accordingly.
* not in the Queue
*/
public void reschedule(long priority, Object obj) {
Entry entry = (Entry) reverseMap.get(obj);
if (entry == null) {
throw new NoSuchElementException();
}
long oldPriority = entry.priority;
entry.priority = priority;
if (priority > oldPriority) {
fixDown(entry.position);
} else {
fixUp(entry.position);
}
}
/**
* Returns true if the priority queue contains no elements.
*/
public boolean isEmpty() {
return size == 0;
}
/**
* Removes all elements from the priority queue.
*/
public void clear() {
// Null out task references to prevent memory leak
for (int i = 1; i <= size; i++) {
queue[i].element = null;
queue[i] = null;
}
reverseMap.clear();
size = 0;
}
/**
* Establishes the heap invariant (described above) assuming the heap
* satisfies the invariant except possibly for the leaf-node indexed by k
* (which may have a priority less than its parent's).
*
* This method functions by "promoting" queue[k] up the hierarchy
* (by swapping it with its parent) repeatedly until queue[k]'s
* priority is greater than or equal to that of its parent.
*/
private void fixUp(int k) {
while (k > 1) {
int j = k >> 1;
if (queue[j].priority <= queue[k].priority) {
break;
}
Entry tmp = queue[j];
queue[j] = queue[k];
queue[j].position = j;
queue[k] = tmp;
queue[k].position = k;
k = j;
}
}
/**
* Establishes the heap invariant (described above) in the subtree
* rooted at k, which is assumed to satisfy the heap invariant except
* possibly for node k itself (which may have a priority greater
* than its children's).
*
* This method functions by "demoting" queue[k] down the hierarchy
* (by swapping it with its smaller child) repeatedly until queue[k]'s
* priority is less than or equal to those of its children.
*/
private void fixDown(int k) {
int j;
while ((j = k << 1) <= size) {
if ((j < size) && (queue[j].priority > queue[j + 1].priority))
j++; // j indexes smallest kid
if (queue[k].priority <= queue[j].priority)
break;
Entry tmp = queue[j];
queue[j] = queue[k];
queue[j].position = j;
queue[k] = tmp;
queue[k].position = k;
k = j;
}
}
}
Danke, werd ich FAST unverändert übernehmen können. Werds mir wohl auch
allgemein als PriorityQueue ablegen.

Malte
Alfred
2005-08-30 16:17:39 UTC
Permalink
Post by Malte Schneider
Danke, werd ich FAST unverändert übernehmen können. Werds mir wohl auch
allgemein als PriorityQueue ablegen.
LTQ!
Alfred
Malte Schneider
2005-08-30 19:07:01 UTC
Permalink
Post by Alfred
Post by Malte Schneider
Danke, werd ich FAST unverändert übernehmen können. Werds mir wohl
auch allgemein als PriorityQueue ablegen.
LTQ!
Alfred
LTQ ?

Sven
Markus Moll
2005-08-30 19:35:57 UTC
Permalink
Hallo
LTQ ?
http://learn.to/quote

Gruß
Markus
Ralf Ullrich
2005-09-02 12:30:00 UTC
Permalink
Nachdem hier ja immer noch über A* diskutiert wird, möchten vielleicht
einige von euch damit auch ein bischen experimentieren. Damit nun nicht
jeder eine Implementation selbst schreiben muss, hier mein Demo-Code mit
dem ich Paul Ebermanns Mutmaßungen überprüft habe.

Durch Implementieren der abstrakten Methoden der Klasse Pathfinder,
lassen sich verschiedene Heuristiken und Metriken austesten; einige
Beispiele sind ja schon im Code enthalten.

Viel Spass damit.

Und mal sehen, ob jemand mit einer besseren Heuristik als Manhattan
aufwarten kann. (OK, evtl. muss die Testmap komplexer gemacht werden.)

cu

package de.comp.lang.java;

import java.util.LinkedList;
import java.util.List;

public abstract class Pathfinder {

private static class Tile {

int x;

int y;

int fullCost;

int guaranteedCost;

int heuristicCost;

Tile prev;

Tile next;

int position; // == 0 means on closed list

boolean isTarget;

/** Construct a wall. */
private Tile() {
x = y = -1;
fullCost = guaranteedCost = heuristicCost = Integer.MIN_VALUE;
}

/** Construct a walkable tile. */
private Tile(int x, int y, int hCost) {
this.x = x;
this.y = y;
fullCost = guaranteedCost = Integer.MAX_VALUE;
heuristicCost = hCost;
}

/** Construct a target tile. */
private Tile(int x, int y) {
this.x = x;
this.y = y;
fullCost = guaranteedCost = Integer.MAX_VALUE;
heuristicCost = Integer.MIN_VALUE;
}

// only for demo
char charForEntry(int xStart, int yStart) {
if ((x < 0) || (y < 0))
return 'W';
if ((x == xStart) && (y == yStart))
return 'S';
if (isTarget)
return '*';
if (position < 10) {
return "C123456789".charAt(position);
}
return 'o';
}

public Tile getNext() {
return next;
}

public Tile getPrevious() {
return prev;
}

public int getX() {
return x;
}

public int getY() {
return y;
}
}

Tile start;

int xStart;

int yStart;

int pathLength;

Tile[] path;

private int width;

private int height;

Tile[] openList;

int openSize;

Tile[] map;

List<Tile> targets;

// remember walls between path finding, could be shared between
// multiple pathfinder instances
private Tile[] walls;

private final Tile WALL = new Tile();

private int steps;

public Pathfinder(int width, int height) {
this.width = width;
this.height = height;
openList = new Tile[width * height + 1];
map = new Tile[width * height];
walls = new Tile[width * height];
targets = new LinkedList<Tile>();
}

final int i4map(int x, int y) {
return x + y * width;
}

public void addBorder() {
for (int x = 0; x < width; x++) {
addWall(x, 0);
addWall(x, height - 1);
}
for (int y = 0; y < height; y++) {
addWall(0, y);
addWall(width - 1, y);
}
}

public void addWall(int x, int y) {
walls[i4map(x, y)] = WALL;
}

public void removeWall(int x, int y) {
walls[i4map(x, y)] = null;
}

public void setStart(int x, int y) {
xStart = x;
yStart = y;
}

public void addTarget(int x, int y) {
targets.add(new Tile(x, y));
}

public void clearTargets() {
targets.clear();
}

public void findPath() {
reset();
steps = 0;
start = lookup(xStart, yStart);
start.guaranteedCost = 0;
for (Tile target : targets) {
lookup(target.x, target.y).isTarget = true;
}
Tile current = null;
while (!isEmpty()) {
++steps;
showMap(steps);
current = removeHead();
if (current.isTarget)
break;
tryStep(current, 1, 1);
tryStep(current, 0, 1);
tryStep(current, -1, 1);
tryStep(current, 1, 0);
tryStep(current, -1, 0);
tryStep(current, 1, -1);
tryStep(current, 0, -1);
tryStep(current, -1, -1);
}
for (Tile t = current; t != null; t = t.prev) {
t.isTarget = true; // mark path demo only
if (t.prev != null) {
t.prev.next = t;
}
pathLength++;
}
showMap(steps);
}

public int[] toXYArray() {
int[] retval = new int[pathLength * 2];
int len = 0;
for (Tile t = start; t != null; t = t.next) {
retval[len++] = t.x;
retval[len++] = t.y;
}
return retval;
}

public int size() {
return pathLength;
}

public Tile get(int index) {
if (path == null) {
path = new Tile[pathLength];
int len = 0;
for (Tile t = start; t != null; t = t.next) {
path[len++] = t;
}
}
return path[index];
}

// demo only
private void showMap(int steps) {
System.out.println("=== #" + steps + " open: " + openSize);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
Tile t = map[i4map(x, y)];
System.out.print(t == null ? '_' : t.charForEntry(xStart,
yStart));
}
System.out.println();
}
}

private void tryStep(Tile current, int dx, int dy) {
Tile next = lookup(current.x + dx, current.y + dy);
int newCost = current.guaranteedCost
+ calcStepCost(current, next, dx, dy);
if (newCost < next.guaranteedCost) {
next.guaranteedCost = newCost;
next.prev = current;
next.fullCost = newCost + next.heuristicCost;
// new full cost is always lower than old full cost,
// so only a fixUp on the position could be needed
fixUp(next.position);
}
}

private Tile lookup(int x, int y) {
Tile tile = map[i4map(x, y)];
if (tile == null) {
tile = new Tile(x, y, calcHeuristicCost(x, y));
map[i4map(x, y)] = tile;
// add to end of open list
openList[tile.position = ++openSize] = tile;
// no fixUp needed, fullCost is MAX_VALUE!!
}
return tile;
}

protected abstract int calcStepCost(Tile from, Tile to, int dx, int
dy);

protected abstract int calcHeuristicCost(int x, int y);

private Tile removeHead() {
Tile head = openList[1];
openList[1] = openList[openSize];
openList[1].position = 1;
openList[openSize--] = null;
fixDown(1);
head.position = 0;
return head;
}

/**
* Returns true if the fullCost openList contains no elements.
*/
public boolean isEmpty() {
return openSize == 0;
}

private void reset() {
for (int i = 1; i <= openSize; i++) {
openList[i].prev = null;
openList[i] = null;
}
openSize = 0;
System.arraycopy(walls, 0, map, 0, walls.length);
start = null;
pathLength = 0;
path = null;
}

/**
* Establishes the heap invariant (described above) assuming the heap
* satisfies the invariant except possibly for the leaf-node
indexed by k
* (which may have a fullCost less than its prev's).
*
* This method functions by "promoting" openList[k] up the
hierarchy (by
* swapping it with its prev) repeatedly until openList[k]'s
fullCost is
* greater than or equal to that of its prev.
*/
void fixUp(int k) {
while (k > 1) {
int j = k >> 1;
if (openList[j].fullCost <= openList[k].fullCost) {
break;
}
Tile tmp = openList[j];
openList[j] = openList[k];
openList[j].position = j;
openList[k] = tmp;
openList[k].position = k;
k = j;
}
}

/**
* Establishes the heap invariant (described above) in the subtree
rooted at
* k, which is assumed to satisfy the heap invariant except
possibly for
* node k itself (which may have a fullCost greater than its
children's).
*
* This method functions by "demoting" openList[k] down the
hierarchy (by
* swapping it with its smaller child) repeatedly until openList[k]'s
* fullCost is less than or equal to those of its children.
*/
private void fixDown(int k) {
int j;
while ((j = k << 1) <= openSize) {
if ((j < openSize)
&& (openList[j].fullCost > openList[j + 1].fullCost))
j++; // j indexes smallest kid
if (openList[k].fullCost <= openList[j].fullCost)
break;
Tile tmp = openList[j];
openList[j] = openList[k];
openList[j].position = j;
openList[k] = tmp;
openList[k].position = k;
k = j;
}
}

public static class AStar extends Pathfinder {

public AStar(int width, int height) {
super(width, height);
}

@Override
protected int calcStepCost(Tile from, Tile to, int dx, int dy) {
return ((dx & dy) == 0 ? 10 : 14);
}

@Override
protected int calcHeuristicCost(int x, int y) {
int hCost = Integer.MAX_VALUE;
for (Tile target : targets) {
int tmp = 10 * (Math.abs(target.x - x) +
Math.abs(target.y - y));
if (tmp < hCost) {
hCost = tmp;
}
}
return hCost;
}

}

public static class Dijkstra extends Pathfinder {

public Dijkstra(int width, int height) {
super(width, height);
}

@Override
protected int calcStepCost(Tile from, Tile to, int dx, int dy) {
return ((dx & dy) == 0 ? 10 : 14);
}

@Override
protected int calcHeuristicCost(int x, int y) {
return 0;
}

}

public static class Better extends Pathfinder {

public Better(int width, int height) {
super(width, height);
}

@Override
protected int calcStepCost(Tile from, Tile to, int dx, int dy) {
return ((dx & dy) == 0 ? 10 : 14);
}

@Override
protected int calcHeuristicCost(int x, int y) {
int hCost = Integer.MAX_VALUE;
for (Tile target : targets) {
int dx = Math.abs(target.x - x);
int dy = Math.abs(target.y - y);
int tmp = 10 * Math.max(dx, dy) + 4 * Math.min(dx, dy);
if (tmp < hCost) {
hCost = tmp;
}
}
return hCost;
}

}

public static class Ebermann extends Pathfinder {

public Ebermann(int width, int height) {
super(width, height);
}

@Override
protected int calcStepCost(Tile from, Tile to, int dx, int dy) {
return ((dx & dy) == 0 ? 100 : 141);
}

@Override
protected int calcHeuristicCost(int x, int y) {
int hCost = Integer.MAX_VALUE;
for (Tile target : targets) {
int dx = Math.abs(target.x - x);
int dy = Math.abs(target.y - y);
int tmp = 100 * (dx + dy) - 66 * Math.min(dx, dy);
if (tmp < hCost) {
hCost = tmp;
}
}
return hCost;
}

}

public static class Pythagoras extends Pathfinder {

public Pythagoras(int width, int height) {
super(width, height);
}

@Override
protected int calcStepCost(Tile from, Tile to, int dx, int dy) {
return ((dx & dy) == 0 ? 100 : 141);
}

@Override
protected int calcHeuristicCost(int x, int y) {
int hCost = Integer.MAX_VALUE;
for (Tile target : targets) {
int dx = Math.abs(target.x - x);
int dy = Math.abs(target.y - y);
int tmp = (int)(100 * Math.sqrt(dx*dx + dy*dy));
if (tmp < hCost) {
hCost = tmp;
}
}
return hCost;
}

}

public static void main(String... args) {
Pathfinder pf;
// pf = new Pathfinder.Dijkstra(15, 9);
// demoPathfinder(pf);
pf = new Pathfinder.AStar(15, 9);
demoPathfinder(pf);
// pf = new Pathfinder.Ebermann(15, 9);
// demoPathfinder(pf);
// pf = new Pathfinder.Pythagoras(15, 9);
// demoPathfinder(pf);
}

private static void demoPathfinder(Pathfinder pf) {
pf.addBorder();
pf.addWall(7, 3);
pf.addWall(7, 4);
pf.addWall(7, 5);
pf.setStart(4, 4);
pf.addTarget(10, 4);
pf.findPath();
System.out.println(pf.getClass().getSimpleName()+":
"+pf.steps+" iterations / path lentgh: "+pf.size());
for (int i = 0; i < pf.size(); i++) {
System.out.println(i + ": " + pf.get(i).getX() + "/"
+ pf.get(i).getY());
}
}
}
Malte Schneider
2005-09-05 09:25:44 UTC
Permalink
Moin,

ich bin leider immernoch nicht dazu gekommen, die vielen Anregungen zu
testen bzw. umzusetzen. Jedenfalls schonmal vielen Dank an die Group.

Malte

Loading...