算法课实验报告解析(4班供参考)

news2025/1/9 15:31:53

有两个题

  • 1.第一题
  • 2.第二题

1.第一题

😋题目描述:

给定一个整数数组A=(ao,a1,…,an-1),若岗且ai>aj,则<ai.aj>就为一个逆序对。例如数组(3,1,4,5,2,)的逆序对有<3,1>、< 3,2>、<4,2>、<5,2>。编写一个实验程序采用分治法求A中逆序对的个数,即逆序数。

思路:

  1. 暴力法

从第一个数开始枚举,找到后面所有小于当前的数。
比如:数组是a[5]=3 1 2 5 4
第一步:找到比3大的所有数(但必须在3后面找)
(3,5) ,(3,4)
第二步:找到比1大的所有数(必须在1后面找)
没有
第三步:找到比2大的所有数
没有
第四步:找到所有比5大的数
(5,4)
综上:该数组的所有逆序对就是:(3,5)(3,4)(5,4)

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;

const int N = 1010;

int w[N];   //  存储数组

int n;
int main()
{
	cout << "输入数组长度:";
	cin >> n;
	cout << "输入数组元素:";
	for (int i = 1; i <= n; i++)  //记录数字
		cin >> w[i];

	int res = 0;
	for (int i = 1; i < n; i++)  //枚举逆序对的第一个数
		for (int j = i + 1; j <= n;j++)   //枚举逆序对的第二个数
		{
			if (w[i] > w[j])
			{
				cout << "(" << w[i] << "," << w[j] << ") ";
				res++;
			}
		}

	cout << endl<< res;
	return 0;
}

2.分治法
分治法的思想:分而治之,将一个大问题分解成若干个小问题。通过解决小问题,从而解决大问题。关于大问题和小问题的关系,有下面几点

  1. 大问题的性质和小问题的性质应该是一样的
  2. 小问题的解决方法和小问题的解决方法也是相同的

对于这道题,分析是这样的:
这道题目使用暴力做法的根本原因就在于数组是“无序”的。

如果将数组一分为二,分成两个小数组,两个小数组都是有序的(从小到大)。那么问题就很简单了。具体看下面的模拟:
假设一个数组:8 9 11 14 9 10 11 13

在这里插入图片描述
由此可以发现,只要每次将两个子数组排好序,那么就可以找到两个子数组的所有逆序对。两个子数组合并和成为新的数组(有序)。这样就保证没有遗漏:

在这里插入图片描述 数组的逆序对=(左半部分的逆序对+右半部分的逆序对)+左右合并数组的逆序对(左半部分一个数,右半部分一个数组成的逆序对)
先从最后一次划分开始,[a4]和[a5]比较,如果a4>a5,那么逆序对+1,并且将a4,a5从小到大排序。那么子数组[a4 a5]排序完成且找到该区间的所有逆序对。此时可以发现[a3],[a4 a5]都是有序的,那么继续找逆序对且将两个区间进行排序。得到[a3 a4 a5]数组是有序的且该数组的所有逆序对都找到。以此类推。。。讲的不是很清楚,看代码更容易懂(个人感觉)

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;

const int N = 1010;
int w[N];   //存储数组
int n;

int merge(int left,int mid,int right)  //合并
{
	int temp[1010];//临时数组
	memset(temp, 0, sizeof(temp));  //数组初始化0
	for (int i = left; i <= right; i++)
		temp[i] = w[i];
	//下面的步骤是归并排序的核心
	//将两个有序的区间合并为一个,边合并的同时边累加逆序数对
	int sum = 0; 
	int i = left, j = mid + 1;
	int k = left;
	while(i<=mid&&j<=right)  //哪个小哪个放进数组,然后指针后移。
	{
		if (temp[i] > temp[j])//枚举后半部分,如果左边当前的值大于右边的值,那么从左边当前的值到后面的所有值多可以组成逆序对
		{
			sum += mid - i + 1;
			w[k++] = temp[j];
			j++;
		}
		else
		{
			w[k++] = temp[i];
			i++;
		}
	}

	while (i <= mid)  //左半部分还有值没有放进数组
		w[k++] = temp[i++];
	while (j <= right)  //右半部分还有值没有放进数组
		w[k++] = temp[j++];

	return sum;
}

int divide(int left,int right)  //divide的中文意思是分开.为什么是int,我们想要记录多少逆序数对,那么每次返回的值都是逆序数对个数
{
	//将区间分为两个部分,不停二分,最后合并
	//凡是递归,先考虑边界,考虑边界的意义是什么
	if (left >= right)  //当区间只有一个值,就不能分了,不和规定。因为我们是需要不同的二分区间,一个单位长度无法二分。
		return 0;
	int mid = (left + right) >> 1;   //等价(left+right)/2
	int a=divide(left, mid);    //左边的逆序数对
	int b=divide(mid + 1, right);  //右边的逆序数对
	int c = merge(left, mid, right);   //整个区间的逆序数对(感觉有点像区间dp的思维)
	return a + b + c;
}

int main()
{
	cout << "输入数组长度:";
	cin >> n;
	cout << "输入数组:";
	for (int i = 1; i <= n; i++)
		cin >> w[i];

	//先分再合。从最底层合的时候就开始排序。这样就保证每一次合并数组都是有序的。
	int res = 0;
	res = divide(1, n);
	for (int i = 1; i <= n; i++)
		cout << w[i] << " ";
	cout << endl;
	cout << "数组的逆序对数量是:" << res << endl;
	return 0;
}

2.第二题

😋题目描述:
已知由n(n≥2)个正整数构成的集合A={ak}(0=<k<n),将其划分为两个不相交的子集A1和A2,元素个数分别是n1和n2,A1和A2中的元素之和分别为S1和S2。设计一个尽可能高效的划分算法,满足|n1-n2|最小且|S1-S2|最大,算法返回|S1-S2|

思路:

  1. 要满足|n1-n2|最小,有两种情况,如果数组长度是偶数,将数组分为等长两半的时候|n1-n2|最小。如果数组长度是奇数,数组分为等长两半,剩余一个元素任意加在一边即可保证|n1-n2|最小。
  2. 要满足|S1-S2|最大,那么要S1尽可能大,S2尽可能小。这样实际上只需要将数组从小到大排序,右边一半之和为S1,左边一半之和为S2

排序算法很多种,其中快速排序是效率最高的🐯(反正算法里面只要和排序有关使用快排准没错)

下面的代码里面的快排函数就是百分之百没有错误的快速排序模板,可以直接背诵,效率极高

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;


const int N = 1010;
int w[N];
int n;

void fast_sort(int left,int right)  //从小到大   快速排序模板,拿去不谢,这个模板过了无数题。
{
	int L = left, R = right;
	int mid = w[(L + R) / 2];
	while (L < R)
	{
		while (mid > w[L])L++;
		while (mid < w[R])R--;
		if (L <= R)
		{
			int temp = w[L];
			w[L] = w[R];
			w[R] = temp;
			L++, R--;
		}
	}
	if (R > left)  //R左边的数一定是小于mid
		fast_sort(left, R);
	if (L < right)  //L右边的数一定大于mid
		fast_sort(L, right);
}

int main()
{
	cout << "输入数组长度:";
	cin >> n;
	cout << "输入数组:";
	for (int i = 1; i <= n; i++)  //输入元素
		cin >> w[i];

	fast_sort(1, n);  //快速排序
	for (int i = 1; i <= n; i++)  //输出一下数组看是否排序成功
		cout << w[i] << " ";
	cout << endl;
	//由于要|n1-n2|最小,那么就二分数组。
	int s1, s2;
	s1 = s2 = 0;
	for (int i = 1; i <= n / 2; i++)  //因为n/2是向下取整,所有如果n是奇数,那么右边会多分一个。如果是偶数,两边分的一样多
	{
		s1 += w[i];   //左边一半的元素之和
	}
	for (int i = n / 2 + 1; i <= n; i++)
	{
		s2 += w[i];    //右边一半的元素之和
	}
	s1 = s1 - s2 > 0 ? s1 - s2 : s2 - s1;  //s1-s2可能是负数,要取正数
	cout << "|s1-s2|的最大值是:" << s1 << endl;
	return 0;
}

新思路!
我们发现:
S1=a1+a2+…an/2
S2=an/2+1+…+an
这代表什么?对于S1,我们实际上不需要一个严格的排过序的数组,而只要满足an/2大于前面所有的数即可。对于S2,不需要排过序的数组,而只要满足an大于前面所有的数即可。

再总结一下,假设有n(假设n是奇数)个数,只要找到一个数target,它满足下面这个性质:

  1. target左边有n/2个元素且target大于他们
  2. target右边有n/2个数且target小于他们

说明:这个代码的细节有点难理解,建议使用老师代码,参考上面思路即可

#include<iostream>
#include<cstdio>
using namespace std;


const int N = 1010;

int a[N];

int fast_sort(int left, int right)//快速排序模板稍微变形一下
{
	int L = left, R = right;
	int target = a[(L + R) / 2];   //基准元素是target,下面的目的就是将所有小于等于target的放在它左边,所有大于等于target的放在它右边
	while (L < R)
	{
		while (a[L] < target)L++;   //如果左边的元素小于target,说明这个元素是合法的,L往右边移动一个单位
		while (a[R] > target)R--;   //如果右边的元素大于target,说明这个元素也是合法的,不用移动位置。R往左边移动一个单位
		if (L <= R)   //3 4 5 7 2 6 9    3 4 5 6 2 7 9
		{
			int temp = a[L];  
			a[L] = a[R];
			a[R] = temp;
			if (L == R)  //此时L左边的数都小于等于target,
				return L;
			L++, R--;
		}
	}
	if (target <= a[L])
		return L;
	else
		return L - 1;
}

void Solve(int n)//目的是找到一个目标值,这个目标值大于等于左边的数,且他们的个数为n/2
{
	bool judge = true;
	int s1 = 0, s2 = 0;
	int left = 1, right = n;   //数组左右边界
	while (judge)
	{
		int k = fast_sort(left, right);  //k表示左边部分的个数(这左边部分都满足小于等于target)
		if (k == n / 2)   //满足条件,退出循环
		{
			judge = false;
		}
		else if (k > n / 2)   //说明target太大,要找一个比target小的数,由于之前排序已经将比target小的数放它左边,所以在左区间找
		{
			right = k - 1;
		}
		else
			left = k + 1;
	}

	for (int i = 1; i <= n / 2; i++)
		s1 += a[i];
	for (int i = n / 2 + 1; i <= n; i++)
		s2 += a[i];

	cout << "s1-s2=" << s2 - s1 << endl;
}

int main()
{
	int n;
	cout << "输入元素个数:";
	cin >> n;
	cout << "输入元素值:";
	for (int i = 1; i <= n; i++)   //输入元素
		cin >> a[i];

	Solve(n);
	return 0;
}

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

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

相关文章

C++STL-string类的实现(上)

在上一篇中&#xff0c;我们知道了string类的一些基本使用&#xff0c;这一篇我们就说一下string类的具体的底层实现。 文章目录1.预前准备1.1 初步的构造和析构1.2 下标的运算符重载2. 深浅拷贝2.1 拷贝构造函数2.2 运算符重载3. 完善前面写的函数3.1 完善构造函数和析构函数…

【自用】Linux服务器部署Oracle并使用数据库管理工具Navicat远程连接(包含远程Navicat配置)

一、服务器端 配置 0.传输oracle安装包和依赖 1.更新依赖 yum update2.检测oracle依赖 rpm -ivh oracle-database-preinstall-19c-1.0-1.el7.x86_64.rpm # 请根据版本选择3.yum安装oracle-database-preinstall yum install oracle-database-preinstall-19c-1.0-1.el7.x86_6…

Azide-PEG-Cholesterol,N3-PEG-Cholesterol,叠氮-PEG-胆固醇PEG试剂供应

化学试剂胆固醇-聚乙二醇-叠氮,其英文名为Cholesterol-PEG-Azide&#xff08;Cholesterol-PEG-N3&#xff09;&#xff0c;它所属分类为DSPE PEG Azide PEG。 试剂胆固醇PEG叠氮的分子量均可定制&#xff0c;有&#xff1a;Cholesterol-PEG 2k-Azide、胆固醇-聚乙二醇 3.4k-叠…

SMBMS系统_准备工作

构建项目Maven/jar 初次构建项目时&#xff0c;思考是不是通过maven创建&#xff0c;使用maven的化需要导入那些依赖&#xff1b; 如果不是使用maven创建项目的话&#xff0c;使用哪些些jar包。 检测验证项目 选择使用maven创建项目完成&#xff0c;可以使用模板&#xff0c…

嵌入式分享合集106

一、可控硅控制电路实例 可控硅是可控硅整流器的简称。可控硅有单向、双向、可关断和光控几种类型。它具有体积小、重量轻、效率高、寿命长、控制方便等优点&#xff0c;被广泛用于可控整流、调压、逆变以及无触点开关等各种自动控制和大功率的电能转换的场合。 单向可控硅是一…

【CloudCompare教程】001:CloudCompare中文版下载与安装图文教程

CloudCompare是一款功能强大的点云后处理软件,本文讲解CloudCompare中文版下载与安装方法。 文章目录 一、CloudCompare下载地址二、CloudCompare安装教程三、CloudCompare中文设置一、CloudCompare下载地址 官方下载地址:http://www.danielgm.net/cc/release/ 二、CloudComp…

vue中使用wangeditor富文本编辑器

官方文档 项目中要求实现富文本编辑器取编辑内容 这种编辑器有好多选择了wangeditor富文本编辑器 首先根据文档安装 yarn add wangeditor/editor # 或者 npm install wangeditor/editor --saveyarn add wangeditor/editor-for-vuenext # 或者 npm install wangeditor/edit…

MySQL进阶实战8,分区表详解

目录一、分区表二、分区的作用三、分区的一些限制四、分区表的增删改查1、select2、insert3、delete4、update五、分区表的类型六、如何使用分区表七、分区表会有哪些问题&#xff1f;1、分区列和索引列不匹配2、选择分区的成本可能很高3、打开并锁住所有底层表的成本可能会很高…

统计信号处理基础 习题解答6-9

题目&#xff1a; 在开关键控&#xff08;OOK&#xff09;的通信系统中&#xff0c;我们发射两个信号中的一个&#xff0c;即 表示bit0&#xff0c;而 表示bit1。假定幅度是正的&#xff0c;为了确定发射的是哪个bit&#xff0c;我们对接收机的波形在符号周期内 进行采样&…

深入了解快速排序和归并排序

作者&#xff1a;~小明学编程 文章专栏&#xff1a;Java数据结构 格言&#xff1a;目之所及皆为回忆&#xff0c;心之所想皆为过往 快速排序和归并排序作为排序中的两个重点&#xff0c;也是面试中最常考的两个知识点&#xff0c;这里带大家详解的了解这两个排序。 目录 快速…

DSPE-PEG-TPP;磷脂-聚乙二醇-磷酸三苯酯;(阻燃剂TPP)是种含磷元素的化合物,可用作无卤环保型阻燃剂

中文名称&#xff1a; 二硬脂酰基磷脂酰乙醇胺-聚乙二醇-磷酸三苯酯&#xff1b;三苯基磷聚乙二醇磷脂 英文简称&#xff1a; DSPE-PEG-TPP,TPP-PEG-DSPE 分子量&#xff1a; 2000,3400,5000等 溶剂: 溶于部分有机溶剂 磷酸三苯酯为无味、无臭的白色结…

JDK8 连接Access数据库

JDK8 连接Access数据库1. 安装JDK82. 下载配置文件3. 源码设置前面我们讲了如何使用Java连接ODBC并配置Access数据库&#xff0c; 参考连接&#xff1a;https://jackwei.blog.csdn.net/article/details/86285822 可以知道JDK8之后已经不支持jdbc-odbc桥接了&#xff0c;如果你可…

windows10上运行magic keyboard和magic mouse

windows10上运行magic keyboard和magic mouse并保持你的mac习惯 所有需要的软件和插件都可以在这里寻找到链接&#xff1a;https://pan.baidu.com/s/1Y8vjRnznqKP7f8dFFrHoGw?pwdvpsy 提取码&#xff1a;vpsy 安装蓝牙 你的windows电脑可能自带了蓝牙&#xff0c;那你直接…

保姆级教程带你从0到1实现基于bitcask的kv存储引擎

愿景 ​ 今年大部分业余时间都在nutsdb的开源贡献上&#xff0c;nutsdb是基于bitcask模型实现的持久化存储引擎&#xff0c;提供了诸如list&#xff0c;set等多种丰富的数据结构。近来很多小伙伴&#xff0c;其中也有一些我的好朋友陆陆续续加入到这个项目上来。为了帮助小伙伴…

tensorflow2 SqueezeNet

前面学习了通过加深网络和加宽网络来改进模型质量&#xff0c;提高模型精度的深度学习backbone模型&#xff08;LeNet,VGGNet,AlexNet,GoogleNet,ResNet),这里介绍如何使网络更快&#xff0c;结构更轻量化的改进深度神经网络模型之一————SqueezeNet&#xff0c;它能够在Ima…

【JavaWeb】文件的上传和下载

文章目录一.文件的上传介绍⭐️1.文件上传及HTTP协议的说明2.commons-fileupload.jar常用API介绍说明二.文件下载⭐️一.文件的上传介绍⭐️ 1.文件上传及HTTP协议的说明 (1).要有一个form标签,methodpost请求 (2).form标签的encType属性值必须为multipart/form-data值 (3).在…

VTK在Windows上的安装

本章介绍在计算机系统上安装VTK。在Microsoft Windows上&#xff0c;可以安装预编译的vtk.exe&#xff0c;也可以从源码自行编译vtk软件。您可能希望了解系统架构&#xff0c;阅读会使编译过程更容易跟踪。如果遇到问题&#xff0c;可以联系vtkusers邮件列表。 2.1 概述 VTK在…

【HTML + CSS】笔记

页面设计 1.HTML&#xff1a;结构框架 2.CSS 3.JS HTML&#xff1a;超文本标记语言 <...>&#xff1a;标签/元素 <!DOCTYPE html>&#xff1a;解释文档类型为html head区域常用标签 <base> 使用后浏览器不再使用当前文档的URL&#xff0c;而使用指定的…

web前端设计与开发期末作品/期末大作业-疫情

Web前端开发技术 描述 网页设计题材&#xff0c;DIVCSS 布局制作,HTMLCSS网页设计期末课程大作业&#xff0c;击疫情致敬逆行者感人类题材 | 致敬逆行者网页设计作品 | 大学生抗疫感动专题网页设计作业模板 | 等网站的设计与制作 | HTML期末大学生网页设计作业 HTML&#xff1a…

Windows之应用安装程序 —— winget

大家都用过Linux中的应用程序安装工具&#xff0c;如yum、apt、rpm等工具进行安装自己想要的一些工具或则软件之类的&#xff0c;当然Linux操作系统还是很强大的有很多类似的命令来安装我们所需要的程序&#xff0c;但是windwos有没有类似于windows这样的应用安装程序呢&#x…