区间并集求解算法实现
最近开发了一个邮政发票系统,其中有个需求是这样的,发票的打印顺序已经排序好了,但是用户不是一次性全部打印,而是分段打印。比如现在有某种发票有1w条,打印顺序为1-10000,用户现在打印了4次,第一次为从1-3000,第二次为 3000-6000,第三次为7000-1000。打印完了后,用户怀疑中间有一段没有打印。又打印了5000-6500的发票,现在我要计算用户共打印了多少条发票?
其实这个问题可以抽象为数学上计算区间的并集。
A=[1,3000], B=[3000,6000], C=[7000,10000], D=[5000,65000]
求 A∪B∪C∪D.
这个题目用一个数轴来求解很简单,但是如果是N个区间的并集呢? 这就不是人干的了.
现在给出算法,思路如下:
假设算出来的集合如下:
…[an,bn]∪[an+1 ,bn+1]∪[an+2, bn+2]∪…
记为Z
现在再加入一个区间 [c,d]
1. 判断 如果c∈Z,那么再判断d,如果d不属于Z,那么我们把包含在[c,d]之间区间都去掉,并修改包含c的那个区间右端点为d;如果d属于Z,那么我们把包含c,d的区间以及他们之间的区间都合并为一个区间
2. 判断 如果c不属于Z,那么把[c,d]加到Z中,…[am,am+1] ∪[c,d] ∪[am+2,am+3]…,然后从区间[c,d]开始遍历,去掉重复的部分。
遍历集合Z的方法为,从某一个区间左端点开始,
代码如下: 共两个类:UnionAlgorithm.java , Interval.java
package test; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * <pre> * 该类用ArrayList表示区间的集合,每一个元素表示一个区间, * ArrayList中元素顺序为这些元素表示的区间按照在数轴上在顺序排列, * * * </pre> * @author luoxian * @since Sep 5, 2008 4:38:09 PM * @version 1.0 */ public class UnionAlgorithm { /** * 存放结果 集合 */ private List value = new ArrayList(); /** * 计算主方法 * * @param param * @return */ public List calc(Interval[] param) { Interval[] interval = check(param); if (interval == null || interval.length == 0) return null; if (value.size() == 0 && interval.length >= 1) value.add(interval[0]); for (int i = 1; i < interval.length; i++) { //判断该区间的左端点在value中的位置 int[] left_position = getPosition(value, 0, interval[i].getLeft()); if (left_position[0] == 1) { //1 左端点属于value的情况 int[] right_position = getPosition(value, left_position[1], interval[i].getRight()); if (right_position[0] == 1) { //1.1 右端点也属于value if (left_position[1] == right_position[1]) continue; ((Interval)value.get(left_position[1])).setRight( ((Interval)value.get(right_position[1])).getRight()); for (int j = left_position[1] + 1; j <= right_position[1]; j++) { value.remove(j); } } else { //1.2右端点不属于value ((Interval)value.get(left_position[1])).setRight(interval[i].getRight()); for (int j = left_position[1] + 1; j < right_position[1]; j++) { value.remove(j); } } } else { //2 左端点不属于value value.add(left_position[1], interval[i]); refresh(left_position[1]); } } return value; } public void refresh(int index) { Interval temp = (Interval)value.get(index); int[] index_right_position = getPosition(value, index + 1, temp.getRight()); if (index_right_position[0] == 1) { Interval include_right = (Interval)value.get(index_right_position[1]); temp.setRight(include_right.getRight()); for (int i = index + 1; i <= index_right_position[1]; i++) { value.remove(i); } } else if (index_right_position[0] == 0){ //删除该区间内的所有区间 for (int i = index + 1; i < index_right_position[1]; i++) { value.remove(i); } } } /** * <pre> * 在集合value中查找点point的位置 * 从序列号为from开始查找 * * 返回:为一个数组,包括两个值[type,index] * 如果type=0,表示point点不在集合value中的任何一个区间内,该点在第index区间的前面 * 如果type=1,表示point点在集合value中的第index的元素区间内 * * (说明:type为0的特殊情况为该点在集合中最后一个区间的后面,此时 * 尽管value.get(value.size())并不存在,我们仍然把index赋值为value.size()) * * </pre> * @param value * @param from * @param point * @return */ public int[] getPosition(List value, int from, int point) { if (from >= value.size()) return new int[]{0, value.size()}; if (point < ((Interval)value.get(from)).getLeft()) { return new int[]{0, from}; } for (int i = from; i < value.size() - 1; i++) { Interval tmp = (Interval)value.get(i); if (tmp.isContain(point)) return new int[]{1,i}; Interval tmpLater = (Interval)value.get(i + 1); if (point >tmp.getRight() && point < tmpLater.getLeft()) return new int[]{0, i + 1}; } //比较最后一个区间 Interval last = (Interval)value.get(value.size() - 1); if (last.isContain(point)) return new int[]{1, value.size() - 1}; else return new int[]{0,value.size()}; } /** * 数组的检查, 数组元素右边的数不得小于右边的元素 * @param duan * @return */ public Interval[] check(Interval[] temp){ //:-不破坏参数原则 Interval[] interval = new Interval[temp.length]; for (int i = 0; i < interval.length; i++) { interval[i] = new Interval(temp[i]); } //:- int length = interval.length; for (int i = 0; i < interval.length; i++) { if (interval[i].getRight() < interval[i].getLeft()) { interval[i] = null; length --; } } Interval[] result = new Interval[length]; int index = 0; for (int i = 0; i < interval.length; i++) { if (interval[i] != null){ result[index] = interval[i]; index ++; } } return result; } public List getValue() { return value; } public static void main(String[] args) { Interval[] v = {new Interval(1,3), new Interval(4,2)}; UnionAlgorithm ua = new UnionAlgorithm(); Interval d1 = new Interval(7,9); Interval d2 = new Interval(5,7); Interval d3 = new Interval(-1,4); Interval d4 = new Interval(8,10); Interval d5 = new Interval(10,12); Interval d6 = new Interval(4,1); Interval d7 = new Interval(8,10); Interval d8 = new Interval(3,14); Interval d9 = new Interval(15,17); Interval c1 = new Interval(0,4545); Interval c2 = new Interval(32,54); Interval c3 = new Interval(123,456); Interval c4 = new Interval(34,54); Interval c5 = new Interval(12,23); Interval[] duan = {d1,d2,d3}; // Interval[] duan = {d1,d2,d3,d4,d5,d6,d7,d8,d9,c1,c2,c3,c4,c5}; long sc = System.currentTimeMillis(); ua.calc(duan); //Thread.sleep(1); sc = System.currentTimeMillis() - sc; System.out.println(ua.getValue() + "花费:" + sc); } } //下面是区间bean package test; /** * 表示一个区间[left, right] * @author luoxian * @since Sep 5, 2008 4:48:15 PM * @version 1.0 */ public class Interval { private int left; private int right; public Interval(Interval interval) { this.left = interval.getLeft(); this.right = interval.getRight(); } public Interval(int left, int right) { this.left = left; this.right = right; } public boolean isContain(int point) { if (point <= right && point >= left) return true; else return false; } public int getLeft() { return left; } public int getRight() { return right; } public void setLeft(int left) { this.left = left; } public void setRight(int right) { this.right = right; } public String toString(){ return "[" + left + " , " + right + "]"; } }