java
数据结构之顺序表
线性表是最简单的一种数据结构,一个线性表有n个具有相同特性的的数据元素的序列。
1、手写顺序表
前驱元素:
若A元素的前面是B元素,则称B是A的前驱元素。没有前驱元素的元素成为头结点。
后继元素:
若A元素的后面是B元素,则称B是A的后继元素。没有后继元素的元素成为尾结点。
通过存储方式的不同,可以将线性表分为两类,一类是顺序表,另一类是链表。
顺序表的基本实现:
- 属性
属性名 | 解释 | 默认值 |
---|---|---|
elements[] | 存放元素的数组 | 长度为5的一个泛型数组 |
count | 当前元素的个数 | 0 |
- 方法
方法名 | 解释 | 返回值 |
---|---|---|
clear() | 清空数组,将count置0 | void |
isEmpty() | 判断数组是否为空 | boolean |
getLength() | 返回数组的长度 | int |
resize(int newSize) | 重新构建一个数组,newSize是新数组的长度 | void |
add(T e) | 在数组元素的最后面添加一个元素e | void |
add(T e,int i) | 在i下标添加元素e | void |
remove(int i) | 移除i下标的元素,返回移除的元素 | e |
indexOf(T e) | 返回指定元素第一次出现的下标,没有这个元素返回-1 | int |
- 代码
package com.tanke.day01;
import java.util.Iterator;
/**
* @author tanKe
* Date on 2021/11/28 9:56
*/
public class MyArrayList<T>{
private T[] elements; // 数组用于存放元素
private int count; // 元素的个数
public MyArrayList() {
elements = (T[]) new Object[5];
this.count = 0;
}
// 有参构造方法
public MyArrayList(int length) {
if (length<0){
throw new IllegalArgumentException("长度必须大于0!");
}
elements = (T[])new Object[length];
this.count = 0;
}
// 将线性表置空
public void clear(){
this.count = 0;
}
// 判断是否为空表
public boolean isEmpty(){
return count == 0;
}
// 获取线性表的长度
public int getLength(){
return this.count;
}
// 把数组进行扩容和缩容
private void resize(int newSize){
// 临时数组保存原数据
T[] temp = elements;
// 创建一个新的数组
elements = (T[]) new Object[newSize];
// 将原数据赋值给新数组
for(int i=0;i<count;i++){
elements[i] = temp[i];
}
}
// 最后面插入一个元素
public void add(T e){
// 判断数组是否满
if (count == elements.length){
resize(2*elements.length);
}
elements[count++] = e;
}
// 在指定下标添加元素
public void add(T e,int i) {
if (i>count || i<0){
throw new IllegalArgumentException("插入下标异常!");
}
// 判断数组是否满
if (count == elements.length){
resize(2*elements.length);
}
// 将i位置及后面的元素后移一位
for (int index=count;index>i;index--){
elements[index] = elements[index-1];
}
// 将元素插入到指定位置
elements[i] = e;
// 元素个数加1
count++;
}
// 移除一个指定下标的元素
public T remove(int i) {
if (i>=count || i<0){
throw new IllegalArgumentException("删除下标异常!");
}
// 记录i下标的元素并返回
T result = elements[i];
// 将i后面的元素依次向前移动一位
for(int index=i;index<count-1;index++){
elements[index] = elements[index+1];
}
count--;
// 把数组进行缩容
if (count<elements.length/4){
resize(elements.length/2);
}
return result;
}
// 返回指定元素第一次出现的下标,没有这个元素返回-1
public int indexOf(T e){
for(int i=0;i<count;i++){
if (e.equals(elements[i])){
return i;
}
}
return -1;
}
}
顺序表的遍历:
通过实现Iterable
接口,重写iterator()
方法,创建一个实现了Iterator
接口的内部类,就可以使用foreach
这种增强的遍历方式进行遍历。
- 实现
Iterable
接口。 - 重写
iterator()
方法,创建内部类。
// 线性表遍历的方法
@Override
public Iterator<T> iterator() {
return new myIterator();
}
private class myIterator implements Iterator<T>{
private int point;
public myIterator() {
this.point = 0;
}
// 如果存在下一个元素
@Override
public boolean hasNext() {
return point<count;
}
// 返回下一个元素
@Override
public T next() {
return elements[point++];
}
}
顺序表的时间复杂度:
由于顺序表的底层实现是数组,数组的长度的固定的,所以在涉及数组的扩容和缩容时,会使事件复杂度增加,所以顺序表的时间复杂度不是一个线性关系。在某些节点增加和删除时,由于数据量可以会导致时间复杂度急剧增加,所以顺序表在查询方面具有优势,但是在插入和删除方面有较明显的缺点。
2、ArrayList
源码分析
ArrayList
是java
中封装的一种顺序表,底层实现依然是数组。
ArrayList
实现了List这个接口,实现所有可选列表操作,并允许所有元素,包括null
。 除了实现List 接口
之外,该类还提供了一些方法来操纵内部使用的存储列表的数组的大小。 该size,isEmpty,get,set,iterator
和listIterator
操作在固定时间内运行。 add
操作以摊余常数运行 ,即添加n个元素需要O(n)个时间。 所有其他操作都以线性时间运行(粗略地说)。 与LinkedList
实施相比,常数因子较低。每个ArrayList
实例都有一个容量 。 容量是用于存储列表中的元素的数组的大小。 它总是至少与列表大小一样大。 当元素添加到ArrayList
时,其容量会自动增长。 没有规定增长政策的细节,除了添加元素具有不变的摊销时间成本。
重要属性:
属性名 | 解释 | 类型 |
---|---|---|
elementData[] | 用于存放数据的数组,Object类型的数组 | { },空数组 |
size | 当前元素的个数 | int |
DEFAULT_CAPACITY | 初始化容量大小10 | int |
两个构造器:
- 无参构造
public ArrayList() {
// DEFAULTCAPACITY_EMPTY_ELEMENTDATA 是一个空object数组
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
- 有参构造之:指定一个容器大小。
// initialCapacity 是一个初始话容器的大小
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
// EMPTY_ELEMENTDATA 是一个空数组
this.elementData = EMPTY_ELEMENTDATA;
} else {
// 如果参数小于0就抛出采参数异常
throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
}
}
- 有参构造器之:指定一个初始化集合。
// 将一个集合中的数据复制到ArrayList中
public ArrayList(Collection<? extends E> c) {
Object[] a = c.toArray();
if ((size = a.length) != 0) {
// 判断是否为一个对象,或者是否有共同的父类,存在就直接赋值
if (c.getClass() == ArrayList.class) {
elementData = a;
} else {
// 如果不是一个对象,就通过数组的方式,将数据一个一个赋值
elementData = Arrays.copyOf(a, size, Object[].class);
}
} else {
// 如果参数是一个空的集合,就将数组置为空数组
elementData = EMPTY_ELEMENTDATA;
}
}
2.1、ensureCapacity
// 确保容器的大小最小始终为10或者是指定的最小minCapacity容量
public void ensureCapacity(int minCapacity) {
// DEFAULT_CAPACITY一个常量为10
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) ? 0 : DEFAULT_CAPACITY;
// 如果指定的最小大小超过10,则将容器大小设置为指定的最小容量minCapacity
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
2.2、calculateCapacity
(私有)
// 计算一个数组的最小
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 如果是一个空数组就返回默认的最小容量和指定的最小容量之间的最大值
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
// 不是一个空数组就返回指定的最小容量
return minCapacity;
}
2.3、ensureCapacityInternal
(私有)
// 确保容器中有空间存放数据
private void ensureCapacityInternal(int minCapacity) {
// ensureExplicitCapacity这个方法可以动态增加数组的长度
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
2.4、ensureExplicitCapacity
(私有)
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 如果最小容量超过数组大小
if (minCapacity - elementData.length > 0)
// grow 方法增加数组的长度
grow(minCapacity);
}
2.5、grow (动态增加数组长度,私有)
private void grow(int minCapacity) {
// 旧的数组长度
int oldCapacity = elementData.length;
// 在旧的长度上扩大3倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 如果扩大3倍的长度还是小于指定的最小长度,就是用指定的最小长度
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// MAX_ARRAY_SIZE 是一个最大数组长度的常量值为Integer.MAX_VALUE - 8,
// 如果比指定的最大数组的长度还大,就设置为Integer这个数的最大值
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 重新创建一个数组,将旧数组的数据拷贝到新数组中
elementData = Arrays.copyOf(elementData, newCapacity);
}
2.6、size
// 返回当前数组元素的个数
public int size() {
return size;
}
2.7、isEmpty
// 判断数组是否为空
public boolean isEmpty() {
return size == 0;
}
2.8、contains
// 如果包含指定元素就返回true
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
2.9、lastIndexOf
// 找指定元素最后一次出现的索引,不存在就返回-1
public int lastIndexOf(Object o) {
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
2.10、toArray
// 将顺序表的数数据转换成数组存储
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
2.11、get(int index)
// 获取指定下标的元素,并返回
public E get(int index) {
// 检查下标是否越界,主要是检查下标超过当前元素个数
rangeCheck(index);
// 没有检查下标小于0,因为小标小于0,在数组取值的时候就会报错
return elementData(index);
}
2.12、set(int index,E element)
// 用指定元素替换对应下标的元素,并返回旧的元素
public E set(int index, E element) {
// 检查下标是否超过元素的个数
rangeCheck(index);
// 保存旧元素
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
2.13、add(E element)
// 在最后面添加一个元素,始终返回true,
public boolean add(E e) {
// 确保容量始终多一个
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
2.14、add(int index, E element)
// 在指定下标插入元素
public void add(int index, E element) {
// 检查下标是否合法
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1);
// 将index下标及后面的元素后移一位
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
2.15、remove(int index)
// 移除指定下标的元素并返回
public E remove(int index) {
// 检查下标是否合法
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
// 将下标后面的数据前移一位
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null;
return oldValue;
}
2.16、remove(E e)
// 移除指定元素,如果成功移除就返回true,如果不存在元素就返回false
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
2.17、clear
// 清空数组
public void clear() {
modCount++;
// 将每个下标的数据全部删除
for (int i = 0; i < size; i++)
elementData[i] = null;
// 将size置为0
size = 0;
}
2.18、addAll(Collection<? extends E> c)
// 将一个集合中的数据全部添加到最后面
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
// 添加数据的长度
int numNew = a.length;
// 增加数组的长度
ensureCapacityInternal(size + numNew);
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
2.19、addAll(int index, Collection<? extends E> c)
// 将一个集合中的数据添加到指定下标的后面
public boolean addAll(int index, Collection<? extends E> c) {
// 检查下标
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
int numMoved = size - index;
// 将下标及后面的数据向后移动numNew位
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,numMoved);
// 将集合的数据复制到对应下标的位置
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}