本文涉及知识点
预备知识
求小于a的数的数量
下面是伪代码
cpp
int i=0;
for(;(i<xs.size())&&(xs[i]<a));i++);
如果xs是升序排序,代码执行结束后xs的钱i个元素就是全部小于a的数。时间复杂度:O(k+1),k是xs中小于a的元素的数量。
快速排序
选择一个标兵(主元素pivot),将小于标兵的值放到left中,其余值放到right中。对left,right分别进行快速排序。
最佳时间复杂度和平均时间复杂度都是O(NlogN),递归logN层,每层递归,任意元素排序且仅排序一次。最坏O(NN)。
空间复杂度:O(NlogN),可以优化到O(logN)。
最简单的实现,标兵是首元素或尾元素。这样会有问题:对于已经升序或降序,left和right 有一个为空。
改进的方式:
一,随机选择。
二,(最小值+最大值)/2。
三,中位数的中位数。
四,三数取中,首元素中间元素尾元素的平均值。
极端情况:数值全部相同也可能有问题。
中位数的中位数
将所有的数分成5个一个组,排序。arr 收集各组的中位数,第arr求中位数的中位数。最终结果在30%到70%分位,无法保证刚好是中位数。令最终结果是y,则有一半的初始组 ≥ y \ge y ≥y,这些组至少3个元素 ≥ y \ge y ≥y,故至少30%的元素小于y。
堆排序
插入删除元素时间复杂度O(logN)。空间复杂度O(n)。由于是满二叉树,故最多logN层,每层只会改变一个节点。小根堆,只需要保证各子树根节点最小,兄弟节点的大小关系不限。
快速选择算法(Quickselect)
从未排序数组中选择最小的k个元素,是快速排序的变种。快速选择算法的平均时间复杂度是 O(N),但最坏情况下的时间复杂度是 O(N^2) ,所以要随机化。
容易理解的版本:
选择标兵,将小于标兵的值放到left中,其它值放到right中。
如果left.size等于k,left就是答案。
如果left.size>k,快速选择(left,k)。
如果left.size()< k,left的全部元素是答案,快速选择(right,k-left.size())。
每次递归都只需要处理left或right,而每次调用left和right的期望长度减半。
从堆中选择<a的所有数
如果堆的根节点<a,将根节点放到结果中,递归处理左右子节点。
如果堆的根节点 ≥ a \ge a ≥a中,返回。
空间复杂度:O(N)
时间复杂度:O(K)
如果一个节点<a,则一定会被处理到。
如果一个节点任意祖先 ≥ a \ge a ≥a,它不会被处理到。
节点 ≥ a \ge a ≥a,父节点<a的节点数不会超过2k。
1 线段树(原书第三节)
刷力扣和洛谷图时,研究过,就不赘述了。
2 优先查找树
离线查询查询区域的点
已知二维点集,各点x,y各不相同,如果相同,可以用合成数进行预处理。将四维查询Q=(left,top,right,bottom)拆成两个三维查询Q1=(left,top,bottom),Q2=(right,top,bottom),则Q=Q2-Q1。将三维查询放到datas中,按x的升序排序。
依次处理datas,
将x小于等于datasi.x的坐标放到二叉树中set,it1=set.lower(top),it2= set.upper(bottom), it1,it2之间的点是全部答案。
优先查找树
如果强制在线,则上述方法失效。
优先查找树是小根堆,即任意子树根节点pt.x最小。性质一 。
任意子树,除根节点外的节点,按下述标准拆分左右子树:按(y,x)排序,前半部分属于左子树,右半部分属于右子树。
性质二 :根节点是root,则左子树left的节点的y ≤ y 1 ≤ \le y1 \le ≤y1≤ 右子树right的节点的y。left的左右节点是left1,left2,则
− ∞ < l e f t 1. y ≤ y 2 ≤ l e f t 2. y ≤ y 1 -\infty < left1.y \le y2 \le left2.y \le y1 −∞<left1.y≤y2≤left2.y≤y1。right的左右节点是right1,right2。则 y 1 ≤ r i g h t 1. y ≤ y 3 ≤ r i g h t 2. x < + ∞ y1 \le right1.y \le y3 \le right2.x < +\infty y1≤right1.y≤y3≤right2.x<+∞
对子树T,查询Q,top,right,bottom。
如果T的根节点.pt.x > right retrun
如果pt.y在left,right之间,将pt放到结果中。
如果yMid不在Q中,则只需要处理一个节点。
如果yMid在Q中,则需要处理两个节点,新的查询Q1,Q2和对应节点的上边界或下边界相等。
如果Q和T的上(下)边界相等,不失一般性,令上边界相等。
如果下边界相等,则无需处理右节点。
如果查询的右边界对于节点的右边界,则右节点正常处理。左节点,取所有小于right的点。
如果查询的右边界小于节点的右边界,则左节点正常处理。
结论 :任意层要么处理一个节点。要么处理两个节点,但这两个节点的边界和子查询的一个边界重合。
故时间复杂度:O(K+logN)。空间复杂度:O(N)
3 区间树(原书第一节)
查询和某矩形有交点的水平或垂直线段。许多书籍线段树和区间树是同一个东西,本章讲解的区间树不是线段树。
线段的一个或两个端点在矩形内
利用分散层叠技术优化二维树后,可以在O(LogN+k)的时间内,查询指定区域的点。
引理10.1:设S为平面上一组共n条与坐标轴平行的线段。对于任一与坐标轴平行的查询窗口,我们都可在O(logn+k)时间内,将至少有一个端点落在该查询窗口内的线段报告出来:为此需要设计一个占用O(nlogn)空间的数据结构,其预处理时间为O(nlogn)。其中,k为实际报告出来的线段条数。
N条已知线段S,求和未知垂直直线L相交的线段
由于L是垂直直线,故可忽略线段的y。S共有2N个线段端点,xMid 是这些端点中位数x。线段的右端点<xMid,放到left;线段的左端点> xMid,放到right;其它放到mid。
令L:= x=x',则
如果x'<mid,无需处理right; 递归处理left;mid中的线段右端点 ≥ x m i d > x ′ \ge xmid >x' ≥xmid>x′,预处理时对mid线段的左端点按升序排序,可在常数时间得到这些点。
如果x'=mid,mid就是答案。
如果x'>mid,无需处理left;递归处理right;mid中的线段左端点 ≤ x m i d < x ′ \le xmid <x' ≤xmid<x′,预处理时对mid线段右端点按降序排序,可在常数时间内得到这些点。
由于xMid是端点x的中位数,故left和right顶多有N个端点,即最多N/2条线段。故所有层次的节点数 ≤ N + N / 2 + N / 4 + N / 8 ⋯ < 2 N \le N+N/2+N/4+N/8 \cdots<2N ≤N+N/2+N/4+N/8⋯<2N
故时间复杂度是:O(K+LogN)
定理10.4:给定一组共n个区间I,其对应的区间树占用O(n)空间,而且能够在O(nlogn)时间内构造出来。对于任意待查询点,都可以借助区间树,在O(logn+k)时间内将包含改点的所有区间报告出来,其中k为实际被报告出来的区间数目。
线段与矩形相交但两个端点都在矩形之外
分两种情况:线段与矩形的左右边界相交;线段与矩形的上下边界相交。xy互换后,其实是一种情况。
我们将区间树扩展一下,使得可以针对垂直线段。left和right处理相同。mid的处理略有不同,以x'<xmid为例。如果线段的左端点在以下区域,则与待查线段L相交:L的左边,L.x1的下边,L.x2的上边。用2维KD树或区域树解决。


扩展阅读
| 算法为骨,CAD为魂 |
|---|
| 请通过博文下载学院导航 |
| 经典文章推荐:C++BFS算法 |
| 活到老,学到老。明朝中后期,大约50%的进士能当上堂官(副部及更高);能当上堂官的举人只有十余人。 |
| 子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。 |
测试环境
操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。
