📕前言
📖文章吸纳了一些大佬的文章,但大部分纯手敲哦,欢迎光临小林课堂!!!
在学习顺序表之前呢,我们得先了解以下 " 何为线性表?"
线性表:是一种在实际中广泛使用得数据结构,常见的线性表有:顺序表,链表,栈,队列 ...
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
我们的今天学习的顺序表的底层结构其实就是 一个数组
顺序表 是使用数组 来完成的一种结构
顺序表介绍:
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
顺序表一般可以分为:
- 静态顺序表:使用定长数组存储元素。( 本篇内容主要围绕静态顺序表展开 )
- 动态顺序表:使用动态开辟的数组存储
模拟实现 顺序表
为什么要模拟实现:
自己模拟实现 简易版的 顺序表的增删查改等主要功能,大致理解顺序表的设计思想
再对比学习 Java 提供的集合类当中的 ArrayList ,在学习 Java 的 ArrayList 常用方法的同时,也能学习源码的思想
📒本文将创建两个Java文件:
My ArraysList.java 用于顺序表的实现,
Test.java 用于对顺序表里的各个接口的测试
顺序表功能接口概览
import java.util.Arrays;
public class MyArrayList {
private int[] elem;//用来存放数据元素
private int usedSize; //代表当前顺序表当中的有效数据个数
//为什么不初始化 usedSize 因为引用类型不初始化的话,默认值就是 零
//
private static final int DEFAULT_SIZE = 2;//定义一个静态常量,更加安全
//初始化顺序表
public MyArrayList() {//这就默认大小是2
this.elem = new int[DEFAULT_SIZE];
}
/*
* 指定容量
* initCapacity
* */
public MyArrayList(int initCapacity) {
this.elem = new int[initCapacity];
}
// 新增元素,默认在数组最后新增
public void add(int data) { }
// 在 pos 位置新增元素
public void add(int pos, int data) { }
// 判定是否包含某个元素
public boolean contains(int toFind) { return true; }
// 查找某个元素对应的位置
public int indexOf(int toFind) { return -1; }
// 获取 pos 位置的元素
public int get(int pos) { return -1; }
// 给 pos 位置的元素设为 value
public void set(int pos, int value) { }
//删除第一次出现的关键字key
public void remove(int toRemove) { }
// 获取顺序表长度
public int size() { return 0; }
// 清空顺序表
public void clear() { }
// 打印顺序表,注意:该方法并不是顺序表中的方法,为了方便看测试结果给出的
public void display() { }
}
基本功能的实现
🌰判断顺序表内存是否已经满了
public boolean isFull() {
if (this.usedSize == this.elem.length) {
return true;
}
return false;
}
🌰对顺序表进行扩容
//对顺序表进行扩容
private void expand() {
this.elem = Arrays.copyOf(
this.elem, this.elem.length * 2);
System.out.println("已经成功扩容至原来的两倍");
}
🌰打印顺序表
/*
* 打印顺序表
* 实际上是 遍历数组
* */
//打印的第一种方式
public void display() {
for (int i = 0; i < this.usedSize; i++) {
System.out.print(this.elem[i] + " ");
}
System.out.println();
}
//打印的第二种方式,用Arrays.toString直接打印
public void display(){
System.out.println(Arrays.toString(this.elem));
}
🌰获取顺序表的有效长度
// 获取顺序表的有效长度
public int size() {
return this.usedSize;
}
🌰清空顺序表
// 清空顺序表
public void clear() {
/*for (int i = 0; i < this.usedSize; i++) {
this.elem[i] = null;
}
//如果这是引用类型,就要这么写,要把一个个对象置为 null 空
*/
this.usedSize = 0;// 注意有效数组长度也要清零
}
🌰自定义异常 (检查 pos 位置是否合法)
/*
* 检查 pos 位置是否合法
* @param pos
* */
private void checkPos(int pos) {//用 private 只是希望在这个类里面使用
if (pos < 0 || pos >= this.usedSize) {
throw new PosOutOfBoundException(pos + " 位置不合法");//自定义异常
// System.out.println("新增位置不合法");
// return;
}
}
定义的异常接口功能
越界时,抛出异常
/**
* @Author XiaoLin
* @Description: 位置超过,越界,抛出异常
* 自定义异常
*/
public class PosOutOfBoundException extends RuntimeException{
public PosOutOfBoundException() {
}
public PosOutOfBoundException(String message) {
super(message);
}
}
顺序表四大功能(增删查改)
一,增加数据
🔢头插
//新增元素,在数组最前面新增
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 add(int data) {
if (isFull()) {
System.out.println("数组满了,需要扩容");
//扩容
expand();
}
this.elem[this.usedSize] = data;
this.usedSize++;
}
🔢指定下标插入
- 判断pos位置是否合法 (在顺序表中,数据是连续的,中间不能有空缺)
- 判断顺序表是否满了,如果满了,需要扩容
- 插入数据(可能需要挪动元素)
// 在 pos 位置新增元素
public void add(int pos, int data) {
if (pos < 0 || pos > this.usedSize) {
throw new PosOutOfBoundException(pos + " 位置不合法");//自定义异常
//System.out.println("新增位置不合法");
//return;
}
//重载
if (isFull()) {
//扩容
this.elem = Arrays.copyOf(this.elem, 2 * this.elem.length);
}
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;
}
checkPos(pos);// 检查输入的pos下标合不合法
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 remove(int toRemove) {
int index = indexOf(toRemove);
if (index == -1) {
System.out.println("没有这个数据!!");
return;
}
for (int i = index; i < this.usedSize - 1; i++) {
this.elem[i] = this.elem[i + 1];
}
//this.elem[usedSize-1] = null; //如果这是引用类型就要这句,
this.usedSize--;
}
三,查找数据
🔢获取指定位置的元素
- 考虑要获取的位置是否合法
- 返回指定位置的元素
// 查找某个元素对应的位置
public int indexOf(int toFind) {
for (int i = 0; i < this.usedSize; i++) {
if (this.elem[i] == toFind) {
return i;
}
}
return -1;
}
🔢获取指定元素所在的位置
// 获取 pos 位置的元素
public int get(int pos) {
checkPos(pos);//调用自定义异常方法
//没找到就会报异常
return this.elem[pos];
}
🔢查找表中是否包含某个元素
// 判定是否包含某个元素
public boolean contains(int toFind) {
for (int i = 0; i < this.usedSize; i++) {
if (this.elem[i] == toFind) {
return true;
}
}
return false;
}
四,修改数据
🙌首先考虑修改的位置是否合法
🙌考虑特殊情况
// 给 pos 位置的元素设为 value [ 更新 ]
public void set(int pos, int value) {
checkPos(pos); //判断要修改的位置合不合法
this.elem[pos] = value;
}
总代码
📖MyArrayList.java
import java.util.Arrays;
/**
* @Author XiaoLin
* @Description:
*/
public class MyArrayList {
private int[] elem;//用来存放数据元素
private int usedSize; //代表当前顺序表当中的有效数据个数
//为什么不初始化 usedSize 因为引用类型不初始化的话,默认值就是 零
//
private static final int DEFAULT_SIZE = 2;//定义一个静态常量
public MyArrayList() {//这就默认大小是2
this.elem = new int[DEFAULT_SIZE];
}
/*
* 指定容量
* initCapacity
* */
public MyArrayList(int initCapacity) {
this.elem = new int[initCapacity];
}
/*
* 打印顺序表
* 实际上是 遍历数组
* */
public void display() {
for (int i = 0; i < this.usedSize; i++) {
System.out.print(this.elem[i] + " ");
}
System.out.println();
}
public void display2(){
System.out.println(Arrays.toString(this.elem));
}
//新增元素,在数组最前面新增
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 add(int data) {
if (isFull()) {
System.out.println("数组满了,需要扩容");
//扩容
expand();
}
this.elem[this.usedSize] = data;
this.usedSize++;
}
//扩容
private void expand() {
this.elem = Arrays.copyOf(
this.elem, this.elem.length * 2);
System.out.println("已经成功扩容至原来的两倍");
}
public boolean isFull() {
if (this.usedSize == this.elem.length) {
return true;
}
return false;
}
// 在 pos 位置新增元素
public void add(int pos, int data) {
if (pos < 0 || pos > this.usedSize) {
throw new PosOutOfBoundException(pos + " 位置不合法");//自定义异常
// System.out.println("新增位置不合法");
// return;
}
//重载
if (isFull()) {
//扩容
this.elem = Arrays.copyOf(this.elem, 2 * this.elem.length);
}
for (int i = this.usedSize - 1; i >= pos; i--) {
this.elem[i + 1] = this.elem[i];
}
this.elem[pos] = data;
this.usedSize++;
}
// 判定是否包含某个元素
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;
}
}
return -1;
}
// 获取 pos 位置的元素
public int get(int pos) {
checkPos(pos);//调用自定义异常方法
return this.elem[pos];
}
// 给 pos 位置的元素设为 value [ 更新 ]
public void set(int pos, int value) {
checkPos(pos);
this.elem[pos] = value;
}
/*
* 检查 pos 位置是否合法
* @param pos
* */
private void checkPos(int pos) {//用 private 只是希望在这个类里面使用
if (pos < 0 || pos >= this.usedSize) {
throw new PosOutOfBoundException(pos + " 位置不合法");//自定义异常
// System.out.println("新增位置不合法");
// return;
}
}
//判断当前顺序表是否为空
public boolean isempty() {
if(this.usedSize == 0){
return true;//空的
}
else return false; //还没空
}
//删除表头元素
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;
}
checkPos(pos);// 检查输入的pos下标合不合法
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 remove(int toRemove) {
int index = indexOf(toRemove);
if (index == -1) {
System.out.println("没有这个数据!!");
return;
}
for (int i = index; i < this.usedSize - 1; i++) {
this.elem[i] = this.elem[i + 1];
}
// this.elem[usedSize-1] = null; //如果这是引用类型就要这句,
this.usedSize--;
}
// 获取顺序表长度
public int size() {
return this.usedSize;
}
// 清空顺序表
public void clear() {
/*for (int i = 0; i < this.usedSize; i++) {
this.elem[i] = null;
}
//如果这是引用类型,就要这么写,要把一个个对象置为 null 空
*/
this.usedSize = 0;// 注意有效数组长度也要清零
}
// 打印顺序表,注意:该方法并不是顺序表中的方法,为了方便看测试结果给出的
}
📖Test.java
import java.util.ArrayList;
import java.util.List;
/**
* @Author XiaoLin
* @Description:
*/
public class Test01_顺序表 {
public static void main(String[] args) {
MyArrayList myArrayList = new MyArrayList();
myArrayList.add(1);
myArrayList.add(2);
myArrayList.add(3);
myArrayList.add(4);
myArrayList.add(5);
myArrayList.display();
myArrayList.add(3, 15);
myArrayList.display();
System.out.print(myArrayList.contains(3));
System.out.println(" 下标为:" + myArrayList.indexOf(3));
System.out.println("============================");
System.out.println("获取 pos 位置的元素 " + myArrayList.get(3));//获取 pos 位置的元素
System.out.println("============================");
System.out.println("给 pos 位置的元素设为 value [ 更新 ]");
myArrayList.set(2, 11);
myArrayList.display();
System.out.println("============================");
System.out.println("删除关键字key");
myArrayList.remove(2);
myArrayList.display();
System.out.println("============================");
System.out.println("清空顺序表");
myArrayList.clear();
myArrayList.display();
}
}
最后
本篇内容主要是对数据结构中【顺序表】内容的介绍,一方面介绍了如何模拟实现简易的顺序表,另外一方面介绍了Java集合框架中的【ArrayList】类的基本使用【ArrayList】
😎顺序表的优点
😣当然也有缺点!!!
所以: 怎么做到随用随分配呢???
总结:什么功能就用其最适的数据结构,写功能前先画图,把思路画出来,先完成功能再考虑优化其时间复杂度和空间复杂度! (致:小菜狗的小林同学!!!)