unity 用法 队列queue_C++ 优先队列priority_queue

本文介绍了C++中的优先队列priority_queue,包括其头文件、结构定义、常用函数以及如何实现排序和自定义比较函数。特别讨论了如何利用优先队列在O(Nlog(k))的时间复杂度内解决找出数组中最大k个数的问题,以及自定义结构和比较函数的编写。优先队列在需要部分排序的场景下能有效提高效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

学习优先队列之前先看个单词队列 queue, 这个单词的读法很多人都能读对吧,音标是 /kjuː/ ,再看一个双端队列 deque,它的音标是 /dek/,应该有人读错了吧,反正我是没读对,刚开始看见一次错一次,现在还好了,基本能记住怎么读了,可是这些队列怎么用呢?

队列就不用多说了,一个先进先出的经典数据结构,那么优先队列是个什么鬼,其实它就是在队列的基础上加上优先两个字,想想怎样才能优先呢?没错——排队!只有排好了队伍才会有落后和优先之分,否则一团乱糟糟的,怎么才能分出优先的,所以优先队列一定应用了排序。

可是排序要怎样实现呢?其实排序这个底层逻辑你是不用管的,你只要把想要的数据放到优先队列里,然后取出的必定是当前状态下最优的,当然,究竟什么是最优的条件是需要你来设定的,也就是说我们需要定义排序的规则。

头文件

优先队列 priority_queue 是队列 queue 的一个变种,头文件是#include ,使用优先队列必须要包含这个头文件。

结构定义

优先队列的结构定义是一个模板类,需要提供三个类型参数:

template<    class T,    class Container = std::vector,    class Compare = std::less<typename Container::value_type>> class priority_queue;

从定义可以看出,虽然要结构是三个参数,但是后两个参数带了默认值,所以针对于普通的数据类型,一般情况下指提供第1个参数就可以了,比如 priority_queue 实际上等价于 priority_queue, less>。

这三个参数的含义分别为:数据类型,容器类型和比较函数,实际上优先队列就是维护了一个装有 T 类型元素的容器 Container,并在入队和出队时对容器内元素使用 Compare 比较函数进行了排序。

这3个参数还要满足一定的要求,并且在使用过程中有些注意事项:

1. 如果类型 T 和 Container 容器中元素类型不一致,那么行为未定义,所以要避免这种情况。

2. Container 必须是序列容器,其实C++中序列容器很多的,比如std::array、std::vector、std::deque、std::list等

3. Container 还必须要支持随机访问,并且有 front()、push_back()、pop_back() 等函数

这样来看只有 std::vector、std::deque 满足容器条件了,而优先队列中使用的默认参数也是 std::vector。

队列排序

一直在说优先队列里使用了排序,而常用的容器是 std::verctor,那么究竟用的是什么排序,又是在什么时候进行的排序呢?实际上这里的排序并不是我们通常拿到数据后使用的冒泡排序、快速排序等,优先队列中的排序本质上是堆排序,但是它不是每次都进行完整的堆排序,而是通过 Container 维护了一个堆结构,每次入队和出队时都进行一次堆调整,所花时间为 log(n),所以用在数据量大的地方,速度比较快。

优先队列使用

当我们大概了解了优先队列的原理后,可以通过使用来进一步熟悉这个结构,下面来看几个例子。

实现排序

#include #include using namespace std;void common_sort(){    int source_data[10] = {3, 5, 8, 1, 10, 2, 9, 15, 13, 16};    // 默认大根堆,实现由大到小排序    priority_queue<int> q;    for (auto n : source_data)         q.push(n);    while (!q.empty())     {        cout << q.top() << endl;        q.pop();    }}

priority_queue 默认构建的是一个大根堆,所以每次从头取数据得到的是一个从大到小的队列排序

albert@home-pc:/mnt/c++/datastruct$ g++ priorityqueue.cpp -o commonsort -std=c++11albert@home-pc:/mnt/c++/datastruct$ ./commonsort16151310985321

如果是完整排序使用优先队列就有些麻烦了,还不如直接调用 std::sort 函数,但是如果只取部分数据的话,优先队列还是非常方便快速的,比如下面这个问题。

取出数组中最大的k个数

这是一个经典的算法题,最容易想到的办法就是遍历,先找到最大的,然后排出这个数再找到最大的,这样找k次就好了,所需时间大概表示为 O(kN)。

还有一个方法是排序,使用 std::sort 排序后,然后依次取出前 k 个数就行了,排序使用快速排序的话可以达到所需时间为 O(Nlog(N)),其实这样已经很优秀了,但是还可以通过优先队列来加速,下面来写一下代码:

#include #include using namespace std;void max_k_num(){    int source_data[10] = {3, 5, 8, 1, 10, 2, 9, 15, 13, 16};    int k = 5;    // 小根堆    priority_queue<int, vector<int>, greater<int>> q;    for (auto n : source_data)     {        if (q.size() == k)         {            if (n > q.top())             {                q.pop();                q.push(n);            }        }        else             q.push(n);    }    while (!q.empty())     {        cout << q.top() << endl;        q.pop();    }}

这里是定义了一个小根堆,堆顶是最小值,当有新元素大于堆顶元素时,并且队列中元素等于k个,需要移除堆顶元素,然后插入新的元素,这样就能保证优先队列中始终拥有最大的k个数,运行结果如下:

albert@home-pc:/mnt/c++/datastruct$ g++ priorityqueue.cpp -o max_k_num -std=c++11albert@home-pc:/mnt/c++/datastruct$ ./max_k_num910131516

因为这里控制堆的规模最大为k,所以这个算法的执行时间大概是O(Nlog(k)),绝大多数情况是优于快速排序的。

自定义结构

使用优先队列时常常要用到自定义结构,这时候就需要自己来写比较函数了,比如输出成绩最好的三个人的信息:

#include #include using namespace std;struct student {    string name;    int score;};struct cmp_custom {    bool operator()(student& x, student& y) {        return x.score > y.score;    }};void max_k_score(){    vector stu_list = {{"Andy", 89}, {"Bella", 79}, {"Cary", 92}, {"Dick", 60}, {"Ray", 70}};    int k = 3;    // 小根堆    priority_queuevector    for (auto stu : stu_list) {        if (q.size() == k) {            if (stu.score > q.top().score) {                q.pop();                q.push(stu);            }        }        else q.push(stu);    }    while (!q.empty()) {        cout << q.top().name << ":" << q.top().score << endl;        q.pop();    }}

输出结果如下,每个人的名字后面跟着分数,结果是分数最大的3个人的信息:

albert@home-pc:/mnt/c++/datastruct$ g++ priorityqueue.cpp -o max_k_score -std=c++11albert@home-pc:/mnt/c++/datastruct$ ./max_k_scoreBella:79Andy:89Cary:92

自定义比较函数的另一种写法

看到上个例子中自定义比较函数的写法比较怪,一般我们在排序时定义的比较函数使用lambda表达式就可以,而这里是不能直接这样写的,需要多转化一步,写成下面这种形式:

auto cmp = [](student& x, student& y) { return x.score > y.score; };priority_queue, decltype(cmp)> q(cmp);

虽然看起来还是有点怪,但总比下面这样要好看的多:

struct cmp_custom {    bool operator()(student& x, student& y) {        return x.score > y.score;    }};priority_queuevector

常用函数

优先队列的常用函数与队列类似,常用的有以下这些,如果想了解详细的用法,请戳在线文档

函数名含义
top访问队列的头部元素
empty判断优先队列内是否有元素
size返回优先队列内元素个数
push向优先队列中插入元素
emplace在优先队列中构造元素
pop从优先队列头部弹出元素
swap与其他容器交换元素

总结

1. 优先队列在一些需要部分排序的场景可以加快访问速度,降低时间复杂度。

2. 优先队列加速所付出的代价就是构建堆结构所需的内存,时间和空间总是一对矛盾共同体。

3. 以自定义结构作为元素的优先队列需要单独编写比较函数,可以使用lambda表达式,并用 decltype(cmp) 推导类型。

4. 需要注意的是这里的优先队列定义,第三个参数的需要的是比较函数的参数类型,而不是比较函数,区分与 std::sort 的不同。

- EOF -

来源:AlbertS https://blog.csdn.net/albertsh/article/details/108552268

欢迎关注『高性能服务器开发』公众号。如果有任何技术或者职业方面的问题需要我提供帮助,可通过这个公众号与我取得联系,此公众号不仅分享高性能服务器开发经验和故事,同时也免费为广大技术朋友提供技术答疑和职业解惑,您有任何问题都可以在微信公众号回复关键字“职业指导”。

750439f6e07624b97243a9a9c31534ed.png

### Unity 中的 A* 寻路算法实现 在 Unity 游戏开发中,A*(A-Star)寻路算法被广泛应用于角色移动和路径规划。以下是基于站内引用的内容以及专业知识整理的一个完整的 A* 算法实现教程。 #### 1. A* 算法的核心概念 A* 是一种启发式搜索算法,其核心在于计算三个主要的成本函数: - **G-Cost**: 起点到当前节点的实际成本。 - **H-Cost**: 当前节点到目标节点的估计成本(通常使用曼哈顿距离或欧几里得距离)。 - **F-Cost**: F = G + H,表示总成本[^1]。 #### 2. 场景准备 为了实现 A* 算法,在 Unity 中需要创建一个二维网格地图作为基础环境。可以通过脚本动态生成网格,或者手动绘制地形并将其转换为可遍历的地图数据结构[^2]。 ```csharp public class GridGenerator : MonoBehaviour { public int gridSizeX = 10; public int gridSizeY = 10; void Start() { GenerateGrid(); } void GenerateGrid() { for (int x = 0; x < gridSizeX; x++) { for (int y = 0; y < gridSizeY; y++) { GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube); cube.transform.position = new Vector3(x, 0, y); cube.transform.localScale = new Vector3(0.9f, 0.9f, 0.9f); cube.name = $"Node_{x}_{y}"; } } } } ``` 上述代码用于生成一个简单的正方形网格地图,便于后续测试路径查找逻辑[^5]。 #### 3. A* 算法的具体实现 以下是一个基本版本的 A* 算法实现: ```csharp using System.Collections.Generic; using UnityEngine; public class Pathfinder : MonoBehaviour { private Node[,] grid; public int gridSizeX = 10; public int gridSizeY = 10; void Start() { grid = CreateGrid(gridSizeX, gridSizeY); List<Node> path = FindPath(new Vector2Int(0, 0), new Vector2Int(8, 7)); Debug.Log("Path Length: " + path.Count); foreach (var node in path) { Debug.Log($"({node.gridX}, {node.gridY})"); } } private Node[,] CreateGrid(int sizeX, int sizeY) { Node[,] nodes = new Node[sizeX, sizeY]; for (int x = 0; x < sizeX; x++) { for (int y = 0; y < sizeY; y++) { bool walkable = Random.value > 0.3f; // 随机设置障碍物 nodes[x, y] = new Node(walkable, x, y); } } return nodes; } public List<Node> FindPath(Vector2Int start, Vector2Int goal) { PriorityQueue<Node> openSet = new PriorityQueue<Node>(); HashSet<Vector2Int> closedSet = new HashSet<Vector2Int>(); openSet.Enqueue(GetNode(start.x, start.y), 0); GetNode(goal.x, goal.y).gCost = float.MaxValue; while (openSet.Count > 0) { var current = openSet.Dequeue(); if (current.gridPosition == goal) { return RetracePath(GetNode(start.x, start.y), GetNode(goal.x, goal.y)); } closedSet.Add(current.gridPosition); foreach (Vector2Int neighbourPos in GetNeighbours(current)) { Node neighbour = GetNode(neighbourPos.x, neighbourPos.y); if (!neighbour.walkable || closedSet.Contains(neighbour.gridPosition)) continue; int tentativeGCost = current.gCost + GetDistance(current, neighbour); if (tentativeGCost < neighbour.gCost) { neighbour.cameFromNode = current; neighbour.gCost = tentativeGCost; neighbour.hCost = GetDistance(neighbour, GetNode(goal.x, goal.y)); if (!openSet.Contains(neighbour)) openSet.Enqueue(neighbour, neighbour.fCost); } } } return null; } private List<Vector2Int> GetNeighbours(Node currentNode) { List<Vector2Int> neighbours = new List<Vector2Int>(); for (int x = -1; x <= 1; x++) { for (int y = -1; y <= 1; y++) { if (x == 0 && y == 0) continue; int checkX = currentNode.gridX + x; int checkY = currentNode.gridY + y; if (checkX >= 0 && checkX < gridSizeX && checkY >= 0 && checkY < gridSizeY) { neighbours.Add(new Vector2Int(checkX, checkY)); } } } return neighbours; } private int GetDistance(Node a, Node b) { int dstX = Mathf.Abs(a.gridX - b.gridX); int dstY = Mathf.Abs(a.gridY - b.gridY); if (dstX > dstY) return 14 * dstY + 10 * (dstX - dstY); return 14 * dstX + 10 * (dstY - dstX); } private List<Node> RetracePath(Node startNode, Node endNode) { List<Node> path = new List<Node>(); Node currentNode = endNode; while (currentNode != startNode) { path.Add(currentNode); currentNode = currentNode.cameFromNode; } path.Reverse(); return path; } private Node GetNode(int x, int y) { if (x >= 0 && x < gridSizeX && y >= 0 && y < gridSizeY) return grid[x, y]; return null; } } [System.Serializable] public class Node { public bool walkable; public int gridX, gridY; public int gCost, hCost; public int fCost => gCost + hCost; public Node cameFromNode; public Vector2Int gridPosition => new Vector2Int(gridX, gridY); public Node(bool _walkable, int _gridX, int _gridY) { walkable = _walkable; gridX = _gridX; gridY = _gridY; gCost = int.MaxValue; hCost = 0; cameFromNode = null; } } // 自定义优先队列类 public class PriorityQueue<T> { private SortedList<int, Queue<T>> queues = new SortedList<int, Queue<T>>(); public T Dequeue() { var firstQueue = queues.Values[0]; T item = firstQueue.Dequeue(); if (firstQueue.Count == 0) queues.RemoveAt(0); return item; } public void Enqueue(T item, int priority) { if (!queues.ContainsKey(priority)) queues[priority] = new Queue<T>(); queues[priority].Enqueue(item); } public int Count => queues.Sum(q => q.Value.Count); public bool Contains(T item) => queues.Values.Any(queue => queue.Contains(item)); } ``` 此代码实现了 A* 算法的基础框架,并支持在一个随机生成的网格环境中寻找最优路径。 --- ### 性能优化建议 对于更复杂的场景,可以直接采用 Unity 提供的 Navigation Mesh(NavMesh),它可以自动生成适合三维空间的导航网格,从而减少开发者的工作量[^4]。 如果需要更高的灵活性,还可以考虑引入第三方插件如 **A\* Pathfinding Project**,它提供了丰富的功能扩展和支持多种类型的寻路需求。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值