数据结构与算法--分治算法-最大子序列和问题

分治算法

  • 用于设计算法的一种常用技巧–分治算法(divide and conquer)。分治算法由两部分组成:
    • 分(divide):递归然后借机较小的问题(基础情况除外)
    • 治(conquer):然后从子问题的解构建原问题的解
  • 分治算法一般在主题逻辑中都至少含有两个递归调用的例程,而正文中只有一个递归调用的例程不算是分治算法。一般坚持子问题是不想交的(即不重叠)

前几章中分治算法

  • 之前我的文章中,我们依据看到有几个分支算法,比如
    • 排序算法中的快速排序
    • 二叉树中的树的遍历

案例分析

最大子序列求和问题
  • 给定(包含有负数)的整数A1, A2, A3,…AN,求解该集合中子序列的和的最大值∑k=ij\sum_{k=i}^jk=ijAk
  • 此问题最简单的方式暴力双循环,在这种情况下算法逻辑简单,但是时间复杂度达到了O(N^2),如下代码实现:
/*** @author liaojiamin* @Date:Created in 16:38 2021/1/29*/
public class MaxSumRec {public static int[] getArrayData(int size) {int[] arrayData = new int[size];Random random = new Random();for (int i = 0; i < size; i++) {int temp = random.nextInt(50);if (temp > 25) {arrayData[i] = temp;} else {int value = temp - 2 * temp;arrayData[i] = value;}}return arrayData;}/*** fun1 双循环 时间复杂度O(N^2)* */public static int getMaxSumRec(int[] arrayData){if(arrayData == null || arrayData.length <= 0){return -1;}if(arrayData.length == 1){return arrayData[0] > 0 ? arrayData[0] : -1;}int max = 0;for (int i = 0; i < arrayData.length; i++) {int sum = 0;for(int j=i; j< arrayData.length; j++){sum +=arrayData[j];if(sum > max){max = sum;}}}return max;}public static void main(String[] args) {int[] arraydata = getArrayData(20);for (int i = 0; i < arraydata.length; i++) {System.out.print(arraydata[i] + ", ");}System.out.println();System.out.println(getMaxSumRec(arraydata));}
}
  • 但是显然这不是最优的解法,对应这个存在复杂度为O(NlogN)的解法,我们如下描述。
  • 我们采用分治算法(divide-and-conquer)策略。他的思想是将问题分成两个大致相等的子问题,然后递归的对它们求解,这是分的部分,治的部分将两个子问题的解补充到一起并做少量的附加工作,然后得到整个问题的解法。
  • 如上案例中,我们最大子序列可能分布在三个地方:
    • 整个序列在输入数组的前半部分:可以递归求解
    • 整个序列在输入数组的后半部分:可以递归求解
    • 整个序列在输入数组的中间部分:需要先求出前半部分的最大和(其中包含最后一个元素),在求后半部分最大和(其中包含首个元素),将两个和相加
  • 我给出如下算法实现:
/*** @author liaojiamin* @Date:Created in 16:38 2021/1/29*/
public class MaxSumRec {public static int[] getArrayData(int size) {int[] arrayData = new int[size];Random random = new Random();for (int i = 0; i < size; i++) {int temp = random.nextInt(50);if (temp > 25) {arrayData[i] = temp;} else {int value = temp - 2 * temp;arrayData[i] = value;}}return arrayData;}/*** 分治算法* */public static int getMaxSumRec_1(int[] arrayData){return getMaxSumRecDivide(arrayData, 0, arrayData.length -1);}public static int getMaxSumRecDivide(int[] arrayData, int left, int right){if(left == right){return arrayData[left] > 0 ? arrayData[left] : -1;}int center = (left + right)/2;int maxLeft = getMaxSumRecDivide(arrayData, left, center);int maxRight = getMaxSumRecDivide(arrayData, center + 1, right);int maxLeftBordeSum =0;int leftBordeSum = 0;for(int i = center; i>= left; i--){leftBordeSum+=arrayData[i];if(maxLeftBordeSum < leftBordeSum){maxLeftBordeSum = leftBordeSum;}}int maxRightBordeSum = 0;int rightBordeSum = 0;for(int i = center+1; i<=right;i++){rightBordeSum+=arrayData[i];if(maxRightBordeSum < rightBordeSum){maxRightBordeSum = rightBordeSum;}}return Math.max(Math.max(maxLeft, maxRight), maxLeftBordeSum+maxRightBordeSum);}public static void main(String[] args) {int[] arraydata = getArrayData(20);for (int i = 0; i < arraydata.length; i++) {System.out.print(arraydata[i] + ", ");}System.out.println();System.out.println(getMaxSumRec_1(arraydata));}
}
  • 如上算法的运行次数分析如下:
    • 令T(N)是求解大小为N的最大子序列的问题所花费的世界。
    • 如果N=1,则只需要执行最基础情况环肥常数时间量,1个单位时间,得到T(1) = 1;
    • 如果N>1,则必须运行两个递归调用,以及之后的循环,和最后的最大值判断
    • 因为分治算法保证每个子问题不重叠,那么我们在循环中必然是每个元素范文一次那么我们for循环总共解除到A_0~A_n-1 每一个元素,因此花费O(N)时间,毫无疑问
    • 接着看递归部分:我们假设N是偶数,那么两个递归的基数是一样的,我们每次递归得到原来基数数据的一半,那么每一个子递归总的花费时间是T(N/2),(此处可以用数学归纳法证明,N被无限二分达到基数为1 的次数,此处略)
    • 因此递归共花费2T(N/2)个时间单位,
    • 如上总数花费时间:2T(N/2) + O(N)
T(1) = 1
T(N) = 2T(N/2) +O(N)
  • 得到如上的数学公式,为简化计算,我们用N代替O(N)项,由于T(N)最终还是要用大O表示,因此这么做不影响答案
  • 此处我们只进行递推,不进行数学证明,依据以上公式,T(N) = 2T(N/2)+N,且T(1) = 1
  • 那么我们得到 T(2) = 2T(1) + 2 = 4 = 22, T(4) = 2T(2) + 4 = 12 = 43 … T(16) = 2T(8) + 16 = 80 = 16*5
  • 若N=2^k,则 T(N) =N*(k+1) = N*LogN +N = O(NlogN)
最大子序列和投机方法
  • 一个循环解决此问题,如下实现:
public static int getMaxSumRec_2(int[] arrayData){int maxSum = 0, thisSum = 0;for (int i = 0; i < arrayData.length; i++) {thisSum+=arrayData[i];if(thisSum > maxSum){maxSum = thisSum;}else if (thisSum < 0){//如果之前项累计不大于0, 则情况之前项和,从小计数thisSum = 0;}}return maxSum;}

再谈快排

  • 快速排序是经典的分支算法应用,我们在之前的排序归纳文章中已经给出过具体的算法分析以及实现,基本算法都是如下几个步骤:
    • 如果S集合中元素个数是0或者1,则返回
    • 取S中任何一个元素V,称为枢纽元(pivot)
    • 将S-{V}(S中其他元素)划分为两个不想交的集合:S1 = { X \in S-{V} |X<=V | } 和 S2 = { X \in S-{V} |X>=V | }
    • 返回{quickSort(S1)} 后跟v,继续返回 quickSort(S2)
  • 以上算法中针对枢纽元的选择上并没有详细处理,因此这就成了一种设计决策,一部分好的实现方法是将这种情形尽可能完美的解决。直观的看,我们洗碗能将集合中的一半关键字分配到S1 中,另外一半分配到S2,很想二叉树。
枢纽元选择
  • 虽然不管枢纽元选择的那个元素,最终都能完成排序,但是有些选择明显更优秀。
  • 错误的选择方式:
    • 我们之前的算法中直接选择的第一个元素作为枢纽元,因为当时的算法说明中数据来源是随机生成数组成的数列,那么这种情况是可以接受的。当时如果输入的数列是已有序的数列,或者反序列,那么这样的枢纽元选取会产生一个最差情况的分割,因为所有的元素不是被分配到S1就是S2,并且这种情况会发生在递归排序的所有调用中。时间复杂度O(N^2)
    • 另外一种方法是选取前两个互异的关键字中的较大者作为枢纽元,不过这种值选取第一个元素作为枢纽元具有相同的问题,
  • 一种安全的做法:
    • 随机选择枢纽元,一般来说这种策略非常安全,除非随机数发生器有问题,因为随机的枢纽元不可能总在接连不断的产生劣质的分割,另一方面,随机数的生成开销比较大,根本减少不了算法其余部分的平均运行时间。
    • 三数中值分割法(Median-of-Three Partitioning) :一组N个数的中值是滴N/2 个最大的数。枢纽元最好的选择数是数组的中值。但是,这很难算出并且明显减慢快排的速度,这样的中值的估计量我们可以通过随机选取三个数并用他们的中值作为枢纽元而得到。而实际上,随机性并没有这么大的帮助,因此一般的做法是使用左端,右端和中间位置的三个元素的中值作为枢纽元。
分割策略
  • 有几种分割策略用于实践,我们给出的分割策略已经被证明能够给出好的结果。我们将枢纽元与最后的元素交换,使得枢纽元离开要被分割的数据段。i从第一个数据开始,j从倒数第二个元素开始。我们用如下动图
    在这里插入图片描述
  • 上图用的交换法,当i在j左边的时候,我们将i右移,移过哪些小于枢纽元的元素,并且建j左移,移过哪些大于枢纽元的元素,当i和j停止时候,i指向一个大元素,j指向一个小元素。如果i在j左边,那么将交换这两个元素。最后效果是将大元素推向右边,小元素推向左边。最后如果i 到了j 的右边,并且停在第一个最大的元素时候,我们停止交换i, j ,并且将pivot与i 交换。
  • 在最后一个步骤当枢纽元与i 所指向的元素交换的时候,我们知道,在位置position < i 的每一个元素都必须小于枢纽元,类似position > i 的元素都大于枢纽元。
  • 必须考虑的一个重要情况,如何处理等于枢纽元情况,i ,j 应该做相同的动作,否则分割将会偏向一方。
  • 极端情况,所有数据相等,那么我们每次都需要交换,虽然没有意义,但是i ,j, 将会在中间交错,时间复杂度O(NlogN)
  • 实际情况,几乎不会存在都相同情况,所以我们让i, j,都停止,并交换。
小数组
  • 对于很小的数组(N <= 20),快速排序不如插入排序快
快速排序实现
/*** @author liaojiamin* @Date:Created in 12:06 2021/2/1*/
public class DivideAndConquerGreat {public static void main(String[] args) {int[] beginArrayData = getArrayData(30);System.out.println("------------------");int[] arrayData = quickSort(beginArrayData);for (int i = 0; i < arrayData.length; i++) {System.out.println(arrayData[i]);}}public static int[] quickSort(int[] arrayData) {if (arrayData == null || arrayData.length <= 1) {return arrayData;}return quickSort(arrayData, 0, arrayData.length - 1);}public static int[] quickSort(int[] arrayData, int left, int right) {if(Math.abs(left - right) <= 20){insertionSort(arrayData, left, right);}else {if (left < right) {int position = swap(arrayData, left, right);quickSort(arrayData, left, position - 1);quickSort(arrayData, position + 1, right);}}return arrayData;}/*** 快排主体实现*/public static int swap(int[] arrayData, int left, int right) {int position = median3(arrayData, left, right);int i = left ;int j = right - 1;while (i < j) {while (i < j && arrayData[i] <= position) {i++;}while (i < j && arrayData[j] >= position) {j--;}if (i < j) {swapElement(arrayData, i, j);}}//position初始位置是right-1swapElement(arrayData, i, right - 1);return i;}/*** 数据交换*/public static void swapElement(int[] arrayData, int i, int j) {int temp = arrayData[i];arrayData[i] = arrayData[j];arrayData[j] = temp;}/*** 三数中值获取*/public static int median3(int[] arrayData, int left, int right) {int center = (left + right) / 2;if (arrayData[center] < arrayData[left]) {swapElement(arrayData, center, left);}if (arrayData[right] < arrayData[left]) {swapElement(arrayData, right, left);}if (arrayData[right] < arrayData[center]) {swapElement(arrayData, right, center);}swapElement(arrayData, center, right - 1);return arrayData[right - 1];}/*** 插入排序*/public static int[] insertionSort(int[] arraydata, int left, int right) {if (arraydata == null || arraydata.length <= 1) {return arraydata;}for (int i = 0; i <= right; i++) {for (int j = i; j > left; j--) {if (arraydata[j - 1] > arraydata[j]) {int temp = arraydata[j - 1];arraydata[j - 1] = arraydata[j];arraydata[j] = temp;}}}return arraydata;}/*** 随机生成数列*/public static int[] getArrayData(int size) {int[] arrayData = new int[size];Random random = new Random();for (int i = 0; i < size; i++) {int temp = random.nextInt(10);if (temp > 0) {arrayData[i] = temp;} else {int value = temp - 2 * temp;arrayData[i] = value;}System.out.println(arrayData[i]);}return arrayData;}
}

上一篇:数据结构与算法–贪婪算法2

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

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

相关文章

请把我不会,换成我可以学

点击蓝字关注&#xff0c;回复“职场进阶”获取职场进阶精品资料一份有位读者跟我说起自己的烦恼&#xff1a;“我到公司已经接近四年了&#xff0c;领导经常让我做一些岗位职责以外的事情。这些东西我都不会&#xff0c;还非让我做。并且一直没有职位上的改变&#xff0c;我怎…

[C++STL]C++实现unordermap容器和unorderset容器

代码如下: #include <iostream> #include <vector> using namespace std;template<typename K,typename V,typename KeyOfValue> class HashTable;//声明template<typename V> struct HashNode {typedef HashNode<V> Node;V _val;Node * _next;…

还不会docker+k8s?2020年,就要面对现实了...

docker的前世今生2010年&#xff0c;几个年轻人&#xff0c;在美国旧金山成立了一家名叫“dotCloud”的公司。这家公司主要提供基于PaaS的云计算技术服务。具体来说&#xff0c;是和LXC有关的容器技术。后来&#xff0c;dotCloud公司将自己的容器技术进行了简化和标准化&#x…

数据结构与算法--重建二叉树

二叉树 树在实际编程中经常遇到地一种数据结构。上一篇中我们解释了二叉树及其原理&#xff0c;从中可以知道&#xff0c;树地操作会涉及到很多指针地操作&#xff0c;我们一般遇到地树相关地问题差不多都是二叉树。二叉树最重要地莫过于遍历&#xff0c;即按照某一顺序访问树…

3分钟掌握Quartz.net分布式定时任务的姿势

长话短说&#xff0c;今天聊一聊分布式定时任务&#xff0c;我的流水账笔记&#xff1a;ASP.NET CoreQuartz.Net实现web定时任务AspNetCore结合Redis实践消息队列细心朋友稍一分析&#xff0c;就知道还有问题&#xff1a;水平扩展后的WebApp的Quartz.net定时任务会多次触发&…

数据结构与算法--利用栈实现队列

利用栈实现队列 上一节中说明了栈的特点 后进先出&#xff0c;我们用数组的方式实现了栈的基本操作api&#xff0c;因此我们对栈的操作是不考虑排序的&#xff0c;每个api的操作基本都是O(1)的世界&#xff0c;因为不考虑顺序&#xff0c;所以找最大&#xff0c;最小值&#x…

ASP.NET Core 配置源:实时生效

在之前的文章 ASP.NET Core 自定义配置源 和 ASP.NET Core etcd 配置源 中主要是介绍如何实现自定义的配置源&#xff0c;但不论内置的和自定义的配置源&#xff0c;都会面临如何使配置修改后实时生效的问题&#xff08;修改配置后在不重启服务的情况下能马上生效&#xff09;。…

分布式事务理论模型

分布式事务 事务的概念&#xff0c;我们第一想到的应该是数据库的事务。所谓数据库事务就是只作为单个逻辑工作单元执行多个数据库操作的时候&#xff0c;数据库需要保证要么都成功&#xff0c;要么都失败&#xff0c;它必须满足ACID特性&#xff0c;即&#xff1a; 原子性&…

[MySQL基础]数据库的相关概念

DB: 数据库(database):存储数据的“仓库”&#xff0c;它保存了一系列有组织的数据。 DBMS: 数据库管理系统(Database Management System):数据库是通过DBMS创建和操作的容器。 SQL: 结构化查询语言(Structure Query Language):专门用来与数据库通信的语言。 SQL的优点: 1.几…

Linq下有一个非常实用的SelectMany方法,很多人却不会用

在平时开发中经常会看到有些朋友或者同事在写代码时会充斥着各种for&#xff0c;foreach&#xff0c;这种程式代码太多的话阅读性特别差&#xff0c;而且还显得特别累赘&#xff0c;其实在FCL中有很多帮助我们提高阅读感的方法&#xff0c;而现实中很多人不会用或者说不知道&am…

.NET Core前后端分离快速开发框架(Core.3.1+AntdVue)

引言时间真快&#xff0c;转眼今年又要过去了。回想今年&#xff0c;依次开源发布了Colder.Fx.Net.AdminLTE(254Star)、Colder.Fx.Core.AdminLTE(335Star)、DotNettySocket(82Star)、IdHelper(47Star)&#xff0c;这些框架及组件都是本着以实际出发&#xff0c;实事求是的态度&…

数据结构与算法--查找与排序另类用法-旋转数组中的最小数字

查找与排序 查找 查找与排序都在程序设计中常被用到的算法。查找相对而言简单&#xff0c;一般都是顺序查找&#xff0c;二分查找&#xff0c;哈希表查找&#xff0c;和二叉排序树查找。其中二分查找是我必须熟悉的一种。哈希表和二叉排序树主要点在于他的数据结构而不是算法…

[MySQL基础]MySQL常见命令介绍

show databases; use 库名; show tables; show tables from 库名 select database(); create table 名字( id int, name varchar(20)); desc 表名; select * from 表名; insert into 表名 (a,b,…,f) values(1,2,3,…,7); update 库名 set name‘lilei’ where id1; delete f…

如何选择好公司

点击蓝字关注&#xff0c;回复“职场进阶”获取职场进阶精品资料一份前几天写了一篇文章&#xff1a;怎么判断自己在不在一家好公司。附带了一个投票调查&#xff0c;结果如下图&#xff1a;调研结果有点点扎心&#xff0c;有点点出乎我的意料。61%的小伙伴&#xff0c;都认为自…

数据结构与算法--再谈递归与循环(斐波那契数列)

再谈递归与循环 在某些算法中&#xff0c;可能需要重复计算相同的问题&#xff0c;通常我们可以选择用递归或者循环两种方法。递归是一个函数内部的调用这个函数自身。循环则是通过设置计算的初始值以及终止条件&#xff0c;在一个范围内重复运算。比如&#xff0c;我们求累加…

同步异步多线程这三者关系,你能给面试官一个满意的回答吗?

前几天一位朋友去面试&#xff0c;面试官问了他同步&#xff0c;异步&#xff0c;多线程之间是什么关系&#xff0c;异步比同步高效在哪&#xff1f;多线程比单线程高效在哪&#xff1f;由于回答的不好&#xff0c;让我帮他捋一下&#xff0c;其实回答这个问题不难&#xff0c;…

分布式事务框架seata

seata 前两篇文中总结了一下分布式事务已经现阶段常用的解决方案&#xff0c;现在来讨论一下现有的分布式事务框架seata&#xff0c;点击此处是seata的官网seata致力于微服务框架下提供高性能和简单易用的分布式事务服务。它提供了AT&#xff0c;TCC&#xff0c;Saga &#xf…

[一起读源码]走进C#并发队列ConcurrentQueue的内部世界 — .NET Core篇

在上一篇《走进C#并发队列ConcurrentQueue的内部世界》中解析了Framework下的ConcurrentQueue实现原理&#xff0c;经过抛砖引玉&#xff0c;得到了一众大佬的指点&#xff0c;找到了.NET Core版本下的ConcurrentQueue源码&#xff0c;位于以下地址&#xff1a;https://github.…

Java语法基础50题训练(上)

题目1: 有两只老虎&#xff0c;一只体重为180kg&#xff0c;一只体重为200kg&#xff0c;请用程序实现判断两只老虎的体重是否相同。 代码如下: public class OperatorTest {public static void main (String[] args) {int w1 180;int w2 200;boolean ans w1 w2?true:f…