C++ 离散化算法设计原则:压缩的都是精华

公众号:编程驿站

1. 离散化

离散化是离散数学中的概念。离散化算法,指把无限空间中的离散数据映射到一个有限的存储空间中,并且对原数据进行有序索引化。主打压缩的都是精化。

离散化流程:

对离散化数列{235,897,458,7654,458,1234}为例。数列中的数据涉及到的数轴区间从07654。诺大的区间中唯有6个数据。相当于仰头看星空,繁星一点一点。遇到这种情况,可以对数列离散化操作。

  • 对原数据排序。

    int datas[6]={235,897,458,7654,458,1234};
    //使用 sort 函数排序
    sort(datas, datas+6); 
    

1.png

  • 对排序后的数据去重。对排序的数据去重最快的方案使用unique函数,此函数本质是将重复的元素移动到数组的末尾,最终尾迭代器指针指向最后一个重复数据,且返回尾迭代器。

    可以计算实际长度:int size= unique(datas,datas+6) - datas;

    int size= unique(datas,datas+6) - datas;
    //输出数据
    for(int i=0; i<size; i++)
    	cout<<datas[i]<<"\t";
    

4.png

2.png

  • 对原数据索引化(也称为离散化)后,原数据分别被映射为{25,1}、{458,2}、{897,3}、{1234,4}、{7654,5}

3.png

原数据离散化后常用操作是查找离散数据的离散(索引)值是多少。如果在离散化过程中使用哈希表存储,查询时间复杂度为O(1)。如果使用数组,最优方案是使用二分搜索算法。

// 二分求出 val 对应离散化的值
int search(int val) {
	// 在、右指针
	int lt = 0,rt = sizeof(datas)/4 - 1;
	while(lt<rt) {
		int mid = lt + rt >> 1;
		if(datas[mid] >= val) rt = mid;
		else lt = mid + 1;
	}
	return lt;
}

测试完整代码。

Tips: 注意search函数的返回值加1 。离散化后的值一般从1开始。

int main() {
//使用 sort 函数排序
	sort(datas, datas+6);
	int size= unique(datas,datas+6) - datas;
	for(int i=0; i<size; i++)
		cout<<datas[i]<<"\t";
	cout<<"查找离散数据离散化后的值"<<endl;
	int res=search(458);
	cout<<res+1;
	return 0;
}

2. 算法应用

什么样的问题可以使用离散化算法?

当问题并不完全关注数据,更多是关注数据之间的相对大小时可以使用分散算法提升解决问题的性能。如区间类型问题……

下面使用几个案例来理解分散算法的应用。

2.1 区间和

题目描述:

假定有一个无限长的数轴,数轴上每个坐标上的数都是 0。现在,我们首先进行 n次操作,每次操作将某一位置 x 上的数加 c
接下来,进行 m 次询问,每个询问包含两个整数 lr ,你需要求出在区间 [ l , r ]之间的所有数的和。

输入格式:

第一行包含两个整数 nm 。接下来 n 行,每行包含两个整数 xc 。再接下来 m行,每行包含两个整数 lr

输出格式:

m行,每行输出一个询问中所求的区间内数字和。

数据范围:

10-9 ≤ x ≤ 109

1 ≤ n ≤ 105

1 ≤ m ≤ 105

10-9 ≤ l ≤ r ≤ 109

− 10000 ≤ c ≤ 10000

输入样例:

3 3
1 2
3 6
7 5
1 3
4 6
7 8

输出样例:

8
0
5

问题解析:

坐标轴上,每一个x坐标位置对应一个值,求在x坐标系上一个区间内所有x坐标上值的和。题目中x坐标的范围是10-9到1010之间,操作次数限制在1到105之间,意味着2*109个坐标中最多只有105个坐标会被指定值。

暴力解题思路:

创建一个二维数组arr[109][2]。因为坐标轴以原点0对称,数组的行表示x坐标的绝对值,列表示方向,0表示向左,1表示向右。比如修改坐标3的值为8。可用arr[3][1]=8存储。如修改坐标-3的值为9,可用arr[3][0]=9存储。前缀和存储在一维数组s[2*109]。计算前缀和时,需要把二维数组坐标转转为一维数组坐标。

因数组长度达到了109。会超成数据溢出,性能堪忧。理论上可行,但实操中会略显麻烦。为了方便讲解这种算法思路,下面把坐标轴绝对值限制在10内。

算法实现流程:

  • 创建二维数组arr[10][2]存储坐标及其对应的值。下图描述了数组和坐标轴的对应关系。坐标轴上的黑色数字表示坐标位置,红色数字表示此坐标位置对应的值。0坐标没有正负之分,0坐标对应的值即可存储在arr[0][0]中,也可以存储在arr[0][1]中。另一个存储空间值为0便可,不影响前缀和的计算。

6.png

  • 创建一维数组s[20],存储坐标轴上坐标值的前缀和。一维数组的长度为20

10.png

  • 计算二维数组的前缀和。这里要注意,访问的二维数组顺序应该由左下角向上然后向右再下向右下解。如下图所示,从负坐标逐渐访问到正坐标。

11.png

  • 这里有二维坐标转换为一维数坐标的细节。如下图显示了把二维数组展开后和一维数组的对应关系。

    计算法则:如果列号为0,10减行号加1为其对应的一维坐标,如果列号为1,则10加行号+1,为对应一维坐标。

12.png

编码实现:

#include <iostream>
#include <cmath>
#define  SIZE 10
using namespace std;
int main(int argc, char** argv) {
	int arr[SIZE+1][2];
	for(int i=0; i<=SIZE; i++)
		for(int j=0; j<2; j++)
			arr[i][j]=0;

	int s[2*SIZE+1]= {0};
	int n,m;
	cin>>n>>m;
	int x,val;
	for(int i=0; i<n; i++) {
		cin>>x>>val;
		if(x<0)
			arr[ abs(x) ][0]=val;
		else
			arr[abs(x)][1]=val;
	}
	//求前缀和
	int tmpx=0;
	for(int i=SIZE; i>=0; i--) {
		//转换坐标
		tmpx=SIZE-i+1;
		s[tmpx] = s[tmpx - 1] + arr[i][0];
	}
	for(int i=0; i<=SIZE; i++) {
		//转换坐标
		tmpx=SIZE+i+1;
		s[tmpx] = s[tmpx - 1] + arr[i][1];
	}
	//求区间和
	int l,r;
	for(int i=0; i<m; i++) {
		cin>>l>>r;
		if(l<0)l=SIZE-abs(l)+1;
		else l=abs(l)+SIZE+1;
		if(r<0)r=SIZE-abs(r)+1;
		else r=abs(r)+SIZE+1;
		cout<<s[r]-s[l-1]<<endl;
	}
	return 0;
}

在坐标范围很大时,上述算法性能堪忧。创建如此大的数组,对空间有极苛刻的要求,稍不留神就会溢出,导致程序崩溃,且坐标换算很麻烦。

区间求和更在意数据的相对大小,虽然坐标范围较大,但真正用到的坐标比范围小很多。可以使用离散化算法思想。把存储范围缩小在105

算法实现流程:

#include <bits/stdc++.h>
#define  SIZE 100000
using namespace std;
//坐标类型
struct Point {
	//坐标
	int x;
	//坐标上对应的值
	int val;
	Point() {
		this->x=0;
		this->val=0;
	}
	Point(int x,int val) {
		this->x=x;
		this->val=val;
	}
   //用于排序,由小到大
	bool operator<(const Point & p ) {
		return this->x<p.x;
	}
    //用于去重
	bool operator==(const Point& other) const {
		return this->x == other.x ;
	}
};
//坐标最多 100000
vector<Point> points;
int n,m;
//前缀和
int sum[SIZE]= {0};
//查找坐标是否已经存储
bool findx(int x) {
	int size=points.size();
	for(int i=0; i<size; i++) {
		if( points[i].x==x )return 1;
	}
	return 0;
}

// 二分求出 val 对应离散化的值
int search(int val) {
	// 在、右指针
	int lt = 0,rt =points.size() - 1;
	while(lt<rt) {
		int mid = lt + rt >> 1;
		if(points[mid].x >= val) rt = mid;
		else lt = mid + 1;
	}
	return lt;
}

int main(int argc, char** argv) {
	cin>>n>>m;
	int x,val;
	for(int i=0; i<n; i++) {
		cin>>x>>val;
		Point p(x,val);
        //存储坐标与其对应值
		points.push_back(p);
	}
	int l,r;
	pair<int,int> ps[SIZE];
	for( int i=0; i<m; i++ ) {
		cin>>l>>r;
		pair<int,int> p= {l,r};
		ps[i]=p;
		if( !findx(l) ) {
			points.push_back( {l,0} );
		}
		if( !findx(r) ) {
			points.push_back( {r,0} );
		}
	}
	//排序
	sort( points.begin(),points.end() );
	//去重
	points.erase(unique(points.begin(), points.end()), points.end());
	//前缀和
	int size=points.size();
	for(int i=0; i<size; i++) {
		if(i==0)
			sum[i]=points[i].val;
		else
			sum[i]=sum[i-1]+points[i].val;
	}
    //查询
	for(int i=0; i<m; i++) {
		l=search(ps[i].first) ;
		r=search( ps[i].second);
		cout<<sum[r]-sum[l-1]<<endl;
	}
	return 0;
}

2.2 最小矩形面积

给定平面上n个点的坐标,求能够覆盖所有这些点的最小矩形面积。其中,矩形可以倾斜放置,边不必平行于坐标轴。

5.png

这里的倾斜放置很不好处理,因为我们不知道这个矩形最终会倾斜多少度。假设我们知道这个矩形的倾角是α,那么答案就很简单了:矩形面积最小时四条边一定都挨着某个点。也就是说,四条边的斜率已经都知道了的话,只需要让这些边从外面不断逼近这个点集直到碰到了某个点。你不必知道这个具体应该怎么实现,只需要理解这可以通过某种方法计算出来,毕竟重点在下面的过程。

我们的算法很显然了:枚举矩形的倾角,对于每一个倾角,我们都能计算出最小的矩形面积,最后取一个最小值。

这个算法是否是正确的呢?我们不能说它是否正确,因为它根本不可能实现。矩形的倾角是一个实数,它有无数种可能,你永远不可能枚举每一种情况。我们说,矩形的倾角是一个“连续的”变量,它是我们无法枚举这个倾角的根本原因。我们需要一种方法,把这个“连续的”变量变成一个一个的值,变成一个“离散的”变量。这个过程也就是所谓的离散化。

我们可以证明,最小面积的矩形不但要求四条边上都有一个点,而且还要求至少一条边上有两个或两个以上的点。试想,如果每条边上都只有一个点,则我们总可以把这个矩形旋转一点使得这个矩形变“松”,从而有余地得到更小的矩形。于是我们发现,矩形的某条边的斜率必然与某两点的连线相同。如果我们计算出了所有过两点的直线的倾角,那么α的取值只有可能是这些倾角或它减去90度后的角(直线按“\”方向倾斜时)这么C(n,2)种。我们说,这个“倾角”已经被我们“离散化”了。

2.3 最小矩形面积

对于某些坐标虽然已经是整数(已经是离散的了)但范围极大的问题,我们也可以用离散化的思想缩小这个规模。

13.png

给定平面上的n个矩形(坐标为整数,矩形与矩形之间可能有重叠的部分),求其覆盖的总面积。平常的想法就是开一个与二维坐标规模相当的二维Boolean数组模拟矩形的“覆盖”(把矩形所在的位置填上True)。可惜这个想法在这里有些问题,因为这个题目中坐标范围相当大(坐标范围为-108到108之间的整数)。

但我们发现,矩形的数量n<=100远远小于坐标范围。每个矩形会在横纵坐标上各“使用”两个值,100个矩形的坐标也不过用了-108到108之间的200个值。也就是说,实际有用的值其实只有这么几个。这些值将作为新的坐标值重新划分整个平面,省去中间的若干坐标值没有影响。我们可以将坐标范围“离散化”到1到200之间的数,于是一个200*200的二维数组就足够了。实现方法正如本文开头所说的“排序后处理”。对[横坐标](或纵坐标)进行一次排序并映射为1到2n的整数,同时记录新坐标的每两个相邻坐标之间在离散化前实际的距离是多少。这道题同样有优化的余地。

3. 总结

本文聊聊离散化算法,当数据趋于离散分布,而且,计算时只在意数据的相对值时,可以使用此算法。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Vue.js+SpringBoot开发电子元器件管理系统

目录 一、摘要1.1 项目简介1.2 项目录屏 二、研究内容三、界面展示3.1 登录&注册&主页3.2 元器件单位模块3.3 元器件仓库模块3.4 元器件供应商模块3.5 元器件品类模块3.6 元器件明细模块3.7 元器件类型模块3.8 元器件采购模块3.9 元器件领用模块3.10 系统基础模块 四、…

从源码解析Kruise(K8S)原地升级原理

从源码解析Kruise原地升级原理 本文从源码的角度分析 Kruise 原地升级相关功能的实现。 本篇Kruise版本为v1.5.2。 Kruise项目地址: https://github.com/openkruise/kruise 更多云原生、K8S相关文章请点击【专栏】查看&#xff01; 原地升级的概念 当我们使用deployment等Wor…

苍穹外卖项目微信支付(没有商户号)的解决方法,超详细!!!

今天在写苍穹外卖项目时&#xff0c;写到微信支付时发现个人无法获取商户号&#xff0c;那么今天我就在这里分享一个方法&#xff0c;可以绕过微信支付实现订单支付的功能。本方法仅仅是绕过微信支付&#xff0c;没有进行真正的微信支付&#xff0c;如果想要体验真正的微信支付…

值类型和引用类型详解(C#)

可能你对值类型和引用类型还不太了解。 值类型和引用类型&#xff0c;是c#比较基础&#xff0c;也必须掌握的知识点&#xff0c;但是也不是那么轻易就能掌握&#xff0c;今天跟着我一起来看看吧。 典型类型 首先我们看看这两种不同的类型有哪些比较典型的代表。 典型值类型…

橘子学es原理01之准备工作

es本身是具备很好的使用特性的&#xff0c;我指的是他的部署方面的&#xff0c;至于后期的使用和运维那还是很一眼难尽的。 我们从这一篇开始就着重于es的一些原理性的的一些探讨&#xff0c;当然我们也会有一些操作性的&#xff0c;业务性的会分为多个栏目来写。比如前面我写的…

java面试(并发)

java线程概念&#xff0c;安全&#xff1f; 进程是系统分配资源的最小单元&#xff0c;线程是操作系统调度的最小单位。线程属于进程。 加锁保证安全。1.JVM提供Synchronized关键字&#xff0c;2.jdk提供各种lock锁 实现多线程方式&#xff1f; 1.继承Thread类&#xff0c;…

【奥威-金蝶云星空BI方案】你要的报表,这里都有!

用金蝶云星空来记账&#xff0c;那确实好&#xff0c;但如果让你再去做一份详细的报表呢&#xff1f;自己开发的话&#xff0c;成本大、耗时长&#xff0c;一旦有了新的需求又要一再开发&#xff0c;长此以往将增加使用者使用难度&#xff0c;降低数据分析对运营决策的时效性。…

2024能源动力、机械自动化与航天航空技术国际学术会议(ICEPMAT2024)

2024能源动力、机械自动化与航天航空技术国际学术会议(ICEPMAT2024) 会议简介 能源动力、机械自动化和航空航天技术国际学术会议&#xff08;ICEPMAT2024&#xff09;将于2024年在北京举行。会议将探讨能源动力、机械自动化、航空航天技术领域的新研究热点、核心技术和发展趋…

迷你世界之建筑生成球体

local x0,y0,z00,30,0--起点坐标 local dx,dy,dz60,60,60--外切长方体横纵竖长度 local count,all0,dx*dy*dz--计数&#xff0c;总数 local m,k10000,0--单次生成方块数&#xff0c;无用循环值 local x,y,z0,0,0--当前坐标 local demath.random(2,19)/2 local id600--方块…

在openEuler中通过KVM可视化安装华为FusionCompute的CNA主机

一、环境说明 在Windows物理主机上通过VMware WorkStation创建一个虚拟机&#xff08;4U4C、16GB内存&#xff0c;400GB磁盘&#xff0c;NAT网络连接&#xff09;&#xff0c;在虚拟机中安装openEuler 22.03 LTS系统&#xff0c;并将该虚拟机作为部署 FusionCompute的服务器&a…

【Linux】 yum命令使用

yum命令 yum&#xff08; Yellow dog Updater, Modified&#xff09; 是一个在 Fedora、CentOS 及其它一些基于 RPM 的 Linux 发行版中使用的包管理器。它允许用户自动安装、更新、配置和删除软件包。yum 由 Python 写成&#xff0c;基于 RPM&#xff08;Red Hat Package Mana…

【C语言】linux内核ipoib模块 - ipoib_tx_poll

一、中文注释 这段代码是 Linux 内核网络栈中与 InfiniBand 协议相关的一个部分&#xff0c;特别是与 IP over InfiniBand (IPoIB)相关。该函数负责去处理IPoIB的发送完成队列&#xff08;发送CQ&#xff09;上的工作请求&#xff08;work completions&#xff09;。以下是对这…

微信小程序开发(实战案例):本地生活 - 列表页面开发(动态渲染处理)、节流防抖(节流阀应用)

文章目录 本地生活 - 列表页面开发一、将九宫格分类换成navigator组件二、动态设置商品列表页的 title三、动态渲染商品列表页面四、上拉触底加载数据五、添加Loading加载效果六、数据加载节流防抖处理 本地生活 - 列表页面开发 导入我们上次写的 本地生活 - 首页开发的项目 运…

MySQL数据库调优之关联查询、排序查询、分页查询、子查询、Group by优化

关联查询优化 1.准备工作 CREATE TABLE IF NOT EXISTS type(id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,card INT(10) UNSIGNED NOT NULL,PRIMARY KEY(id));CREATE TABLE IF NOT EXISTS book( bookid INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, card INT(10) UNSIGNED N…

李宏毅2023机器学习作业1--homework1——python语法

# 定义list del_col del_col [0, 38, 39, 46, 51, 56, 57, 64, 69, 74, 75, 82, 87] # 删除raw_x_train中del_col的列&#xff0c;axis为1代表删除列 raw_x_train np.delete(raw_x_train, del_col, axis1) # numpy数组增删查改方法 # 定义列表get_col get_col [35, 36, 37,…

openssl3.2 - 编译 - zlib.dll不要使用绝对路径

文章目录 openssl3.2 - 编译 - 编译时的动态库zlib.dll不要使用绝对路径概述测试zlib特性在安装好的目录中是否正常笔记70-test_tls13certcomp.t80-test_cms.t对测试环境的猜测从头再编译测试安装一次测试一下随便改变位置的openssl用到zlib时是否好使测试一下随便改变位置的op…

【爬虫逆向实战篇】定位加密参数、断点调试与JS代码分析

文章目录 1. 写在前面2. 确认加密参数3. 加密参数定位4. XHR断点调试 【作者主页】&#xff1a;吴秋霖 【作者介绍】&#xff1a;Python领域优质创作者、阿里云博客专家、华为云享专家。长期致力于Python与爬虫领域研究与开发工作&#xff01; 【作者推荐】&#xff1a;对JS逆向…

实战一个 Jenkins 构建 CI/CD流水线 的简单配置过程哈

引言&#xff1a;上一期我们讲述了gitlabCI/CD工具的介绍&#xff0c;工具之争&#xff0c;本期我们介绍Jenkins CI/CD 目录 一、Jenkins介绍 1、Jenkins概念 2、Jenkins目的 3、特性 4、产品发布流程 二、安装Jenkins 1、安装JDK 2、安装Jenkins 1、上传压缩包 2、…

(done) 如何判断一个矩阵是否可逆?

参考视频&#xff1a;https://www.bilibili.com/video/BV15H4y1y737/?spm_id_from333.337.search-card.all.click&vd_source7a1a0bc74158c6993c7355c5490fc600 这个视频里还暗含了一些引理 1.若 AX XB 且 X 和 A,B 同阶可逆&#xff0c;那么 A 和 B 相似。原因&#xff1…

北航复试知识点总结

2024.2.25 住行 报道+机试+两天面试=4天 面试流程 (每个人大概20min,早一点到考场!) 形式:5位老师(一记录,四提问) 老师 陆峰 办公地址:北京航空航天大学新主楼H1033 电子邮箱: lufeng@buaa.edu.cn 个人主页:http://shi.buaa.edu.cn/lufeng/ 面试礼仪 于无形中…