一.线性表
1.1 线性表的定义
线性表 ( linear list ) 是 n 个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结 构,常见的线性表:顺序表、链表、栈、队列...线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
1.2 List接口
站在数据结构的角度来看,List就是一个线性表,即n个具有相同类型元素的有限序列,在该序列上可以执行增删查改等操作
List是一个接口,其框架如下
在Java的集合结构中,List是个接口,不能用来实例化,如果要去使用,只能实例化List的实现类
我们下文的ArrayList就实现类List接口
二.顺序表
顺序表是用一段 物理地址连续 的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改
2.1 顺序表的模拟实现
下面简单实现一下int类型的顺序表
public class SeqList {
private int[] array;//定义整型数组
private int size;//size表示数组的有效元素个数
private final int DEFAULT_CAPACITY=10;//定义数组的初始容量为10
SeqList() {
array=new int[DEFAULT_CAPACITY];
}
//扩展数组容量
private void inCapacity(int capacity) {
this.array= Arrays.copyOf(this.array,capacity);
}
//添加数组元素
public void add(int data) {
if(this.size==this.array.length) {
inCapacity(this.array.length*2);
}
this.array[this.size++]=data;
}
//判断下标pos是否有效
private boolean isValid(int pos) {
if(pos<0||pos>this.size)
return false;
return true;
}
//指定添加下标为pos的元素
public void add(int pos,int data) {
if(!isValid(pos)) {
throw new OutOfRangeException("添加元素的下标越界");
}
for(int i=this.size;i>pos;i--) {
this.array[i]=this.array[i-1];
}
this.array[pos]=data;
this.size++;
}
//查找toFind元素的下标
public int indexOf(int toFind) {
for(int i=0;i<this.size;i++) {
if(this.array[i]==toFind)
return i;
}
return -1;
}
//查找数组是否包含toFind元素
public boolean contains(int toFind) {
if(indexOf(toFind)==-1) return false;
return true;
}
//获取下标为pos的元素
public int get(int pos) {
if(!isValid(pos)) {
throw new OutOfRangeException("获取元素的下标越界");
}
return array[pos];
}
/设置下标为pos的元素是value
public void set(int pos,int value){
if(pos<0||pos>=this.size) {
throw new OutOfRangeException("设置元素的下标越界");
}
this.array[pos]=value;
}
//移除元素toRemove
public void remove(int toRemove) {
if(this.size==0) return;
int index=indexOf(toRemove);
if(index==-1) return;
for(int i=index;i<this.size-1;i++) {
this.array[i]=this.array[i+1];
}
this.size--;
}
//获取数组有效长度
public int size(){
return this.size;
}
//清空数组
public void clear() {
this.size=0;
}
//显示数组全部元素(是为了方便调试使用的)
public void display() {
for(int i=0;i<size;i++) {
System.out.print(array[i]+" ");
}
System.out.println();
}
}
//下面是下标不合法时抛的异常
public class OutOfRangeException extends RuntimeException{
public OutOfRangeException() {
}
public OutOfRangeException(String message) {
super(message);
}
}
2.2 ArrayList介绍
ArrayList的底层结构就是前面说的顺序表,在集合框架中, ArrayList 是一个普通的类
根据上面的图片可以知道
- ArrayList是泛型类,在实例化的时候必须传入泛型参数
- ArrayList实现了RandomAccess接口,表示ArrayList支持随机访问
- ArrayList实现了Cloneable接口,表示ArrayList可以克隆
- ArrayList实现了Serializable接口,表示ArrayList可以序列化
- ArrayList支持单线程,在多线程的情况下不安全,尽量使用Vector或CopyOnWriteArrayList
- ArrayList底层是一个连续的空间,并且支持动态扩容
2.3 ArrayList的使用
2.3.1 ArrayList的构造
ArrayList提供了三种构造方法
1.无参构造
其中,elementData就是ArrayList的底层数组,可以看到,如果使用无参构造,elementData会指向一个空数组
既然无参构造并没有给ArrayList的底层数组分配内存空间,那么如果往ArrayList对象里面增添一个元素,是不是会抛异常?
public class Test {
public static void main(String[] args) {
ArrayList<Integer> a=new ArrayList<>();//调用无参构造
a.add(1);//增添元素
}
}
结果并没有抛出异常,在debug下我们可以看到数组的有效长度变成了1
这就涉及到add方法的实现了,一起剖析一下源码吧
1.如果add元素时原数组是空数组,就会将数组扩容为10,并增添新的元素
2.如果使用add增添元素,当数组容量不够时,就会自动扩容为原来容量的1.5倍,这是为了减少数组的扩容次数,也就意味着减少了数组的拷贝
2.初始化时指定容量
如果传入的initialCapacity>0,就让elementData数组初始化为该容量
如果initialCapacity==0,就让elementData指向一个空数组
如果initialCapacity<0,就抛异常
3. 使用其他Collection初始化ArrayList
举例
public class Test { public static void main(String[] args) { ArrayList<Integer> a=new ArrayList<>(); a.add(1); a.add(2); a.add(3); a.add(4); ArrayList<Integer> b=new ArrayList<>(a);//使用a初始化顺序表b ArrayList<String> c=new ArrayList<>(a);//报错,传入的类型参数是Integer,不是String的子类 } }
通过Debug调试一下
可以看到,顺序表b的元素和a完全一致
2.3.2 ArrayList的常见操作
下面提供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<E>
subList
(int fromIndex, int toIndex)
| 截取[fromIndex,toIndex)的部分list |
注:
1.使用remove方法时,如果传入的是int数据类型,编译器优先使用remove(int Index)方法
如果目的就是删除这个元素,解决办法是进行装包
2. subList方法返回的是对象的部分引用,更改子顺序表会影响原顺序表
public static void main(String[] args) { ArrayList<String> list=new ArrayList<>(); list.add("hello"); list.add("welcome"); list.add("abcd"); List<String> sublist=list.subList(0,2);//获取下标为0,1的部分列表 sublist.set(0,"iodiobo");//将字列表的第一个元素改为idiobo System.out.println(list);//输出原顺序表 }
2.3.3 ArrayList的遍历
和数组一样,ArrayList也有for+下标和增强for循环的遍历方式
1.for+下标
public static void main(String[] args) { ArrayList<Integer> a=new ArrayList<>(); a.add(1); a.add(2); a.add(3); a.add(4); for (int i = 0; i < a.size(); i++) { System.out.print(a.get(i)+" "); } }
2.for-each
public static void main(String[] args) { ArrayList<Integer> a=new ArrayList<>(); a.add(1); a.add(2); a.add(3); a.add(4); for(int e:a) { System.out.print(e+" "); } }
第三种方法就是使用迭代器,使用示例如下
public static void main(String[] args) { ArrayList<Integer> a=new ArrayList<>(); a.add(1); a.add(2); a.add(3); a.add(4); Iterator<Integer> it=a.iterator();//也可以a.listIterator或者 //a.listIterator(int Index)表示从Index开始 while(it.hasNext()) { System.out.print(it.next()+" "); } }
2.4 ArrayList练习
使用ArrayList实现洗牌操作
//定义Card类
public class Card {
private String color;
private int number;
public Card(String color, int number) {
this.color = color;
this.number = number;
}
@Override
public String toString() {
return "color=" + color +
" number=" + number+" ";
}
}
public class Test {
private static String[] colors={"♥","♠","♣","♦"};
//获取52张牌
public static ArrayList<Card> getCards() {
ArrayList<Card> cards=new ArrayList<>(52);
for(int i=1;i<=13;i++) {
for(int j=0;j<4;j++) {
Card card=new Card(colors[j],i);
cards.add(card);
}
}
return cards;
}
//进行洗牌操作
public static void shuffle(ArrayList<Card> cards) {
for(int i=cards.size()-1;i>0;i--) {
Random random=new Random();
int j=random.nextInt(i);
Card tmp=cards.get(i);
cards.set(i,cards.get(j));
cards.set(j,tmp);
}
}
public static void main(String[] args) {
ArrayList<Card> cards=getCards();
shuffle(cards);
//定义一张三人桌
ArrayList<ArrayList<Card>> persons=new ArrayList<>();
for(int i=0;i<3;i++) {
persons.add(new ArrayList<Card>());
}
//共十五张牌,每五张牌让三个人轮流摸
for(int i=0;i<5;i++) {
for(int j=0;j<3;j++) {
persons.get(j).add(cards.remove(0));
}
}
//输出每个人的牌
for(int i=0;i<3;i++) {
System.out.println(persons.get(i));
}
//输出剩下的牌
System.out.println(cards);
}
}
2.5 ArrayList小结
ArrayList的好处就是它的物理空间是连续的,可以使用下标进行修改和查找,时间复杂度均为O(n),相对地,用ArrayList删除和增添元素时都要移动其他元素,尤其是当该元素的下标为0时,时间复杂度为O(n)
因此,顺序表适用于经常查找或更新元素的操作