算法基础之分治法

算法原理

对于一个规模为 n n n 的子问题,若该问题可以容易地解决则直接解决,否则将其分解为 k k k 个规模较小的子问题,这些子问题相互独立且与原问题形式相同。递归地解决这些子问题,然后将各子问题的解合并得到原问题的解,这种算法设计策略叫分治法。

分治法所能解决的问题一般具有以下特征:

  • 该问题的规模缩小到一定的程度就可以容易地解决。
  • 该问题可以分解为若干个规模较小的相似问题。
  • 利用该问题分解出的子问题的解可以合并为该问题的解。
  • 该问题所分解出的各子问题是相互独立的,即子问题之间不包含公共的子问题。

分治法的一般算法设计如下:

SolutionType Solve(ProblemType P) {if(Small(P)) return Result(P);else {Divector<int>de(P,P1,P2,…,Pk);return Merge(Solve(P1), Solve(P2),..., Solve(Pk));}
}

其中 S m a l l ( P ) Small(P) Small(P) 用来判断问题 P P P 的规模是否已经足够小,当问题 P P P 的规模足够小时,直接进行求解并返回结果 R e s u l t ( P ) Result(P) Result(P),否则,将问题 P P P 分解为若干子问题,并逐个进行求解,最后将所有子问题的解 R i Ri Ri 合并得到问题 P P P 的解并返回。

冒泡排序的交换次数

题目描述

给定一个数列 a a a,求对这个数列进行冒泡排序所需要的交换次数(此处冒泡排序指每次找到满足 a i > a i + 1 a_i>a_{i+1} ai>ai+1 i i i,交换 a i a_i ai a i + 1 a_{i+1} ai+1,直到这样的 i i i 不存在为止的算法)。

输入输出

输入:数列元素个数 n n n n n n 个数列元素。

输出:交换次数。

解题思路

求所需交换次数等价于求满足 i < j i<j ij a i > a j a_i>a_j aiaj ( i , j ) (i,j) (i,j) 数对的个数,也即求数列 a a a 的逆序数。

假设要统计数列 A A A 中逆序对的个数,为此,可以将数列 A A A 分成两半得到数列 B B B 和数列 C C C,于是,对于数列 A A A 中所有的逆序对 ( a i , a j ) (a_i,a_j) (ai,aj),必然属于以下情况之一:

  • ( a i , a j ) (a_i,a_j) (ai,aj)属于数列 B B B 的逆序对;
  • ( a i , a j ) (a_i,a_j) (ai,aj) 属于数列 C C C 的逆序对;
  • a i a_i ai 属于数列 B B B a j a_j aj 属于数列 C C C

对于情况①和②,可以通过递归求得。对于情况③,需要做的就是对于 C C C 中的每一个元素,统计在数列 B B B 中比它大的元素的个数,再把结果相加。最后再将③中情况所得结果相加,便得到数列 A A A 的逆序数。

情况③进行统计时,如果采用普通方法,即使用两个 f o r for for 循环逐个比较,时间复杂度较高。因此,借鉴归并排序的思想,在进行统计的同时边将两个子数列进行归并,由递归的特性可知,进行归并时,两子数列也是有序的,如此,情况③统计只需扫描一遍数列。

代码实现
typedef vector<int> vi;int main() {// 输入int n;cin >> n;vi A(n);for (int i = 0; i < n; ++i)cin >> A[i];// 求解并输出cout << solve(A);
}
/*** 求数列a的逆序数* @param a 数列* @return 	逆序数*/
int solve(vector<int> &a) {int n = a.size();// 数列元素个数小于等于1,逆序数为0if (n <= 1)return 0;// 二分vector<int> b(a.begin(), a.begin() + n / 2);vector<int> c(a.begin() + n / 2, a.end());// 情况1与情况2(子问题)int cnt = solve(b) + solve(c);// 情况3int ai = 0, bi = 0, ci = 0;while (ai < n) {if (bi < b.size() && (ci == c.size() || b[bi] <= c[ci])) {a[ai++] = b[bi++];} else {// b[bi..n/2-1] 都比 c[ci] 大cnt += n / 2 - bi;a[ai++] = c[ci++];}}// 返回合并所得解return cnt;
}

时间复杂度: O ( n l o g ( n ) ) O(nlog(n)) O(nlog(n))

空间复杂度: O ( n l o g ( n ) ) O(nlog(n)) O(nlog(n)),归并排序的空间复杂度实际可降至 O ( n ) O(n) O(n)

最近点对问题

题目描述

给定平面上的 n n n 个点,求距离最近的两个点的距离。

输入输出

输入:第一行输入点的个数 n n n,第二行输入 n n n 个点的横坐标 x i x_i xi,第三行输入 n n n 个点的纵坐标 y i y_i yi

输出:距离最近的两个点的距离。

解题思路

将所有点按x坐标(按y坐标也可)分成左右两半,那么最近点对的距离就是下面三者的最小值。

  • p p p q q q 同属于左半边时,点对 ( p , q ) (p,q) (p,q) 距离的最小值;
  • p p p q q q 同属于右半边时,点对 ( p , q ) (p,q) (p,q) 距离的最小值;
  • p p p q q q 属于不同区域时,点对 ( p , q ) (p,q) (p,q) 距离的最小值。

对于情况①和②可以递归求解,情况③稍微复杂。假设情况①和②所求得的最小距离为 d d d,所以在情况③中便不需要考虑距离显然大于等于 d d d 的点对。

先考虑 x x x 坐标。假设将点划分为左右两半的直线为 l l l,其 x x x 坐标为 x 0 x_0 x0,只需考虑那些到直线 l l l 距离小于 d d d 的点,也即 x x x 坐标满足 x 0 − d < x < x 0 + d x_0-d<x<x_0+d x0dxx0+d 的点。

再考虑 y y y 坐标。对于每个点,只考虑那些 y y y 坐标相差小于 d d d 的点,同时,为了避免重复计算,规定只考虑与 y y y 坐标不比自己大的点组成的点对。因此,对于每个点 p p p,只需要考虑与 y y y 坐标满足 y p − d < y < y p y_p-d<y<y_p ypdyyp 的点组成的点对。

为了将所有点按 x x x 坐标分成左右两半,需要先将所有点按 x x x 坐标排序。为了避免重复考虑,在处理情况③前,需要将待考虑的点按 y y y 坐标排序(由于分治法求解最近点对问题与归并排序在结构上的相似性,此处借鉴归并排序的思想)。

代码实现
#define INF 1.79E+308
typedef pair<double, double> pdd;int main() {// 输入int n;cin >> n;pdd *ps = new pdd[n];for (int i = 0; i < n; ++i)cin >> ps[i].first;for (int i = 0; i < n; ++i)cin >> ps[i].second;// 按x坐标排序sort(ps, ps + n, compX);// 求解并输出最近点对的距离cout << solve(ps, n);
}
/*** 求最近点对的距离* @param ps 所有点* @param n 点的个数* @return 最近点对的距离*/
double solve(pdd *ps, int n) {if (n <= 1)return INF;// 中线int m = n >> 1;double x = ps[m].first;// 处理情况1和2,得到目前距离最小值ddouble d = min(solve(ps, m), solve(ps + m, n - m));// 按y坐标从小到大进行排序(归并排序)inplace_merge(ps, ps + m, ps + n, compY);// 处理情况3vector<pdd> pl;for (int i = 0; i < n; ++i) {// 排除到直线l的距离大于等于d的点if (fabs(ps[i].first - x) >= d)continue;// 从后往前检查b中y坐标相差小于d的点for (int j = pl.size() - 1; j >= 0; --j) {double dy = ps[i].second - pl[j].second;if (dy >= d)break;double dx = ps[i].first - pl[j].first;d = min(d, sqrt(dx * dx + dy * dy));}// 记录到直线l的距离小于d的点pl.push_back(ps[i]);}return d;
}
// 按x坐标从小到大排序
bool compX(const pdd &p1, const pdd &p2) {return p1.first < p2.first;
}// 按y坐标从小到大排序(归并排序)
bool compY(const pdd &p1, const pdd &p2) {return p1.second < p2.second;
}

时间复杂度: O ( n l o g ( n ) ) O(nlog(n)) O(nlog(n))

空间复杂度: O ( n ) O(n) O(n)

经验总结

由于递归特别适合解决结构自相似问题,故分治法通常采用递归实现,但并非只能采用递归实现。当采用递归实现时,在每层递归中,需要完成“分”、“治”、“合”三个步骤。

“分”指的是,将原问题分解为若干规模较小、相互独立、与原问题形式相同的子问题。子问题的规模应大致相同,子问题的个数 k k k 视具体情况而定,一般来说, k k k 可以取 2 2 2。“分”这一步尤为重要,若不能将原问题分解为若干符合要求的子问题,说明此问题不适合采用分治法,若分解不恰当,会降低算法效率,甚至得出错误结果。数列通常按中点位置进行二分,树通常按重心分割(重心指使得删除该顶点后得到的最大子树的顶点数最少的顶点),平面通常按照坐标进行分割。

“治”指的是,若子问题规模 n n n 足够小则直接求解,否则,递归求解子问题。子问题规模 n n n 究竟小到何种程度才算足够小需要视具体问题而定。

“合”指的是,合并各个子问题的解,得到原问题的解。需要注意的是,当子问题所考虑到的解对于原问题来说不完整时,还需要考虑遗漏的解,如最近点对问题中的情况③。

当递归体中需要使用到排序时,可以借鉴归并排序的思想,从而做到边求解边排序,降低排序的时间复杂度。分治法的基本思想较为简单,就是不断地将问题划分成子问题,直到子问题能够快速求解,当然,需要满足实验原理中所列的条件。

需要注意的就是,划分子集时需要做到不遗漏、不重复。对于最值问题(最大值、最短距离等),有时为了代码编写方便,可以允许部分重复,但如果重复考虑的情况太多,可能会提高时间复杂度,这需要权衡,对于计数类问题(方案数等),一般情况下是不允许重复的,如果重复考虑某些情况,很可能就会得出错误的结果。

END

文章文档:公众号 字节幺零二四 回复关键字可获取本文文档。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/42339.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

单链表详解(2)

三、函数定义 查找节点 //查找结点 SLTNode* SLTNodeFind(SLTNode* phead, SLTDataType x) {assert(phead);SLTNode* pcur phead;while (pcur){if (pcur->data x){return pcur;}pcur pcur->next;}return NULL; } 查找节点我们是通过看数据域来查找的&#xff0c;查…

Arm64 基础指令集介绍

按照字母排序顺序&#xff1a; ● ADC&#xff1a;带进位加法。 ● ADCS&#xff1a;带进位加法&#xff0c;设置标志位。 ● ADD (extended register)&#xff1a;扩展寄存器加法。 ● ADD (immediate)&#xff1a;立即数加法。 ● ADD (shifted register)&#xff1a;移位寄存…

【MySQL05】【 undo 日志】

文章目录 一、前言二、undo 日志&#xff08;回滚日志&#xff09;1. 事务 id2. undo 日志格式2.1 INSERT 对应的 undo 日志2.2 DELETE 对应的 undo 日志2.3 UPDATE 对应的 undo 日志2.3.1 不更新主键2.3.2 更新主键 2.3 增删改操作对二级索引的影响2.4 roll_pointer 3. FIL_PA…

Windows 网络重置

netsh int ip reset 命令是用于重置 Windows 操作系统中的网络设置和配置的命令。 在网络故障排除、修复网络连接问题以及清除可能存在的网络配置冲突时非常有用。 命令详解&#xff1a; netsh: 用于配置各种网络设置 int: 用于管理网络接口 ip: 用于管理网络接口的 IP 配…

layui项目中的layui.define、layui.config以及layui.use的使用

第一步:创建一个layuiTest项目&#xff0c;结构如下 第二步&#xff1a;新建一个test.js,利用layui.define定义一个模块test,并向外暴露该模块&#xff0c;该模块里面有两个方法method1和method2. 第三步&#xff1a;新建一个test.html&#xff0c;在该页面引入layui.js&#x…

基于FPGA的LDPC编译码算法设计基础知识

基于FPGA的LDPC编译码算法设计基础知识 数字电路&#xff08;数电&#xff09;知识模拟电路&#xff08;模电&#xff09;知识1. 放大器1.1. 晶体管放大器1.2. 运算放大器1.3. 管子放大器&#xff08;真空管放大器&#xff09;微处理器/单片机知识其他相关知识 基于FPGA的算法设…

neo4j 图数据库:Cypher 查询语言、医学知识图谱

neo4j 图数据库&#xff1a;Cypher 查询语言、医学知识图谱 Cypher 查询语言创建数据查询数据查询并返回所有节点查询并返回所有带有特定标签的节点查询特定属性的节点及其所有关系和关系的另一端节点查询从名为“小明”的节点到名为“小红”的节点的路径 更新数据更新一个节点…

python爬虫和用腾讯云API接口进行翻译并存入excel,通过本机的Windows任务计划程序定时运行Python脚本!

项目场景&#xff1a; 提示&#xff1a;这里简述项目相关背景&#xff1a;定时爬取外网的某个页面&#xff0c;并将需要的部分翻译为中文存入excel 接下了的&#xff0c;没学过的最好看一下 基本爬虫的学习 【爬虫】requests 结合 BeautifulSoup抓取网页数据_requests beauti…

Vue CoreVideoPlayer 一款基于 vue.js 的轻量级、优秀的视频播放器组件

大家好,我是程序视点的小二哥!今天小二哥给大家推荐一款非常优秀的视频播放组件 效果欣赏 介绍 Vue-CoreVideoPlayer 一款基于vue.js的轻量级的视频播放器插件。 采用Adobd XD进行UI设计&#xff0c;支持移动端适配,不仅功能强大&#xff0c;颜值也是超一流&#xff01; Vue-…

第一次构建一个对话机器人流程解析(二)

1. 问答机器人的组成-基于知识图谱的搜索 在教育场景下&#xff0c;若学生有关于学习内容的提问&#xff0c;或业务层面的提问&#xff0c;则要求问答机器人的回答必须精准&#xff0c;来满足业务的要求因此需要通过知识图谱来快速检索&#xff0c;所提内容的相关信息&#xf…

数字系统与进制转换

数字系统 数字逻辑是计算机科学的基础&#xff0c;它研究的是如何通过逻辑门电路&#xff08;与门、或门、非门等&#xff09;实现各种逻辑功能。数字系统则是由数字逻辑电路组成的系统&#xff0c;可以实现各种复杂的运算和控制功能。在计算机科学中&#xff0c;数字逻辑和数…

C++ 假设今天是星期日,那么过a^b天之后是星期几?

题目 假设今天是星期日&#xff0c;那么过a^b天之后是星期几&#xff1f; 【输入】 两个正整数a&#xff0c;b&#xff0c;中间用单个空格隔开。0<a≤100,0<b≤10000。 【输出】 一个字符串&#xff0c;代表过a^b天之后是星期几。 其中&#xff0c;Monday是星期一&…

自定义波形图View,LayoutInflater动态加载控件保存为本地图片

效果图: 页面布局: <?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="…

C#多线程并行计算实例

在C#中实现多线程并行计算可以通过使用 Task 和 Parallel 类来实现。这里给出两个简单的示例&#xff0c;一个是使用 Task&#xff0c;另一个是使用 Parallel.ForEach。 使用 Task 进行多线程并行计算 using System; using System.Threading.Tasks;class Program {static voi…

Kubernetes基于helm部署jenkins

Kubernetes基于helm安装jenkins jenkins支持war包、docker镜像、系统安装包、helm安装等。在Kubernetes上使用Helm安装Jenkins可以简化安装和管理Jenkins的过程。同时借助Kubernetes&#xff0c;jenkins可以实现工作节点的动态调用伸缩&#xff0c;更好的提高资源利用率。通过…

MySQL Innodb存储引擎中,当页默认的大小是16K时,页中最多存放多少行的记录?

1、题目引入 Innodb存储引擎是面向行的(row-oriented)&#xff0c;也就是说数据的存放按行进行&#xff0c;每页存放的行记录是有硬性定义的&#xff0c;当页默认的大小是16K时&#xff0c;页中最多存放多少行的记录&#xff1f; A、1600 行B、8192 行C、16383 行D、7992 行 …

基于Python协同过滤的旅游景点推荐系统,采用Django框架,MySQL数据存储,Bootstrap前端,echarts可视化实现

随着旅游业的迅速发展&#xff0c;个性化旅游推荐系统成为提升用户体验和促进旅游市场增长的重要工具。本研究旨在设计并实现一种基于Python协同过滤的旅游景点推荐系统&#xff0c;结合Django框架、MySQL数据库存储、Bootstrap前端框架以及echarts数据可视化技术&#xff0c;为…

Flask发布一个及时止损(止盈)服务(二)

生成可视化的止盈止损结果&#xff08;图片&#xff09; 妈的&#xff0c;还是得用 akshare&#xff0c;还需要指定python版本3.9以上 conda remove -n fonxsys --all conda search pythonconda create -n fonxsys python3.9 conda activate fonxsys python.exe -m pip insta…

【粉丝福利 | 第8期】值得收藏!推荐10个好用的数据血缘工具

⛳️ 写在前面参与规则&#xff01;&#xff01;&#xff01; ✅参与方式&#xff1a;关注博主、点赞、收藏、评论&#xff0c;任意评论&#xff08;每人最多评论三次&#xff09; ⛳️本次送书1~4本【取决于阅读量&#xff0c;阅读量越多&#xff0c;送的越多】 目前市面上绝…

数据迁移探索

概念 数据迁移是指将数据从一个计算环境或存储系统移动到另一个计算环境或存储系统。 随着公司业务的发展&#xff0c;出于成本优化、系统升级、分库分表、整合数据等原因。数据迁移工作在日常工作中会陆续出现。 我们可以将数据迁移分成两个部分&#xff0c;第一部分是数据…