数据结构基础之动态数组

本文探讨了数据结构的基础,包括线性结构、树结构和图,并重点分析了动态数组的实现与应用。动态数组在Java中的应用如HashMap的红黑树,以及在文件压缩算法中的哈夫曼树。文章详细阐述了动态数组的添加、删除、修改和查询操作的时间复杂度,并指出在特定操作序列下可能导致的时间复杂度震荡问题。为解决这个问题,提出了在删除元素后延迟缩容的懒惰策略,当元素数量达到数组容量的四分之一时才进行缩容。
摘要由CSDN通过智能技术生成

数据结构基础

  • 数据结构研究的是数据如何在计算机中进行组织和存储,使得我们可以高效的获取数据或者修改数据
  • 数据结构类型
    • 线性结构:数组、栈、队列、链表
    • 树结构:红黑树、二叉树、AVL
    • 还有其他结构比如图
  • 数据结构的应用
    • MySql中的B+tree
    • JAVA中的HashMap的红黑树
    • 文件压缩算法,哈夫曼算法

动态数组

  • 数组最大的有点:快速查询,但是不能动态扩容
  • 创建一个可以动态变化的数组 定义一个size指向最后一个元素的后面一个元素,capacity表示数组的容量
package com.frank;

/**
 * Description:动态数组
 *
 * @author: frank
 * @date: 2021/1/21 21:28
 */
public class MyArray<E> {

    /**
     * 数组的下个索引位置
     */
    private int nextIndex;
    /**
     * 数组
     */
    private E[] data;

    /**
     * 数组初始化大小
     */
    private static int initCapacity = 10;

    public MyArray(int capacity) {
        data = (E[])new Object[capacity];
        nextIndex = 0;
    }

    /**
     * 构造默认构造函数,大小为10
     */
    public MyArray() {
        this(initCapacity);
    }

    /**
     * @return 数组中元素的个数
     */
    public int size() {
        return nextIndex;
    }

    /**
     * @return 数组是否为空
     */
    public boolean isEmpty() {
        return nextIndex == 0;
    }

    /**
     * 数组的最后添加一个元素
     * @param item 添加的元素
     */
    public void addLast(E item) {
        add(nextIndex, item);
    }

    /**
     * 像数组的头部添加元素
     * @param item 添加的元素
     */
    public void addFirst(E item) {
        add(0, item);
    }

    public void add(int index, E item) {
        if (index < 0 || index > nextIndex) {
            throw new IllegalArgumentException("index error");
        }

        if (nextIndex == data.length) {
            // 进行扩容,扩容的长度为原来的2倍
            resize(2 * data.length);
        }

        // 把index后面的元素移动一个位置即可
        for (int i = nextIndex - 1; i > index - 1; i--) {
            data[i+1] = data[i];
        }
        data[index] = item;
        nextIndex ++;
    }

    /**
     * 获取指定位置的元素
     * @param index
     * @return
     */
    public E get(int index) {
        if (index < 0 || index >= nextIndex) {
            throw new IllegalArgumentException("index illegal");
        }
        return data[index];
    }

    /**
     * 设置值
     * @param index
     * @param item
     */
    public void set(int index, E item) {
        if (index < 0 || index >= nextIndex) {
            throw new ArrayIndexOutOfBoundsException("index illegal");
        }
        data[index] = item;
    }

    public E findElement(int index) {
        return data[index];
    }

    /**
     *
     * @param item
     * @return 不存在的时候返回-1
     */
    public int findIndex(E item) {
        for (int i = 0; i < nextIndex; i++) {
            if (data[i].equals(item)) {
                return i;
            }
        }
        return -1;
    }

    /**
     * 是否包含某个元素
     * @param item
     * @return
     */
    public boolean contains(E item) {
        for (int i = 0; i < nextIndex; i++) {
            if (item.equals(data[i])) {
                return true;
            }
        }
        return false;
    }

    public E remove(int index) {
        if (index < 0 || index >= nextIndex) {
            throw new ArrayIndexOutOfBoundsException("remove index error");
        }

        E item = data[index];
        // 把index位置之后的元素都往前移动一个位置,nextIndex--
        for (int i = index; i < nextIndex - 1; i++) {
            data[i] = data[i + 1];
        }
        nextIndex --;

        // 缩容版本一,假如元素的个数只有数组大小的一半且长度不小于2的时候的时候缩小容量
        /*if (nextIndex == data.length / 2 && data.length / 2 != 0) {
            resize(data.length / 2);
        }*/

        // 缩容版本二,假如元素的个数只有数组大小的四分之一的时候缩小容量
        // 这样可以降低add->remove->add->remove造成频繁扩容缩容带来的时间复杂度
        if (nextIndex == data.length / 4 && data.length / 2 != 0) {
            resize(data.length / 2);
        }
        return item;
    }

    public E removeElement(E item) {
        int index = findIndex(item);
        return remove(index);
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(String.format("MyArray size=%d capacity=%d\r\n", nextIndex, data.length));
        sb.append('[');
        for (int i = 0; i < nextIndex; i++) {
            sb.append(data[i]);
            if (i != nextIndex - 1) {
                sb.append(",");
            }
        }
        sb.append(']');
        return sb.toString();
    }

    private void resize(int newCapacity) {
        E[] newData = (E[])new Object[newCapacity];
        for (int i = 0; i < nextIndex; i++) {
            newData[i] = data[i];
        }
        data = newData;

    }
}

  • 分析动态数组的时间复杂度
操作综合时间复杂度
增加O(n)
删除O(n)
修改带索引O(1),否则为O(n)
查询带索引O(1),否则为O(n)
  • 复杂度震荡
    • 在执行一个扩容的操作时候,立马又执行了缩容的操作,体现在代码中的是addLast(扩容),removeLast(缩容),反复执行出现复杂度震荡
public void add(int index, E item) {
        if (index < 0 || index > nextIndex) {
            throw new IllegalArgumentException("index error");
        }

        if (nextIndex == data.length) {
            // 进行扩容,扩容的长度为原来的2倍
            resize(2 * data.length);
        }

        // 把index后面的元素移动一个位置即可
        for (int i = nextIndex - 1; i > index - 1; i--) {
            data[i+1] = data[i];
        }
        data[index] = item;
        nextIndex ++;
    }

// 删除方法
public E remove(int index) {
        if (index < 0 || index >= nextIndex) {
            throw new ArrayIndexOutOfBoundsException("remove index error");
        }

        E item = data[index];
        // 把index位置之后的元素都往前移动一个位置,nextIndex--
        for (int i = index; i < nextIndex - 1; i++) {
            data[i] = data[i + 1];
        }
        nextIndex --;

        // 缩容,假如元素的个数只有数组大小的一半的时候缩小容量
        if (nextIndex == data.length / 2) {
            resize(data.length / 2);
        }
        return item;
    }
  • 数组优化
    • 在执行removeLast缩容resize的时候过于着急,可以采取懒惰(Lazy)的解决方式:当数组的大小缩容到原来的容量的四分之一时,这个时候将数组的容量缩小为原来的一半
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值