目录
一、线性表
线性表(linear list)是n个具有相同特性的数据元素的有限序列。
若有多个元素,则第一个元素无前驱,最后一个元素无后继,其他元素有且仅有一个前驱和后继
是一种在实际应用中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列....
线性表在逻辑结构上是线性结构,也就是说连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
二、顺序表
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删改查。
三、ArrayList
- 是以泛型方式实现的,使用时必须要先实例化
- 底层是一段连续的空间,并且可以动态扩容,是一个动态类型的顺序表
- Vector线程安全,ArrayList线程不安全,在多线程中可以选择Vector或CopyWriteArrayList
1、构造方法
ArrayList() | 无参构造(默认空数组0内存,默认只有在第一次进行添加元素操作时才会分配10的内存) |
ArrayList(Collection<? extends E> c) | 利用其他Collection构建ArrayList,并且泛型约束可以是E也可以是E的子类(E即ArrayList存储数据的类型) |
ArrayList(int initialCapacity) | 指定顺序表初始容量 |
2、常见操作
boolean add(E e) | 尾插e |
boolean addAll(Collection<? extends E> c) | |
void add(int index,E element) | |
E remove(int index) | |
boolean remove(Object o) | 删除遇到的第一个o(这里传递的参数必须为引用类型,比如new Integer(2)) |
E get(int index) | |
E set(int index,E e) | |
void clear() | 清空 |
boolean contains(Object o) | |
int indexOf(Object o) | 返回第一个o所在的下标 |
int LastIndexOf(Object o) | 返回最后一个o所在的下标 |
List<E> subList(int fromIndex,int toIndex) | 截取部分list(返回的是原列表的视图,对子列表的操作实际上是对原列表的操作) |
3、对ArrayList的遍历
遍历List指的是逐个访问List中的每个元素并执行某些操作。
而直接通过System.out.print(list)不属于对list的遍历,这实际上是将整个List对象转换成字符串并打印出来,而不是遍历List中的每个元素。
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
//AbstractCollection重写了toString
System.out.print(list);//[1, 2, 3, 4, 5]
//1、使用下标+for遍历:1 2 3 4 5
for (int i = 0; i < list.size(); i++)
System.out.print(list.get(i) + " ");
//2、借助foreach遍历:1 2 3 4 5
for (Integer i : list)
System.out.print(i + " ");
//3、使用迭代器:1 2 3 4 5
Iterator<Integer> it = list.listIterator();
while(it.hasNext()){
System.out.print(it.next() + " ");
}
}
4、扩容机制
①检查是否真正需要扩容,如果是则调用grow准备扩容
②预估需要扩容的大小
- 初步预估按照1.5倍大小扩容
- 如果用户所需大小超过预估1.5倍大小,则按照用户所需大小扩容
- 真正扩容之前检测是否能扩容成功,防止太大导致扩容失败
③使用copyOf进行扩容
5、具体应用
(1)CVTE面试题
删除第一个字符串出现的所有第二个字符串中的字符
str1:Welcome to CVTE
str2:come
输出:Wl t VT
public class Main {
//集合实现
public static List<Character> func(String str1, String str2) {
List<Character> list = new ArrayList<>();
for (int i = 0; i < str1.length(); i++) {
char ch = str1.charAt(i);
char lowerCh=Character.toLowerCase(ch);
char upperCh=Character.toUpperCase(ch);
if (!str2.contains(ch + "") && !str2.contains(lowerCh+"") && !str2.contains(upperCh+"")) {
list.add(ch);
}
}
return list;
}
//字符串实现
public static String removeChars(String str1, String str2) {
StringBuilder sb = new StringBuilder();
for (char c : str1.toCharArray()) {
char lowerChar = Character.toLowerCase(c);
char upperChar = Character.toUpperCase(c);
if (!str2.contains(lowerChar+"") && !str2.contains(upperChar+"") && !str2.contains(String.valueOf(c))) {
sb.append(c);
}
}
return sb.toString();
}
public static void main(String[] args) {
String str1="Welcome to CVTE";
String str2="come";
System.out.println("字符串实现:"+removeChars(str1,str2));
List<Character> list=func(str1,str2);
System.out.print("集合实现:");
for (char ch:list) {
System.out.print(ch);
}
}
}
(2)简单的洗牌算法
public class Card {
public int rank;
public String suit;
public Card(int rank, String suit) {
this.rank = rank;
this.suit = suit;
}
@Override
public String toString() {
return String.format("[%s%d]",suit,rank);
}
}
public class CardDemo {
public static final String[] SUITS={"♠","♥", "♣", "♦"};
//买一副牌
private static List<Card> buyDeck(){
List<Card> deck=new ArrayList<>(52);
for (int i = 0; i < 4; i++) {
for (int j = 1; j <=13; j++) {
String suit=SUITS[i];//四种之一
int rank=j;
Card card=new Card(rank,suit);
deck.add(card);
}
}
return deck;
}
private static void swap(List<Card> deck,int i,int j){
Card card=deck.get(i);
deck.set(i,deck.get(j));
deck.set(j,card);
}
//洗牌
private static void shuffle(List<Card> deck){
Random random=new Random(20190905);
for (int i = deck.size()-1; i > 0; i--) {
int r=random.nextInt(i);//生成随机数[0,i)
swap(deck,i,r);
}
}
public static void main(String[] args) {
//1、买一副牌
List<Card> deck=buyDeck();
System.out.println("刚买回来的牌:"+deck);
//2、洗牌
shuffle(deck);
System.out.println("洗过的牌:"+deck);
}
}
//3、3个人,每轮一人拿1张牌,总共拿5轮
List<List<Card>> hands=new ArrayList<>();//集合hands元素为各个成员的牌List<Card>
hands.add(new ArrayList<>());
hands.add(new ArrayList<>());
hands.add(new ArrayList<>());
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 3; j++) {
hands.get(j).add(deck.remove(0));//每次揭牌都是0下标的牌
}
}
System.out.println("A手中的牌:"+hands.get(0));
System.out.println("B手中的牌:"+hands.get(1));
System.out.println("C手中的牌:"+hands.get(2));
System.out.println("剩余的牌:"+deck);
(3) 杨辉三角
给定一个非负整数numRows,生成杨辉三角的前numRows行。每个数是它左上方和右上方数的和
输入:numRows=5
输出:[[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]
public List<List<Integer>> generate(int numRows) {
List<List<Integer>> ret=new ArrayList<List<Integer>>();
for(int i=0;i<numRows;i++){
List<Integer> row=new ArrayList<Integer>();
for(int j=0;j<=i;j++){
if(j==0 || j==i){
row.add(1);
}else{
row.add(ret.get(i-1).get(j-1)+ret.get(i-1).get(j));
}
}
ret.add(row);
}
return ret;
}
6、ArrayList的问题及思考
(1)优点:可以通过下标进行随机访问,时间复杂度O(1),适合对静态数据进行查找和更新
(2)缺点:
①ArrayList底层使用连续的空间,任意位置删除或插入元素时,需要将该位置后续元素整体往前或往后搬移,时间复杂度O(N),不适合删除和插入操作
②扩容需要申请新空间,拷贝数据,释放旧空间,有不小的消耗
③扩容一般是呈1.5倍的增长,势必会有一定的空间浪费。(特别是扩多了但只用一点点)