A*算法的优化

改进OpenList开启列表

使用优先队列代替List
使用有限队列能够节约对OpenList的遍历

优先队列

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

namespace YBZ.Algorithm {
    
    
    public class PriorityQueue<T> where T : new ()
    {
    
    
        public int size;
        public int capacity;
        private T[] elements;

        // 是否为空
        public bool IsEmpty {
    
     get => size == 0; }

        /// 范围顶部元素
        public T Top {
    
     get => elements[0]; }

        /// 优先队列的模式
        private PriorityQueueMode _comparator;

        public enum PriorityQueueMode {
    
    
            less = -1, // 最小优先队列
            equal = 0, // 相等的排在一起
            greater = 1 // 最大优先队列
        }

        /// <summary>
        /// 以CMP(a,b) 为例:
        /// 当a>b时,返回1,表示放右边
        /// 当a==b时,返回0,表示不变
        ///  当a<b时,返回-1,表示放左边
        /// </summary>
        private Func<T,T,int> CMP;
        
        /// <summary>
        /// 构造函数, 必须实现
        /// </summary>
        /// <param name="CMP"></param>
        /// <param name="capacity"></param>
        /// <param name="priorityQueueMode"></param>
        public PriorityQueue(Func<T,T,int> CMP, PriorityQueueMode priorityQueueMode = PriorityQueueMode.less, int capacity = 1) {
    
    
            this.CMP = CMP;
            this.size = 0; // 数组索引从0开始
            this.capacity = capacity;
            this.elements = new T[capacity];
            this._comparator = priorityQueueMode;
        }

        /// <summary>
        /// 入队
        /// </summary>
        /// <param name="value"></param>
        public void Push(T value) {
    
    
            if (size == capacity) {
    
    
                ExpandCapacity();
            }
            elements[size++] = value;

            ShiftUp();
        }

        /// <summary>
        /// 出队
        /// </summary>
        public void Pop() {
    
    
            if(size == 0) {
    
    
                return;
            }
            size--;
            Swap(ref elements[0], ref elements[size]);

            ShiftDown();
        }

        /// <summary>
        /// 清空队列
        /// </summary>
        public void Clear() {
    
    
            size = 0;
        }

        /// <summary>
        /// 返回位于Queue开始处的对象但不将其移除。
        /// </summary>
        /// <returns>返回第一个队列中元素</returns>
        public T Peek() {
    
    
            return Top;
        }

        /// <summary>
        /// 扩展队列的容量
        /// </summary>
        private void ExpandCapacity() {
    
    
            capacity = Mathf.CeilToInt(capacity * 1.5f);
            T[] temp = new T[capacity];

            for (int i = 0; i < elements.Length; i++) {
    
    
                temp[i] = elements[i];
            }
            elements = temp;
        }
        
        // 从下到上 重排序 
        private void ShiftUp() {
    
    
            int cur = size - 1 ;
            int parent = ( cur -1 ) >> 2;
            while (cur > 0) 
            {
    
    
                if (CMP(elements[cur],elements[parent]) == (int)_comparator) {
    
    
                    Swap(ref elements[cur], ref elements[parent]);
                    cur = parent;
                    parent = (cur - 1) >> 2;
                } else break;
            }
        }

        // 从上到下 重排序
        private void ShiftDown() {
    
    
            int cur = 0;
            int child = 1;
            while (child < size) {
    
    
                if (child + 1 < size && CMP(elements[child +1], elements[child]) == (int)_comparator) {
    
    
                    child++;
                }

                if (CMP(elements[child], elements[cur]) == (int)_comparator){
    
    

                    Swap(ref elements[child], ref elements[cur]);
                    cur = child;
                    child = cur << 1 + 1;
                } else break;
            }
        }

        /// <summary>
        /// 交换传入的两个元素
        /// </summary>
        /// <param name="lhs"></param>
        /// <param name="rhs"></param>
        private void Swap(ref T lhs,ref T rhs) {
    
    
            T temp = lhs;
            lhs = rhs;
            rhs = temp;
        }

        /// <summary>
        /// 返回队列中的所有元素,对于ToString()函数,值类型会返回值,引用类型会返回数据类型
        /// </summary>
        /// <returns></returns>
        public override string ToString() {
    
    
            string result = "";
            foreach (var v in elements) {
    
    
                result += v.ToString();
            }
            return result;
        }
    }
}

改进F = G + H启发式

加权函数

采用加权函数W(n)将启发函数改进为F = G + W * H
对于W为H的系数函数,当H越大的时候W返回的越大,要求尽快的达到目标区域;当H越小,则要求路径越准确

// 加权函数 ,具体比例可以自己决定,
public int W(int H) {
    
    
	int w = 1;
	if (w > 500) {
    
    
	    w = 5;
	} else if (w > 300) {
    
    
	    w = 4;
	} else if(w > 100){
    
    
	    w = 3;
	}else if (w > 50) {
    
    
	    w = 2;
	} else {
    
    
	    w = 1;
	}
	return w;
}

减少拐点

同时也可以在Cost上增加系数要求尽可能走直线,使走斜线是走直线代价的两倍,但是这样只能由于特定要求,比如需要走直线的时候,否侧我认为无论是走直线,走斜线效果一样(走直线会减少邻居节点的添加)

/// <summary>
/// 额外代价
/// </summary>
/// <param name="current">当前节点</param>
/// <param name="neighbor">邻居节点</param>
/// <param name="goal">终点</param>
/// <returns></returns>
public double Cost(Node current,Node neighbor, Node goal)
{
    
    
    Node parent = current.parent;
    // 起点
    if (parent == null) return 0;
    // 走直线
    if (neighbor.x == parent.x || neighbor.y == parent.y) return 0;
    // 拐向终点的点
    if (neighbor.x == goal.x || neighbor.y == goal.y) return 1;
    // 普通拐点
    return 2;
}

可穿障碍物

对Cost函数上如果是障碍物就大幅度增加开销,而不是直接忽略

1.正方形节点,如果正常开销是10,那么障碍物上的开销就必须大幅度大于10, cost == 100,
2.一般还是不存在可穿障碍物

预加载邻居节点

优化Node的GetNeighbor函数使其能够在路径搜索开启前就已经完成。

using System.Linq;
public override List<NodeBase> CacheNeighbors()
    {
    
    
        return Neighbor = GridManager.Instance.Tiles.Where(t => Coords.GetDistance(t.Value.Coords) == 1).Select(t => t.Value).ToList();
    }

JPS(Jump Point Search)

跳点搜索,优先走斜线,尽可能才减掉不需要的邻居节点
相对于矩形节点,走直线只会得到三个新的邻居节点
走斜线,就能得到五个新的邻居节点,能够快速增加自己节点搜索

Map优化

控制维度

控制Map的大小

分层优化(预加载路径)

1.将大面积的的地区先作为一个节点,比如从莫斯科到北京就要求先从俄罗斯到中国,在这过程中逐步缩小范围,莫斯科到中国边境,再从边境到北京市,在到目的地。
2.对于俄罗斯到中国的路径,采用预加载模式,每次从俄罗斯到中国都采用这条路径。
3.调用前对比,大致对比开销,如果开销大幅度低于预加载好的路径,则走自己计算的路径

猜你喜欢

转载自blog.csdn.net/blastospore/article/details/131767746