目录
😽个人主页: tq02的博客_CSDN博客-C语言,Java,Java数据结构领域博主
🌈梦的目标:努力学习,向Java进发,拼搏一切,让自己的未来不会有遗憾。
🎁欢迎各位→点赞👍 + 收藏⭐ + 评论📝+关注✨
本章讲解内容:顺序表的讲解
使用编译器:IDEA
一.顺序表
顺序表(ArrayList):用一段物理地址连续的存储单元依次存储数据元素的线性结构,通常采用数组存储。在数组上完成数据的增删查改。
二.顺序表的手动实现
2.1顺序表的创建
import java.util.Arrays;
public class MyArraysList {
private int[] elem;
private int usedSize; // 默认值是0
private static final int DEFAULT_SIZE = 4; // 定义为常量,更加安全
// 初始化顺序表
public MyArraysList() {
this.elem = new int[DEFAULT_SIZE];
}
}
这边是创建顺序表的类,不仅要创建出来,我们还需要学会使用,例如增、删、查、改。
2.2.基本功能的实现
2.2.1扩容顺序表
// 对顺序表进行扩容 public void expand() { this.elem = Arrays.copyOf(this.elem, this.usedSize * 2); System.out.println("已经成功扩容至原来的两倍"); // 给用户提醒 }
注: 在进行增加元素的操作时,当数组满了,则需要扩容,才能容纳下一个元素
2.2.2 判断顺序表是否为满
/** * 判断当前顺序表是不是满了 * @return true->满了,false->还没满 */ public boolean isFull() { if (this.usedSize == this.elem.length) return true; else return false; }
注:扩容前得判断是否数组满了,只有满了,才需要扩容
2.2.3 判断顺序表是否为空
/** * 判断当前顺序表是否为空 * @return true->空的,false->还没空 */ public boolean isempty() { if (this.usedSize == 0) { return true; } else return false; }
注:如果需要删除顺序表的元素,得判断顺序表是否为空,毕竟空的顺序表无法删除元素。
2.2.4打印顺序表
// 打印顺序表 public void display() { for (int i = 0; i < this.elem.length; i++) { System.out.print(this.elem[i] + " "); } System.out.println(); } }
注:顺序表存放了数据,得显示出来,展示你存放的数据
2.2.5清空顺序表
// 清空顺序表 public void clear() { for (int i = 0; i < this.usedSize; i++) { this.elem[i] = 0; } this.usedSize = 0; // 注意有效数组长度也要清零 }
注:当代码结束之后,需要释放顺序表内存。
2.3四大功能的实现
2.3.1增加元素
头插法:
// 新增元素,在数组最前面新增 public void addHead(int data) { if (isFull()) { System.out.println("数组满了,需要扩容"); expand(); } else { // 从usedSize下标开始,不会数组越界(此时的elem.length > usedSize) for (int i = this.usedSize; i > 0; --i) { this.elem[i] = this.elem[i - 1]; // 从后往前挪动数据,为的是给顺序表的表头腾出来 } this.elem[0] = data; // 在顺序表开头插入 this.usedSize++; // 数组有效长度加一 }
注:每次增加的元素,都放在顺序表的第一位。
尾插法:
//在数组最后新增 public void addTail(int data) { if (isFull()) { System.out.println("数组满了需要扩容"); expand(); } else this.elem[this.usedSize++] = data; }
注:每次插入元素,直接放在顺序表的最后一位。
指定下标值插入:
// 在 pos 位置新增元素 public void addPos(int pos, int data) { if (pos < 0 || pos > usedSize) { System.out.println("pos位置不合法"); return; } if (isFull()) { System.out.println("数组满了需要扩容"); expand(); } else { // 如果插入位置在顺序表的中间,要注意挪动元素,从后向前挪动,这样不会造成元素值的覆盖 for (int i = this.usedSize - 1; i >= pos; --i) { this.elem[i + 1] = this.elem[i]; } this.elem[pos] = data; // 挪动完毕,可以赋值插入 this.usedSize++; // 当前元素数加一 } }
若在对应下标插入元素,则下标值后面的所有元素整体向后移一位。
注意:需要判断数组是否满了、插入位置在元素下标范围内。
2.3.2删除元素
头删:
// 删除表头元素 public void removeHead() { if (isempty()) { System.out.println("顺序表为空,删除不合法"); return; } // 从第一个元素开始,用后面元素的值覆盖掉前面的值,遍历整个数组就相当于把第一个元素用覆盖的方式抹去了 for (int i = 1; i < this.usedSize; i++) { this.elem[i - 1] = this.elem[i]; } this.elem[this.usedSize - 1] = 0; // 减少了一个元素,所以之前最后一个元素置0 this.usedSize--; // 不要忘记改变有效数组的长度 }
注:从第二个元素开始,每一个元素覆盖前面一个,再将最后一个元素清零。
尾删:
// 删除表尾元素 public void removeTail() { if (isempty()) { System.out.println("顺序表为空,删除不合法"); return; } this.elem[this.usedSize - 1] = 0; // 直接将最后一个元素置0就完成了尾删 this.usedSize--; // 不要忘记改变有效数组的长度 }
注:直接删除末尾元素,将最后一个元素置0
指定下标元素的删除:
// 指定下标元素的删除 public void removePos(int pos) { if (isempty()) { System.out.println("顺序表为空,删除不合法"); return; } if (pos < 0 || pos >= this.usedSize) { System.out.println("pos下标不合法"); } else { for (int i = pos; i < this.usedSize - 1; ++i) { this.elem[i] = this.elem[i + 1]; // 从要删除的下标开始,用后边元素的值覆盖掉前面的值,就完成了删除 } this.elem[this.usedSize - 1] = 0; // 最后一个元素置0 this.usedSize--;// 删除后更改顺序表的有效长度 } }
注:与头删法相似。
删除首次出现的元素:
//删除第一次出现的关键字key public void removeKey(int toRemove) { if (isempty()) { System.out.println("顺序表为空,删除不合法"); return; } for (int i = 0; i < this.usedSize; i++) { if (this.elem[i] == toRemove) { // 注意是this.usedSize - 1,将此时 i 之后的元素统一往前搬移一个位置 for (int j = i; j < this.usedSize - 1; ++j) { this.elem[j] = this.elem[j + 1]; } this.elem[this.usedSize - 1] = 0; // 要完整的删除,将挪动的最后一个元素的原本位置 置空 this.usedSize--; // 删除后不要忘记更改顺序表的有效长度 return; // 只删除第一次出现的 } } }
注:找到对应下标值,然后再进行覆盖,在删除的后面全体元素向前挪一位
2.3.3查找元素
获取指定位置元素:
// 获取 pos 位置的元素 public int getPos(int pos) { if (pos < 0 || pos >= this.usedSize) { // 注意这里当pos==this.usedSize也是不合法的,因为此时的pos下标所要获取的是顺序表中第usedSize+1个元素 System.out.println("pos的位置不合法"); return -1; } else { return this.elem[pos]; } }
注:考虑要获取的位置是否合法、返回指定位置的元素
获取指定元素所在的位置:
// 查找某个元素所对应顺序表中的位置 public int indexOf(int toFind) { for (int i = 0; i < this.usedSize; i++) { if (this.elem[i] == toFind) { return i; } } System.out.println("在数组中没有找到该元素"); return -1; }
注:返回下标值
2.3.4更改数据
在对应下标值,进行修改:
// 给 pos 位置的元素设为 value public void setPos(int pos, int value) { if (pos < 0 || pos > this.usedSize) { System.out.println("pos位置不合法"); return; } if (pos == this.usedSize) { // 对这种情况要单独处理,此时相等于增加元素 this.elem[pos] = value; this.usedSize++; } else { this.elem[pos] = value; } }
几乎是查找的步骤上,对其修改。
总代码
📝MyArraysList.java
MyArraysList.java用于顺序表的实现
import java.util.Arrays;
public class MyArraysList {
private int[] elem;
private int usedSize; // 默认值是0
private static final int DEFAULT_SIZE = 4; // 定义为常量,更加安全
// 初始化顺序表
public MyArraysList() {
this.elem = new int[4];
}
// 对顺序表进行扩容
public void expand() {
this.elem = Arrays.copyOf(this.elem, this.usedSize * 2);
System.out.println("已经成功扩容至原来的两倍"); // 给用户提醒
}
/**
* 判断当前顺序表是否为空
* @return true->空的,false->还没空
*/
public boolean isempty() {
if (this.usedSize == 0) {
return true;
}
else return false;
}
/**
* 判断当前顺序表是不是满了
* @return true->满了,false->还没满
*/
public boolean isFull() {
if (this.usedSize == this.elem.length) return true;
else return false;
}
// 打印顺序表
public void display() {
for (int i = 0; i < this.elem.length; i++) {
System.out.print(this.elem[i] + " ");
}
// System.out.println(Arrays.toString(this.elem));或者用Arrays.toString打印也行
System.out.println();
}
// 新增元素,默认在数组最后新增
public void addTail(int data) {
if (isFull()) {
System.out.println("数组满了需要扩容");
expand();
}
else this.elem[this.usedSize++] = data;
}
// 新增元素,在数组最前面新增
public void addHead(int data) {
if (isFull()) {
System.out.println("数组满了,需要扩容");
expand();
}
else {
// 从usedSize下标开始,不会数组越界(此时的elem.length > usedSize)
for (int i = this.usedSize; i > 0; --i) {
this.elem[i] = this.elem[i - 1]; // 从后往前挪动数据,为的是给顺序表的表头腾出来
}
this.elem[0] = data; // 在顺序表开头插入
this.usedSize++; // 数组有效长度加一
}
}
// 1、判断pos位置是否合法(在顺序表中,数据是连续的,中间不能有空缺)
// 2、判断顺序表是否满了,如果满了,需要扩容
// 3、插入数据(可能需要挪作元素)
// 在 pos 位置新增元素
public void addPos(int pos, int data) {
if (pos < 0 || pos > usedSize) {
System.out.println("pos位置不合法"); return;
}
if (isFull()) {
System.out.println("数组满了需要扩容");
expand();
}
else {
// 如果插入位置在顺序表的中间,要注意挪动元素,从后向前挪动,这样不会造成元素值的覆盖
for (int i = this.usedSize - 1; i >= pos; --i) {
this.elem[i + 1] = this.elem[i];
}
this.elem[pos] = data; // 挪动完毕,可以赋值插入
this.usedSize++; // 当前元素数加一
}
}
// 删除表头元素
public void removeHead() {
if (isempty()) {
System.out.println("顺序表为空,删除不合法");
return;
}
// 从第一个元素开始,用后面元素的值覆盖掉前面的值,遍历整个数组就相当于把第一个元素用覆盖的方式抹去了
for (int i = 1; i < this.usedSize; i++) {
this.elem[i - 1] = this.elem[i];
}
this.elem[this.usedSize - 1] = 0; // 现在的最后一个元素是原来的倒数第二个元素, 所以原来的最后一个有效元素要置0
this.usedSize--; // 不要忘记改变有效数组的长度
}
// 删除表尾元素
public void removeTail() {
if (isempty()) {
System.out.println("顺序表为空,删除不合法");
return;
}
this.elem[this.usedSize - 1] = 0; // 直接将最后一个元素置0就完成了尾删
this.usedSize--; // 不要忘记改变有效数组的长度
}
// 指定下标元素的删除
public void removePos(int pos) {
if (isempty()) {
System.out.println("顺序表为空,删除不合法");
return;
}
if (pos < 0 || pos >= this.usedSize) {
System.out.println("pos下标不合法");
}
else {
for (int i = pos; i < this.usedSize - 1; ++i) {
this.elem[i] = this.elem[i + 1]; // 从要删除的下标开始,用后边元素的值覆盖掉前面的值,就完成了删除
}
this.elem[this.usedSize - 1] = 0; // 要完整的删除,将挪动的最后一个元素的原本位置 置空
this.usedSize--;// 删除后不要忘记更改顺序表的有效长度
}
}
//删除第一次出现的关键字key
public void removeKey(int toRemove) {
if (isempty()) {
System.out.println("顺序表为空,删除不合法");
return;
}
for (int i = 0; i < this.usedSize; i++) {
if (this.elem[i] == toRemove) {
// 注意是this.usedSize - 1,将此时 i 之后的元素统一往前搬移一个位置
for (int j = i; j < this.usedSize - 1; ++j) {
this.elem[j] = this.elem[j + 1];
}
this.elem[this.usedSize - 1] = 0; // 要完整的删除,将挪动的最后一个元素的原本位置 置空
this.usedSize--; // 删除后不要忘记更改顺序表的有效长度
return; // 只删除第一次出现的
}
}
}
/**
* /判定是否包含某个元素
* @param toFind 要查找的元素
* @return true->包含, false->不包含
*/
public boolean contains(int toFind) {
for (int i = 0; i < this.usedSize; i++) {
if (this.elem[i] == toFind) return true;
}
return false;
}
// 查找某个元素对应的位置
public int indexOf(int toFind) {
for (int i = 0; i < this.usedSize; i++) {
if (this.elem[i] == toFind) {
return i;
}
}
System.out.println("在数组中没有找到该元素");
return -1;
}
// 获取 pos 位置的元素
public int getPos(int pos) {
if (pos < 0 || pos >= this.usedSize) {
System.out.println("pos的位置不合法");
return -1;
}
else {
return this.elem[pos];
}
}
// 给 pos 位置的元素设为 value
public void setPos(int pos, int value) {
if (pos < 0 || pos > this.usedSize) {
System.out.println("pos位置不合法");
return;
}
if (pos == this.usedSize) { // 对这种情况要单独处理,此时相等于增加元素
this.elem[pos] = value;
this.usedSize++;
}
else {
this.elem[pos] = value;
}
}
// 获取顺序表长度
public int size() {
return this.usedSize;
}
// 清空顺序表
public void clear() {
for (int i = 0; i < this.usedSize; i++) {
this.elem[i] = 0;
}
this.usedSize = 0; // 注意有效数组长度也要清零
}
}
📝Test.java
Test.java用于顺序表的各个接口的测试
/**
* 对顺序表进行测试的代码
*/
public class Test {
public static void main(String[] args) {
MyArraysList myArraysList = new MyArraysList();
// 尾插四个数字
myArraysList.addTail(12);
myArraysList.addTail(32);
myArraysList.addTail(17);
myArraysList.addTail(32);
// 进行扩容
myArraysList.expand();
// 指定在pos位置新增元素
myArraysList.addPos(1, 777);
// 打印当前的顺序表
System.out.print("第一次测试的顺序表为:");
myArraysList.display();
// 删除顺序表中首次出现的元素32
myArraysList.removeKey(32);
// 获取顺序表中 1下标的元素值
int tmp = myArraysList.getPos(1);
System.out.println("顺序表中下标为1的元素的值为:" + tmp);
// 给顺序表中1下标的元素设为value
myArraysList.setPos(1, 100);
// 打印此时的顺序表
System.out.print("修改后第二次测试的顺序表为:"); // 此时1下标的值为100
myArraysList.display();
// 顺序表此时的有效长度
System.out.println("顺序表的有效长度为:" + myArraysList.size());
// 清空顺序表
myArraysList.clear();
System.out.print("第三次测试的顺序表为:");
myArraysList.display();
System.out.println("清空顺序表后,顺序表的有效长度为:" + myArraysList.size());
}
}
三.ArrayList 讲解
实现了List接口,底层为动态类型顺序表,也就是说在Java中,已经写好了顺序表,我们可以在了解顺序表的基础原理之后,我们就可以更好的使用该类。
代码实现:
public static void main(String[] args) {
// ArrayList创建,推荐写法
// 构造一个空的列表
List<Integer> list1 = new ArrayList<>();
// 构造一个具有10个容量的列表
List<Integer> list2 = new ArrayList<>(10);
list2.add(1);
list2.add(2);
list2.add(3);
// list2.add("hello"); // 编译失败,List<Integer>已经限定了,list2中只能存储整形元素
// list3构造好之后,与list中的元素一致
ArrayList<Integer> list3 = new ArrayList<>(list2);
// 避免省略类型,否则:任意类型的元素都可以存放,使用时将是一场灾难
List list4 = new ArrayList();
list4.add("111");
list4.add(100);
}
ArrayList使用的方法
方法 | 解释 |
boolean add(E e) | 尾插e |
void add(int index,E element) | 将e插入index的位置 |
boolean addAII(Collection <?extendsE>c | 尾插c中的元素 |
E remove(int index) | 删除index位置元素 |
boolean remove(Object o) | 删除遇见的第一个 o |
E get(int index) | 获取下标index 位置元素 |
E set(int index,E element) | 将下标index 位置元素设置为element |
void clear() | 清空 |
boolean contains(Object o) | 判断o是否在线性表内 |
代码实现:
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("JavaSE");
list.add("JavaWeb");
list.add("JavaEE");
list.add("JVM");
list.add("测试课程");
System.out.println(list);
// 获取list中有效元素个数
System.out.println(list.size());
// 获取和设置index位置上的元素,注意index必须介于[0, size)间
System.out.println(list.get(1));
list.set(1, "JavaWEB");
System.out.println(list.get(1));
// 在list的index位置插入指定元素,index及后续的元素统一往后搬移一个位置
list.add(1, "Java数据结构");
System.out.println(list);
// 删除指定元素,找到了就删除,该元素之后的元素统一往前搬移一个位置
list.remove("JVM");
System.out.println(list);
// 删除list中index位置上的元素,注意index不要超过list中有效元素个数,否则会抛出下标越界异常
list.remove(list.size()-1);
System.out.println(list);
// 检测list中是否包含指定元素,包含返回true,否则返回false
if(list.contains("测试课程")){
list.add("测试课程");
}
}
四.总结
数据结构是一门单独的学科,千位千位不要理解为只有Java才有,C语言等其他语言也能够实现此物理逻辑,C语言实现顺序表:C语言的顺序表构建