数据结构之动态数组

数据结构

数据结构--用于储存和管理数据的方式

就是以下几种方式:

 神奇的网站

这个网站可以完成对各种数据结构的演示 推荐大家去使用 网址如下:

数据结构和算法动态可视化 (Chinese) - VisuAlgo

线性表之动态数组

线性表

线性表:具有n个相同类型元素的有限序列

 几个概念

索引0处的节点对于索引1处的节点来说称之为前驱节点

索引1处的节点对于索引0处的节点来说称之为后继节点

索引0处的节点称之为首节点 索引n-1处的节点称之为尾节点

常见的线性表:数组、链表、栈、队列、哈希表

数组

数组:即顺序储存的线性表 就是元素的内存地址是连续的

假使我有一个这样一个数组:int[] array = new int[]{11, 22, 33};

 数组的弊端

许多编程语言都无法进行数组容量的动态改变 但是我们肯定是希望能够按需改变我们的数组容量 因此我们可以设置这样一个接口 那就是ensureCapacity()方法

数组的接口设计

int size():记录数组元素个数

boolean isEmpty():查看数组是否为空

boolean contains(E element):查看数组是否包含指定元素

void add(E element):将指定元素添加到数组最后

E get(int index):获取指定位置处的元素

E set(int index, E element):修改指定位置处的元素 并返回修改前指定位置处的元素

void add(int index, E element):往指定位置处添加指定元素

E remove(int index):删除指定位置处的元素

int indexOf(E element):查看指定元素的位置

void clear():清空数组

数组的代码实现

public class ArrayList{
    // 定义两个私有成员变量
    private int size;
    private int[] elements;
    // 定义两个常量
    private static final int DEFAULT_CAPACITY = 10;
    private static final int ELEMENT_NOT_FOUND = -1;
    // 定义一个带参构造方法
    public ArrayList(int capacity){
        // 为数组容量重新赋值 
        capacity = (capacity <= DEFAULT_CAPACITY) ? DEFAULT_CAPACITY : capacity;
        // 然后为数组赋初始值
        elements = new int[capacity];                        
    }
    // 定义一个无参构造方法
    public ArrayList(){
        // 直接调用带参构造方法即可
        this(DEFAULT_CAPACITY);
    }
    // 获取数组元素个数
    public int size(){
        return size;
    }
    // 判断数组是否为空
    public boolean isEmpty(){
        return size == 0;
    }
    // 判断数组是否包含指定元素
    public boolean contains(int element){
        return indexOf(element) != -1;
    }
    // 将指定元素添加到数组末尾处
    public void add(int element){
        // 直接调用add(int index, int element)方法
        add(size, element);
    }
    // 将指定元素添加到指定位置处
    public void add(int index, int element){
        // 首先进行边界检查
        rangeCheckForAdd(index);
        // 然后将指定索引后面的元素均后退一步 但是是倒叙遍历
        for(int i = size - 1; i >= index; i--){
            elements[i + 1] = elements[i];
        }
        // 然后将指定元素插入到指定位置处
        elements[index] = element;
        // 然后最后导致size++
        size++;
    }
    // 获取指定索引处的元素
    public int get(int index){
        // 首先进行边界检查
        rangeCheck(index);
        // 直接返回指定索引处的元素
        return elements[index];
    }
    // 重置指定位置处的元素
    public int set(int index, int element){
        // 首先进行边界检查
        rangeCheck(index);
        // 首先获取指定位置处上的元素
        int oldElement = elements[index];
        // 重置指定位置处上的元素
        element[index] = element;
        // 返回指定位置处的旧值
        return oldElement;
    }
    // 删除指定位置处的元素 并且返回
    public int remove(int index){
        // 首先进行边界检查
        rangeCheck(index);
        // 定义一个变量 用于记录指定位置处上的元素
        int oldElement = elements[index];
        // 然后遍历数组 将指定位置后面的元素向前进一步
        for(int i = index; i < size - 1; i++){
            elements[i] = elements[i + 1];
        }
        // 最后导致size--
        size--;
        // 然后返回指定位置处上的旧值
        return oldElement;
    }
    // 获取指定元素在数组中的索引 如果没有 就返回一个常量
    public int indexOf(int element){
        // 遍历数组
        for(int i = 0; i < size; i++){
            if(elements[i] == element)return i;
        }
        // 如果在数组中找不到的话 那么就直接返回常量
        return ELEMENT_NOT_FOUND;
    }
    // 清空数组
    public void clear(){
        // 数组的清空其实上是逻辑清空 实际上并没有真正清空 而是让你访问不到而已
        size = 0;
    }
    // 正常的边界检查
    public void rangeCheck(int index){
        if(index < 0 || index >= size){
            outOfBounds(index);
        }
    }
    // 针对add()方法的边界检查
    public void rangeCheckForAdd(int index){
        if(index < 0 || index > size){
            outOfBounds(index);
        }
    }
    // 对索引越界做出相关处理
    public void outOfBounds(int index){
        throw new IndexOutBoundsException("Size:" + size + " Index:" + index);
    }
    // 重写toString方法
    @override
    public String toString(){
        // 创建一个StringBuilder对象
        StringBuilder sb = new StringBuilder();
        // 添加前缀
        sb.append("Size:").append(size).append(" [");
        // 遍历数组 将内中元素拼接到sb后面
        for(int i = 0; i < size; i++){
            if(i != 0){
                sb.append(", ");
            }
            sb.append(elements[i]);
        }
        // 添加后缀
        sb.append("]");
        // 返回最终结果
        return sb.toString();
    }
}

添加泛型以后的代码实现

public class ArrayList<E>{
    // 定义两个私有成员变量
    private int size;
    private E[] elements;
    // 定义两个常量
    private static final DEFAULT_CAPACITY = 10;
    private static final ELEMENT_NOT_FOUND = -1;
    // 定义两个构造方法
    public ArrayList(int capacity){
        // 为数组容量重新赋值
        capacity = (capacity <= DEFAULT_CAPACITY) ? DEFAULT_CAPACITY : capacity;
        elements = (E[])new Object[capacity];   
    }
    public ArrayList(){
        // 直接调用带参构造方法
        this(DEFAULT_CAPACITY);
    }
    // 获取数组元素个数
    public int size(){
        return size;
    }
    // 判断数组是否为空
    public boolean isEmpty(){
        return size == 0;
    }
    // 判断数组是否含有指定元素
    public boolean contains(E element){
        return indexOf(element) != -1;
    }
    // 将指定元素添加到数组末尾处
    public void add(E element){
        // 直接调用add(int index, E element)方法
        add(size, element);
    }
    // 将指定元素添加到指定位置处
    public void add(int index, E element){
        // 对指定索引进行边界检查
        rangeCheckForAdd(index);
        // 判断是否需要进行扩容操作 必要是需要进行扩容
        ensureCapacity(size + 1);
        // 然后将指定索引后面的元素均往后移动一步 记得是倒叙遍历
        for(int i = size; i > index; i--){
            elements[i] = elements[i - 1];
        }
        // 然后将指定元素插入到指定位置处
        elements[index] = element;
        // 最后导致size++
        size++;
    }
    // 删除指定位置处的元素
    public E remove(int index){
        // 首先对指定索引进行边界检查
        rangeCheck(index);
        // 然后定义一个变量 用于储存指定索引处的元素
        E oldElement = elements[index];
        // 然后遍历指定索引后面的元素 并将他们均往前走一步 记的是正序遍历
        for(int i = index + 1; i < size; i++){
            elements[i - 1] = elements[i];
        }
        // 然后在size--的同时顺便完成最后索引处元素的置空
        elements[--size] = null;
        // 返回指定索引处的旧值
        return oldElement;
    }
    // 获取指定索引处的元素
    public E get(int index){
        // 首先对指定索引进行边界检查
        rangeCheck(index);
        // 返回指定索引处的元素
        return elements[index];
    }
    // 重置指定索引处的元素
    public E set(int index, E element){
        // 首先对指定索引进行边界检查
        rangeCheck(index);
        // 然后定义一个变量 用于记录指定索引处的旧值
        E oldElement = elements[index];
        // 然后重置指定索引处的值
        elements[index] = element;
        // 返回最终结果
        return oldElement;
    }
    // 获取指定索引在数组中的索引
    public int indexOf(E element){
        // 有可能出现参数为空的现象 所以需要进行条件判断
        if(element == null){
            // 遍历数组 然后看有无对应的元素
            for(int i = 0; i < size; i++){
                if(elements[i] == null)return i;
            }
        }else{
            // 遍历数组 
            for(int i = 0; i < size; i++){
                if(elements[i].equals(element))return i;
            }
        }
        // 如果数组中没有对应的值的话 那么就直接返回常量
        return ELEMENT_NOT_FOUND;
    }
    // 清空数组
    public void clear(){
        // 我们这次需要做的是将每一个数组元素的引用给消除掉 从而销毁每一个对象所占有的内存空间
        for(int i = 0; i < size; i++){
            elements[i] = null;
        }
        // 最终必然会导致size=0
        size = 0;
    }
    // 正常的边界检查
    public void rangeCheck(int index){
        if(index < 0 || index >= size){
            outOfBounds(index);
        }
    }
    // 针对add()方法的边界检查
    public void rangeCheckForAdd(int index){
        if(index < 0 || index > size){
            outOfBounds(index);
        }
    }
    // 对索引越界进行处理
    public void outOfBounds(int index){
        throw new IndexOutOfBoundsException("Size:" + size + " Index:" + index);
    }
    // 重写toString方法
    public String toString(){
        // 创建一个StringBuilder对象
        StringBuilder sb = new StringBuilder();
        // 添加前缀
        sb.append("Size:").append(size).append(" [");
        // 遍历数组 将每个元素都拼接到sb后面去
        for(int i = 0; i < size; i++){
            if(i != 0){
                sb.append(", ");
            }
            sb.append(elements[i]);
        }
        // 添加后缀
        sb.append("]");
        // 返回最终结果
        return sb.toString();
    }
    // 数组扩容操作
    public void ensureCapacity(int size){
        // 首先获取以下旧数组的容量
        int oldCapacity = elements.length;
        // 如果参数没有超过旧数组的容量的话 那么就不做处理
        if(oldCapacity >= size)return;
        // 重新定义数组容量
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // 然后创建一个新数组
        E[] newElements = (E[])new Object[newCapacity];
        // 然后将旧数组的元素直接拷贝到新数组中
        for(int i = 0; i < size; i++){
            newElements[i] = elements[i];
        }
        // 然后将旧数组指向新数组
        elements = newElements;
        // 最后进行以下提示
        System.out.println(oldCapacity + "扩容为" + newCapacity);
    }
}

添加泛型后的代码实现的一些细节

空值的讨论

如果允许数组中储存空值的话 那么就可以在indexOf()中进行分类讨论

但是如果不允许数组中储存空值的话 那么可以在add()方法中把控输入并且不需要进行分类讨论 输入如下代码

public void add(int index, E element){
    if(element == null)return;
    ……
}

对象数组详解

1.为什么这个对象数组储存的是地址而不是值呢?

假使我的代码如下所示

Object[] objects = new Object[10];
Object[0] = new people(11, 170);

你看上面这个例子:首先Object类元素占用4个字节的内存空间

而people类则含有两个成员变量 都是int类型 所以一个people类占用8个字节 试想一下一个4个字节的内存空间怎么能够做得到储存8个字节的数据 做不到 因此我们才采用数组存值、然后通过引用指向数据的方式

2.elements = null 和 elements[i] = null 的区别?

 

对于elements = null来说 他首先会让它所指向的对象数组销毁掉 然后再让对象数组所指向的对象占用的内存空间给销毁掉

而对于elements[i] = null 来说 他直接将它所指向的对象所占用的内存空间给销毁掉了

3.我们怎么知道对象有无死亡?

我们可以在销毁的对象内部重写finalize()方法 比如你是销毁的people类对象 那么你就是在people类内部重写finalize()方法

然后你可以在主方法中写一个System.gc()用于提醒jvm回收机制赶紧回收垃圾 因为jvm回收机制是会在内存快满的时候才开始进行清理工作 而我们的时间可是很宝贵的 哪里耗得起

动态数组的内存管理细节

这边我主要想解释以下第二个图

为什么要添加框出来的代码呢?这是因为遍历完毕后 size-1和size-2索引处的地址指向相同到时候如果想要删除的话 是不能完全删除掉的 因为数据被两个引用指向 你其实只删除了一个引用

查看动态数组的源代码

你自己写完以后 可以对照着java官方给出的版本进行对照 应该吻合度还蛮高的 按住ctrl键不放即可 然后点击java.util.ArrayList

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

axihaihai

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值