专业编程基础技术教程

网站首页 > 基础教程 正文

学点算法(一)——ArrayList内部数组实现元素去重

ccvgpt 2024-10-12 14:05:43 基础教程 10 ℃


在ArrayList中,内部实际存储的元素的是数组,要实现元素去重,即是对该数组进行去重。常规思路是借助外部数据结构,如HashMap等来实现数据查找是否存在后去重,这里我们来实现一个利用内部自身数组的去重方法。

学点算法(一)——ArrayList内部数组实现元素去重

为了简要说明,我们自己来实现一个ArrayList,省略泛型实现,并且只实现add和remove方法,用来添加元素和删除元素,最后再实现我们要讲解的deduplicate去重方法。

MyArrayList实现

public class MyArrayList {
    private int size; // 规模
    private Object[] elems; // 数据区
	
    /**
     * 构造数组容量为c的ArrayList
     *
     * @param c 数组容量
     */
    public MyArrayList(int c) {
        this.elems = new Object[c];
        this.size = 0;
    }

    /**
     * 添加元素
     * @param o 元素对象
     */
    public void add(Object o) {
        if (this.elems.length <= this.size) {
            throw new ArrayIndexOutOfBoundsException("数组容量已满,请勿添加元素");
        }
        this.elems[size++] = o;
    }

    /**
     * 删除索引为i的元素
     * @param i 索引
     * @return 索引为i的元素
     */
    public Object remove(int i) {
        if (i < 0 || this.size <= i) {
            throw new ArrayIndexOutOfBoundsException("数组大小为" + this.size);
        }
        Object o = elems[i];
        System.arraycopy(elems, i + 1, elems, i, this.size - (i + 1));
        size--;
        return o;
    }

    /**
     * toString方法实现,方便我们查看数组元素情况
     * @return
     */
    @Override
    public String toString() {
        if (elems == null) {
            return "null";
        }

        int iMax = size - 1;
        if (iMax == -1) {
            return "[]";
        }

        StringBuilder b = new StringBuilder();
        b.append('[');
        for (int i = 0; ; i++) {
            b.append(elems[i]);
            if (i == iMax) {
                return b.append(']').toString();
            }
            b.append(", ");
        }
    }
}

我们先来测试一下add方法,往数组里面添加10个元素,如下:

MyArrayList myList = new MyArrayList(10);
for (int i = 0; i < 10; i++) {
    // 依次添加0~9的元素
    myList.add(i);
}
System.out.println(myList);

打印情况如下:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

符合我们的预期。

再来测试一下remove方法,在上述add方法后的基础上删除索引为5的元素,如下:

myList.remove(5);
System.out.println(myList);

打印情况如下:

[0, 1, 2, 3, 4, 6, 7, 8, 9]

符合我们的预期。

去重方法实现

借助remove方法实现

因为我们只能借助内部数组,所以在判断元素是否存在的时候,我们要去遍历数组判断之前是否出现过该元素,步骤如下:

  1. 从索引为1位置开始遍历数组元素(因为索引为0时,只有一个元素,无需判断)。
  2. 对于第i个元素,到该数组中从头开始查找之前是否出现过该元素。
  3. 如果出现了该元素,调用remove方法删除。
  4. 遍历结束。

代码实现如下:

/**
 * 去重
 */
public void deduplicate() {
    if (this.size <= 1) {
        // 只有一个元素,无需去重,直接返回
        return;
    }
    // 从索引为1开始遍历数组元素
    for (int i = 1; i < size; i++) {
        for (int j = 0; j < i; j++) {
            // 对于第i个元素,到该数组中继续从头开始查找之前是否出现过该元素
            if (Objects.equals(elems[j], elems[i])) {
                // 之前出现过该元素,调用remove方法将它删除
                remove(i);
                // 如果元素删除后,遍历索引也需要减一个元素大小,这步很容易遗漏,要注意
                i--;
                // 已经判定有重复元素,并且已经删除,查找过程结束
                break;
            }
        }
    }
}

测试代码如下:

MyArrayList myList = new MyArrayList(20);
myList.add(123);
myList.add(24);
myList.add(42);
myList.add(24);
myList.add(123);
myList.add(56);
myList.add(92);
myList.add(68);
myList.add(24);
myList.add(56);
myList.add(73);
System.out.println(myList);
myList.deduplicate();
System.out.println(myList);

运行结果如下:

[123, 24, 42, 24, 123, 56, 92, 68, 24, 56, 73]
[123, 24, 42, 56, 92, 68, 73]

符合我们的预期。

标记清除法实现

如果使用remove方法实现,在每次删除元素的时候,都要O(n)的算法复杂度,这里我们可以暂时先把元素记下来,不直接删除,在最后遍历结束时,统一进行删除。我们这里可以用null进行标记(如果不允许元素为null的话),表示元素为待删除元素。

元素移动过程如下所示,绿色表示就位元素(已移动完成):


最后绿色的部分表示我们需要的元素。

代码实现如下:

public void deduplicate() {
    if (this.size <= 1) {
        // 只有一个元素,无需去重,直接返回
        return;
    }
    // 重复元素个数
    int dupCnts = 0;
    // 从1开始遍历数组元素
    for (int i = 1; i < size; i++) {
        for (int j = 0; j < i; j++) {
            if (Objects.equals(elems[j], elems[i])) {
                // 之前出现过该元素,将它标记为null
                elems[i] = null;
                // 重复元素个数+1
                dupCnts++;
                break;
            }
        }
    }
    if (dupCnts == 0) {
    	// 没有重复元素
    	return;
    }
    int j = 1; // null元素区间开始位置
    int k = 1; // 非null元素区间开始位置
    int len = 0; // 非null元素区间长度
    // 删除标记为null的元素
    for (int i = 1; // 遍历索引
         i < size;
         i++) {
        if (elems[i] == null) {
            // 如果为null元素
            if (len > 0) {
                // 如果有待移动的非null元素
                if (j < k) {
                    // 如果需要移动,如果j==k则不需要移动
                    // 将非null区间元素移到null元素区间开始的位置
                    System.arraycopy(elems, k, elems, j, len);
                }
                // 移动之后null元素的开始位置需要更新
                j = j + len;
                // 移动完毕,非null元素区间长度重置为0
                len = 0;
            }
        } else {
            // 如果为非null元素
            if (len == 0) {
                // 如果非null区间元素长度为0,表示一个新的非null元素区间开始
                // 更新k的值
                k = i;
            }
            // 非null元素区间长度+1
            len++;
        }
    }
    if (len > 0) {
        // 需要处理末尾剩余元素
        // 这里j肯定小于k,无需判断
        System.arraycopy(elems, k, elems, j, len);
    }
    // 因为删除了重复元素,所以数组大小需要减去相应元素个数
    size -= dupCnts;
}

测试结果与上述结果相同。

Tags:

最近发表
标签列表