在说到顺序表时,最开始要提到的必然是线性表,下面先说说线性表
线性表
是n个具有相同特性的数据元素的有限序列,常见线性表:顺序表、链表、栈、队列
线性表在逻辑上是线性结构,但在物理结构上却不一定连续,也就是说在物理上存储时,线性表会以数组和链式结构存储
例如图上所示
关于线性表,我们会有以下的思考和总结
顺序表
简单说完线性表后,我们来具体说说我们本次的主角顺序表。
即是一段物理地址连续的存储单元一次存储数据元素的线性结构,一般情况下采用数组存储。
在数组上完成数据的增删查改
如
//新增元素,默认在数组最后新增
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 class MyArrayList {
private int[] elem;//存放数据元素
private int usedSize;//usedSize代表当前顺序表当中有效数据个数
private static final int DEFAULT_SIZE = 2;
public MyArrayList() {
this.elem = new int[DEFAULT_SIZE];
}
//指定容量 initCaoacity
public MyArrayList(int initCaoacity) {
this.elem = new int[initCaoacity];
}
这些是我们前面的定义
首先我们先分析display()
public void display()
public void display() {
for (int i = 0; i < this.usedSize; i++) {
System.out.println(this.elem[i] + "");
}
}
display函数的实质就是遍历数组
public void add()
即新增元素,默认在数组最后新增
//新增元素,默认在数组最后新增
public void add(int data) {
if(isFull()){
//扩容
this.elem= Arrays.copyOf(this.elem,2*this.elem.length)
}
this.elem[this.usedSize] = data;
this.usedSize++;
}
public boolean isFull(){
if(this.usedSize == this.elem.length){
return true;
}
return false;
}
数据结构是一门逻辑非常严谨的学科,不能直接就新增元素,假如只到下标4我们的空间就满了,那么此时我们要考虑越界,考虑扩容。当然,在我们考虑是否扩容是,我们要关注常量DEFAULT_SIZE定义的值,此时当我们在main方法中实现一个1,2,3,4,5时,我们可以发现,会扩容两次,具体可以自己调试试一下。(最好是自己来debug一下,加深理解)
public void add(int pos, int data)
这个方法与上面add,构成了方法的重载。但与上面的add()有不同,这个add是从从中插入一个数据,我们思考到底层代码的逻辑就是挪,把数据逐步从最后面挪开
可以给大家画一个图加深理解一下。
把4挪到5,3挪到4,2挪到3,我们一定是从后往前挪,我们的核心思想就是elem[i+1]=elem[i]
public void add(int pos, int data) {
//pos合法性判断
if(pos<0 || pos>this.usedSize){
throw new PosOutOfBoundsException("位置不合法");
//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 class PosOutOfBoundsException extends RuntimeException {
public PosOutOfBoundsException() {
}
public PosOutOfBoundsException(String message) {
super(message);
}
}
测试截图如下
public boolean contains(int toFind){return true}
判断是否包含某个元素
public boolean contains(int toFind) {
for (int i=0;i<this.usedSize;i++) {
if (this.elem[i] == toFind){ //==或equals或compareTo
return true;
}
}
return true;
}
简单的想法就是进行一遍遍历,但如果当我们的要找的是一个引用类型,此时我们就不能用等号来
常用的我们需要用到equals和compareTo两个,但compareTo返回的类型是整形,比较大于的话返回1,小于返回-1,等于返回0。而equals的返回值是true和false,因此我们要使用哪种方法就要具体环境具体来应用。
public int indexOf(int toFind){return -1}
查找某个元素对应位置
此方法与Boolean contains方法高度重合,我们要做的只需要改变返回值
public int indexOf(int toFind) {
for (int i=0;i<this.usedSize;i++) {
if (this.elem[i] == toFind){
return i;
}
}
return -1;
}
两个方法测试 ↓
public int get(int pos){return -1}
获取pos位置的元素
public int get(int pos) {
//判断pos的合法性
if(pos<0 || pos >= this.usedSize) {
//位置不合法就抛出异常
throw new PosOutOfBoundsException("位置不合法");
}
return this.elem[pos];
}
异常的代码上面有,就不多说了
public void set(int pos,int value){}
给pos位置的元素设为value
大家可能会认为set会与add比较相像
但这个设置我们可以理解为更新,不一定来移动元素,直需要pos在合法范围内,我们便可以直接把pos覆盖掉
同样的,我们要先检查pos的合法性,但总是需要写两行代码来检查pos的合法性,我们觉得代码太冗杂了,所以我们采用一个封装的思想,把这两行代码写成一个checkpos方法。↓
//给pos位置的元素设为value 或者叫更新
public void set(int pos, int value) {
checkPos(pos);
this.elem[pos] = value;
}
public void checkPos(int pos) {
if (pos < 0 || pos >= this.usedSize) {
//位置不合法就抛出异常
throw new PosOutOfBoundsException("位置不合法");
}
}
public void remove(int toRemove){}
删除第一次出现的关键词key
//删除第一次出现的关键字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];
}
//最后因为是从后往前移,少一个元素,我们用--来更新usedsized
this.usedSize--;
}
可以实现删去头个元素,也可以删尾巴,同时不存在的东西删掉也有显示。
public int size(){return 0;}
获取顺序表的长度
//获取顺序表的长度
public int size() {
return this.usedSize;
}
public void clear(){}
清空数据表
最简单的方法全部置为0,当我们面对的基本数据类型是没有区别,也完全没有问题。
但当我们数组中存储的是一个个地址时,比如
我们就必须要牵扯一下内存泄露这个问题
也就是说0(elem[0])下标的地址上一定引用了一个对象,当我们用usedsized=0时,这种置零方法不可取, 我们clear要清空列表,我们只是把usedsized=0,置为0认为0下标没有可用的内存了,那么理论上来说0x88就不该再去占用我的内存,同样我们也可用说ox87,ox86这些就一直无法释放内存了
简单来说就是地址的引用,内存一直不被释放。
以[i]=[i+1]为例,当我要删除,我们要从后往前覆盖,我们就要知道[i+1]会一直被引用,一直不被释放,我们面对引用数据类型时应该怎么办呢?
最复杂,最笨的方法就是把它们一个一个置为null
public void clear() {
//面对引用数据类型
/*for (int i=0;i<this.usedSize;i++){
this.elem[i]=null;
}*/
this.usedSize=0;
}
目前,我们就自己实现了一个顺序表,写完了顺序表的整体的底层代码,在以后我们要用到这些方法时,只需要直接引用就好了。
整体的源代码就如下
MyArrayList.java
import java.lang.reflect.Array;
import java.util.Arrays;
public class MyArrayList {
private int[] elem;//存放数据元素
private int usedSize;//usedSize代表当前顺序表当中有效数据个数
private static final int DEFAULT_SIZE = 2;
public MyArrayList() {
this.elem = new int[DEFAULT_SIZE];
}
//指定容量 initCaoacity
public MyArrayList(int initCaoacity) {
this.elem = new int[initCaoacity];
}
//新增元素,默认在数组最后新增
public void add(int data) {
if(isFull()){
//扩容
this.elem= Arrays.copyOf(this.elem,2*this.elem.length);
}
this.elem[this.usedSize] = data;
this.usedSize++;
}
public boolean isFull(){
if(this.usedSize == this.elem.length){
return true;
}
return false;
}
//在pos位置新增元素
public void add(int pos, int data) {
//pos合法性判断
if(pos<0 || pos>this.usedSize){
//位置不合法就抛出异常
throw new PosOutOfBoundsException("位置不合法");
//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 true;
}
//查找某个元素对应位置
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) {
if(pos<0 || pos >= this.usedSize) {
//位置不合法就抛出异常
throw new PosOutOfBoundsException("位置不合法");
}
return this.elem[pos];
}
//给pos位置的元素设为value 或者叫更新
public void set(int pos, int value) {
checkPos(pos);
this.elem[pos] = value;
}
public void checkPos(int pos) {
if (pos < 0 || pos >= this.usedSize) {
//位置不合法就抛出异常
throw new PosOutOfBoundsException("位置不合法");
}
}
//删除第一次出现的关键字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];
}
//最后因为是从后往前移,少一个元素,我们用--来更新usedsized
this.usedSize--;
}
//获取顺序表的长度
public int size() {
return this.usedSize;
}
//清空顺序表
public void clear() {
//面对引用数据类型
/*for (int i=0;i<this.usedSize;i++){
this.elem[i]=null;
}*/
this.usedSize=0;
}
//打印顺序表
public void display() {
for (int i = 0; i < this.usedSize; i++) {
System.out.println(this.elem[i] + "");
}
}
}
PosOutOfBoundsException.java
public class PosOutOfBoundsException extends RuntimeException {
public PosOutOfBoundsException() {
}
public PosOutOfBoundsException(String message) {
super(message);
}
}
Test.java
public class Test {
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.add(3,199);
System.out.println(myArrayList.contains(1));
System.out.println(myArrayList.indexOf(10));*/
/*System.out.println(myArrayList.get(1));
System.out.println(myArrayList.get(3));
System.out.println(myArrayList.get(5));*/
/*myArrayList.set(0,100);*/
myArrayList.remove(1);
myArrayList.remove(5);
myArrayList.remove(21312);
myArrayList.display();
}
}