1. 程式人生 > >Java實現陣列去重和兩陣列交併集

Java實現陣列去重和兩陣列交併集

前言

Java平臺

陣列去重

基本的陣列去重法

HashMap實現陣列去重

兩陣列交集

基本的兩陣列求交法

HashMap版的兩陣列求交法

兩陣列並集

基本的兩陣列求並法

HashMap版的兩陣列求並法

Matlab平臺

Matlab處理陣列去重

Matlab求兩陣列交集

Matlab求兩陣列並集

後記


前言

        前幾天,有人問我兩陣列的交併集如何實現,我當時回覆是使用HashMap進行操作。轉念一想,這是個數學問題,那就必須得看看Matlab原始碼是如何實現,發現都是通過陣列去重實現,因此我索性就將這三者混在一起,寫篇部落格。

Java平臺

        在Java平臺處理陣列問題,大多數都是遍歷陣列,然後逐資料處理。對於陌生問題,我們處理的方式都是先解決問題,再優化解決問題的方式,因此,大多數演算法都會有簡單(能處理問題,但是效率比較低)的演算法和優化版本的演算法。下面我也是先給出我自己的基本處理方法,再通過思考進而實現它的優化演算法。

陣列去重

基本的陣列去重法

        對於陣列去重,其實我們可以像氣泡排序一般,逐個比較,非重複我就裝在另一個unique陣列中,如果這個資料有重複,我就檢視unique陣列是否已經包含你,包含了,就忽略,不包含,就將該資料也裝進unique陣列中。Talk is cheap. 程式碼如下:

public static int[] unique(int[] array){
    int len = -1;
    if(array == null || (len = array.length) < 2){
        return len == -1 ? null : Arrays.copyOf(array, len);
    }
    int[] uniqueArray = new int[len];
    int uniqueCount = 0;
    outer:
    for(int value : array){
        for(int i = 0; i < uniqueCount; i++){
            if(uniqueArray[i] == value){
                continue outer;
            }
        }
        uniqueArray[uniqueCount++] = value;
    }
    uniqueArray = Arrays.copyOf(uniqueArray, uniqueCount);
    Arrays.sort(uniqueArray);  // These unique values in array are in disorder.
    return uniqueArray;
}

HashMap實現陣列去重

        觀察陣列去重後的最終結果,發現所有資料都互不相同(這句話是廢話,不然怎麼叫去重呢,哈哈哈!),如果我們能保證我們最終結果的各資料都互不相同,且涵蓋原先陣列的所有值,這問題就解決了。涵蓋陣列所有值,通過遍歷,倒是好解決,如何使最終結果的各資料都互不相同,此時我們要想到什麼資料結構能保證資料的唯一性,腦袋裡面瞬間反應就是二叉樹和雜湊表,進而想想Java集合庫有沒有這兩種資料結構的實現,沒有就自己造輪子,查了查,倒是有很多,比如二叉樹就有TreeMap和TreeSet,雜湊表的就有HashMap、HashSet和HashTable,至於併發包裡面的就先不考慮了,暫時我們還沒涉及併發處理的情況。所有我們只要從這上面隨便選一種就可以了。看標題就知道,我選擇雜湊表中的HashMap來實現陣列的唯一化。程式碼如下:

public static int[] unique(int[] array){
    int len = -1;
    if(array == null || (len = array.length) < 2){
        return len == -1 ? null : Arrays.copyOf(array, len);
    }
    HashMap<Integer, Object> uniqueMap = new HashMap<>(); // No certain, no initial
    for(int value : array){
        uniqueMap.put(value, null); // Equivalent to HashSet
    }
    int uniqueNums = uniqueMap.size();
    int[] uniqueArray = new int[uniqueNums];
    for(Integer key : uniqueMap.keySet()){
        uniqueArray[--uniqueNums] = key;
    }
    Arrays.sort(uniqueArray); // For sequenced array
    return uniqueArray;
}

兩陣列交集

基本的兩陣列求交法

        對於兩陣列求交集,其實處理原理和氣泡排序法差不多,一個數組逐個比較另一個數組的所有值,找到相同的了,就看unique陣列是否包含該值,包含,就忽略,不包含,就直接新增。程式碼如下:

public static int[] intersect(int[] aArray, int[] bArray){
    if(aArray == null || bArray == null){
        return null;
    }
    int aLen = 0, bLen = 0;
    if((aLen = aArray.length) == 0 || (bLen = bArray.length) == 0){
        return new int[0];
    }
    int intersectLen = (aLen > bLen) ? bLen : aLen;
    int[] intersectArray = new int[intersectLen];
    int iCount = 0;
    for(int aValue : aArray){
        boolean isContain = false;
        for(int bValue : bArray){
            if(aValue == bValue){
                isContain = true;
                break;
            }
        }
        if(isContain){
            boolean isExist = false;
            for(int i = 0; i < iCount; i++){
                if(intersectArray[i] == aValue){
                    isExist = true;
                    break;
                }
            }
            if(!isExist){
                intersectArray[iCount++] = aValue;
            }
        }
    }
    intersectArray = Arrays.copyOf(intersectArray, iCount);
    Arrays.sort(intersectArray);
    return intersectArray;
}

HashMap版的兩陣列求交法

        求兩個陣列的交集,如果先對某個陣列實現去重,再另外一個數組與之逐個比較,有相等的值,那該值就可以新增到交集中。也是利用帶唯一性資料結構來解決該問題。這次依舊使用HashMap來實現該演算法,程式碼如下:

public static int[] intersect(int[] aArray, int[] bArray){
    if(aArray == null || bArray == null){
        return null;
    }
    int aLen = 0, bLen = 0;
    if((aLen = aArray.length) == 0 || (bLen = bArray.length) == 0){
        return new int[0];
    }
    HashMap<Integer, Boolean> intersectMap = new HashMap<>();
    for(int aValue : aArray){
        intersectMap.put(aValue, true);
    }
    int intersectLen = (aLen > bLen) ? bLen : aLen;
    int[] intersectArray = new int[intersectLen];
    int iCount = 0;
    for(int bValue : bArray){
        Boolean isAdd = intersectMap.get(bValue);
        if(isAdd != null && isAdd){ // Can only be added once.
            intersectArray[iCount++] = bValue;
            intersectMap.put(bValue, false);
        }
    }
    intersectArray = Arrays.copyOf(intersectArray, iCount);
    Arrays.sort(intersectArray);
    return intersectArray;
}

兩陣列並集

基本的兩陣列求並法

        求並集相當於對兩個陣列分別求unique,再剔除兩unique陣列的交集。程式碼如下:

public static int[] union(int[] aArray, int bArray){
    int aLen = 0;
    if(aArray == null || (aLen = aArray.length) == 0){
        return unique(bArray);
    }
    int bLen = 0;
    if(bArray == null || (bLen = bArray.length) == 0){
        return unique(aArray);
    }
    int unionLen = aLen + bLen;  // May throw OutOfMemoryError or NegativeArraySizeException 
    int[] unionArray = new int[unionLen];
    int uCount = 0;
    outer:
    for(int aValue : aArray){
        for(int i = 0; i < uCount; i++){
            if(unionArray[i] == aValue){
                continue outer;
            }
        }
        unionArray[uCount++] = aValue;
    }
    outer:
    for(int bValue : bArray){
        for(int i = 0; i < uCount; i++){
            if(unionArray[i] == bValue){
                continue outer;
            }
        }
        unionArray[uCount++] = bValue;
    }
    unionArray = Arrays.copyOf(unionArray, uCount);
    Arrays.sort(unionArray);
    return unionArray;
}

HashMap版的兩陣列求並法

        對兩陣列求並也可以看做是對這兩個陣列所組成的大陣列求unique。同樣使用HashMap處理,程式碼如下:

public static int[] union(int[] aArray, int[] bArray){
    if(aArray == null || aArray.length == 0){
        return unique(bArray);
    }
    if(bArray == null || bArray.length == 0){
        return unique(aArray);
    }
    HashMap<Integer, Object> unionMap = new HashMap<>();
    for(int aValue : aArray){
        unionMap.put(aValue, null);
    }
    for(int bValue : bArray){
        unionMap.put(bValue, null);
    }
    int unionLen = unionMap.size();
    int[] unionArray = new int[unionLen];
    for(int key : unionMap.keySet()){
        unionArray[--unionLen] = key;
    }
    Arrays.sort(unionArray);
    return unionArray;
}

Matlab平臺

        Matlab內部的矩陣運算全部都是用針對特定CPU在彙編級別優化過的矩陣運算庫實現的,所以該語言效率的主要體現在矩陣化操作,而Java唯一的優勢就是迴圈(只是相對於Matlab來說)。Matlab的演算法思想主要是圍繞矩陣化操作來展開,並且對迴圈處理極為排斥(隨著版本更新,迴圈問題好了點。),因此有些習慣類C型語言的人反而會寫出超低效率的Matlab程式碼。接下來我們就來看看Matlab是如何處理上面的這些問題的,雖然我是用Java實現,但矩陣化思想依然保留其中。

Matlab處理陣列去重

        演算法思路:該演算法利用了差分來剔除重複值。首先對陣列進行排序,初始化長度為陣列長度的boolean陣列diff來儲存資料的差分資訊,如果差分等於0,說明該值重複,diff陣列在此記作false,不等於零則記作true。最後遍歷diff陣列,將為true值下標的值新增到unique陣列。再把第一個數新增其中(第一個數肯定與前面不重複),最後為了讓結果好看,排序unique陣列即可。

public static int[] unique(int[] array){
    int len = -1;
    if(array == null || (len = array.length) < 2){
        return len == -1 ? null : Arrays.copyOf(array, len);
    }
    array = Arrays.copyOf(array, len); // Avoid polluting the original array.
    Arrays.sort(array);
    boolean[] diffs = new boolean[len];
    diffs[0] = true;
    int uCount = 1;
    for(int i = 1; i < len; i++){
        if(array[i] != array[i - 1]){
            uCount++;
            diffs[i] = true;
        }
    }
    int[] uniqueArray = new int[uCount];
    for(int i = 0, index = 0; i < len; i++){
        if(diffs[i]){
            uniqueArray[index++] = array[i];
        }
    }
    return uniqueArray;
}

Matlab求兩陣列交集

        演算法思路:該演算法依舊是利用了差分的性質,只不過這次比較隱蔽。它首先把兩陣列a和b都進行unique操作,得到兩個unique陣列,再將這兩個陣列拼接在一起,對其排序,得到陣列c,如果a和b有交集2,那麼陣列c中一定有兩個挨著的2,因此陣列c中所有相鄰且相等的數值(差分值等於0)都是陣列a和b的交集。程式碼如下:

public static int[] intersect(int[] aArray, int bArray){
    if(aArray == null || bArray == null){
        return null;
    }
    if(aArray.length == 0 || bArray.length == 0){
        return new int[0];
    }
    int[] uA = unique(aArray);
    int[] uB = unique(bArray);
    int uaLen = uA.length;
    int ubLen = uB.length;
    int uabLen = uaLen + ubLen;
    int[] sortuAuB = new int[uabLen]; // // May throw OutOfMemoryError and NegativeArraySizeException.
    System.arraycopy(uA, 0, sortuAuB, 0, uaLen);
    System.arraycopy(uB, 0, sortuAuB, uaLen, ubLen);
    Arrays.sort(sortuAuB);
    int indInterABLen = uaLen > ubLen ? ubLen : uaLen;
    int[] indInterAB = new int[indInterABLen];
    int iCount = 0;
    for(int i = 1; i < uabLen; i++){
        if(sortuAuB[i] == sortuAuB[i - 1]){
            indInterAB[iCount++] = sortuAuB[i++]; // The next absolutely unequal.
        }
    }
    return Arrays.copyOf(indInterAB, iCount);
}

Matlab求兩陣列並集

        演算法思路:把兩個陣列拼接起來,所有工作全交由unique處理,哈哈哈!

public static int[] union(int[] aArray, int[] bArray){
    int aLen = 0;
    if(aArray == null || (aLen = aArray.length) == 0){
        return unique(bArray);
    }
    int bLen = 0;
    if(bArray == null || (bLen = bArray.length) == 0){
        return unique(aArray);
    }
    int abLen = aLen + bLen;
    int[] unionAB = new int[abLen]; // // May throw OutOfMemoryError and NegativeArraySizeException
    System.arraycopy(aArray, 0, unionAB, 0, aLen);
    System.arraycopy(bArray, 0, unionAB, aLen, bLen);
    return unique(unionAB); // Call unique to do all the work.
}

後記

        雖然看起來Java平臺處理的程式碼思路和Matlab平臺處理的大相徑庭,但細想起來其實是處理資料方式造成的差異。Java擁有唯一性資料的資料結構,因此直接使用這種資料結構就能解決問題,而Matlab並無這些複雜的資料結構,只有矩陣操作,因此它想要獲取資料的唯一性資訊,就只能通過排序和差分來實現。綜上來看,這三類問題都是通過資料的唯一性來解決。

        這一篇博文沒有任何引用,主要是因為我當時利用唯一性,很快就寫完這些演算法了,並且進行單元測試還沒有問題,由於時間的關係就沒有參考別人的博文。這三個基本法是我寫博文時,臨時加上去的,當時只是想寫Java平臺和Matlab平臺程式碼思路的對比,並沒考慮這麼多,後來想了想還是加上去了。當時寫的時候,腦袋裡面全是用唯一性進行處理,最後強迫自己,只能用最基本矩陣操作來實現,才寫出這三個基本法,哎,竟然被自己知道的東西所限制,頭疼!