目录
3.6.将数组指定位置index处的元素值修改为新的值newData
3.11.校验当前传入的index是否合法(用在查询,设置,删除)
1.定义
基于动态数组实现的线性表。
用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般用数组存储。在数组上完成数据的增,删,查,改。
2.分类
2.1.静态顺序表
用定长数组存储。适用于确定知道需要存多少数据的场景。
原生int[]数组的问题:一旦定义一数组,长度固定,无法修改。int[] arr = new int[10];
a.数组长度开辟过小,能存储的数据太少;
b.数组长度开辟过大,会浪费大量空间。
2.2.动态顺序表(动态数组DynamicArray)
根据当前存储的数据元素,动态地进行数组的扩容(需要自己实现)。
实际上元素还是存储在int[]中,将原生int[]做了一层包装,使其具有动态扩容的功能。
3.顺序表的实现
所有数据结构无外乎CRUD四大方法。
import java.util.Arrays;
public class DynamicArray {
//定义属性
//elementData就是具体存储元素的数组
private int[] elementData; //是一个数组引用,未赋值,默认值为null
//size当前动态数组中实际存储的有效元素个数
private int size;
//elementData.length数组长度(含无效元素)
//无参构造方法
public DynamicArray() {
this.elementData = new int[10]; //不知道长度,默认存10个
}
//有参构造方法
public DynamicArray(int size) {
this.elementData = new int[size]; //规定好长度
}
/**
* 3.1.在数组头部添加元素
* @param data
*/
public void addFirst(int data) {
//此时内部小数组已满,需要扩容
if(size == elementData.length) {
grow();
}
for (int i = size - 1; i >= 0; i--) { //size-1是最后一个元素索引
elementData[i + 1] = elementData[i];
}
//此时index=0,数组头部空间空出来
elementData[0] = data;
size++;
}
/**
* 3.2.在数组尾部添加元素
* @param data
*/
public void addLast(int data) {
if(size == elementData.length) {
grow();
}
//数组最后一个元素下标索引为size-1,在其后即size处添加元素
elementData[size] = data;
size++;
}
/**
* 3.3.在数组任意index处添加元素
* @param index
* @param data
*/
public void addIndex(int index, int data) {
//先判断边界条件,index是否合法
if(index < 0 || index > size) {
System.err.println("index非法,无法进行插入!");
return;
}
//判断当前数组是否已满
if(size == elementData.length) {
grow();
}
if(index == 0) {
addFirst(data);
} else if(index == size) {
addLast(data);
} else {
for (int i = size - 1; i >= index; i--) {
elementData[i + 1] = elementData[i];
}
//此时index位置空出
elementData[index] = data;
size++;
}
}
public void addIndex2(int index, int data) {
if(index < 0 || index > size) {
System.err.println("index非法,无法进行插入!");
return;
}
if(size == elementData.length) {
grow();
}
//数据插入
for (int i = size - 1; i >= index; i--) {
elementData[i + 1] = elementData[i];
}
//此时index位置空出
elementData[index] = data;
size++;
}
public void addFirst2(int data) {
addIndex(0, data);
}
public void addLast2(int data) {
addIndex(size, data);
}
/**
* 3.4.取得当前数组长度
* @return
*/
public int getSize() {
return size;
}
/**
* 3.5.取得当前数组index位置处的元素值
* @param index
* @return
*/
public int get(int index) {
if(rangeCheck(index)) {
return elementData[index];
}
return -1;
}
/**
* 3.6.将数组指定位置index处的元素值修改为新的值newData
* @param index
* @param newData
* @return 修改前的元素值
*/
public int set(int index, int newData) {
if(rangeCheck(index)) {
int oldValue = elementData[index];
elementData[index] = newData;
return oldValue;
}
return -1;
}
/**
* 3.7.判断当前数组中是否包含指定元素值data
* @param data
* @return
*/
public boolean contains(int data) {
for (int i = 0; i < size; i++) {
if(elementData[i] == data) {
return true;
}
}
return false;
}
/**
* 3.8.删除数组index索引处的元素值
* @param index
*/
public void removeIndex(int index) {
if(rangeCheck(index)) {
//从index位置开始,将之后的元素统一前移一位
for (int i = index; i < size - 1; i++) {
elementData[i] = elementData[i + 1];
}
size--;
//确保数组中最后元素也还原为默认值
elementData[size] = 0;
}
}
/**
* 3.9.删除数组中所有值等于value的元素
* @param value
*/
public void removeValue(int value) {
for (int i = 0; i < size; i++) {
while(elementData[i] == value && i < size) { //i < size保证不会越界,此处可能会出现多个连续待删除的元素
//此时位置i就是待删除元素下标
removeIndex(i);
}
}
}
/**
* 3.10.内部扩容方法
*/
private void grow() {
int oldLength = elementData.length;
int newLength = oldLength << 1;
//将原数组搬移到新数组
elementData = Arrays.copyOf(elementData, newLength);
}
/**
* 3.11.校验当前传入的index是否合法(用在查询,设置,删除)
* @param index
* @return
*/
private boolean rangeCheck(int index) {
if(index < 0 || index >= size) {
System.err.println("当前index非法,index >= 0 && index < size");
return false;
}
return true;
}
/**
* 3.12.将当前动态数组转换为字符串 序列化
* @return
*/
public String toString() {
//每个元素都在elementData中存储
String ret = "[";
for (int i = 0; i < size; i++) {
ret += elementData[i];
if(i != size - 1) {
ret += ", ";
}
}
ret += "]";
return ret;
}
//测试
public static void main(String[] args) {
DynamicArray dynamicArray = new DynamicArray(2);
dynamicArray.addFirst(1);
dynamicArray.addFirst(2);
dynamicArray.addFirst(3);
dynamicArray.addLast(4);
dynamicArray.addLast(4);
dynamicArray.addLast(4);
dynamicArray.addIndex(1, 11);
dynamicArray.removeIndex(2);
dynamicArray.removeValue(4);
dynamicArray.set(2,9);
System.out.println(dynamicArray.get(0));
System.out.println(dynamicArray.contains(9));
System.out.println(dynamicArray);
}
}
3.1.在数组头部添加元素
/**
* 在数组头部添加元素
* @param data
*/
public void addFirst(int data) {
//此时内部小数组已满,需要扩容
if(size == elementData.length) {
grow();
}
//将数组头部以及之后的元素依次向后移动一个单位(从后往前移动)
for (int i = size - 1; i >= 0; i--) { //(size - 1)是最后一个元素的索引
elementData[i + 1] = elementData[i];
}
//此时数组头部位置空出来,将元素插入即可
elementData[0] = data;
size++;
}
3.2.在数组尾部添加元素
/**
* 在数组尾部添加元素
* @param data
*/
public void addLast(int data) {
//扩容
if(size == elementData.length) {
grow();
}
//数组中最后一个元素下标索引为size - 1, 在其后即size处添加元素
elementData[size] = data;
size++;
}
3.3.在数组任意index处添加元素
/**
* 在数组任意index处添加元素
* @param index
* @param data
*/
public void addIndex(int index, int data) {
//边界条件判断index是否合法
//index = 0 头插; index = size 尾插
if(index < 0 || index > size) {
System.err.println("index非法,无法进行插入!");
return;
}
//判断是否需要扩容
if(size == elementData.length) {
grow();
}
//数据插入
if(index == 0) { //头插
addFirst(data);
} else if(index == size) { //尾插
addLast(data);
} else { //在index处添加
//将index处及之后元素依次向后移动一个单位(从后向前移动)
for (int i = size - 1; i >= index ; i--) {
elementData[i + 1] = elementData[i];
}
//此时index处位置空出,插入元素即可
elementData[index] = data;
size++;
}
}
3.4.取得当前数组长度
/**
* 取得当前数组长度
* @return
*/
public int getSize() {
return size;
}
3.5.取得当前数组index位置处的元素值
/**
* 取得当前index处的元素值
* @param index
* @return
*/
public int get(int index) {
if(rangeCheck(index)) {
return elementData[index];
}
return -1;
}
3.6.将数组指定位置index处的元素值修改为新的值newData
/**
* 将数组指定位置index处的元素值修改为新的值newData
* @param index
* @param newData
* @return 修改前的元素
*/
public int set(int index, int newData) {
if(rangeCheck(index)) {
int oldValue = elementData[index];
elementData[index] = newData;
return oldValue;
}
return -1;
}
3.7.判断当前数组中是否包含指定元素值data
/**
* 判断当前集合中是否包含指定元素data
* @param data
* @return
*/
public boolean contains(int data) {
for (int i = 0; i < size; i++) {
if(elementData[i] == data) {
return true;
}
}
return false;
}
3.8.删除数组index索引处的元素值
/**
* 删除数组index索引处的元素值
* @param index
*/
public void removeIndex(int index) {
//判断index合法
if(rangeCheck(index)) {
//从index位置开始,将之后的元素统一前移一个单位(从前向后移)
for (int i = index; i < size - 1; i++) { //i < size - 1 走到倒数第二个元素(为了不越界)
elementData[i] = elementData[i + 1]; //elementData[i + 1] 走到最后一个元素
}
size--;
//确保数组中最后一个元素也还原为默认值
elementData[size] = 0;
}
}
3.9.删除数组中所有值等于value的元素
/**
* 删除数组中所有值等于value的元素
* @param value
*/
public void removeValue(int value) {
for (int i = 0; i < size; i++) { //i < size 能走到最后一个元素
while(elementData[i] == value && i < size) { //i < size 保证不会越界,此处可能会出现多个连续的待删除的元素,用while保证删除的正确性
//此时位置i就是待删除元素的下标
removeIndex(i);
}
}
}
3.10.内部扩容方法
/**
* 内部扩容方法
*/
private void grow() {
int oldLength = elementData.length;
int newLength = oldLength << 1; //左移一位相当于 * 2
//将原数组搬移到新数组
elementData = Arrays.copyOf(elementData, newLength);
}
3.11.校验当前传入的index是否合法(用在查询,设置,删除)
/**
* 效验当前传入的index是否合法(用在查询,设置,删除)
* @param index
* @return
*/
private boolean rangeCheck(int index) {
if(index < 0 || index >= size) {
//错误输出
System.err.println("当前index非法,index >= 0 && index < size");
return false;
}
return true;
}
3.12.将当前动态数组转换为字符串(序列化)
/**
* 将当前动态数组转换为字符串(序列化)
* @return
*/
public String toString() {
String ret = "[";
for (int i = 0; i < size; i++) {
ret += elementData[i];
if(i != size - 1) {
ret += ", ";
}
}
ret += "]";
return ret;
}
4.此种数据结构的特点
4.1.优点
- 查询快:当知道数组索引index时,可以立即查询元素值;
- 修改快:当知道数组索引index时,可以立即修改元素值。
4.2.缺点
- 在顺序表的头部/中间位置插入元素,删除元素时很耗时。时间复杂度为O(n),存在大量元素搬移工作;(而链表的插入和删除时间复杂度是O(1))
- 顺序表的扩容也是一个耗时操作,需要释放旧空间,拷贝原有数组的值,申请新空间O(n);
- 一般来说,顺序表的扩容都是按照原来的一倍扩容,造成大量空间的浪费。
PS:关于IDEA的调试
将出问题代码的那一行左边加断点:
选择调试运行:
调试区: