Unity 中 A*寻路(AStar,A星)的优化,二叉堆,双向队列,哈希表

这篇具有很好参考价值的文章主要介绍了Unity 中 A*寻路(AStar,A星)的优化,二叉堆,双向队列,哈希表。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

 概述

前篇:A星寻路的简单实现

A星寻路,在2D地图下使用频率较高

本篇基于上一篇文章实现的A星寻路进一步优化。利用二叉堆代替了原先openList的数据结构,

改进了path返回时的操作,以及在搜索时的性能开销。

c# Sort函数和堆排序比较

c#中的Sort函数,在实现方面采用的是快速排序。

在日常的使用上,好像已经很满足需求了,快速排序的时间复杂度为O(nlogn),堆排序的时间复杂度也为O(nlogn)。两者看起来速度基本一致。

但是当每次选择的主元都是当前子数组的最小或最大值时,快速排序的时间复杂度是最差的。这种情况下,快速排序退化为类似于选择排序或插入排序的时间复杂度,即 O(n^2)。

而堆排序最好,最坏,平均时间复杂度都是O(nlogn)。

在元素定位方面,快速排序需要依次搜索。堆能够更高效的搜索到。

同时,本文不再等openList累积一段时间后再进行排序,而是在每个结点添加进openList时自动定位;移出openList时,自动调整结构。需要频繁的移动和调整结点位置,此时堆更合适这种操作。

二叉堆简介

了解改进之前,需要先了解一下二叉堆的基本概念。本文用到的二叉堆和数据结构里,堆排序中的堆并无出入。但是在使用方面略有不同,而不是直接的调用堆排序代替List中的Sort函数。

二叉堆,实际上是一颗完全二叉树,满足两点性质:

  1. 可分为大根堆小根堆
    1. 大根堆:任意结点的值都不小于其子结点的值
    2. 小根堆:任意结点的值都不大于其子结点的值
  2. 除了最底层的叶子结点,其它层的结点数都达到最大值。且仅对于叶子结点来说,从开始叶子结点到最后一个叶子结点之间没有空缺。

一般用顺序表存储,也即下标从0开始

数组长度为n,假设结点父结点孩子结点存在

  • 下标为i元素,左孩子为 2 * i + 1,右孩子为2 * i + 2
  • 父节点为 (i - 1) / 2
  • 非叶子结点下标满足 i < (n - 1) / 2

对于小根堆,中的两个核心操作

  • 上滤操作:将新结点添加进二叉堆尾部,如果该结点值小于父节点的值,则将该结点与父节点换位,直到不满足小于的情况
  • 下滤操作:如果某结点值小于两个孩子结点中的最小值,那么将该结点与比较小的孩子结点换位,直到不满足此情况

二叉堆结构在A*中的应用

将openList维护成小根堆结构,这样根结点的值一定是最小值。取最小值时间复杂度为O(1)

将结点加入小根堆尾部,并执行上滤操作,时间复杂度为 O(logn)

将根结点从小根堆中删除时,先交换根结点根最后一个元素,然后对新根结点执行下滤操作,时间复杂度为 O(logn)


堆代码中额外写了堆排序的代码,可以用来代替自带的Sort排序,在基数大的情况下,堆排序更好一点。本文不再使用排序

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

public class HeapSort : BaseManger<HeapSort>
{
    /// <summary>
    /// 二叉堆排序
    /// </summary>
    /// <param name="list"></param>
    public void MyHeapSort(List<AStarNode> list = null)
    {
        if (list.Count < 2 || list is null) return;
        Heapify(list, list.Count - 1);
        for (int i = list.Count - 1; i > 0; i--)
        {
            //依次将最后一个未排序元素与堆顶元素交换
            Swap(list, 0, i);
            //交换完,i位置为已排序位置,i - 1 确定新的未排序界限
            SifeDown(list, 0, i - 1);
        }
    }

    /// <summary>
    /// 堆化,建立小根堆
    /// </summary>
    /// <param name="list"></param>
    /// <param name="r">未排序序列最后元素下标</param>
    private void Heapify(List<AStarNode> list, int r)
    {
        //r - 1 >> 1 为最后一个非叶子结点下标
        for(int hole = (r - 1) >> 1; hole >= 0; hole--)
        {
            SifeDown(list, hole, r);
        }
    }

    /// <summary>
    /// 下滤
    /// </summary>
    /// <param name="list"></param>
    /// <param name="hole">需要下滤的结点的下标</param>
    /// <param name="r">未排序序列最后元素下标</param>
    public void SifeDown(List<AStarNode> list, int hole, int r)
    {        
        //列表可能为空,造成hole下标越界
        if (hole > list.Count - 1) return;
        AStarNode target = list[hole];
        int child = hole * 2 + 1;
        while(child <= r)
        {
            //确定值较小的孩子的下标
            if(child < r && list[child + 1].f < list[child].f) child++;
            if (list[child].f < target.f)
            {
                list[hole] = list[child];
                //hole继续下滤
                hole = child;
                //更新新的孩子下标
                child = hole * 2 + 1;
            }
            else
            {
                break;
            }
            list[hole] = target;
        }
    }

    /// <summary>
    /// 上滤
    /// </summary>
    /// <param name="list"></param>
    /// <param name="hole"></param>
    public void SifeUp(List<AStarNode> list, int hole)
    {
        AStarNode target = list[hole];
        int parent = (hole - 1) >> 1;
        while(hole > 0 && target.f < list[parent].f)
        {
            list[hole] = list[parent];
            hole = parent;
            parent = (hole - 1) >> 1;
        }
        list[hole] = target;
    }

    /// <summary>
    /// 删除根节点
    /// </summary>
    /// <param name="list"></param>
    public void DeleteHeapRoot(List<AStarNode> list)
    {
        if(list.Count == 0) return;
        int lastIndex = list.Count - 1;
        //交换根节点和最后一个结点
        Swap(list, 0, lastIndex);
        //移除最后一个结点
        list.RemoveAt(lastIndex);
        //将新的根节点下滤,维持小根堆性质
        SifeDown(list, 0, lastIndex - 1);
    }

    void Swap(List<AStarNode> list, int i, int j)
    {
        if (i == j || i < 0 || j < 0 || i > list.Count - 1 || j > list.Count - 1) return;
        AStarNode tmp = list[i];
        list[i] = list[j];
        list[j] = tmp;
    }
}

 在原先FindPath函数中作修改

            //排序
            //openList.Sort(SortRuler);
            
            //选择新的父节点
            startNode = openList[0];       
            
            //添加到closeList
            closeList.Add(startNode);
            //openList.RemoveAt(0);
            HeapSort.Getinstance().DeleteHeapRoot(openList);

 在原先AddNearlyNode2OpenList函数中修改

        //将结点加入openList
        openList.Add(an);
        HeapSort.Getinstance().SifeUp(openList, openList.Count - 1);

返回列表path优化

原先需要对添加完的path列表再执行反转操作

使用双向队列优化

在双向链表中,插入或删除一个节点的时间复杂度为 O(1),而在列表中通常是 O(n)。

    if (startNode == endNode)
    {
        LinkedList<AStarNode> path = new LinkedList<AStarNode>();
        path.AddFirst(startNode);

        while (endNode.father != null)
        {
            path.AddFirst(endNode.father);
            endNode = endNode.father;
        }

        return new List<AStarNode>(path);
    }

Contains的优化

C# 中的Contains采用的是遍历的方式,在数据结构中搜索。数据基数过大,就太影响性能了

采用Dictionary类型 来代替原来List类型的openList 和 closeList 

原先的Contains的时间复杂度取决于元素数量,O(n)

Dictionary的类实现为哈希表,一次查找时间复杂度为,O(1)

提升非常可观

对于障碍点统计和寻路消耗f的优化思考

此条优化,针对地图中障碍点不变化,并且进行不同路径寻路的情况下。

对于一次寻路操作,会计算规划路径过程中,周围存在的障碍点。并且路径中搜索过的点都会计算f的值

可以再添加一个记录每个格子相邻障碍点的数组,在地图初始化的时候统计全部结点。这样在判断某个结点周围是否有障碍时,可以在该数组中查看。但是在初始加载的时候,可能会消耗一部分性能


本文仅为学习过程中的一个总结,可能存在纰漏,按需查看文章来源地址https://www.toymoban.com/news/detail-770104.html

到了这里,关于Unity 中 A*寻路(AStar,A星)的优化,二叉堆,双向队列,哈希表的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Godot插值、贝塞尔曲线和Astar寻路

    线性插值是采用一次多项式上进行的插值计算,任意给定两个值A和B,那么在A和B之间的任意值可以定义为: P(t) = A * (1 - t) + B * t,0 = t = 1。 数学中用于线性拟合,游戏应用可以做出跟随效果(宠物跟随、npc跟随) 贝塞尔是插值的应用之一。贝塞尔曲线是为工业设计,是图形

    2024年04月14日
    浏览(43)
  • P3378 【模板】二叉堆

    最小堆插入 从新增的最后一个结点的父结点开始,用要插入元素向下过滤上层结点(相当于要插入的元素向上渗透) 最小堆删除 c++优先队列(priority_queue)用法详解

    2024年02月12日
    浏览(24)
  • 【二叉树】【单调双向队列】LeetCode239:滑动窗口最大值

    map|动态规划|单调栈|LeetCode975:奇偶跳 C++算法:滑动窗口总结 单调双向队列 二叉树 给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 返回 滑动窗口中的最大值 。

    2024年02月04日
    浏览(56)
  • 【Unity 3D】C#中数组、集合、栈、队列、哈希表、字典的讲解(附测试代码)

    觉得有帮助请点赞关注收藏~~~ 数组时有序的元素序列,存在有限个相同的变量的集合叫做数组名,组成数组二点各个变量称为数组的分量,又称为数组的元素,有时也称为下标变量,用于区分数组的各个元素的数组编号称为下标。 初始化数组 datatype [] arrayname datetype指定存储

    2024年02月04日
    浏览(40)
  • 【Unity】一篇文章搞定AStar(A*)算法

    AStar(A*)算法,是一种在静态网格中求解最短路径直接有效的搜索方法。在游戏开发中,A*算法常应用于部分RPG游戏和策略战棋类游戏。对于Unity开发者来说,掌握A*算法也是十分有必要的。不过在了解A*算法之前,有必要先回顾一下深度优先算法(DFS)、广度优先算法(BFS)

    2024年02月02日
    浏览(59)
  • 机器人寻路算法双向A*(Bidirectional A*)算法的实现C++、Python、Matlab语言

    最近好久没更新,在搞华为的软件挑战赛(软挑),好卷只能说。去年还能混进32强,今年就比较迷糊了,这东西对我来说主要还是看运气,毕竟没有实力哈哈哈。 但是,好歹自己吭哧吭哧搞了两周,也和大家分享一下自己的收获吧,希望能为后来有需要的同学提供一些帮助

    2024年04月13日
    浏览(44)
  • 性能优化3-分帧寻路+寻路任务统一管理

    当项目里的地图越来越大,一些性能上的问题开始逐渐出现,比如寻路。玩家在操控角色移动的时候,指引需要实时更新,同时一些npc也需要做移动,容易出现cpu占用率短时间过高,甚至掉帧的情况。 去年底的时候,由于希望在性能优化方面做一些研究,在论坛找到了江南百

    2023年04月19日
    浏览(31)
  • DSt:数据结构的最强学习路线之数据结构知识讲解与刷题平台、刷题集合、问题为导向的十大类刷题算法(数组和字符串、栈和队列、二叉树、堆实现、图、哈希表、排序和搜索、动态规划/回溯法/递归/贪心/分治)总

    Algorithm:【算法进阶之路】之算法面试刷题集合—数据结构知识和算法刷题及其平台、问题为导向的十大类刷题算法(数组和字符串、链表、栈和队列、二叉树、堆、图、哈希表、排序和搜索、回溯算法、枚举/递归/分治/动态规划/贪心算法)总结 目录 相关文章

    2024年02月08日
    浏览(56)
  • Unity(四十七):寻路网格-内置组件实现自动寻路避障

    配置寻路区域 Navigation Static 配置静态游戏对象 Navigation Static 导航网格生成 Navigation 在 Navigation 窗口进行烘焙(菜单: Window AI Navigation )中进行处理的 自动寻路并绘制路线 Nav Mesh Agent 、 NavMeshPath 属性 功能 Agent Size Radius 代理的半径,用于计算障碍物与其他代理之间的碰撞

    2024年01月15日
    浏览(53)
  • Unity 2022 版本 寻路 NavMesh

    官方教程地址 https://docs.unity3d.com/Packages/com.unity.ai.navigation@1.1/manual/index.html 首先装包 先给地图 和 阻挡 设置为静态 然后给地上行走的地方 添加组件 可以直接bake 然后会显示蓝色的可行走路径 player 添加插件 然后给角色添加脚本 搞定 场景内添加两个圆柱体 并设置为静态 起始

    2024年02月07日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包