篇章四 数据结构——顺序表
目录
1.List
1.1 什么是 List
1.2 常见接口介绍
1.3 List的使用
2. ArrayList 与 顺序表
2.1 线性表
2.2 顺序表
1.什么是顺序表
2.为什么要有顺序表?
3.自己实现一个add方法
2.3 ArrayList简介
2.4 ArrayList使用
1. ArrayList的构造
1.1 ArrayList 基础属性的含义编辑
1.2 ArrayList() 解析
1.3 ArrayList(int initialCapacity) 解析
1.4 ArrayList(Collection c) 解析
2. ArrayList 的常见操作
2.1 add(int index, E element)
编辑
2.2 remove的区分
2.3 subList 存在问题
3. ArrayList的遍历
4. ArrayList的扩容
5.ArrayList的练习
5.1 用于字符串
5.2 杨辉三角:
5.3 洗牌算法
2.5. ArrayList的缺点
1.List
1.1 什么是 List
在 集合框架 中,List 是一个接口,继承自 Collection
Collection 也是一个接口,该接口 规范 了后序容器中常用的一些方法, 具体如下:
Iterable 也是一个接口,表示实现该接口的类是可以 逐个元素遍历 的,具体如下:
List的官方文档
但是站在 数据结构 的角度来看,List 就是一个 线性表。
即:n 个具有 相同类型元素 的 有限序列。
在该序列上可以执行 增删改查 以及 遍历 等操作。
1.2 常见接口介绍
List中提供了好多的方法,具体如下:
虽然方法比较多,但是常用方法如下:
增:add addAll
删:remove clear
改:set
查:get indexOf lastIndexOf
裁:subList
1.3 List的使用
注意:List是个接口,并不能直接用来实例化。
如果要使用,必须去实例化List的实现类。在集合框中,ArrayList 和 LinkedList 都实现了List接口。
2. ArrayList 与 顺序表
2.1 线性表
线性表 (linear list)是 n 个具有 相同特性 的数据元素的有序序列。
常见的线性表:顺序表、链表、栈、队列 ......
线性表在 逻辑 上是线性结构,也就说是 连续的一条直线 。但是在 物理结构 上并不一定是连续的,线性表在物理上存储时,通常以 数组和链式结构 的形式存储。
2.2 顺序表
1.什么是顺序表
顺序表是用一段 物理地址连续的存储单元 依次存储数据元素的线性结构
2.为什么要有顺序表?
数组本身自带的方法无法满足需求。
需要我们自己定义一个类,让类来提供方法,帮助操作数组。
3.自己实现一个add方法
画图:
代码:
public void add(int data) {if (isFull()) {grow();}this.array[this.usedSize] = data;this.usedSize++;}private void grow() {this.array = Arrays.copyOf(this.array, 2*this.array.length)}public boolean isFull() {return this.usedSize == array.length;}
逻辑非常严谨。
所以感觉很简单的功能,要写很多代码来保证严谨性。
2.3 ArrayList简介
在集合框架中,ArrayList是一个普通的类,实现了List接口,具体框架图如下:
【说明】
泛型:
ArrayList是以泛型方式实现的,使用时必须要先实例化
接口:
ArrayList实现了RandomAccess接口,表明ArrayList支持随机访问
ArrayList实现了Cloneable接口,表明ArrayList是可以clone的
ArrayList实现了Serializable接口,表明ArrayList是支持序列化的
并发:
和Vector不同,ArrayList不是线程安全的,在单线程下可以使用,在多线程中可以选择Vector或者CopyOnWriteArrayList
底层:
ArrayList底层是一段连续的空间,并且可以动态扩容,是一个动态类型的顺序表
2.4 ArrayList使用
1. ArrayList的构造
public static void main(String[] args) {// ArrayList创建// 构造一个空的列表List<Integer> list1 = new ArrayList<>();// 构造一个具有10个容量的列表List<Integer> list2 = new ArrayList<>(10);list2.add(1);list2.add(2);list2.add(3);// 利用 list2 构造 list3List<Integer> list3 = new ArrayList<>(list2);}
1.1 ArrayList 基础属性的含义
1.2 ArrayList() 解析
此处涉及到一部分扩容,仔细理解,扩容剩余部分在扩容部分进行讲解.
1.3 ArrayList(int initialCapacity) 解析
1.4 ArrayList(Collection<? extends E> c) 解析
2. ArrayList 的常见操作
2.1 add(int index, E element)
2.2 remove的区分
此处红线原因:
因为这个操作过时了,但是可以用就是用的很少。
2.3 subList 存在问题
public static void main(String[] args) {ArrayList<Integer> test = new ArrayList<>();test.add(1);test.add(2);test.add(3);test.add(4);test.add(5);System.out.println(test);List<Integer> list = test.subList(1, 3);System.out.println(list);
}
[1,3)
输出结果:
此时对裁剪后的数组进行改变,会出现与预期不符合的效果.
public static void main3(String[] args) {ArrayList<Integer> test = new ArrayList<>();test.add(1);test.add(2);test.add(3);test.add(4);test.add(5);System.out.println(test);List<Integer> list = test.subList(1, 3);System.out.println(list);System.out.println("======================================");list.set(0, 99);System.out.println(test); // 预期 1 2 3 4 5System.out.println(list); // 预期 99 3}
结果:
原因(如下图表示和代码分析):直接引用并没有创建新的对象
3. ArrayList的遍历
下面代码介绍了 ArrayList 的四种遍历方式
public static void main(String[] args) {ArrayList<Integer> test = new ArrayList<>();test.add(1);test.add(2);test.add(3);test.add(4);test.add(5);System.out.println(test);System.out.println("===使用迭代器输出 listIterator 拓展功能===");ListIterator<Integer> iterator3 = test.listIterator(test.size());while (iterator3.hasPrevious()) {System.out.print(iterator3.previous() + " ");}System.out.println();System.out.println("===使用迭代器输出 iterator===");Iterator<Integer> iterator = test.iterator();while (iterator.hasNext()) {System.out.print(iterator.next() + " ");}System.out.println();System.out.println("===使用迭代器输出 listIterator===");ListIterator<Integer> iterator2 = test.listIterator();while (iterator2.hasNext()) {System.out.print(iterator2.next() + " ");}System.out.println();int size = test.size();System.out.println("===for循环输出===");for (int i = 0; i < size; i++) {System.out.print(test.get(i) + " ");}System.out.println();System.out.println("===for each循环输出===");for (Integer x : test) {System.out.print(x + " ");}System.out.println();
}
4. ArrayList的扩容
总结:
检测是否真正需要扩容,如果是调用grow准备扩容
预估需要库容的大小初步预估按照1.5倍大小扩容如果用户所需大小超过预估1.5倍大小,则按照用户所需大小扩容,真正扩容之前检测是否能扩容成功,防止太大导致扩容失败
使用copyOf进行扩容
具体解析如下图:
5.ArrayList的练习
5.1 用于字符串
代码:
为什么 str2.contains(ch + "") 要 + ""
因为contains的参数是 CharSequence 而不是 Character
5.2 杨辉三角:
public List<List<Integer>> generate(int numRows) {List<List<Integer>> ret = new ArrayList<>(); List<Integer> list0 = new ArrayList<>();list0.add(1);ret.add(list0); // 从第二行开始 进行求每个元素for (int i = 1; i < numRows; i++) {// 处理第一个元素List<Integer> curRow = new ArrayList<>();curRow.add(1); // 中间List<Integer> preRow = new ArrayList<>();preRow = ret.get(i - 1);for (int j = 1; j < i; j++) {int val1 = preRow.get(j);int val2 = preRow.get(j - 1);curRow.add(val1 + val2);} // 尾巴curRow.add(1);ret.add(curRow);
}
5.3 洗牌算法
买牌:
public List<Cart> buyCard() {List<Cart> cartList = new ArrayList<>();for (int i = 1; i <= 13; i++) {for (int j = 0; j < 4; j++) {int rank = i;String suit = suits[j];Cart cart = new Cart(suit, rank);cartList.add(cart);}}return cartList;
}
洗牌:
public void shuffle(List<Cart> cartList) {Random random = new Random();for (int i = cartList.size() - 1; i > 0; i--) {int index = random.nextInt(cartList.size());swap(cartList, i, index);}
}private void swap(List<Cart> cartList, int i, int j) {Cart tmp = cartList.get(i);cartList.set(i, cartList.get(j));cartList.set(j, tmp);
}
玩牌:
public List<List<Cart>> play(List<Cart> cartList) {List<Cart> hand0 = new ArrayList<>();List<Cart> hand1 = new ArrayList<>();List<Cart> hand2 = new ArrayList<>(); List<List<Cart>> hand = new ArrayList<>();hand.add(hand0);hand.add(hand1);hand.add(hand2); for (int i = 0; i < 5; i++) {for (int j = 0; j < 3; j++) {Cart cart = cartList.remove(0);hand.get(j).add(cart);}} return hand;
}
2.5. ArrayList的缺点
-
ArrayList底层使用连续的空间,任意位置插入或删除元素时,需要将该位置后序元素整体往前或者往后搬移,故时间复杂度为O(N)
-
扩容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
-
扩容一般是呈 1.5 倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。