关于数值段相交的算法

dfasri 2012-01-17 03:52:04
有两个数值段列表, 例如:
A列表: 1 ~ 5, 10 ~ 16, 19 ~ 23, 34 ~ 50 共4个数值段
B列表: 3 ~ 7, 9 ~ 13, 21 ~ 33 共3个数值段

求两个列表的相交后的数值段, 结果应该为:
结果: 3 ~ 5, 10 ~ 13, 21 ~ 23 的这样一个列表

有什么高效算法来计算这个结果?
...全文
174 12 打赏 收藏 转发到动态 举报
写回复
用AI写文章
12 条回复
切换为时间正序
请发表友善的回复…
发表回复
dfasri 2012-05-21
  • 打赏
  • 举报
回复
有人给了基本过程, 我再改良而已
用户 昵称 2012-05-21
  • 打赏
  • 举报
回复
基本上自问自答的。
dfasri 2012-01-21
  • 打赏
  • 举报
回复
之前的代码没有测试, 这个代码是经过测试的正确代码
带有默认的条件:
1. 范围总是按从小到大的顺序的.
2. 输入范围不会存在像 12 ~ 13, 13 ~ 14 或 12 ~ 13, 14 ~ 15的区域, 虽然不允许, 但还是可以运算, 只是运算出来的范围可能会存在 13 ~ 13, 14 ~ 14, 但实际是可以合并成 13 ~ 14的.
3. 当输入的范围不存在 X ~ MAX 段, 默认会拿最后一个范围的最大值+2 ~ MAX 作补充. 即 一个范围段为: 1 ~ 3, 4 ~ 6, 在运算时, 会默认为 1 ~ 3, 4 ~ 6, 8 ~ MAX 作为输入.

BOOL VXIntersect( DWORD32* pVXRanges, ULONG ulCnt ) // 交运算
{
DWORD32 dwMin = 0;
DWORD32 dwMax;
DWORD32 dwOldMin;
BOOL bFind;
if (0 != ulCnt % 2 || 0 == ulCnt) // 不是2的倍数肯定是一个错误的范围数组, 不能够进行交运算
return FALSE;
ULONG i = 0;
m_pVXCursor = m_pVXHead;
do
{
dwMax = pVXRanges[i] - 1;
bFind = FALSE;
while (NULL != m_pVXCursor)
{
if (m_pVXCursor->dwVXMin > dwMin) // 查找不到对应段
break;
if (dwMin >= m_pVXCursor->dwVXMin && m_pVXCursor->dwVXMax >= dwMin) // 查找到对应段
{
bFind = TRUE;
break;
}
m_pVXCursor = m_pVXCursor->pVXNext;
}
if (TRUE == bFind && dwMax <= m_pVXCursor->dwVXMax) // 小端查找到, 并且大端也在本范围段内时
{
if (dwMin == m_pVXCursor->dwVXMin && dwMax == m_pVXCursor->dwVXMax) // 刚好复盖整个范围, 当前段删除即可
VXDelete();
else if (dwMin == m_pVXCursor->dwVXMin) // 小段刚好全部合并, 大段写进去即可
{
m_pVXCursor->dwVXMin = dwMax + 1;
//m_pVXCursor = m_pVXCursor->pVXNext;
}
else if (dwMax == m_pVXCursor->dwVXMax) // 大段刚好全部合并, 留下小段即可
{
m_pVXCursor->dwVXMax = dwMin - 1;
m_pVXCursor = m_pVXCursor->pVXNext;
}
else // 不能够闭合, 要创建新的段
{
dwOldMin = m_pVXCursor->dwVXMin;
m_pVXCursor->dwVXMin = dwMax + 1;
VXInsertBefore( dwOldMin, dwMin - 1 );
}
}
else
{
if (TRUE == bFind)
{
if (m_pVXCursor->dwVXMin == dwMin) // 刚好合并时, 删除当前段即可
VXDelete();
else // 不能够闭合, 把当前段的最大值修改即可
{
if (0 == dwMin)
{
printf("error! impossible be zeor of min here!");
system("pause");
}
m_pVXCursor->dwVXMax = dwMin - 1;
m_pVXCursor = m_pVXCursor->pVXNext;
}
}
while (NULL != m_pVXCursor)
{
if (m_pVXCursor->dwVXMax <= dwMax) // 只要小于大端, 就可以直接删除
{
VXDelete();
continue;
}

if (m_pVXCursor->dwVXMin > dwMax) // 当前块的小端比当前比较的大端还大, 代表不需要再继续处理了
break;

if (m_pVXCursor->dwVXMax >= dwMax)
{
if (m_pVXCursor->dwVXMax == dwMax) // 刚好闭合, 删除这个块
VXDelete();
else // 不能闭合, 修改最小值
{
m_pVXCursor->dwVXMin = dwMax + 1;
//m_pVXCursor = m_pVXCursor->pVXNext;
}
break;
}
m_pVXCursor = m_pVXCursor->pVXNext;
}
}
i ++;
dwMin = pVXRanges[i] + 1;
i ++;
}while (i < ulCnt);
if (0 != dwMin)
{
if (NULL == m_pVXCursor)
m_pVXCursor = &m_VXEnd;
while (NULL != m_pVXCursor)
{
if (m_pVXCursor->dwVXMin > dwMin) // 查找不到对应段
break;
if (dwMin >= m_pVXCursor->dwVXMin && m_pVXCursor->dwVXMax >= dwMin) // 查找到对应段
{
if (dwMin == m_pVXCursor->dwVXMin && dwMin == m_pVXCursor->dwVXMax) // 刚好复盖整个范围, 当前段删除即可
VXDelete();
else if (dwMin == m_pVXCursor->dwVXMin) // 实现插入单个区间, 刚好为边界就直接加即可
m_pVXCursor->dwVXMin = dwMin + 1;
else if (dwMin == m_pVXCursor->dwVXMax) // 大段刚好全部合并, 留下小段即可
m_pVXCursor->dwVXMax = dwMin - 1;
else // 非边界时要创建一个新的区域
{
dwOldMin = m_pVXCursor->dwVXMin;
m_pVXCursor->dwVXMin = dwMin + 1;
VXInsertBefore( dwOldMin, dwMin - 1 );
}
break;
}
m_pVXCursor = m_pVXCursor->pVXNext;
}
}
m_pVXCursor = m_pVXHead;
return TRUE;
}
dfasri 2012-01-20
  • 打赏
  • 举报
回复
采用补集并运算的过程大概流程描述就是:

整个过程就是归纳为:
先用小端查找
查找到时
当前段为起点
判大端是否也在本段内
在本段内
小端跟小端合并, 大端跟大端合并, 即插入一个新段(假如刚好为边界值是不用分段)
不在本段内
本段的大端变成当前查找的小端作为大端.
用大端查找位置, 所经过的段假如大端比当前查找的大端要小, 直接删除
查找不到, 删除完毕即可.
查找得到, 根据是否刚好为边界来进行决定分段还是不分段.
查找不时
当前段为起点
用大端查找后段, 所经过的段假如大端比当前查找的大端要小, 直接删除
查找不到, 删除完毕即可
查找得到, 根据是否刚好为边界来进行决定分段还是不分段.

即查找到跟查找不到时, 是小了一个在本段内进行比较的过程.


由于有相应的业务条件, A 交 B 的结果要放回 A 当中, 所以内部也有相应的删除双向链表操作. 并且假如交数 B 当中不存在 X~MAX 段的话, 那么可以确保 X+1 是属于补集范围内的, 所以最后会有一个单值段的并运算.
代码如下:

BOOL VXIntersect( DWORD32* pVXRanges, ULONG ulCnt ) // 交运算
{
DWORD32 dwMin = 0;
DWORD32 dwMax;
DWORD32 dwOldMin;
BOOL bFind;
if (0 != ulCnt % 2 || 0 == ulCnt) // 不是2的倍数肯定是一个错误的范围数组, 不能够进行交运算
return FALSE;
ULONG i = 0;
m_pVXCursor = m_pVXHead;
do
{
dwMax = pVXRanges[i] - 1;
bFind = FALSE;
while (NULL != m_pVXCursor)
{
if (m_pVXCursor->dwVXMin > dwMin) // 查找不到对应段
break;
if (dwMin >= m_pVXCursor->dwVXMin && m_pVXCursor->dwVXMax >= dwMin) // 查找到对应段
{
bFind = TRUE;
break;
}
m_pVXCursor = m_pVXCursor->pVXNext;
}
if (TRUE == bFind && dwMax <= m_pVXCursor->dwVXMax) // 小端查找到, 并且大端也在本范围段内时
{
if (dwMin == m_pVXCursor->dwVXMin && dwMax == m_pVXCursor->dwVXMax) // 刚好复盖整个范围, 当前段删除即可
VXDelete();
else if (dwMin == m_pVXCursor->dwVXMin) // 小段刚好全部合并, 大段写进去即可
{
m_pVXCursor->dwVXMin = dwMax;
m_pVXCursor = m_pVXCursor->pVXNext;
}
else if (dwMax == m_pVXCursor->dwVXMax) // 大段刚好全部合并, 留下小段即可
{
m_pVXCursor->dwVXMax = dwMin;
m_pVXCursor = m_pVXCursor->pVXNext;
}
else // 不能够闭合, 要创建新的段
{
dwOldMin = m_pVXCursor->dwVXMin;
m_pVXCursor->dwVXMin = dwMax;
VXInsertBefore( dwOldMin, dwMin );
}
}
else
{
if (TRUE == bFind)
{
if (m_pVXCursor->dwVXMin == dwMin) // 刚好合并时, 删除当前段即可
VXDelete();
else // 不能够闭合, 把当前段的最大值修改即可
{
if (0 == dwMin)
{
printf("error! impossible be zeor of min here!");
system("pause");
}
m_pVXCursor->dwVXMax = dwMin - 1;
}
}
while (NULL != m_pVXCursor)
{
if (m_pVXCursor->dwVXMax < dwMax) // 只要小于大端, 就可以直接删除
{
VXDelete();
continue;
}

if (m_pVXCursor->dwVXMin > dwMax) // 当前块的小端比当前比较的大端还大, 代表不需要再继续处理了
break;

if (m_pVXCursor->dwVXMax >= dwMax)
{
if (m_pVXCursor->dwVXMax == dwMax) // 刚好闭合, 删除这个块
VXDelete();
else // 不能闭合, 修改最小值
m_pVXCursor->dwVXMin = dwMax + 1;
break;
}
m_pVXCursor = m_pVXCursor->pVXNext;
}
}
i ++;
dwMin = pVXRanges[i] + 1;
i ++;
}while (i < ulCnt);
if (0 != dwMin)
{
if (NULL == m_pVXCursor)
m_pVXCursor = &m_VXEnd;
if (dwMin == m_pVXCursor->dwVXMin) // 实现插入单个区间, 刚好为边界就直接加即可
m_pVXCursor->dwVXMin = dwMin + 1;
else // 非边界时要创建一个新的区域
{
dwOldMin = m_pVXCursor->dwVXMin;
m_pVXCursor->dwVXMin = dwMin + 1;
VXInsertBefore( dwOldMin, dwMin - 1 );
}
}
m_pVXCursor = m_pVXHead;
}


分支挺多, 流程是很简单的, 大部分分支是为了不乱去new和delete. 并且实现确保不会存在 2 ~ 4, 5 ~ 8 这样的可合并分段, 2 ~ 4, 5 ~ 8应该是直接为 2 ~ 8 分段的.
结贴了.
fronz 2012-01-19
  • 打赏
  • 举报
回复
楼主继续
我旁观
BombZhang 2012-01-18
  • 打赏
  • 举报
回复
下面的完全满足你的要求了:

void GetIntersect(CArray<INTPARE,INTPARE> &TableA,CArray<INTPARE,INTPARE> &TableB,CArray<INTPARE,INTPARE> &TableRes)
{
TableRes.RemoveAll();

INTPARE PareA,PareB;
int a,b,c,d,nStartPoint=0;

for (int i=0;i<TableA.GetCount();i++)
{
PareA=TableA[i];
for (int j=nStartPoint;j<TableB.GetCount();j++)
{
PareB=TableB[j];

if(PareA.b<PareB.a)
{
break;
}

a=PareA.a-PareB.a;
b=PareA.a-PareB.b;
c=PareA.b-PareB.a;
d=PareA.b-PareB.b;

if(a==0 || b==0 || c==0 || d==0 || (a^b)>>31 || (b^c)>>31 || (c^d)>>31)//有交叉
{
INTPARE pare;
pare.a=max(PareA.a,PareB.a);
pare.b=min(PareA.b,PareB.b);

TableRes.Add(pare);
nStartPoint=j;
}
}
}
}
BombZhang 2012-01-18
  • 打赏
  • 举报
回复
上面改的不对,再换个方法
BombZhang 2012-01-18
  • 打赏
  • 举报
回复
去掉*/很简单:

a+=abs(a);
b+=abs(b);
c+=abs(c);
d+=abs(d);

if(a==0 || b==0 || c==0 || d==0)//有交叉
{
INTPARE pare;
pare.a=max(PareA.a,PareB.a);
pare.b=min(PareA.b,PareB.b);

TableRes.Add(pare);
}
dfasri 2012-01-18
  • 打赏
  • 举报
回复
[Quote=引用 2 楼 bombzhang 的回复:]

C/C++ code

typedef struct
{
int a,b;
}INTPARE;

void GetIntersect(CArray<INTPARE,INTPARE> &TableA,CArray<INTPARE,INTPARE> &TableB,CArray<INTPARE,INTPARE> &TableRes)
{
TableRes……
[/Quote]

非常感谢提供的代码, 但这种方式效率不算高吧. 里面还有-,*和/运算, -可以忽略不计, 但* 和 /开销挺大的. 而且很明显, 只要发现B当前范围均比A当前范围要大的时候, 就应该停止B循环, 进入下一个A范围的处理了.

我之前的条件漏说了个, 是范围段都是从小到大排好顺序的. 加上这个条件, 还得要加入下次B的循环应该是从上次循环最后被修改的范围开始查找, 不需要每次都从头查找.

虽然说是这样说, 但写代码的时候, 却发现所说的方案很难实现, 变化非常多, 人眼看上去就容易得出结果, 但机器要去做的时候, 找不到循环点..
dfasri 2012-01-18
  • 打赏
  • 举报
回复
[Quote=引用 6 楼 bombzhang 的回复:]

下面的完全满足你的要求了:
C/C++ code

void GetIntersect(CArray<INTPARE,INTPARE> &TableA,CArray<INTPARE,INTPARE> &TableB,CArray<INTPARE,INTPARE> &TableRes)
{
TableRes.RemoveAll();

INTPARE P……
[/Quote]
非常感谢, 这个效率就高很多了, B是只会循环一次了. 只要在A循环里面加多一个判断条件, 假如很明显A剩下的范围已经超出了所有B的范围, 就应该跳出A的循环了, 因为已经没有相交部分.

看了你的例子后, 我也想到了一个新的办法, 目前用相交的的方式, 好像可以采用并运算的方式来简化过程.
像上述的例子:
A列表: 1 ~ 5, 10 ~ 16, 19 ~ 23, 34 ~ 50 共4个数值段
B列表: 3 ~ 7, 9 ~ 13, 21 ~ 33 共3个数值段
把B列表取补集, 变成 0 ~ 2, 8 ~ 8, 14 ~ 20, 34 ~ MAX, 这些就是B列表的补集.(范围是0 ~ MAX)
利用这个补集, 好像可以更使判断过程更简单.
我写好了也发上来.
BombZhang 2012-01-17
  • 打赏
  • 举报
回复

typedef struct
{
int a,b;
}INTPARE;

void GetIntersect(CArray<INTPARE,INTPARE> &TableA,CArray<INTPARE,INTPARE> &TableB,CArray<INTPARE,INTPARE> &TableRes)
{
TableRes.RemoveAll();

INTPARE PareA,PareB;
int a,b,c,d;
for (int i=0;i<TableA.GetCount();i++)
{
PareA=TableA[i];
for (int j=0;j<TableB.GetCount();j++)
{
PareB=TableB[j];

a=PareA.a-PareB.a;
b=PareA.a-PareB.b;
c=PareA.b-PareB.a;
d=PareA.b-PareB.b;

if(a!=0) a/=abs(a);
if(b!=0) b/=abs(b);
if(c!=0) c/=abs(c);
if(d!=0) d/=abs(d);

if(a*b<=0 || b*c<=0 || c*d<=0)//有交叉
{
INTPARE pare;
pare.a=max(PareA.a,PareB.a);
pare.b=min(PareA.b,PareB.b);

TableRes.Add(pare);
}
}
}
};

Kaile 2012-01-17
  • 打赏
  • 举报
回复
用2个map保存数据, 轮询其中一个,再查找另外一个map中有没有相等的元素
内容简介  《实时碰撞检测算法技术》详细阐述了与碰撞检测问题相关的高效解决方案及相应的数据结构和算法,主要包括:碰撞检测系统中的设计问题、数学和几何学入门、包围体、基本图元测试、层次包围体技术、空间划分、BSP树层次结构、凸体算法、基于GPU的碰撞检测、数值健壮性、几何健壮性以及优化操作。另外,《实时碰撞检测算法技术》还提供了相应的算法、代码以及伪代码,以帮助读者进一步理解计算方案的实现过程。  《实时碰撞检测算法技术》适合作为高等院校计算机及相关专业的教材和教学参考书,也可作为相关开发人员的自学教材和参考手册。第1章 概述1.1 内容概览1.2 关于本书的代码第2章 碰撞检测系统中的设计问题2.1 碰撞算法的设计因素2.2 应用程序中对象的表达方式2.3 查询类型2.4 环境模拟参数2.5 性能2.6 健壮性2.7 实现与使用的简洁性2.8 小结第3章 数学和几何学入门3.1 矩阵3.2 坐标系统和顶点3.3 向量3.4 质心坐标3.5 直线、光线和线3.6 平面和半空间3.7 多边形3.8 多面体3.9 凸包计算3.10 域3.11 Minkowski和与Minkowski差3.12 小结第4章 包围体4.1 BV期望特征4.2 轴对齐包围盒4.3 Spheres球体4.4 方向包围盒4.5 球扫掠体4.6 半空间相交体4.7 其他类型的包围体4.8 小结第5章 基本图元测试5.1 最近点计算5.2 图元测试5.3 直线、光线和有向线相交测试5.4 其他类型的测试5.5 动态相交测试5.6 小结第6章 层次包围体技术6.1 层次结构设计问题6.2 层次结构的构建策略6.3 层次结构的遍历6.4 包围体层次结构示例6.5 合并包围体6.6 高效的树型表达方式及遍历6.7 通过缓存机制改善查询6.8 小结第7章 空间划分第8章 BSP树层次结构第9章 凸体算法第10章 基于GPU的碰撞检测第11章 数值健壮性第12章 几何健壮性第13章 优化操作参考文献

19,468

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC 图形处理/算法
社区管理员
  • 图形处理/算法社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧