最近開發了一個郵政發票系統,其中有個需求是這樣的,發票的打印順序已經排序好了,但是用戶不是一次性全部打印,而是分段打印。比如現在有某種發票有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 + "]"; } }
