关于数值段相交的算法

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 的这样一个列表

有什么高效算法来计算这个结果?
...全文
180 12 打赏 收藏 转发到动态 举报
AI 作业
写回复
用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中有没有相等的元素

19,472

社区成员

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

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