线性表
线性表是n个具有相同特性的数据元素的有限序列。线性表是一种结构,常见的线性表:顺序表、链表、栈、队列……
线性表在逻辑上是线性结构,也就是连续的一条直线,但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
数组表:
链表:
顺序表
顺序表是用一段物理地址连续的存储单元依次数据元素的线性结构,一般情况下采用数组存储,在数组上完成数据的增删查改。
我们先自己有代码实现,这样才能更好理解源代码是如何是实现的,要学习类,跟着源码学习。
接口的实现
public class MyArrayList {
public int[] elem;
public int usedSize;//0
//默认容量
private static final int DEFAULT_SIZE = 10;//static:数组属于类,不属于对象,final:不可修改数据
public MyArrayList() {
this.elem = new int[DEFAULT_SIZE];
}
/**
* 打印顺序表:该方法并不是顺序表中的方法,为了方便看测试结果给出的
* <p>
* 根据usedSize判断即可
*/
public void display() {
if(isEmpty()==false){
throw new NullException("数组空异常");
}
for (int i = 0; i < this.size(); i++) {
System.out.print(this.elem[i] + " ");
}
System.out.println();
}
// 新增元素,默认在数组最后新增
public void add(int data) {
//1、检查当前的顺序表是不是满了?
if (isFull()) {
//2、如果满了就要扩容,扩容成原来的两倍
this.elem= Arrays.copyOf(this.elem,2*this.elem.length);
//throw new IndexOutOfException("数组越界访问");//throw就需要catch
}
this.elem[this.size()] = data;
this.usedSize++;
}
/**
* 判断当前的顺序表是不是满的!
*
* @return true:满 false代表空
*/
public boolean isFull() {
// if(this.size()>=this.elem.length){
// return true;
// }
// return false;
return this.size()>=this.elem.length;
}
/**
* 判断访问的下标是否正确
* @param pos
* @return
*/
private boolean checkPosInAdd(int pos) {
//下标数字小于0或者大于等于目前长度
if(pos<0||pos>=size()){
throw new IndexOutOfException("下标访问异常");
}
return true;//合法
}
/**
* 在 pos 位置新增元素
* 漏:如果Pos下标不合法,那么就会抛出一个PosWrongFullException
* @param pos
* @param data
*/
//异常声明throws:处在方法声明中参数列表后,当方法中抛出运行时异常,用户不想处理该异常,借此throws将异常抛出给方法的调用者来处理。
public void add(int pos, int data) throws IndexOutOfException {
//不能满的时候添加元素
if(isFull()){
System.out.println("满了");
throw new IndexOutOfException("数组已经满了,不能再添加了");
}
//不合法下标--因为是条件,所以可以添加到最后一个位置上
if(pos<0||pos>size()){
throw new IndexOutOfException("下标不合法");
}
// //特殊位置--就在末尾加
// if()
// 不能隔着存入元素-判断下标是否合理--发现这个代码也包含了特殊位置末尾
//pos一定是合法的
//1、开始挪动数据
for (int i = size()-1; i >=pos; i--) {
this.elem[i+1]=this.elem[i];
}
// int i=size()-1;
// while(i>pos){
// this.elem[i]=this.elem[i+1];
// i--;
// }
//2、插入数据
this.elem[pos]=data;
//3、总数据加加
this.usedSize++;
//先移动后添加
}
/**
* 判定是否包含某个元素
* @param toFind
* @return
*/
public boolean contains(int toFind) {
//万一要是数组为空怎么办——会发生空异常
if(isEmpty()==false){
throw new NullException("数组为空");
}
for (int i = 0; i < this.size(); i++) {
if(this.elem[i]==toFind){
System.out.println("找到了,在第"+(i+1)+"个");
return true;
}
}
return false;
}
// 查找某个元素对应的位置
public int indexOf(int toFind) {
//首先判断数组不能为空
if(isEmpty()==false){
throw new NullException("数组为空");
}
for (int i = 0; i < size(); i++) {
if(this.elem[i]==toFind){
return i+1;
}
}
return -1;
}
// 获取 pos 位置的元素
public int get(int pos) {
//数组是否为空
if(isEmpty()==false){
throw new NullException("数组为空");
}
//访问下标位置是否合法--这里是访问,因此当下标等于数组长度时,也不可以
if(checkPosInAdd(pos)==false){
throw new IndexOutOfException("下标不合法");
}
return this.elem[pos];
}
/**
* 判断数组不为空
* true 不空 false 空
* @return
*/
private boolean isEmpty() {
if(this.size()==0){
return false;
}
return true;
}
// 给 pos 位置的元素设为【更新为】 value
public void set(int pos, int value) {
//数组是否为空
if(isEmpty()==false){
throw new NullException("数组为空");
}
//访问下标位置是否合法--这里是访问,因此当下标等于数组长度时,也不可以
if(checkPosInAdd(pos)==false){
throw new IndexOutOfException("下标不合法");
}
this.elem[pos]=value;
}
/**
* 删除第一次出现的关键字key
*
* @param key
*/
public void remove(int key) {
//判断是否为空
if(isEmpty()==false){
throw new NullException("数组为空,无法删除");
}
//第一种方法
// //先找到在哪里,然后再先移
// int i = 0;
// for (i = 0; i < size(); i++) {
// if(this.elem[i]==key){
// break;//找到就退出来
// }
// }
// //说明已经找到了
// if(i<size()) {
// for (int j = i; j < size() - 1; j++) {//限制条件是size()-1,因为如果是限制条件是size(),数组遍历到最后一个元素,就会发生数组越界,
// //当删除的是最后一个元素,这个代码就不行
// this.elem[j] = this.elem[j + 1];
// }
// this.usedSize--;
//
// }
// //特殊位置:删除最后一个元素
// if(i==size()-1){
// this.usedSize--;
// }
//
//第二种方法
int index=this.indexOf(key);
if(index==-1){
System.out.println("没有这个数字");
return;//无返回值
}
for(int i=index;i<this.size()-1;i++){
this.elem[i]=this.elem[i+1];
}
this.usedSize--;
}
//获取顺序表长度
public int size() {
return this.usedSize;
}
// 清空顺序表
public void clear() {
this.usedSize=0;
//如果数组是引用类型的,就需要一个一个NULL
// for (int i = 0; i < size(); i++) {
// this.elem[i]=null;
// }
}
}
ArrayList简介
ArrayList使用
JAVA集合类一般都在java.util包里面:
ArrayList的构造
方法 | 解释 |
---|---|
ArrayList(int Capacity) | 指定顺序表初始容量 |
ArrayList() | 无参构造,默认为空数组(推荐这个写法) |
ArrayList(Collection<? extends E> c) | 利用其他Collection构建ArrayList |
第一个构造方法:
第二种构造方法:
可以发现当不指定大小创建数组,默认为空数组
第三种构造方法:
构造一个包含指定集合的元素的列表,按照它们由集合的迭代器返回的顺序。
在java中,为了获取实际的数组,这个数组包含了列表的内容,可以调用toArray()方法来实现
copyOf:数组的拷贝
//ArrayList创建,推荐写法
//构造一个空的列表
ArrayList<Integer> list1=new ArrayList<>();
ArrayList<Integer> list2=new ArrayList<>(3);
list2.add(1);
list2.add(2);
//list2.add("hello");//编译异常,ArrayList<Integer> 已经限定了,list2中只能存储整型元素
//list3构造完成好之后,与list2中的元素一致
ArrayList<Integer> list3=new ArrayList<>(list2);
//可以不用指定顺序表数据类型,并且任何元素都可以存放,使用时将是一场灾难crisis
ArrayList list4=new ArrayList();
list4.add("hello");
list4.add(520);
ArrayList常见操作
方法 | 解释 |
---|---|
boolean add(E e) | 尾插 e |
void add(int index, E element) | 将 e 插入到 index 位置 |
boolean addAll(Collection<? extends E> 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是否在线性表中 |
int indexOf(Object o) | 返回第一个o所在下标 |
int lastIndexOf(Object o) | 返回最后一个o的下标 |
List subList(int fromIndex, int toIndex) | 截取部分list |
List<String> list=new ArrayList<>();
list.add("hello");
list.add("java");
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,"h");
System.out.println(list.get(1));
//在list的index位置插入指定元素,index及后续的元素逐一往后移动
list.add(1,"喵喵");
System.out.println(list);
//删除指定元素,找到了就删除,该元素之后的元素逐一往前覆盖
list.remove("jvm");
System.out.println(list);
//删除list中index位置上的元素,注意访问的下标要合法,否则下界越界异常
list.remove(3);
System.out.println(list);
//检测是否包含指定元素,包含返回true,否则返回false
if(list.contains("jvm")){
System.out.println("包含jvm");
}else{
System.out.println("不包含");
}
//查找指定元素第一次出现的位置:indexOf从前往后找,lastIndexOf从后往前找
list.add("JavaSE");
System.out.println(list.indexOf("JavaSE"));
System.out.println(list.lastIndexOf("JavaSE"));
//使用list中[0,2)之间的元素构成一个新的ArrayList返回
List<String> ret=list.subList(0,2);
System.out.println(ret);
list.clear();
System.out.println(list.size());
ArrayList的遍历
ArrayList<Integer> list1=new ArrayList<>();
list1.add(1);
list1.add(2);
list1.add(3);
//fori遍历
for (int i = 0; i < list1.size(); i++) {
System.out.print(list1.get(i)+" ");
}
//for-each遍历
for (Integer integer:list1) {
System.out.println(integer+" ");
}
//使用迭代器:Iterator-父类,listIterator-子类
Iterator<Integer> it=list1.listIterator();
while(it.hasNext()){
System.out.print(it.next()+" ");
}
ArrayList的扩容机制
提出一个问题:
当我们调用不带参数的构造方法的时候,它是如何添加数据的?
其中Math.max(10,var1):返回其中的最大值。
总结:当我们调用不带参数的构造方法的时候,第一次的add的时候,才将分配大小为10的内存。
放在第11个的时候(grow)怎么扩容?
采用当前长度的1.5倍扩容。## 顺序表的思考
1、扩容之后,可能就带来空间的浪费
2、当前顺序表,每次删除一个元素或者插入一个元素【比如删除/插入都是第一个元素】,都需要将每个元素移动
总结顺序表:
ArrayList不适合使用在频繁的插入和删除的场景,适合给定下标位置进行查看元素,此时可以到达O(1)。