目录
1、先来认识一下线性表
1.1、对非空的线性表或者线性结构的特点:
1.2、线性表的实现方式
2、顺序表
2.1、定义一个类,实现顺序表
2.2、顺序表的操作方法
2.2.1、打印顺序表(display)
2.2.2、获取顺序表的长度(size)
2.2.3、判断顺序表中是否包含某个元素(contains)
2.2.4、查找某个元素对应的位置(indexOf)
2.2.5、新增元素,默认在数组最后新增(add)
2.2.6、在pos位置新增元素
2.2.7、获取pos位置的元素
2.2.8、将pos位置的元素修改为value
2.2.9、删除某个位置的元素
2.2.10、清空顺序表
1、先来认识一下线性表
- 线性表(linear list)是n个具有相同特性的数据元素的有限序列。线性表是一种在实际中广泛使用的数据结构,常见的线性表有:顺序表、链表、栈、队列...
- 线性表在逻辑上是线性结构,也就是说连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以顺序结构和链式结构的形式存储。
1.1、对非空的线性表或者线性结构的特点:
- 线性表中第一个元素被称为表头元素,有且只有一个。
- 线性表中最后一个元素被称为表尾元素,有且之后一个。
- 除表头元素外,结构中的每个数据元素有且只有一个直接前驱;
- 出表尾元素外,结构中的每个数据元素有且只有一个直接后继;
1.2、线性表的实现方式
线性表有两种实现方式:
- 顺序存储结构:简称顺序表;
- 链式存储结构:简称链表;
2、顺序表
顺序表是由一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删改查。
2.1、定义一个类,实现顺序表
public class MyArrayList {
public int[] elem;
public int useSize;//当前数组当中存储了多少数据。
public static final int DEFAULT_SIZE = 10;
public MyArrayList(){//定义一个数组默认大小为10
this.elem = new int[DEFAULT_SIZE];
}
//重载MyArrayList方法,初始化数组的时候,数组大小由调用者,控制
public MyArrayList(int d){
this.elem = new int[d];
}
}
2.2、顺序表的操作方法
2.2.1、打印顺序表(display)
通过遍历来打印顺序表,在定义类的时候useSize被用来记录数组当中录入的元素的个数,所以录入多少个元素,遍历多少(useSize)次。只打印有效元素。
public void display(){
for (int i = 0; i < this.useSize; i++) {
System.out.println(this.elem[i]+" ");
}
}
2.2.2、获取顺序表的长度(size)
直接将useSize的值返回就可以。
public int size(){
return this.useSize;
}
2.2.3、判断顺序表中是否包含某个元素(contains)
通过循环来查找顺序表中是否存在这个元素,若有,输出true,若没有,输出false
public boolean contains(int toFind){
for(int i = 0;i < this.useSize;i++){
if(this.elem[i] == toFind){//因为在定义数组的时候,采用的是基本数据类型,所以这里使用==比较
//若定义数组的时候采用的是引用数据类型,则使用equals方法来进行比较
return true;
}
}
return false;
}
2.2.4、查找某个元素对应的位置(indexOf)
public int indexOf(int toFind) {
for (int i = 0; i < this.useSize; i++) {
if (this.elem[i] == toFind) {
return i;//找到这个数据返回下标。
}
}
return -1;//这里返回-1 ,因为数组没有 负数下标。
}
2.2.5、新增元素,默认在数组最后新增(add)
主要逻辑就是在useSize位置,将新元素放入。但是这里存在一个问题,就是数组在满了的情况下,再存入数据,就会数组越界,那么还得判断数组是否满了,满了对数组进行扩容。
/*
* 判断数组是否满了
*/
public Boolean isFull(){
if(this.useSize == this.elem.length){
return true;
}
return false;
//也可以直接return
//return this.useSize == this.elem.length
}
/*
*扩容,对原来的数组扩大原来的2倍
*/
private void resize(){
this.elem = Arrays.copyOf(this.elem,2*this.elem.length);
}
public void add(int data){
if(this.isFull()){
resize();//扩容,对原来的数组扩大二倍
}
this.elem[this.useSize] = data;
this.useSize++;
}
对add这个方法进行测试
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.display();
myArrayList.add(5);
myArrayList.display();
myArrayList.add(6);
myArrayList.display();
}
当添加6元素的时候,数组发生扩容。
2.2.6、在pos位置新增元素
在有效元素的某个位置,插入一个数据,就需要将某个位置在内的,后面所有的数据整体向后移动。
例如,在下标为2的位置插入一个数据,就需要将2下标以后的数据整体向后移动
编写思路:
- 找到有效数组的最后一位的下标为useSize-1,从后往前 (目标位置),将元素像后移动一个,数组下标 向前移动一位,当数组下标小于目标位置下标时,结束。
- 当然主逻辑写完之后,还有判断pos的合法性。
- pos不能是负数,数组没有负数下标;
- 在顺序表中,除了表头元素外,每个元素都要自己的直接前驱,所以上述的图片中,不能直接在5位置插入数据。
- 在顺序表中,除了表尾元素外,每个元素都要有自己的直接后继.
代码示例:
//在pos位置新增数据
public void add(int pos,int data){
checkAddindex(pos);//检查pos的合法性
if(isFull()){//检查数组是否满了
resize();//满了进行扩容
}
for (int i = useSize-1; i >= pos; i--) {
elem[i+1] = elem[i];
}
elem[pos] = data;
useSize++;
}
/*
* add方法的,检查add数据的时候,pos是否是合法的
* */
private void checkAddindex(int pos){
if(pos < 0 || pos > useSize){//数组的useSize位置没有元素,所以useSize位置之后不能插入元素
throw new AddIndexOutOfException("add元素的时候,位置不合法,请检查位置的合法性");
}
}
测试一下这个方法
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.display();
try{
myArrayList.add(1,99);
}catch(AddIndexOutOfException e){
e.printStackTrace();
}
myArrayList.display();
}
2.2.7、获取pos位置的元素
- 直接返回pos位置的元素就可以,
- 当然也要检查pos位置的合法性,这里的检查pos位置合法性(checkGetIndex),当要获取pos位置的元素时,useSize下标处,没有元素,所以不用获取该下标的元素。
// 获取pos位置的元素
public int get(int pos){
checkGetIndex(pos);
return elem[pos];
}
/*
*get方法的,检查pos位置的合法性
*/
private void checkGetIndex(int pos){
if(pos < 0 || pos >= useSize){//想要得到pos下标位置的元素的时候,useSize位置没有元素,所以不用得到该下标的元素。
throw new IndexOutOfException("get获取元素的时候,位置不合法,请检查位置的合法性");
}
}
}
对该方法进行检测
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.display();
try{
System.out.println(myArrayList.get(2));
}catch(IndexOutOfException e){
e.printStackTrace();
}
}
当想要获取4下标的元素,4下标没有元素,所以编译器会报错异常
try{
System.out.println(myArrayList.get(4));
}catch(IndexOutOfException e){
e.printStackTrace();
}
}
2.2.8、将pos位置的元素修改为value
- 将数组pos位置的元素修改为value
- 当然这里也要检查pos位置的合法性,这里只需要调用checkIndex方法即可。
- 要修改元素,首先要在数组的有效范围内修改,那么当数组当中元素放满的时候,不能超过数组的最大范围,所以pos要小于useSize,也要大于0下标。
//检查pos位置的合法性
private void checkIndex(int pos){
if(pos < 0 || pos >= useSize){//当数组放满时,useSize下标是越界的。
throw new IndexOutOfException("位置不合法,请检查位置的合法性");
}
}
//将pos位置的元素修改为value
public void set(int pos, int value) {
checkIndex(pos);
elem[pos] = value;
}
}
测试该方法
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.set(5, 12);
myArrayList.display();
}
2.2.9、删除某个位置的元素
思路:
- 首先调用IndexOf方法,查找某个元素对应的位置,有查找的元素,返回该数据的下标,若没有,返回-1;
- 再判断IndexOf方法返回的是-1,则提示没有这个数据,返回false,该方法结束。
- 再循环将数组元素向前挪动;
- 删除一个元素之后,数组的有效元素个数减一;
- 现在数组实际存在5个元素,但是删除以为之后,应该只剩4位,所以对最后一位置0.
代码:
//删除数组中的某个元素
public boolean remove(int toRemove){
int index = indexOf(toRemove);//查找数组中,某个元素对应的位置。
if(index == -1){
System.out.println("没有这个数据");
return false;
}
for (int i = index; i < useSize-1; i++) {
elem[i] = elem[i+1];
}
useSize--;
elem[useSize]=0;//这里因为useSize--,所以直接将elem[useSize]元素置0。
// elem[useSize]=null;当数组类型是引用类型时,将其值为null.
return true;
}
}
对该方法进行测试
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.remove(2);//删除数组的元素2
myArrayList.display();
}
}
❗❗❗提示:这个方法的时间复杂度位O(N);
2.2.10、清空顺序表
public void clear(){
/*当数组的类型为引用数据类型时,可以用这种写法
* for(int i = 0 ;i < useSize; i++){
* elem[i] = null;
* }
* */
useSize = 0;
}
通过调试来看
将useSize置为0,可以看见数组当中的元素没有置0.
通过运行代码可以看见 ,通过调用打印方法,没有输出结果。因为再写输出方法(display)时,是通过for循环来输出的,for循环通过useSize类控制。
❗❗❗总结:
- 顺序表(ArrayList)底层采用连续的空间,任意位置的元素插入和删除,都需要将元素整体向后或者先前挪动,再最坏的情况下,他们的时间复杂度都为O(N).
- 顺序表不是很适合,频繁的对数据进行插入和删除的场景。
- 顺序表适合,给定下标的随机访问,它的效率非常的高。
- 增容需要申请空间,拷贝数组,释放空间。会有不小的消耗。
- 增容一般时成2倍的增长,势必会有一定的空间浪费。例如当前空间为5,满了以后增容到10,我们再继续插入了1个数据,后面没有数据插入了,那么就造成了4个空间的浪费。