Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?

[C++] Jak ulepszyć moją implementację algorytmu A*?

Ostatnio zmodyfikowano 2018-04-05 01:10
Autor Wiadomość
pekfos
» 2018-04-02 22:03:32
C/C++
* currentNode = allNodes[ surroundingNodes[ a ].i ];
while( currentNode != beginningNode )
{
   
    //currentNode = &allNodes[currentNode->parent];
    //cout << currentNode->parent;
    sf::Vector2i n;
    n.x = currentNode->X;
    n.y = currentNode->Y;
    foundPath.push_back( n );
    * currentNode = allNodes[ currentNode->parent ]; //tutaj program się wywala
}
Ta pętla jest nieskończona, bo nie modyfikujesz currentNode.

Do tego metoda getNode() jest jedną wielką tragedią. Kopiujesz obraz, tylko dla odczytania jego rozmiaru - który i tak znasz, bo to rozmiar allNodes. I jakby tego było mało, tamta pętla nie wnosi nic poza masą zbędnych obliczeń. Ten sam efekt można osiągnąć bez żadnych pętli.
P-170415
twoxu
Temat założony przez niniejszego użytkownika
» 2018-04-03 00:01:50
To w takim razie jak zmodyfikować currentNode?
Próbowałem usunąć * przed currentNode i dodać & przed allNodes[currentNode->parent]; i efekt jest taki sam.
P-170428
darko202
» 2018-04-03 15:31:04
1.
straszny jest ten Twój algorytm  :(
nie rozumiem go,
nie widzę też związku z algorytmem A*

w algorytmie A* wyraźnie mowa jest o grafie
https:/​/elektron.elka.pw.edu.pl​/~jarabas/ALHE/notatki3.pdf

Ty wykorzystując "vector" tworzysz coś co jest raczej listą -> tzn. tak mi się wydaje

2.
jeśli celem zadania jest przy pomocy algorytmu A*
mamy znaleźć ścieżkę z punktu A do B na (kwadratowego obrazka o rozmiarach w zakresie od 100x100 do 200x200 )
Biały kolor oznacza przeszkodę, zielony możliwa droga do wybrania
(odcień zieleni oznaczać może chyba koszt drogi )

graf jest skończony - zawiera tylko punkty o odpowiednim kolorze
oraz mozliwe krawędzie
bardzo proste jest zbudowanie grafu na którym zastosujemy np. Algorytm A*

3
inaczej
np.
krok pierwszy -> punkt startu A
krok drugi -> 4 możliwe kierunki (góra, lewo, dół, prawo)
następne kroki -> 3 możliwe kierunki (kierunek skąd przyszliśmy odrzucamy)
                  następuje skrzyżowanie dróg z 2 kierunków (tu wybieramy drogę z mniejszą wagą)
aż dojdziemy do punktu końcowego B  

grafem jest (punkty obrazka o odpowiednim kolorze, krawędzie z wagą (pomiędzy sąsiednimi punktami) )
stosujemy do niego np. Algorytm A* i jest rozwiązanie

jak to piszę to dokładnie omawialiśmy taki algortym na zajęciach "algorytmy i struktury danych"
choć robiliśmy to na kartce


4.
w kodzie pokazałeś funkcje
i mozna znaleść

sr.parent = n.i;
...
* currentNode = allNodes[ currentNode->parent ]; //tutaj program się wywala


nie widzę kodu budującego (wypełniające Node) z obrazka
dlatego trudno się do tego odnieść

wyjatek std::bad_alloc::what()
http://en.cppreference.com/w​/cpp/memory/new/bad_alloc
 
 pokazuje, że gdzieś jest nieprawidłowa wartość
 
 zastosuj mniejszy obrazek 4x4 i zdebuguj program zwracając uwage na punkty gdzie
gdzie wykonujesz operacje

n.i =... ?
n.parent = ..?

gdy
Node n;
P-170434
pekfos
» 2018-04-03 15:37:52
w algorytmie A* wyraźnie mowa jest o grafie
Ty wykorzystując "vector" tworzysz coś co jest raczej listą -> tzn. tak mi się wydaje
A co to jest graf..? Jak graf wygląda w C++ twoim zdaniem? Każdy węzeł zaalokowany osobno i połączony z sąsiadami po wskaźnikach? Tak się składa że tu, jak i w każdym innym przypadku wyszukiwania drogi na mapie prostokątnej, węzły są bardzo regularnie zorganizowane i połączone. Modelowanie tego wskaźnikami i toną alokacji to strata czasu dla programisty i potem dla komputera.
P-170436
twoxu
Temat założony przez niniejszego użytkownika
» 2018-04-03 16:34:09
Owszem, w algorytmie A* jest coś takiego jak graf, ale to zależy od tego kto pisał poradnik. Ja na przykład czytałem ten: http://homepages.abdn.ac.uk/f.guerin/pages/teaching/CS1013/practicals/aStarTutorial.htm
i nie było ani jednego wystąpienia słowa "graph", a więc jest to sprawa dość uznaniowa.

Mój algorytm mimo być może dziwnej formy pracuje jak A* i zwraca wynik jak A*.
 
Moje Node są wypełniane na początku pracy funkcji Pathfind()
C/C++
for( int z = 0; z < col.getSize().x; z++ )
{
    for( int q = 0; q < col.getSize().y; q++ )
    {
        Node n;
        n.X = z;
        n.Y = q;
        n.i = allNodes.size();
        allNodes.push_back( n );
    }
}
std::cout << "Nodes assigned to Vector" << std::endl;
//Now, calculate A and B values for all nodes.
for( int l = 0; l < allNodes.size(); l++ )
{
    //allNodes[l].A = abs(dx-allNodes[l].X) + abs(dy-allNodes[l].Y); //MANHATTAN METHOD
    //allNodes[l].B = abs(bx-allNodes[l].X) + abs(by-allNodes[l].Y);
    allNodes[ l ].A = sqrt( pow( dx - allNodes[ l ].X, 2 ) + pow( dy - allNodes[ l ].Y, 2 ) ); //EUCLIDEAN METHOD
    allNodes[ l ].B = sqrt( pow( bx - allNodes[ l ].X, 2 ) + pow( by - allNodes[ l ].Y, 2 ) );
}
bad alloc jest dlatego że pętla while(currentNode != beginningNode) wykonuje się zbyt dużo razy, i nadal nie wiem dlaczego. Podobno nie zmieniam currentNode, ale próbowałem zarówno
*currentNode = allNodes[currentNode->parent]
jak i
currentNode = &allNodes[currentNode->parent]
i nie ma pożądanego efektu.
P-170438
darko202
» 2018-04-04 14:43:43
1.

... nie było ani jednego wystąpienia słowa "graph", a więc jest to sprawa dość uznaniowa.

już na samym początku autor cytowanego przez Ciebie artykułu robi założenie
" When you read about pathfinding elsewhere, you will often see people discussing nodes. Why not just call them squares?"
 Kiedy czytasz o odnajdywaniu ścieżek gdzie indziej, często widzisz ludzi omawiających węzły. Dlaczego po prostu nie nazwać ich kwadratami?
 czyli tam gdzie mówi o "kwadratach" mamy do czynienia z wierzchołkami (wezłami), a "scieżki" są krawędziami.
a z definicji :
 "graf to zbiór wierzchołków, które mogą być połączone krawędziami w taki sposób, że każda krawędź kończy się i zaczyna w którymś z wierzchołków."
 
dlatego w sesie definicyjnym mamy do czynienia z grafem


modny jest teraz "relatywizm"  tzn. wszystko jest względne zależy od ...

ale problemy :
"dwukrotnego powiekszenia kwadratowego basenu, gdy w jego rogach rosną 4 palmy bez ich wycinania"
" dwukrotnego powiekszenia kwadratowej sadzawki, gdy w jego rogach rosną 4 jabłonie bez ich wycinania"

jest to ten sam problem.
ale opisowo co innego :)

2.
ps.
algorytm A* - zobaczyłem wczoraj - po Twoim zgłoszeniu - opis nie jest ważny, ale sedno problemu jest takie samo.
przeczytałem wskazany opis i nie widzę różnicy sie od mojego (poza braniu w kolejnym kroku 8 punktów) 

czy możesz byc tak uprzejmy i wskazać mi w cytowanej funkcji Pathfind()
kolejne kroki z opisu tego algorytmu

"
1) Add the starting square (or node) to the open list.

2) Repeat the following:

a) Look for the lowest F cost square on the open list. We refer to this as the current square.

b) Switch it to the closed list.

c) For each of the 8 squares adjacent to this current square …

• If it is not walkable or if it is on the closed list, ignore it. Otherwise do the following.            


•  If it isn’t on the open list, add it to the open list. Make the current square the parent of this square. Record the F, G, and H costs of the square. 


• If it is on the open list already, check to see if this path to that square is better, using G cost as the measure. A lower G cost means that this is a better path. If so, change the parent of the square to the current square, and recalculate the G and F scores of the square. If you are keeping your open list sorted by F score, you may need to resort the list to account for the change.


d) Stop when you:
• Add the target square to the closed list, in which case the path has been found (see note below), or
• Fail to find the target square, and the open list is empty. In this case, there is no path.   

3) Save the path. Working backwards from the target square, go from each square to its parent square until you reach the starting square. That is your path.

"


napisałem to, bo wydaje mi sie to istotne np.
 * Add the starting square (or node) to the open list.  -> Ty dodajesz wszystkie
 * b) Switch it to the closed list.
 
 
ale jak pisałem wcześniej nie rozumiem Twojego algorytmu :)

3.
na samym początku funkcji Pathfind() użyłeś zmiennej/stałej currentMap, ktorej nie ma udostępnionym kodzie.

//Firstly, assign all nodes to a Vector.
    col = maps[ currentMap ].colImage;


4.

bad alloc jest dlatego że pętla while(currentNode != beginningNode) wykonuje się zbyt dużo razy, i nadal nie wiem dlaczego.

*
użyj przechwytywania wyjatków try ... catch

po przechwyceniu wyświetl stan zmiennych używanych w tej pętli

choć w codeBlock jest normalny debuger -
zatrzymasz i widzisz stan nawet złożonych struktur

*
w pętli deklarujesz nowe zmienne np.
 vector < Node > surroundingNodes;
czy nie prowadzi to do wyczerpania zasobów ?

 *
   openNodes.erase( openNodes.begin() + 0 );
   vector < Node > surroundingNodes;
   surroundingNodes = getSurroundingNodes( * currentNode, col );
 
  jak się zachowa gdy openNodes jest już pusta ?
  wskazujemy na co ? 
 
5.
jezeli tworzysz klasy  "class Node", to mógłbyś dodać konstruktor + funkcje
podobnie stworzyc klasę dla "vector < Node >" z odpowiednimi metodami
uprościłoby to kod :)

6.
próbujesz zrealizować "wiele" w jednej funkcji/metodzie/klasie,
a sensem programowania obiektowego jest zrealizować "jedną funkcjonalność" 

Powodzenia !
P-170453
twoxu
Temat założony przez niniejszego użytkownika
» 2018-04-04 22:42:21
Mój algorytm różni się tym [od reszty przykładów w internecie] że zamiast listy otwartych i zamkniętych Node mam listę allNodes w której znajdują się wszystkie Node niezależnie od stanu (każda instancja Node zawiera w sobie swój index w allNodes jako int) oraz listę otwartą. Jeżeli Node jest zamknięty, wartość used=true i funkcja getSurroundingNodes nie może go zwracać.

1. Mój algorytm pracuje od celu do startu, tak aby po wyśledzona ścieżka od razu była w prawidłowej kolejności.
C/C++
currentNode = destinationNode;
currentNode->isOpen = true;
openNodes.push_back( * currentNode );
2. Szukanie otwartego Node z najmniejszą wartością i usuwanie z openNodes
C/C++
sort( openNodes.begin(), openNodes.end(), compC );
* currentNode = allNodes[ openNodes[ 0 ].i ];
openNodes.erase( openNodes.begin() + 0 );
C/C++
bool compC( const Node & n1, const Node & n2 )
{
    return allNodes[ n1.i ].C < allNodes[ n2.i ].C;
}
3. Pobieranie otaczających Node i ich przetwarzanie
C/C++
vector < Node > surroundingNodes;
surroundingNodes = getSurroundingNodes( * currentNode, col );
if( !surroundingNodes.empty() )
{
    for( int a = 0; a < surroundingNodes.size(); a++ )
    {
        allNodes[ surroundingNodes[ a ].i ].C = allNodes[ surroundingNodes[ a ].i ].B + allNodes[ surroundingNodes[ a ].i ].A;
        if( surroundingNodes[ a ].X == beginningNode->X && surroundingNodes[ a ].Y == beginningNode->Y )
        {
            //cout << "Path has been found!!Nodes:"<<foundPath.size()<<endl;
            allNodes[ surroundingNodes[ a ].i ].parent = currentNode->i;
            abDebug.saveToFile( "debug.png" );
            foundPath.push_back( sf::Vector2i( allNodes[ surroundingNodes[ a ].i ].X, allNodes[ surroundingNodes[ a ].i ].Y ) );
            //NOW trace back the path and then return it.
            //*currentNode = allNodes[surroundingNodes[a].i];
            * currentNode = allNodes[ surroundingNodes[ a ].i ];
            while( 1 )
            {
                try
                {
                    sf::Vector2i n;
                    n.x = currentNode->X;
                    n.y = currentNode->Y;
                    foundPath.push_back( n ); //<---------------------161 linia
                    //cout << foundPath.size() << endl;
                    currentNode = & allNodes[ currentNode->parent ];
                }
                catch( const std::bad_alloc & e )
                {
                    break;
                }
            }
            viewPath( foundPath, abDebug );
            return foundPath;
        }
        if( !allNodes[ surroundingNodes[ a ].i ].isOpen )
        {
            allNodes[ surroundingNodes[ a ].i ].isOpen = true;
            allNodes[ surroundingNodes[ a ].i ].parent = currentNode->i;
            abDebug.setPixel( surroundingNodes[ a ].X, surroundingNodes[ a ].Y, sf::Color::Cyan );
            openNodes.push_back( allNodes[ surroundingNodes[ a ].i ] );
        }
    }
}
Jak widać użyłem try catch i takie rozwiązanie zadziałało w 8 z 20 przypadków kompilowania projektu od nowa (tak, kompilowałem wszystko 20 razy).
W przypadkach gdy nie zadziałało, pomimo try catch następował std::bad_alloc na linii 161 (zaznaczone w kodzie wyżej).
Jak cout nie jest "wykomentowany", to wypluwa jakieś dziesiątki milionów, i pewnie dlatego [dziesiątki milionów instancji Node w foundPath] tak powoli to działa, a w ostateczności wywala bad alloc.
W przypadkach gdy ZADZIAŁA, to ścieżka jest poprawnie zaznaczona w obrazku debugowym, ale pewnie i tak foundPath ma rozmiar wielu milionów.
Zamieniłem while(currentNode != beginningNode) na while(1) bo i tak nie robi różnicy ;p

Racja z tym pustym openNodes, muszę jeszcze to uwzględnić, ale to nie powinno już stwarzać nowych problemów.

Teraz co do debugowania..
Ustawiłem breakpointy na try oraz catch i klikałem Debug/Continue obserwując wartości w currentNode i wszystko wyglądało poprawnie (wartości X oraz Y wskazywały na to że to faktycznie jest ścieżka) przez parędziesiąt kliknięć, potem nagle wszystkie wartości przestały się zmieniać, mimo wykonania instrukcji currentNode = &allNodes[currentNode->parent]. Pewnie gdybym klikał dziesiątki milionów razy to manualnie doprowadziłbym do bad alloc. Więc wniosek jest taki że program nie wykrywa kiedy ma to skończyć.
Nagle
C/C++
if( currentNode == & allNodes[ currentNode->parent ] )
     break;

dodałem takiego potwora i zdaje się że to pomogło. Program teraz wyszukuje i kataloguje ścieżkę (bynajmniej składającą się z dziesiątek milionów Node) za każdym razem bez żadnych straszliwych bad alloców i mogłem wywalić try catch bo na pewno odbijał się na wydajności mojego algorytmu.

Nadal jest dużo miejsca na poprawy a wygenerowanie tej ścieżki zajęło parę sekund, więc temat zostawiam otwartym.
Dzięki wszystkim za pomoc


P-170461
darko202
» 2018-04-05 00:35:26
A.
na pewno dmuchałeś kiedyś balonik.
w trakcie dmuchania balonik powiekszał się.

podobnie dziala ten algorytm A* - ja to tak zrozumiałem

od1.
dlatego (moim zdaniem) nie można działać na wszystkich węzłach jednocześnie

balonik rośnie z punktu A,
staje w miejscach, które coś stanowi opór,
aż dojdzie do punktu B

od2.
sortowanie po C=1 lub 1.41 : co daje ?

dla danego kroku wazna jest tylko powierzchnia balonika - sąsiednie wezły
a nie  równocześnie dla wszystkich możliwych punktów (~200x200)


B.
wezmy szachownicę 
  A B C D E F G H
1 . . . . . . . .
2 . S . . . . . .
3 . . . . . . . .
4 . . . . . . . .
5 . P P P P . . .
6 . . . . . . . .
7 . . . . K . . .
8 . . . . . . . .

chcemy  znalezc droge z S do K, a P to przeszkoda

pierwszy krok mamy 8 MożliwychDróg : wybieramy z puli MozliwePunkty { A1, A2,..A8, B1,..., H8} - {B2}

MożliweDrogi = {
 < (B2, A1) , 1.4 >,
 < (B2, A2) , 1  >,
 < (B2, A3) , 1.4  >,
 < (B2, B1) , 1  >,
 < (B2, C1) , 1.4  >,
 < (B2, C2) , 1  >,
 < (B2, C3) , 1.4  >,
 < (B2, B3) , 1  >
}

następny dla każdej drogi szukamy kolejnych z puli MozliwePunkty { A1, A2,..A8, B1,..., H8} - {B2, A1, A2, A3, B1, B3, C1, C2, C3}

(B2, A1) , 1.4  -> nie ma punktów sasiadujacych -> droga odpada
(B2, A2) , 1    -> nie ma punktów sasiadujacych -> droga odpada
(B2, A3) , 1.4  -> mają {A4, B4}
(B2, B1) , 1    -> nie ma punktów sasiadujacych -> droga odpada
(B2, C1) , 1.4  -> mają {D1, D2}
(B2, C2) , 1    -> mają {....
(B2, C3) , 1.4  -> mają {...
(B2, B3) , 1    -> mają {A4, B4}


skąd mamy
(B2, A3, A4) , 1.4 + 1
(B2, A3, B4) , 1.4 + 1.4
....
(B2, B3, A4) , 1 + 1.4
(B2, B3, B4) , 1 + 1 

jak widać są drogi mają ten sam początek i koniec
np.

(B2, A3, A4) , 2.4
(B2, B3, A4) , 2.4
jedną z nich o większej wadze odrzucamy

i znów zmniejsza sie też pula MozliwePunkty

Z powstałej puli MożliweDrogi sprawdzamy, czy nie znalezliśmy rozwiazania

jeśli nie krok poprzedni  powtarzamy, do :
znalezienia drogi lub stwierdzenia nie ma takiej drogi

C.
akurat w tym przypadku obrazka łatwiej by pewnie było operować na
tablicy : Node [][], a nie wektor <Node> 
ale z drugiej strony tak jest bardziej uniwersalnie.


D.

 pomimo try catch następował std::bad_alloc na linii 161 (zaznaczone w kodzie wyżej).

C/C++
try
{
    sf::Vector2i n;
    n.x = currentNode->X;
    n.y = currentNode->Y;
    foundPath.push_back( n ); //<---------------------161 linia
    //cout << foundPath.size() << endl;
    currentNode = & allNodes[ currentNode->parent ];
}
catch( const std::bad_alloc & e )
{
    break;
}

mam nadzieję, że mi wybaczysz, iż nie zauważyłem że w linii 161 także odwołujemy się do "currentNode" (n.x = currentNode->X;)
jako i w linii 163
   

Niestety nie jestem doskonały :)


w catch mógłbyś to sprawdzić 
if ( currentNode == Null)


Powodzenia !
P-170464
1 « 2 » 3
Poprzednia strona Strona 2 z 3 Następna strona