数据结构与算法设计分析——分治法

news2024/12/25 9:24:39

目录

  • 一、分治法的定义
  • 二、分治法的基本步骤
  • 三、分治法的应用
    • (一)查找算法
      • 二分(折半)查找
    • (二)排序算法
      • 1、交换排序——快速排序
      • 2、归并排序

一、分治法的定义

分而治之可称为分治法,即逐个击破,分而治之,含义是将一个复杂问题分解成多个子问题来解决,一直分下去直到每个子问题都可以简单地求解出来,最后合并所有的解,从而得到复杂问题的解。该方法在《数据结构》中的应用场景有查找算法(二分查找)、排序算法(快速排序归并排序)等等。

一个问题可以采用分治法的特征有以下:
①问题可分解为很多小规模的相同子问题;【前提】
②分解后的子问题后可以很容易地解决;
③各个子问题是相互独立的;【效率】
④分解的子问题的解最后可以合并。【关键】

二、分治法的基本步骤

分治法
分解
治理
合并
  • 分治法主要分为分解、治理两大步骤,分解,是将问题分解成多个规模上较小内容上相互独立性质上与原问题相同的子问题;治理,是对各个子问题直接求解,若仍不容易解决,则再进行进一步分解后再求解,由于性质相同,所以求解子问题的解决方法和原问题是相同的;治理中包含合并,是将得到的各个子问题的解进行合并,从而得到原问题的解。

三、分治法的应用

(一)查找算法

二分(折半)查找

  • 二分查找中运用到了分治法,它属于一种线性查找,只适用于有序的顺序表,首先,将有序序列分为两部分,每次取中间元素与查找元素进行比较,若为中间元素则停止查找,此时查找成功;若小于中间元素则沿序列左半部分继续缩小范围进行查找【左半部分分解和治理】,若大于中间元素则沿序列右半部分继续缩小范围进行查找【右半部分分解和治理】,一直缩小直到查找到该元素为止,此时查找成功,否则,查找失败。

其基本步骤如下:
(1)通过一个一维数组s[n]来存放具有n个元素的有序顺序表,设查找元素为x;
(2)初始化查找范围,令变量low=0,high=n-1,分别指向数组的头和尾;
(3)取中间元素,即变量mid=⌊(low+high)/2⌋(向下取整,取比它小的最大整数);
(4)将指定查找的元素x与中间元素进行比较,若相等,则查找成功,查找的元素即为mid指向的位置;若不相等,根据大小关系,选择中间元素的另一边元素继续进行比较:
①若查找关键字小于中间元素,左半部分分解和治理,low不变,high=mid-1;
②若查找关键字大于中间元素,右半部分分解和治理,high不变,low=mid+1。
(5)重复以上(3)、(4)步骤,直到查找成功或查找范围超出(low>high)为止结束算法。

通过二分查找算法在有序序列{-7,-2,0,1,3,4,5,9}中查找元素6。

假定该有序序列存放在一个一维数组s[8]中,长度为8,如下:

下标01234567
元素-7-2013459

首先,令low=0,high=7,即mid=⌊(low+high)/2⌋=⌊7/2⌋=⌊3.5⌋=3,将序列一分为二:
在这里插入图片描述
由于x=6大于mid=3的元素1,即6>1,此时向右半部分分解和治理,即x可能位于序列s[mid+1,high]范围内,即s[4,7]中。此时high=7不变,low=mid+1=4,mid=⌊(low+high)/2⌋=⌊11/2⌋=⌊5.5⌋=5,将序列一分为二:
在这里插入图片描述
由于x=6大于mid=5的元素4,即6>4,继续向右半部分分解和治理,即x可能位于序列s[mid+1,high]范围内,即s[6,7]中。此时high=7不变,low=mid+1=6,mid=⌊(low+high)/2⌋=⌊13/2⌋=⌊6.5⌋=6,将序列一分为二:
在这里插入图片描述
由于x=6大于mid=6的元素5,即6>5,继续向右半部分分解和治理:
在这里插入图片描述
此时仍未找到要查找的元素,所以查找失败,序列中无该元素。

  • 通过以上可以得出,当有序序列为n=1时,查找元素x需要的时间复杂度为T(n)=O(1);而当n>1时,子问题的规模为n/2,其递归关系式为T(n)=T(n/2x)+xO(1),即令n=2x,则x=log2n,二分查找的T(n)=O(1)+O(log2n),即二分查找的时间复杂度为O(log2n)。

也可以通过在折半判定树求出时间复杂度,由于比较次数最多不会超过树的高度h=⌈log2(n+1)⌉,即折半查找的时间复杂度为O(log2n)。

(二)排序算法

在之前的文章,数据结构学习笔记—— 排序算法总结【ヾ(≧▽≦*)o所有的排序算法考点看这一篇你就懂啦!!!】,我们分析过:

  • 快速排序、堆排序和归并排序是改进型的排序算法,其平均时间复杂度均为O(nlog2n),快速排序和归并排序都采用分治的思想,而堆排序是通过使用这种数据结构。
排序算法平均时间复杂度最好时间复杂度最坏时间复杂度
快速排序O(nlog2n)O(nlog2n)O(n2)
堆排序O(nlog2n)O(nlog2n)O(nlog2n)
归并排序O(nlog2n)O(nlog2n)O(nlog2n)

1、交换排序——快速排序

快速排序基于分治,通过多次划分操作来实现排序思想。每一趟排序中选取一个关键字作为枢轴,枢轴将待排序的序列分为两个子序列,比枢轴小的元素移到其前,比枢轴大的元素移到其后,这是一趟快速排序,然后分别对两个部分按照枢轴划分规则继续进行排序,直至每个区域只有一个元素为止,最后达到整个序列有序。快速排序的代码中有递归的应用,其递归的进行需要栈来辅助,代码如下:

/*快速排序*/
void QuickSort(int r[],int low,int high) {
	int temp,i=low,j=high;
	temp=r[i];	//将其设为枢轴,对序列进行划分
	while(i<j) {
		while(i<j&&r[j]>=temp)	//从右往左寻找,找到小于temp的元素
			j--;
		r[i]=r[j];	//放在枢轴temp的左边
		while(i<j&&r[i]<=temp)	//从左往右寻找,找到大于temp的元素
			i++;
		r[j]=r[i];	//放在枢轴temp的右边
	}
	r[i]=temp;	//一趟快速排序结束,枢轴temp被放到其最终位置
	if(low<i-1)
		QuickSort(r,low,i-1);	//递归,对枢轴temp的左边区域进行快速排序
	if(i+1<high)
		QuickSort(r,i+1,high);	//递归,对枢轴temp的右边区域进行快速排序
}

其基本步骤如下:
(1)分解,首先选择一个枢轴元素temp,枢轴元素的位置大小为low ≤ temp ≤ high,以该元素划分为两个子序列;

由于快速排序每趟只确定枢轴元素的最终位置,所以第n趟快速排序完成时,会有n个以上的元素处于其最终结果位置上,即它们两边的元素分别比它大或小。

(2)治理,对子序列进行治理,然后使两个子序列中的所有元素小于等于和大于等于枢轴元素,即[low,temp-1] ≤ temp和[temp+1,high] ≥ temp:
①求解子问题,对枢轴元素两边的子序列[low,temp-1](枢轴temp的左边区域)、[temp+1,high](枢轴temp的右边区域)分别递归调用快速排序函数QuickSort(r,low,i-1)、QuickSort(r,i+1,high)继续进行快速排序;
②合并子问题,当两个子序列都有序时,由于是基于枢轴元素排序的,所以整体序列也呈有序。

设一组初始记录关键字序列{5,8,6,3,2},以第一个记录关键字5为基准进行一趟从大到小快速排序。

以第一个元素5为枢轴,原位置空出,i和j指向序列的头、尾元素,开始进行第一趟快速排序:
在这里插入图片描述
整个过程保证i指针左边是比枢轴元素小的元素,j指针右边是比枢轴元素大的元素(j指针找小于,i指针找大于)。首先对于j,从右往左一直寻找,找到小于枢轴元素的元素,若找到则j停下,由于元素2大于枢轴元素5,此时j的值与i的值交换:
在这里插入图片描述
在这里插入图片描述
然后对于i,从左往右一直寻找,找到大于枢轴元素的元素,若找到则i停下,由于元素2小于则继续向右,到元素8停下,8>5:
在这里插入图片描述
在这里插入图片描述
j继续移动,由于元素8大于则继续向左,到元素3停下,3<5,此时j与i交换:
在这里插入图片描述
在这里插入图片描述
…………重复步骤:
在这里插入图片描述
在这里插入图片描述
j继续移动,此时i与j相遇,最终位置即是枢轴元素的位置:
在这里插入图片描述
故快速排序的结果为{2,3,5,6,8}。

可以从划分操作和原序列排列上进行分析其时间复杂度:

  • 当快速排序的初始序列为有序或逆序时(或划分取的枢轴元素为当前序列最大或最小元素),为最坏情况,当n=1时为T(n)=O(1),而n>1时,递归函数为T(n)=T(n-1)+O(n)=O[n(n+1)/2]=O(n2),即最坏时间复杂度会达到O(n2);而初始序列越接近无序或基本上无序时(或划分取的枢轴元素为当前序列中值元素),为最好情况,当n=1时为T(n)=O(1),而n>1时,递归函数为T(n)=2T(n/2)+O(n)=O(nlog2n),即最好时间复杂度为O(nlog2n);当平均情况下,其平均时间复杂度也为O(nlog2n)。
排序算法平均时间复杂度最好时间复杂度最坏时间复杂度
快速排序O(nlog2n)O(nlog2n)O(n2)
  • 快速排序中需借助来进行递归,其空间复杂度与递归层数(栈的深度)有关,最坏情况下二叉树为最大高度,为n层,即最大递归深度,所需要的栈的空间为n,即最坏空间复杂度为O(n);而最好情况下为二叉树的最小高度⌊
    log2n ⌋,即最小递归深度,此时需要的栈的空间为⌊
    log2n ⌋,即最好空间复杂度为O(log2n);平均情况下,所需要的栈的空间为log2n,即平均空间复杂度为O(log2n)。

2、归并排序

归并排序是将两个或两个以上的有序表组合成一个新的有序表,它也采用的是分治思想,例如将两个有序表合并成一个有序表。另外,对于N个元素进行k路归并排序,其排序的趟数m满足km=N,即m=⌈logkN⌉(⌈⌉表示向上取整,取比自己大的最小整数)。

归并排序也是一个递归的过程,分别对划分后的左右子序列进行处理,Merge()函数中借助到了一个辅助数组r1,首先将划分的子序列放在该数组相邻位置,每次从该数组的两段子序列中取出元素进行比较,较小者放回原本的数组r[]中,代码如下,:

/*归并*/
void Merge(int r[],int low,int mid,int high) {
	int *r1=(int *)malloc((high-low+1)*sizeof(int));	//辅助数组r1 
	for(int k=low; k<=high; k++)
		r1[k]=r[k];	//将r中的所有元素复制到r1中 
	for(i=low,j=mid+1,k=i; i<mid&&j<=high; k++) {//low指向为第一个有序表的第一个元素,j指向第二个有序表的第一个元素
		if(r1[i]<=r1[j])	//比较r1的左右两段中的元素 
			r[k]=r1[i++];	//将较小值复制到r1中 
		else
			r[k]=r[j++];
	}
	while(i<=mid)
		r[k++]=r1[i++];	//若第一个表没有归并完的部分复制到尾部 
	while(i<=high)
		r[k++]=r1[j++];	//若第二个表没有归并完的部分复制到尾部 
}

/*归并排序*/
void MergeSort(int r[],int low,int high) {
	if(low<high) {
		int mid=(low+high)/2;	//划分 
		MergeSort(r,low,mid);	//对左有序子表递归 
		MergeSort(r,mid+1,high);	//对右有序子表递归 
		Merge(r,low,mid,high);	//归并
	}
}

以二路归并排序为例,其基本步骤如下:
(1)分解,将序列分为两个大致相同的子序列;

(2)治理,对子序列进行治理:
①求解子问题,将两个子序列递归进行排序;
②合并子问题,将已经呈有序的两个子序列进行合并,从而整体序列也呈有序。

例如,对于序列{34,15,13,93,65,74,20,17},对其进行归并排序,基本过程如下:

将初始序列分为8个只含有1个元素的子序列:
在这里插入图片描述
第一趟归并,两两归并,形成若干个由两个元素组成的子序列:
在这里插入图片描述
第二趟归并,继续两两归并,形成两个由四个元素组成的子序列:
在这里插入图片描述
第三趟归并,继续两两归并,即可形成一个完整的有序序列:
在这里插入图片描述
最终序列为{13、15、17、20、34、65、74、93}。

  • 归并排序中,比较次数与初始序列无关,即分割子序列与初始序列是无关的,所以其时间复杂度没有最坏和最好情况。当有序序列为n=1时,需要的时间复杂度为T(n)=O(1);而当n>1时,分割的子序列所需规模为2T(n/2),Merge()函数的时间为O(n)进行比较和复制,所以递归关系式为T(n)=2xT(n/2x)+xO(n),即令n=2x,则x=log2n,归并排序的T(n)=nO(1)+log2n,即归并排序的时间复杂度为O(nlog2n)。
排序算法平均时间复杂度最好时间复杂度最坏时间复杂度
归并排序O(nlog2n)O(nlog2n)O(nlog2n)
  • 由于归并排序中也用到了栈,其递归工作栈的空间复杂度为O(log2n),由于另外还需用到辅助数组,其空间复杂度为O(n),所以该排序算法的空间复杂度为O(n)。

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

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

相关文章

Qt开发学习笔记02

将窗口设为提示框 Qt::ToolTipQt 数据库连接池 #ifndef SQLITE_H #define SQLITE_H#include <QSqlDatabase> #include <QSqlError> #include <QSqlQuery> #include <QQueue> #include <QMutex> #include <QDebug> #include "../con…

安装spark并配置高可用

0、说明 上一篇文章讲了如何安装hadoop&#xff0c;这里将spark的详细安装步骤记录在这里。 其中实现了spark的高可用配置&#xff0c;即将zookeeper配置到spark集群中。对于资源管理也配置了yarn模。并开启了spark-sql的配置&#xff0c;可以通过jdbc链接spark。 spark 集群…

Three.js真实相机畸变效果模拟

有没有想过如何在 3D Web 应用程序中模拟物理相机&#xff1f; 在这篇博文中&#xff0c;我将向你展示如何使用 Three.js和 OpenCV 来完成此操作。 我们将从模拟针孔相机模型开始&#xff0c;然后添加真实的镜头畸变。 具体来说&#xff0c;我们将仔细研究 OpenCV 的两个失真模…

【C++】Stack Queue -- 详解

一、stack的介绍和使用 1、stack的介绍 https://cplusplus.com/reference/stack/stack/?kwstack 1. stack 是一种容器适配器&#xff0c;专门用在具有后进先出操作的上下文环境中&#xff0c;其删除只能从容器的一端进行元素的插入与提取操作。 2. stack 是作为容器适配器被…

改变世界-生成式人工智能

麦肯锡在其《生成人工智能的经济潜力&#xff1a;下一个生产力前沿》中声称&#xff0c;“ChatGPT、GitHub Copilot、Stable Diffusion 等生成式人工智能应用程序以 AlphaGo 没有的方式吸引了世界各地人们的想象力&#xff0c;这要归功于它们广泛的实用性——几乎任何人都可以使…

[mysql工具]Windows批处理方式实现MySQL定期自动备份

Windows批处理方式实现MySQL定期自动备份 对MySQL数据库而言&#xff0c;大部分数据库工具都具有备份功能&#xff0c;但并不能做到定期自动备份&#xff0c;在Windows环境下&#xff0c;手工备份MySQL是很繁琐的&#xff0c;所以我们通过MySQL提供的备份命令mysqldump&#xf…

如何防止重复提交订单

产生的原因 一种是由于用户在短时间内多次点击下单按钮&#xff0c;或浏览器刷新按钮导致。另一种则是由于Nginx或类似于SpringCloud Gateway的网关层&#xff0c;进行超时重试造成的。由于网速等原因造成页面卡顿&#xff0c;用户重复刷新提交页面黑客或恶意用户使用 postman…

maven配置代理

1.找到文件 find / -name "settings.xml" 当 maven 无法正常访问网络时候&#xff0c;需要通过代理进行访问 找到Maven的setting.conf文件 2.找到proxies 在maven的 setting.conf文件中找到 默认找到的时候文件 这里是被注释的。 3.配置如下 3.1配置截图 <…

QML 带框最大化显示方法

1.QML窗口最大化很多会给出如下方法: visibility: "FullScreen" 此方法不好的方面是没有最大化&#xff0c;最小化&#xff0c;关闭按钮 2.通过showMaximized() 方法可以满足我们需求:在onCompleted 方法中执行 实现的效果如下:

前后端分离计算机毕设项目之基于SpringBoot的无人智慧超市管理系统的设计与实现《内含源码+文档+部署教程》

博主介绍&#xff1a;✌全网粉丝10W,前互联网大厂软件研发、集结硕博英豪成立工作室。专注于计算机相关专业毕业设计项目实战6年之久&#xff0c;选择我们就是选择放心、选择安心毕业✌ &#x1f345;由于篇幅限制&#xff0c;想要获取完整文章或者源码&#xff0c;或者代做&am…

浏览器自动化神器:Automa 轻松实现任务编排 | 开源日报 No.52

usememos/memos Stars: 13.8k License: MIT memos&#xff0c;一个轻量级的、自托管的备忘录中心。开源且永久免费。 开源且永久免费使用 Docker 可以在几秒钟内完成自我托管支持 Markdown 格式可定制和共享提供 RESTful API 用于自助服务 mamoe/mirai Stars: 12.6k Licen…

2023.10.7 Java 创建线程的七种方法

目录 继承 Tread 类&#xff0c;重写 run 方法 实现 Runnable 接口 使用匿名内部类&#xff0c;继承 Thread 类 使用匿名内部类&#xff0c;实现 Runable 接口 使用 Lambda 表达式 使用线程池创建线程 实现 Callable 接口 继承 Tread 类&#xff0c;重写 run 方法 自定…

uni-app项目成功编译到微信开发者工具出现警告:当前组件仅支持 uni_modules 目录结构 ,请升级 HBuilderX 到 3.1.0 版本以上!

问题描述 为什么uni-app项目编译成功后&#xff0c;运行到微信开发者工具&#xff0c;却出现警告&#xff1a;当前组件仅支持 uni_modules 目录结构 &#xff0c;请升级 HBuilderX 到 3.1.0 版本以上&#xff01; 初识uni-app的童鞋&#xff0c;经常会问&#xff1a;我使用HBui…

2019年[海淀区赛 第2题] 阶乘

题目描述 n的阶乘定义为n!n*(n -1)* (n - 2)* ...* 1。n的双阶乘定义为n!!n*(n -2)* (n -4)* ...* 2或n!!n(n - 2)*(n - 4)* ...* 1取决于n的奇偶性&#xff0c;但是阶乘的增长速度太快了&#xff0c;所以我们现在只想知道n!和n!!末尾的的个数 输入格式 一个正整数n &#xff…

酷开会员 | 亚运会来啦!酷开系统陪你一起看赛事!

第十九届亚洲运动会已经开始啦&#xff01;坐标杭州&#xff0c;本次亚运会有来自亚洲45个国家和地区的1.2万余名运动员参赛&#xff0c;是史上规模最大、覆盖面最广的一届亚运会。它是亚洲具有世界性影响的体育盛会&#xff0c;来自亚洲各国和地区的运动员在赛场上奋力拼搏&am…

南美阿根廷市场最全分析开发攻略,收藏一篇就够了

聊到阿根廷&#xff0c;大家可能对阿根廷的足球印象比较深&#xff0c;比如球星梅西&#xff0c;不管是不是球迷应该大部分都有听说过&#xff0c;阿根廷作为南美洲面积第二大的国家&#xff0c;市场潜力也是非常不错的&#xff0c;今天就主要来聊一下关于阿根廷市场的一些相关…

工作流程引擎有几个特点?可以提高办公效率吗?

如果想要实现高效率的自动化办公&#xff0c;还依靠传统的办公软件是没有办法实现的。在自动化发展程度越来越高的今天&#xff0c;职场办公也拥有了优质的办公软件&#xff0c;助力实现高效率办公。低代码技术平台是专业的企业级应用低代码平台&#xff0c;其中的工作流程引擎…

深入理解树状数组 | 京东物流技术团队

树状数组 树状数组&#xff08;BIT, Binary Indexed Tree&#xff09;是简洁优美的数据结构&#xff0c;它能在很少的代码量下支持单点修改和区间查询&#xff0c;我们先以a[] {1, 2, 3, 4, 5, 6}数组为例建立树状数组看一下树状数组的样子&#xff1a; 可以发现&#xff1a;不…

websocket协议 | http协议

文章目录 一、前言二、websocket协议2.1 怎么建立websocket连接 三、HTTP协议3.1 特点3.2 报文格式3.3 连接方式三次握手四次挥手 3.4 版本HTTP 1.0HTTP 1.1 3.1 http长轮询场景&#xff1a;扫码登陆 四、二者比较4.1 相同4.2 区别1.通讯方式不同2.通信效率3.数据格式 一、前言…

XD 文件怎么打开,一分钟快速搞定

Adobe XD 是一款强大的用户界面和用户体验设计工具&#xff0c;广泛用于创建交互式原型、网站和移动应用程序&#xff0c;其中包含设计的所有元素和交互信息。 如果你拿到.xd 文件&#xff0c;却没有安装 Adobe XD 软件&#xff0c;下载安装步骤也很繁琐&#xff0c;纠结如何打…