250x250
기리도
기리도의 개발새발 개발 일지
기리도
전체 방문자
오늘
어제
  • 분류 전체보기 (44)
    • Unity (6)
      • 모듈식 프로그래밍 (1)
    • C# (10)
    • 자료구조,알고리즘 (2)
    • 운영체제 (10)
      • 공룡책 (3)
      • 그림으로 쉽게 배우는 운영체제(인프런 강의) (7)
    • 리팩토링 (1)
    • 네트워크 (13)
      • 네트워크 장비 (13)
    • C, C++ 문법 (1)
      • 기타 (0)
      • C (1)
      • C++ (0)
    • 디자인 패턴 (1)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 통신
  • 게임개발
  • 길찾기
  • 개발공부
  • C#
  • 네트워크
  • 네트워크 게임
  • 알고리즘
  • 네트워킹
  • 프로그래밍
  • OS
  • 개발
  • 브릿지
  • 공부
  • 인프런
  • 스위치
  • 탄환
  • 유니티
  • Unity
  • 운영체제

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
기리도

기리도의 개발새발 개발 일지

자료구조,알고리즘

A* 알고리즘을 이용한 길찾기2 : Heap을 이용한 최적화

2022. 3. 4. 17:51
728x90

저번 포스팅에서 사용한 A* 알고리즘은 Cost_F가 가장 낮은 노드를 찾기 위해 매번 모든 열린 목록을 탐색해야 한다는 단점이 있었다. 이번 포스팅에서는 Heap(각 노드가 자식 노드를 최대 두 개씩 가지는 일종의 이진 트리 자료구조. 최대값과 최소값을 빠르게 찾아낼 수 있다)을 이용해 A* 알고리즘을 효율적으로 수정해 보았다. 코드는 다음과 같다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

public class Heap<T> where T : IHeapItem<T>
{
    //노드 배열을 지정하는 대신 T유형의 배열을 지정
    T[] Items;
    int CurrentItemCount;
    
    //힙은 최대 크기를 정해준다.
    public Heap(int maxHeapsize)
    {
        //gridSizeX, gridSizeY 를 곱하면 최대값을 알 수 있다.
        Items = new T[maxHeapsize];
    }
    //새 항목을 힙에 추가하기 위한 함수
    public void Add(T item)
    {
        item.HeapIndex = CurrentItemCount;//추가될 항목의 인덱스는 현재까지 등록된 항목의 숫자와 같다
        Items[CurrentItemCount] = item;
        SortUp(item);
        CurrentItemCount++;
    }

    //가장 비용이 낮은 항목부터 제거 == 루트 항목 제거
    public T RemoveFirst()
    {

        T FirstItem = Items[0];
        CurrentItemCount--;
        Items[0] = Items[CurrentItemCount];
        Items[0].HeapIndex = 0;

        SortDown(Items[0]);
        return FirstItem;
    }
    public void UpdateItem(T item)
    {
        SortUp(item);
    }
    public int Count
    {
        get { return CurrentItemCount; }
    }
    public bool Contains(T item)
    {
        return Equals(Items[item.HeapIndex], item);
    }
    void SortDown(T item)
    {
        while (true)
        {
            //자식 인덱스 구하기
            int childrenIndexLeft = item.HeapIndex * 2 + 1;
            int childrenIndexRight = item.HeapIndex * 2 + 2;
            int SwapIndex = 0;

            //childrenIndexLeft의 자식이 존재하는지 확인
            if (childrenIndexLeft < CurrentItemCount)
            {
                SwapIndex = childrenIndexLeft;
               //childrenIndexRight의 자식이 존재하는지 확인
                if (childrenIndexRight < CurrentItemCount)
                {
                    if(Items[childrenIndexLeft].CompareTo(Items[childrenIndexRight]) < 0)
                    {
                        SwapIndex = childrenIndexRight;
                    }
                }
                //현재 노드와 자식 노드를 비교해 자식 노드의 항목이 더 작으면 자식과 위치 변경
                if (item.CompareTo(Items[SwapIndex]) < 0)
                {
                    Swap(item, Items[SwapIndex]);
                }
                else
                {
                    return;
                }
            }
            //자식이 없는 경우
            else 
            {
                return;
            }
        }
    }
    void SortUp(T item)
    {
        int ParentIndex = (item.HeapIndex - 1) / 2;

        while (true)
        {
            T ParentItem = Items[ParentIndex];

            //Add 함수에서 추가된 항목이 부모 항목보다 크다면 추가된 항목과 부모 항목의 자리를 바꾼다 == 추가된 항목이 부모가 됨.
            if (item.CompareTo(ParentItem) > 0)
            {
                Swap(item, ParentItem);
            }
            else 
            {
                break;
            }
            ParentIndex = (item.HeapIndex - 1) / 2;  //스왑한 뒤 다시 그 위의 부모와도 비교하기 위해 부모의 인덱스를 새로 구함
        }
    }
    void Swap(T itemA, T itemB)
    {
        Items[itemA.HeapIndex] = itemB;
        Items[itemB.HeapIndex] = itemA;

        int itemAindex = itemA.HeapIndex;
        itemA.HeapIndex = itemB.HeapIndex;
        itemB.HeapIndex = itemAindex;
    }
}
public interface IHeapItem<T> :IComparable<T>
{
    int HeapIndex
    {
        get;
        set;
    }
}

  힙을 만들어준 뒤 기존의 Node 스크립트에 IHeapItem<Node>를 상속해준다. Node 스크립트는 다음과 같다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Node : IHeapItem<Node>
{
    public bool bWalkable;
    public Vector3 WorldPositon;

    public Node Parent;
    public int Cost_G;
    public int Cost_H;

    public int Grid_x;
    public int Grid_y;
    int heapIndex;
    
    public int Cost_F
    {
        get {
            return Cost_G + Cost_H;
        }
    }

    public Node(bool _Walkable, Vector3 _WorldPos, int _gridx, int _gridy)
    {
        bWalkable = _Walkable;
        WorldPositon = _WorldPos;

        Grid_x = _gridx;
        Grid_y = _gridy;
    }
    public int HeapIndex
    {
        get {
            return heapIndex;
        }
        set
        {
            heapIndex = value;
        }
    }
    public int CompareTo(Node nodeToCompare)
    {
        int compare = Cost_F.CompareTo(nodeToCompare.Cost_F);
        if (compare == 0)
        {
            compare = Cost_H.CompareTo(nodeToCompare.Cost_H);
        }
        return -compare;
    }
}

  마지막으로, PathFind도 수정해주면 끝.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Diagnostics;

public class PathFind : MonoBehaviour
{
    Grid Grid;
    public Transform Seeker, Target;

    private void Awake()
    {
        Grid = GetComponent<Grid>();
    }
    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        { 
            FindPath(Seeker.position, Target.position); 
        }
    }
    void FindPath(Vector3 startPos, Vector3 TargetPos)
    {
        Node StartNode = Grid.NodeWorldPoint(startPos);
        Node TargetNode = Grid.NodeWorldPoint(TargetPos);
        
        Stopwatch sw = new Stopwatch();
        sw.Start();

        Heap<Node> openset = new Heap<Node>(Grid.MaxSize);
        HashSet<Node> closeset = new HashSet<Node>();

        openset.Add(StartNode);

        while (openset.Count > 0)
        {
            Node CurrentNode = openset.RemoveFirst();
            
            closeset.Add(CurrentNode);

            if (CurrentNode == TargetNode)
            {
                sw.Stop();
                print("PathFind time : " + sw.ElapsedMilliseconds + "ms");
                ReTracePath(StartNode, TargetNode);
                return;
            }

            foreach (Node neighbour in Grid.GetNeighbour(CurrentNode)) {
                if (!neighbour.bWalkable || closeset.Contains(neighbour))
                {
                    continue;
                }

                int newMovementCostToNeighbour = CurrentNode.Cost_G + GetDistance(CurrentNode, neighbour);
                if (newMovementCostToNeighbour < neighbour.Cost_G || !openset.Contains(neighbour))
                {
                    neighbour.Cost_G = newMovementCostToNeighbour;
                    neighbour.Cost_H = GetDistance(neighbour, TargetNode);
                    neighbour.Parent = CurrentNode;

                    if (!openset.Contains(neighbour))
                        openset.Add(neighbour);
                }

            }
        }
    }

    void ReTracePath(Node startNode, Node endNode)
    {
        List<Node> Path = new List<Node>();
        Node CurrentNode = endNode;

        while (CurrentNode != startNode)
        {
            Path.Add(CurrentNode);
            CurrentNode = CurrentNode.Parent;
        }
        Path.Reverse();

        Grid.path = Path;
    }

    int GetDistance(Node nodeA, Node nodeB)
    {
        int dstX = Mathf.Abs(nodeA.Grid_x - nodeB.Grid_x);
        int dstY = Mathf.Abs(nodeA.Grid_y - nodeB.Grid_y);

        if (dstX > dstY)
            return 14 * dstY + 10 * (dstX - dstY);
        return 14 * dstX + 10 * (dstY - dstX);
    }
}
728x90

'자료구조,알고리즘' 카테고리의 다른 글

A* 알고리즘을 이용한 길찾기  (0) 2022.03.01
    '자료구조,알고리즘' 카테고리의 다른 글
    • A* 알고리즘을 이용한 길찾기
    기리도
    기리도
    공부한 내용을 정리해서 올리는 블로그입니다.

    티스토리툴바