数据结构与算法02-数组的自实现ArrayList

一、数组介绍

  • 线性表是最基本、最简单、也是最常用的一种数据结构,一个线性表是n个具有相同特性的数据元素的有序数列。其中数组就是一种顺序表,顺序表存储是将数据元素放到一块连续的内存存储空间相邻数据元素的存放地址也相邻。 生活中的线性表:火车,糖葫芦
  • 数组是一种顺序存储的线性表,可以存储多个值,每个元素可以通过索引进行访问,所有元素的内存地址是连续的。

1.Java中数组的三种声明

  1. 数组类型 [] 数组名 = {em1,em2,em3,…,emN}; 声明数组的时候初始化,一共N个元素,例如:

int[] array = {3,5,4,8,12,5};//一共六个元素

  1. 数组类型[] 数组名 = new 数组类型[N] 用new关键字声明数组的同时指定数组长度,例如:

String[] str = new String[6]; //数组长度为6,即数组有六个元素

  1. 数组类型[] 数组名 = new 数组类型[] {em1,em2,em3,…,emN}; 用new关键字声明数组的同时初始化数组,例如:

int[] array = new int[] {2,4,5,6,8,9}; //array数组一共五个元素

注:数组一旦声明,数组长度就已经确定。每个数组都有一个length属性,不可改变。可以改变数组元素。

2. 优点

  1. 空间利用率高。
  2. 查询速度高效,通过下标来直接存取。

3. 缺点

  1. 插入和删除比较慢,比如:插入或者删除一个元素时,整个表需要遍历移动元素来重新排一次顺序。
  2. 不可以增长长度,有空间限制,当需要存取的元素个数可能多于顺序表的元素个数时,会出现"溢出"问题。当元素个数远少于预先分配的空间时,空间浪费巨大。

二、ArrayList自实现(动态数组)

1. 需求

  • 底层采用数组 实现动态扩容数组(ArrayList)

2. 面对的问题

  • 数组一旦创建,容量不可变
  • 扩容的条件

3.动态数组接口设计

intsize();// 元素的数量
booleanisEmpty();// 是否为空
booleancontains(E element);// 是否包含某个元素
voidadd(E element);// 添加元素到最后面
Eget(intindex);// 返回index位置对应的元素
Eset(intindex,E element);// 设置index位置的元素
voidadd(intindex,E element);// 往index位置添加元素
Eremove(intindex);// 删除index位置对应的元素
intindexOf(E element);// 查看元素的位置
voidclear();// 清除所有元素

4. 具体实现

/**
* 动态可变数组 自动扩容
*/
public class DynamicArray<E> {
	private int size = 0;//保存当前元素长度
	//定义默认初始化容量
	private static final int DEFAULT_CAPACITY = 10;
	//查找失败返回值
	private static final int ELEMENT_NOt_FOUND = -1;
	//用于保存数组元素
	private E[] elements = (E[]) new Object[DEFAULT_CAPACITY];
	
	public DynamicArray() {
		this(DEFAULT_CAPACITY);
	}
	/**
	* 带参初始化
	*
	* @param capacity 初始化容量
	*/
	public DynamicArray(int capacity) {
		if (capacity < 10){
			elements = (E[]) new Object[DEFAULT_CAPACITY];
		} else {
			elements = (E[]) new Object[capacity];
		}
	}
	
	/**
	* 检查索引越界
	*
	* @param index 当前访问索引
	*/
	private void checkIndex(int index) {
		if (index < 0 || index >= size) {
			throw new IndexOutOfBoundsException("索引越界" + "允许范围 size:0
=> " + (size - 1) + " 当前索引:" + index);
		}
	}
	/**
	* 检查添加索引越界
	*
	* @param index 添加位置的索引
	*/
	private void checkAddIndex(int index) {
		if (index < 0 || index > size) {
			throw new IndexOutOfBoundsException("索引越界" + "允许范围 size:0
=> " + (size) + " 当前索引:" + index);
		}
	}
	/**
	* 确保数组容量够用
	*/
	private void ensureCapacity() {
		//扩容1.5倍
		E[] newElements = (E[]) new Object[elements.length +
(elements.length >> 1)];
		for (int i = 0; i < size; i++) {
			newElements[i] = elements[i];
		}
		elements = newElements;
	}
	//无参构造方法
	public DyTest() {
	}
	/**
	* 带参初始化
	*
	* @param capacity 初始化容量
	*/
	public DyTest(int capacity) {
		if (capacity < 10) {
			elements = (E[]) new Object[DEFAULT_CAPACITY];
		} else {
		elements = (E[]) new Object[capacity];
		}
	}
	/**
	* 返回当前元素的数量
	*
	* @return 当前元素的个数
	*/
	public int size() {
		return size;
	}
	/**
	* 当前数组是否为空
	* 空:true
	* 非空:false
	*
	* @return 返回true | false
	*/
	public boolean isEmpty() {
		return size == 0;
	}
	/**
	* 是否包含某个元素
	*
	* @param element
	* @return 返回true | false
	*/
	public boolean contains(E element) {
		if (element == null) {
			for (int i = 0; i < size; i++) {
				if (elements[i] == null) return true;
			}
		} else {
			for (int i = 0; i < size; i++) {
				if (element.equals(elements[i])) return true;
			}
		}
		return false;
	}
	/**
	* 添加元素到尾部
	*
	* @param element 待添加的元素
	*/
	public void add(E element) {
		if (size > elements.length - 1) {
			ensureCapacity();
		}
		elements[size++] = element;
	}
	/**
	* 返回对应索引的值 不存在返回-1
	*
	* @param index 元素的索引
	* @return 对应值 | -1
	*/
	public E get(int index) {
		checkIndex(index);
		return elements[index];
	}
	/**
	* 设置index位置元素的值
	*
	* @param index 需要设置的位置索引
	* @param element 设置的值
	* @return 返回原先的值
	*/
	public E set(int index, E element) {
		checkIndex(index);//检查索引越界
		E old = elements[index];
		//
		elements[index] = element;
		return old;
	}
	/**
	* 向index位置添加元素
	*
	* @param index 插入位置的索引
	* @param element 插入的元素
	*/
	public void add(int index, E element) {
		checkAddIndex(index);//检查索引越界
		// 从最后一个往右移是为了避免左边的数据被覆盖
		for (int i = size; i > index; i--) {
			elements[i] = elements[i - 1];//把元素右移
		}
		elements[index] = element;
		size++;
	}
	/**
	* 移除index位置元素
	*
	* @param index 被移除元素的索引
	* @return 返回原先值
	*/
	public E remove(int index) {
		checkIndex(index);
		E old = elements[index];
		//从移除位置的下一个元素开始,把元素往左移
		for (int i = index; i < size; i++) {
			elements[i] = elements[i + 1];
		}
		elements[--size] = null;//清空最后一个元素
		return old;
	}
	/**
	* 查找元素
	*
	* @param element 需要查找的元素(注意)可能为null
	* @return 返回该元素索引 | -1
	*/
	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 (element.equals(elements[i])) {
					return i;
				}
			}
		}
		return ELEMENT_NOT_FOUND;
	}
	/**
	* 清空所有元素
	*/
	public void clear() {
		for (int i = 0; i < size; i++) {
			elements[i] = null;
		}
	}
	/**
	* 返回元素集合size:5, [1, 3, 4 ,5 ,7 ]
	*
	* @return
	*/
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder("size:" + size + " => [");
		for (int i = 0; i < size; i++) {
			if (i != 0) {
				sb.append(" ,");
			}
			sb.append(elements[i]);
		}
		sb.append("]");
		return sb.toString();
	}
}
## 代码解释说明:
  1. 代码第4行尖括号表示泛型,声明当前编写的这个类的类型,E代表宽泛的任意类型

  2. 代码第7行默认开辟10个容量,是因为Java底层也是开的10个,有需要的可以自己改大或者改小

  3. 代码第11行(E[]) 表示将类型转换为泛型

  4. 最好保留无参初始化,因为当后期重新new一个的时候,没有参数用起来很麻烦;无参和带参初始化,这叫做重载,这是面向对象的一个重要特征。

  5. 在设计是否包含某个元素的时候,做了一个是否为null的判断,因为在java中数组是可以存储null值的,即允许为空,所以当如果要查找的元素是null,就需要进行判断;

  6. 注意,==和equals是有区别的

    1. == 对于基本类型来说,就是比较其值是否相等;

    例如 a=1,b=1,那么a==b的值为true

    1. == 对于引用类型来说,就是比较对象内存地址存储的值是否相等;

    例如 a、b都为一个对象,其中都包含属性name,a.name = “张三”;b.name = “李四”;那么a.name == b.name 的值为false

    1. equals方法也是比较对象内存地址是否相等,但由于可以重写,所以比==使用起来更为宽泛
  7. 实现add在尾部添加元素方法时,要注意判断扩容条件,当 size > element.length - 1 就需要进行扩容。扩容一定是重新开辟一块新的空间,而不是在原来的空间里进行操作。

  8. elements.length + (elements.length >> 1)式中;>> 1代表除以2的意思,就是转化为2进制后向右移一位,相当于除以2(箭头向左是除以2的意思,运算速度和效率比使用乘法除法快,一个小技巧罢了),就相当于 elements.length 的0.5倍;前面再加上一个,所以总共是1.5;相当于扩容1.5倍;

  9. 实现add向index位置添加元素时,重新判断 index必须 > size,而不是 >= size;且是是从最后一个元素向右移,而不是从index位置开始向右移,因为这样会覆盖

  10. remove方法中可以考虑缩容,缩容的条件可以自己写,例如当前元素的个数小于数组长度的一半,就进行缩容,但缩容不是必须的;

  11. toString方法上方的@Override是重写的意思,因为在Java底层已经提供了一个toString方法,但如果你想改变输出的格式,可以自己写一个方法,用@Override告诉计算机覆盖掉原来的方法,使用自己重写的方法

  12. 重写的toString方法中调用了Java提供的StringBuilder的API,用来字符串拼接

  13. 最后的toString方法没有放在接口里面。

三、刷一道力扣题吧~

在这里插入图片描述在这里插入图片描述
在这里插入图片描述

解析:

  • 这题比较简单,有很多种方法,这里写一种我觉得效率比较高的方法:使用双指针,如果遇到需要写双层循环的时候,就可以考虑双指针;
  • 大概思路就是,有两个指针 i 和 j ,它们同时往前移动,当指针 i 遇到数字0的时候,就将第 i 个元素的值赋给第 j 个,然后再继续往前走,直到最后一个元素。
  • 代码如下:

在这里插入图片描述

四、总结

  • 线性表数组算是数据结构中最简单和常见的一种结构了,后面还会有更多简单高效的数据结构出现。
  • 巩固自己的同时也希望可以帮到大家~
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值