目录
3.4 List<Integer> 还是 ArrayList<Integer> ?
1. 线性表
线性表(linear list)是 n 个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列......
线性表在逻辑上是线性结构(含 顺序存储、链式存储),也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
2. 顺序表(ArrayList)
2.1 什么是顺序表?
我们知道,要对数组进行增删查改等操作,就需要用到一些方法,把这些方法跟数组抽象成一个类,就成了顺序表(ArrayList)。那么顺序表可以有以下定义:顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
2.2 顺序表的使用
2.2.1 ArrayList 的构造方法
方法 | 解释 |
ArrayList() | 无参构造 |
ArrayList(Collection<? extends E> c) | 利用其他 Collection 构建 ArrayList |
ArrayList(int initialCapacity) | 指定顺序表初始容量 |
1. 无参的构造方法:
可以看到,依旧未给 elementData 这个数组分配空间。
2. 指定顺序表初始容量:
3. 利用其他 Collection 构建 ArrayList :
?是通配符,具体内容待后面文章讲解。
ArrayList(Collection<? extends E> c) 的意思是:传入的类一定是实现了 Collection 这个接口的,并且其泛型的参数类型是 E (E 是 ArrayList 泛型参数类型)或是 E 的子类。
public static void main(String[] args) {
LinkedList<Integer> list = new LinkedList<>();
list.add(666);
list.add(888);
list.add(999);
ArrayList<Number> arrayList = new ArrayList<>(list);
arrayList.add(101);
System.out.println(arrayList);
}
通过上图可知,LinkedList 类是实现 Collection 接口的,并且代码中,其泛型接收的参数类型是 Integer ,而 ArrayList 的泛型接收的参数类型是 Number,我们知道 Integer 是 Number 的子类。
2.2.2 ArrayList 的常规操作
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) | 截取部分 list |
import java.util.ArrayList;
import java.util.List;
public class Text {
public static void main(String[] args) {
List<String> arrayList = new ArrayList<>();
//尾插:
arrayList.add("trees");
arrayList.add("the sun");
//指定位置插入元素:
arrayList.add(1,"flowers");
//尾插 List 类型 list 对象中的元素:
List<String> list = new ArrayList<>();
list.add("blue sky");
list.add("birds");
list.add("grass");
arrayList.addAll(list);
System.out.println(arrayList);
//获取某下标的元素:
System.out.println(arrayList.get(0));
//将某下标的元素设置成输入元素:
arrayList.set(0,"forests");
System.out.println(arrayList);
//删除指定下标的元素:
arrayList.remove(2);
System.out.println(arrayList);
//删除首个输入的元素:
arrayList.remove("blue sky");
System.out.println(arrayList);
//判断某元素是否在 arraylist 中:
System.out.println(arrayList.contains("grass"));
//返回首个输入元素的下标:
System.out.println(arrayList.indexOf("birds"));
}
}
2.2.3 ArrayList 的遍历
ArrayList 可以使用三方方式遍历:for循环+下标、foreach、使用迭代器,前两种是遍历 ArrayList 的最常用的方法。
1. for循环+下标
public class Text {
public static void main(String[] args) {
List<Character> arrayList = new ArrayList<>();
arrayList.add('S');
arrayList.add('O');
arrayList.add('S');
for (int i = 0; i < arrayList.size(); i++) {
System.out.print(arrayList.get(i)+" ");
}
}
}
2. foreach
public static void main(String[] args) {
List<String> arrayList = new ArrayList<>();
arrayList.add("Romeo");
arrayList.add(" and ");
arrayList.add("Juliet");
for(String x:arrayList){
System.out.print(x);
}
}
3. 使用迭代器
public static void main(String[] args) {
List<Integer> arrayList = new ArrayList<>();
arrayList.add(100);
arrayList.add(101);
arrayList.add(102);
arrayList.add(103);
ListIterator<Integer> iterator = arrayList.listIterator();
while(iterator.hasNext()){
System.out.print(iterator.next()+" ");
}
}
2.3 顺序表的优缺点
顺序表的优点:
1. 当下标给定时,查找速度非常快,时间复杂度为 O(1),ArrayList 更适合于给定下标查找元素。
顺序表的缺点:
1. ArrayList底层使用连续的空间,任意位置插入或删除元素时,需要将该位置后序元素整体往前或者往后搬移,故时间复杂度为O(N),基于此,顺序表不适合频繁对数据进行插入和删除。
2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
3. 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继 续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。
3. 练习题
3.1 练习1 一道面试题
要求删除 S1 中出现在 S2 的字符,使 S1 :wl t th prgraing wrld!
S1 :"welcome to the progamming world!"
S2 : "come"
import java.util.ArrayList;
import java.util.List;
public class Text {
public static void main(String[] args) {
List<Character> list = new ArrayList<>();
String s1 = "welcome to the programming world!";
String s2 = "come";
for (int i = 0; i < s1.length(); i++) {
char ch = s1.charAt(i);
if(!s2.contains(ch+"")){
list.add(ch);
}
}
for (int i = 0; i < list.size(); i++) {
System.out.print(list.get(i)+" ");
}
}
}
3.2 练习2 杨辉三角形
根据分析可知,杨辉三角每一行的值可以使用二维数组来存放,并且有 [i][j] = [i-1][j-1] + [i-1][j] 的规律:
因此,可以写出以下代码:
public List<List<Integer>> generate(int numRows) {
List<List<Integer>> list = new ArrayList<>();
List<Integer> row = new ArrayList<>();
row.add(1);
list.add(row);
for (int i = 1; i < numRows; i++) {
List<Integer> prerow = list.get(i-1);
List<Integer> currow = new ArrayList<>();
//每一行的第一个 1
currow.add(1);
//中间部分的值
for (int j = 1; j < prerow.size(); j++) {
currow.add(prerow.get(j) + prerow.get(j-1));
}
//最后一行的 1
currow.add(1);
list.add(currow);
}
return list;
}
3.3 练习3 洗牌算法
每一张牌抽象成一个类:
public class Poker {
private String suit ;
private int number;
public Poker(String suit, int number) {
this.suit = suit;
this.number = number;
}
public String getSuit() {
return suit;
}
public void setSuit(String suit) {
this.suit = suit;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
@Override
public String toString() {
return "{"
+ suit + + number +
'}';
}
}
一副扑克牌的制作,洗牌,分牌这些操作放在 Game 这个类中:
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class Game {
public static final String[] suits = {"♠","♥","♣","♢"};
public List<Poker> buyPokers(){
List<Poker> pokers = new ArrayList<>();
for (int i = 0; i < 4; i++) {
for (int j = 1; j <= 13; j++) {
Poker poker = new Poker(suits[i],j);
pokers.add(poker);
}
}
return pokers;
}
public void shuffle(List<Poker> pokers){
for (int i = pokers.size()-1; i >0 ; i--) {
Random random = new Random();
int index = random.nextInt(i);
swap( pokers,i,index);
}
}
public void swap(List<Poker> pokers,int i,int j){
Poker temp = pokers.get(i);
pokers.set(i,pokers.get(j));
pokers.set(j,temp);
}
public List<List<Poker>> game(List<Poker> pokers){
List<List<Poker>> hand = new ArrayList<>();
List<Poker> hand1= new ArrayList<>();
List<Poker> hand2= new ArrayList<>();
List<Poker> hand3= new ArrayList<>();
hand.add(hand1);
hand.add(hand2);
hand.add(hand3);
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 3; j++) {
hand.get(j).add(pokers.remove(0));
}
}
return hand;
}
}
在 Text 类调用 Game 类:
public class Text {
public static void main(String[] args) {
Game game = new Game();
//得到一副除去大小王的牌:
List<Poker> pokers = game.buyPokers();
System.out.println(pokers);
//洗牌:
game.shuffle(pokers);
System.out.println(pokers);
//三人轮流抽五张牌:
List<List<Poker>> hand = game.game(pokers);
for (int i = 0; i < hand.size(); i++) {
System.out.println("第"+(i+1)+"个人的牌为"+hand.get(i));
}
//剩下的牌为:
System.out.println(pokers);
}
}
输出:
3.4 List<Integer> 还是 ArrayList<Integer> ?
List<Integer> pokers = new ArrayList<>();
用接口对象接收,好处是发生了向上转型以及可以接收所有实现这个接口的对象,但坏处也很明显,那就是只能使用 List 类里的属性和方法。
ArrayList<Integer> pokers = new ArrayList<>();
该种写法,当前类的方法都能调用。
两种写法其实都行,根据具体的需要来决定用哪一种。