前言
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 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 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 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平台代码思路的对比,并没考虑这么多,后来想了想还是加上去了。当时写的时候,脑袋里面全是用唯一性进行处理,最后强迫自己,只能用最基本矩阵操作来实现,才写出这三个基本法,哎,竟然被自己知道的东西所限制,头疼!