关于分区排序的算法思考及实现

博客围绕机器人工作区域分区排序算法展开。先指出合理排序对机器人完成任务的重要性,接着阐述算法设计思路,包括用图结构表征分区、构建分区图、计算相邻关系,最后说明可根据图结构按需求输出分区顺序,如用bfs方式。

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

一、引言

   对于很多机器人产品,都需要去某个区域进行工作(不限于扫地机的清扫任务、无人机的覆盖式搜索、割草机的区域割草等等),对于一次完整的任务而言,首先会将工作空间切割成若干个小区域,然后一个个小区域的完成,从而实现一次完整的任务
    那么问题来了,如何进行小区域的排序是评判这次完整任优略的准则之一,好的排序可以提高效率、提升用户体验等等。


二、算法设计思路

1、用数据结构表征分区

假如有以下分区图,你如何用一种结构去描述呢?
在这里插入图片描述
我刚开始想到的就是以某个重要的区域作为根节点构建一颗多叉树,假如以1号区域作为根节点,则这棵多叉树可以表征为:
在这里插入图片描述
但大家可能一眼就看出来了,多叉树有一个很明显的问题,它是有向的,即parent节点指向child节点,而且多叉树的特性是一个parent可以有多个child,但是一个child只能有一个parent,所以它不能完整的表征分区关系(比如2-3是相连的,但多叉树无法进行表征);
进一步的思考,图结构是不是最合适呢?分区图本身就是一种图,图结构就再合适不过了,如下图所示:
在这里插入图片描述

2、构建分区之间的图

定义一个简单的图结构体GNode

typedef struct GNode {
    int element;   // 当前元素
    set<GNode *> neighbors;  // 所有的邻居
} GNode;

建立连接关系

// 建立连接关系
void setNeighbor(GNode *node, GNode *neighbor) {
    node->neighbors.insert(neighbor);
    neighbor->neighbors.insert(node);
}

思路是不是很清奇,构建所有分区的GNode指针,然后setNeighbor,分区之间的图结构就结束了。
大家可能也发现了,其中的关键是怎么知道哪两个GNode是相邻的呢?请看下文

3、计算分区之间的相邻关系

我们以一副实际地图为例:一个大区域被四根分割线分成5个小区域

在这里插入图片描述

为了输出两两相邻关系,我刚开的思路是这样的,先给区域编号,然后计算每个分割线的小垂线所经过的区域,准备实现时,发现这种方法貌似不太优雅,于是咨询了朋友,有了个更好的方案,如下:

第一步:分割线解耦。如下图所示,原始分割线的条数为4条,根据其交点关系变成互不耦合的6条。
思路:先判断哪些点是共线的,再将共线的点按照x值(或者y值)的从大到小(从小到大)排序,最后将共线组按照顺序前后组合即可得到6条分割线
在这里插入图片描述

第二步:求小区域轮廓。求取每个小区域的内轮廓。
思路:cv::findContours(map, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE);可以实现
在这里插入图片描述

第三步:轮廓匹配。根据处理后分割线的中心点每个轮廓的距离判断轮廓关系。即根据距离阈值进行两两配对。如下图所示1、4两区域分割线的中心点到两个区域轮廓的距离一定是最近的。
思路:cv::pointPolygonTest(contour, center_point, true)暴力求解每个点到轮廓的距离,根据距离阈值进行两两配对。
在这里插入图片描述

输出相邻关系:
neighbor: 0-1 、0-2 、1-0
neighbor: 1-2 、1-4
neighbor: 2-0 、2-4 、2-1 、2-3
neighbor: 3-2
neighbor: 4-2 、4-1

4、根据图结构输出分区顺序

输出分区顺序是需要根据需求来的,有了分区之间的图结构后,可玩性就很高了。我举例以其中一种bfs的方式进行输出

// BFS
void tranversalBfs(GNode *root) {
    deque<GNode *> node_deque;
    set<GNode *> node_set;
    // 先找到根结点的所有子结点
    for (const auto &p : root->neighbors) {
        node_deque.emplace_back(p);
        node_set.insert(p);
    }
    // 输出的结果 vector结构
    vec_results_.emplace_back(root_.element);
    // 为了去重  set结构
    set_results_.insert(root_.element);

    while (!node_deque.empty()) {
        for (const auto &p : node_deque) {
            cout << p->element << " - ";
        }
        cout << endl;

        // 取出最前面一个元素
        GNode *node = node_deque.front();
        node_deque.pop_front();
        vec_results_.emplace_back(node->element);
        set_results_.insert(node->element);

        // 找这个元素的下一个层级所有元素
        vector<GNode *> temp_vec;
        for (const auto &p : node->neighbors) {
            if (set_results_.count(p->element) == 0) {
                temp_vec.emplace_back(p);
            }
        }
        for (const auto &p : temp_vec) {
            // 如果当前元素等于上次最后一个,将其子放到最前面 最后一个赋予新值
            if (update_last_element) {
                node_deque.emplace_front(p);
                node_set.insert(p);
            } else {
                node_deque.emplace_back(p);
                node_set.insert(p);
            }
        }
    }
}
<think>嗯,用户想让我介绍一下快速排序算法的C++实现。首先,我得确认自己对快速排序的理解是否正确。快速排序是一种分治算法,对吧?它的基本思想是选择一个基准元素,然后将数组分成两部分,一部分比基准小,另一部分比基准大,然后递归地对这两部分进行排序。 接下来,我需要回忆一下具体的实现步骤。通常,快速排序有三个主要步骤:选择基准、分区、递归排序分区过程是关键,这里可能需要详细说明。比如,如何选择基准元素?常见的有选第一个元素、最后一个元素,或者中间元素,甚至随机选择。不同的选择方式会影响算法性能,尤其是在处理已排序数组时。 然后,分区的具体实现。比如,使用双指针法,一个从左边开始找比基准大的元素,另一个从右边找比基准小的元素,然后交换它们,直到指针相遇。这时候将基准放到正确的位置,这样基准左边的元素都不大于它,右边的都不小于它。 接下来,考虑如何在C++中实现这个算法。可能需要一个分区函数和一个递归的快速排序函数。分区函数的参数通常包括数组、起始索引和结束索引。返回值是基准元素的正确位置,以便递归调用时分割左右子数组。 还要注意递归的终止条件,当子数组的长度为0或1时,就不需要继续递归了。否则可能会导致栈溢出或者无限循环。 然后,我需要考虑示例代码的结构。比如,先写分区函数,再写快速排序函数,最后在main函数中测试。测试用例可能需要包括普通数组、已排序数组、逆序数组,以及有重复元素的数组,以验证算法的正确性。 另外,关于基准的选择,这里示例可能选的是中间元素,这样在大多数情况下表现较好。不过,如果是处理已排序的数组,中间元素可能不如随机选择效果好,但为了示例简单,可能还是选中间元素更容易理解。 还要注意交换元素的方法,使用std::swap函数或者手动交换。在C++中,包含<algorithm>头文件可以使用swap,但如果是自己实现的,可能需要传递数组的引用或指针。 然后,编写代码的时候要注意边界条件,比如数组的索引是否正确,循环的条件是否有误。例如,在分区函数中,左右指针移动时是否越界,交换的条件是否正确。 测试部分,可能需要输出排序前后的数组,以直观显示结果。比如,原始数组是{5, 2, 9, 1, 5, 6},排序后应该是{1, 2, 5, 5, 6, 9}。 可能还需要提到快速排序的时间复杂度,平均是O(n log n),最坏情况是O(n²),但通过随机选择基准可以降低最坏情况出现的概率。空间复杂度主要是递归调用栈的深度,平均是O(log n)。 用户可能还关心稳定性,但快速排序是不稳定的排序算法。如果有稳定性要求,可能需要选择其他算法。 最后,总结快速排序的优缺点,适用场景,比如数据量大且数据分布随机时效率高,而对于小数组可能不如插入排序高效,所以有些混合排序算法会结合两者。 现在,我需要把这些思考整理成一个结构化的回答,分步骤解释快速排序的原理、C++实现,并给出示例代码和测试结果,同时分析时间复杂度和注意事项。确保代码正确,并且注释清晰,方便用户理解。</think>快速排序(Quick Sort)是一种基于**分治思想**的高效排序算法,平均时间复杂度为 $O(n \log n)$。以下是其核心原理和 C++ 实现详解: --- ### **一、算法原理** 1. **分治策略** - **选基准**:从数组中任选一个元素作为基准(pivot)。 - **分区**:将数组分为两部分,左侧元素 ≤ 基准,右侧元素 ≥ 基准。 - **递归**:对左右子数组重复上述过程。 2. **关键操作:分区(Partition)** - 通过双指针技巧,将数组元素与基准比较并交换位置,最终确定基准的正确位置。 --- ### **二、C++ 代码实现** #### **1. 分区函数** ```cpp int partition(int arr[], int low, int high) { int pivot = arr[(low + high) / 2]; // 选中间元素为基准(可优化为随机选择) int i = low - 1; // 左指针起点 int j = high + 1; // 右指针起点 while (true) { do { i++; } while (arr[i] < pivot); // 找左侧≥pivot的元素 do { j--; } while (arr[j] > pivot); // 找右侧≤pivot的元素 if (i >= j) return j; // 指针相遇时返回分区点 std::swap(arr[i], arr[j]); // 交换不符合条件的元素 } } ``` #### **2. 递归排序** ```cpp void quickSort(int arr[], int low, int high) { if (low < high) { int pi = partition(arr, low, high); // 获取分区点 quickSort(arr, low, pi); // 递归左半部(包含pi) quickSort(arr, pi + 1, high); // 递归右半部 } } ``` #### **3. 使用示例** ```cpp #include <iostream> #include <algorithm> // 用于 std::swap int main() { int arr[] = {5, 2, 9, 1, 5, 6}; int n = sizeof(arr) / sizeof(arr[0]); quickSort(arr, 0, n - 1); std::cout << "排序结果: "; for (int i = 0; i < n; i++) { std::cout << arr[i] << " "; } return 0; } ``` --- ### **三、关键点解析** 1. **基准选择优化** - 示例中选择中间元素,但实际建议**随机选择**以避免最坏情况 $O(n^2)$(如数组已排序时)。 2. **循环条件** - 使用 `do-while` 确保至少执行一次指针移动,处理元素等于基准的情况。 3. **稳定性** - 快速排序是**不稳定排序**,相同元素可能改变相对顺序。 --- ### **四、时间复杂度** | 场景 | 时间复杂度 | |------------|------------| | 平均情况 | $O(n \log n)$ | | 最坏情况 | $O(n^2)$(可通过随机化避免) | | 空间复杂度 | $O(\log n)$(递归栈深度) | --- ### **五、与其他算法对比** - **优势**:内循环紧凑,实际效率常优于其他 $O(n \log n)$ 算法。 - **劣势**:最坏情况性能差,适合处理**大规模随机数据**,小数据可用插入排序优化。 如果需要处理大规模数据或追求高实时性,快速排序是理想选择。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值