boxmoe_header_banner_img

菜就多练喵

文章导读

[C#][Unity][Ib_Core]Ib_Collections.PriorityQueue——优先队列


avatar
Ib_Mccf 2025年11月16日 49

功能:每次插入和弹出都能保证头部元素是集合中的最小值。

示例1:排序,时间复再度O(logN)。(堆排序)

//需要排序的原始集合
private List<int> list = new List<int>(){ 1, 12, 34, 234, 1, 90,-25,24,-54555, 5,-56, 12, 5,6, 151, 2, -55, 0, -125, -5, -21341, 123, -2, -12313, 123, 352 };
void Start()  
{
    StringBuilder sb = new StringBuilder("原始集合:");
    foreach (int item in list)
        sb.AppendFormat("{0},",item.ToString());
    sb.Remove(sb.Length - 1, 1);
    Ib_Log.Info(sb.ToString());
    sb.Clear();
    sb.Append("升序排序:");
    
    PriorityQueue<int,int> priorityQueue = new PriorityQueue<int,int>();
    //将所有元素插入优先队列。
    foreach(var item in list) priorityQueue.Push(item,item);
    
    int cnt = priorityQueue.Count;
    for (int i = 0; i < cnt; i++)
    {
        //依次弹出头部元素。每次弹出都会重新维护最小堆,保证头元素是最小值。
        sb.Append(priorityQueue.Pop());
        sb.Append(",");
    }
    
    sb.Remove(sb.Length - 1, 1);
    

将传入优先队列的Value值*-1后,即可得到降序排列。

//用-item作为值传入。   
foreach(var item in list) priorityQueue.Push(item,-item);

示例2:优化A*算法

A*中每次需要选择一个权重最低的点,可以用优先队列进行优化。每次获取权重最小的点,直接排序需要O(N)的时间,通过优先队列弹出只需要O(logN)的时间。

IEnumerator AStar(Vector3Int _initialPos, Vector3Int _endPos)
{
    tMap.ClearAllTiles(); //清空演示的网格 与算法无关
    float D = 0.95f; //g代价的系数,系数<1,越小越趋近终点,>1趋近起点
    var openList = new PriorityQueue<AStarGrid, float>();
    var closeList = new HashSet<Vector3Int>();
    var map = new Dictionary<Vector3Int, AStarGrid>();
    AStarGrid initialGrid = new() //起始点
    {
        aPos = _initialPos,
        parent = null
    };
    openList.Push(initialGrid, 0); //起始点加入openlist
    var cGrid = openList.Top();
    while (openList.Count > 0 && cGrid.aPos != _endPos)
    {
        cGrid = openList.Pop(); //从openlist中取出f最小的AStarGrid
        map.Remove(cGrid.aPos);
        
        if (JPS_ShowSlow) //演示,与算法无关
        {
            yield return new WaitForFixedUpdate();
            tMap.SetTile(cGrid.aPos, tTb);
        }

        if (cGrid.aPos == _endPos) //找到终点
        {
            Debug.Log("A*Found" + closeList.Count);
            CalFollowTimeCost_Follow();
            DrawLineByAStarGrid(cGrid); //画路径
            yield break;
        }

        closeList.Add(cGrid.aPos); //加入closeList
        for (int i = 0; i < dir8.Length - (fourDir ? 4 : 0); i++) //fourDir = true 则判断四个方向
        {
            int x = cGrid.aPos.x + dir8[i].x;
            int y = cGrid.aPos.y + dir8[i].y;
            var nPos = new Vector3Int(x, y);
            var nGrid = new AStarGrid { aPos = nPos };
            //判断该点是否合法,未越界,不是障碍物,不在closeList中
            if (nPos.x <= 0 || nPos.x >= 2 * currentX || nPos.y <= 0 || nPos.y >= 2 * currentY ||
                wall.GetTile(nPos) != null || closeList.Contains(nPos))
                continue;
            nGrid.g = D * (fourDir ? 1 : Mathf.Sqrt(dir8[i].x * dir8[i].x + dir8[i].y * dir8[i].y)) + cGrid.g; //计算g
            nGrid.h = fourDir ? H_Manhattan(nGrid.aPos, _endPos) : H(nGrid.aPos, _endPos); //计算h
            //查找openlist中是否有该点
            if (!map.ContainsKey(nGrid.aPos)) //若没有有该点,则加入openlist
            {
                nGrid.parent = cGrid;
                FollowCost++; //与算法无关
                openList.Push(nGrid, nGrid.F);
                map.Add(nGrid.aPos, nGrid);
                if (JPS_ShowSlow)
                {
                    tMap.SetTile(nPos, jTb);
                }

                if (ShowF) CreateText(nGrid.aPos, nGrid.F.ToString("#0.0"), Color.blue);
            }
            else //若有,且当前点的g小于原来点g,则重新计算g+h(当前点覆盖原来点)
            {
                if (nGrid.g < map[nGrid.aPos].g)
                {
                    nGrid = cGrid;
                }

                if (ShowF) CreateText(nGrid.aPos, nGrid.F.ToString("#0.0"), Color.red);
            }
        }
    }
    yield break;
}

源码:

namespace Ib_Core
{
    using System.Collections.Generic;
    using System;

    namespace Ib_Collections
    {
        /// <summary>
        /// 按Value升序排序的优先队列。Value需要继承IComparable接口
        /// </summary>
        /// <typeparam name="TKey">键</typeparam>
        /// <typeparam name="TValue">值</typeparam>
        public class PriorityQueue<TKey, TValue> where TValue : IComparable<TValue>
        {
            private List<(TKey, TValue)> list;
            private int cnt;
            public int Count => cnt;
            public PriorityQueue(int capacity = 4)
            {
                list = new List<(TKey, TValue)>(capacity);
                cnt = 0;
            }

            public TKey Top() => Count > 0 ? list[0].Item1 : default;
            
            /// <summary>
            /// 通过元组插入元素
            /// </summary>
            /// <param name="pair"></param>
            public void Push((TKey, TValue) pair)
            {
                if (cnt < 0) cnt = 0;
                cnt++;
                if (cnt <= list.Count)
                {
                    list[cnt - 1] = pair;
                }
                else list.Add(pair);

                SwapUp(Count - 1);
            }
            /// <summary>
            /// 通过键值对插入元素
            /// </summary>
            /// <param name="a"></param>
            /// <param name="b"></param>
            public void Push(TKey a, TValue b)
            {
                var pair = (a, b);
                Push(pair);
            }
            /// <summary>
            /// 弹出顶部元素。并重新维护最小堆
            /// </summary>
            /// <returns></returns>
            public TKey Pop()
            {
                var re = list[0].Item1;
                list[0] = list[Count - 1];
                cnt--;
                SwapDown(0);
                return re;
            }
            /// <summary>
            /// 清除元素内容
            /// </summary>
            public void Clear()
            {
                list.Clear();
                cnt = 0;
            }

            #region 构造堆
            
            /// <summary>
            /// 向上建堆
            /// </summary>
            /// <param name="index"></param>
            private void SwapUp(int index)
            {
                if (index <= 0) return;
                int top;
                if (index % 2 == 0) top = (index - 2) / 2;
                else top = (index - 1) / 2;
                if (list[top].Item2.CompareTo(list[index].Item2) > 0)
                {
                    (list[top], list[index]) = (list[index], list[top]);
                    SwapUp(top);
                }
            }
            /// <summary>
            /// 向下建堆
            /// </summary>
            /// <param name="index"></param>
            private void SwapDown(int index)
            {
                int l = (index * 2) + 1;
                int r = (index * 2) + 2;
                if (l >= Count) return;
                int nextIndex = -1;
                if (list[index].Item2.CompareTo(list[l].Item2) > 0)
                {
                    nextIndex = l;
                }

                if (r < Count && list[index].Item2.CompareTo(list[r].Item2) > 0)
                {
                    if (nextIndex == -1 || list[l].Item2.CompareTo(list[r].Item2) > 0)
                    {
                        nextIndex = r;
                    }
                }

                if (nextIndex == -1) return;
                (list[nextIndex], list[index]) = (list[index], list[nextIndex]);
                SwapDown(nextIndex);
            }

            #endregion
        }
    }
}


评论(0)

查看评论列表

暂无评论


发表评论

表情 颜文字

插入代码