线性表是n个具有相同特性的数据元素的有限序列
常见的线性表:顺序表、链表、栈、队列...
①逻辑上:线性表是线性结构,即一条连续的直线
②物理上:不一定是连续的,通常用数组和链式结构形式存储
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构
(顺序表属于常见线性表的一种)
数组存储
(在数组上完成数据的增删查改)
①顺序表本质上就是通过方法来操作数组
②由于顺序表的底层是数组,而数组又是连续的,因此顺序表在逻辑上和物理上都是连续存储的
import java.util.Arrays;
//模拟顺序表的底层实现
public class SeqList {
private int[] elem; //定义一个elem数组,因为我们说过顺序表的底层就是个数组
private int usedSize; //记录当前顺序表当中有多少个有效的数据,即放了几个元素
private static final int DEFAULT_CAPACITY = 5;
public SeqList() {
//初始化SeqList,就初始化了数组个数为5个
this.elem = new int[DEFAULT_CAPACITY];
}
// (1)打印顺序表,注意:该方法并不是顺序表中的方法,为了方便看测试结果给出的
public void display() {
for (int i = 0; i < this.usedSize; i++) {
System.out.print(this.elem[i] +" ");
}
System.out.println();
}
// (2)新增元素,默认在最后一个数据后面新增
public void add(int data) {
//首先得判断满的情况
if(isFull()) {
resize(); //扩容方法
}
this.elem[usedSize] = data;
usedSize++;
}
public boolean isFull() {
//判断是否满了
return usedSize == elem.length;
}
private void resize() {
//使用copyof扩容,既能复制又能增长数组
//将原来的数组长度乘以2放进一个新数组赋给自己
//并把原来数组内容复制到新数组
elem = Arrays.copyOf(elem,2*elem.length);
}
// (3)判定是否包含某个元素
// 如果是数组存储的是引用数据类型要用equals比较
public boolean contains(int toFind) {
for (int i = 0; i < this.usedSize; i++) {
if(elem[i] == toFind) {
return true;
}
}
return false;
}
// (4)查找某个元素对应的位置 返回下标
public int indexOf(int toFind) {
for (int i = 0; i < this.usedSize; i++) {
if(elem[i] == toFind) {
return i;
}
}
return -1; //表示找不到这个元素
}
// (5)获取 pos 位置的元素
public int get(int pos) {
if(!checkPos(pos)) {
//pos位置下标不合法抛出自定义异常
throw new PosOutBoundsException("get 获取数据时,位置不合法!");
}
return elem[pos];
}
private boolean checkPos(int pos) {
//判断位置的合法性
if(pos < 0 || pos >= usedSize) {
return false;
}
return true;
}
// (6)获取顺序表长度
public int size() {
return this.usedSize;
}
// (7)给pos位置的元素设为 value【更新的意思】
// 将原来的元素改为新的元素
public void set(int pos, int value) {
if(!checkPos(pos)) {
//位置不合法同样抛出自定义异常
throw new PosOutBoundsException("set 数据时,位置不合法!");
}
this.elem[pos] = value;
}
// (8)在 pos 位置新增元素
public void add(int pos, int data) {
if(pos < 0 || pos > this.usedSize) {
//这里的pos > this.usedSize即可,不用等于
//为什么大于不行?因为是顺序表,是连续存储的
//对于顺序表来说,你要插入一个数据,必须要保证插入的位置前面是有元素的
throw new PosOutBoundsException("add 元素的时候,pos位置不合法!");
}
//判断是否满的情况
if(isFull()) {
resize();
}
//挪数据
//如果你要放的元素位置下标已经有元素了,就把现有的元素往后挪动
//我们选择从最后一个元素先开始移,因此i=this.usedSize-1
for (int i = this.usedSize-1; i >= pos ; i--) {
this.elem[i+1] = this.elem[i];
}
//存数据
this.elem[pos] = data;
this.usedSize++;
}
// (9)删除第一次出现的关键字key
// 思路:1.明确要删除元素的下标 2.后一个值覆盖前一个值
public void remove(int toRemove) {
if(isEmpty()) {
return;
}
//找到你要找的关键字下标
int index = indexOf(toRemove);
//等于-1表示没有你要删除的数字
if(index == -1) {
return;
}
//覆盖
for (int i = index; i < usedSize-1; i++) {
this.elem[i] = this.elem[i+1];
}
//删除完元素个数-1
usedSize--;
//如果是引用类型需要置为null
//elem[usedsize]=null;
}
public boolean isEmpty() {
return usedSize == 0;
}
// (10)清空顺序表
public void clear() {
//clear的作用是将地址置为null,但是现在村粗的是基本数据类型而不是引用数据类型
/*for (int i = 0; i < usedSize; i++) {
this.elem[i] = null;
}*/
usedSize = 0;
}
}
1.集合类是Java数据结构的实现
2.集合类是可以往里面保存多个对象的类,存放的是对象,不同的集合类有不同功能和特点
集合是与数组类似,也是一种容器,用于装数据的
①数组
(1)大小:数组定义完成并启动后,类型确定、长度固定
(2)增删改查:在个数不能确定,且要进行增删数据操作的时候,数组是不太合适的
(3)功能:数组功能单一
②集合
(1)大小:集合的大小不固定,启动后可以动态变化,类型也可以选择不固定
(2)增删改查:集合非常适合做元素个数不确定,且要进行增删操作的业务场景
(3)功能:集合还提供了丰富多样、全方面的功能
ArrayList是一个集合类,实现了List接口
(可以通过上述的集合类总图了解)
①ArrayList是动态数组,是一个动态类型的顺序表,可动态扩容,长度可变
(ArrayList底层是基于数组实现的,是一段连续的空间,但ArrayList是动态数组)
②ArrayList的动态扩容通常是按1.5倍扩容
1.ArrayList是以泛型方式实现的
(使用时必须要先实例化)
2.ArrayList实现了RandomAccess接口
(ArrayList支持随机访问)
3.ArrayList实现了Cloneable接口
(ArrayList支持clone)
4.ArrayList实现了Serializable接口
(ArrayList是支持序列化的)
5.ArrayList不是线程安全的
(在单线程下可以使用,在多线程中可以选择Vector或者CopyOnWriteArrayList)
泛型举例:
ArrayList
: 其实就是一个泛型类,可以在编译阶段约束集合对象只能操作某种数据类型(1)ArrayList
: 此集合只能操作字符串类型的元素(2)ArrayList
: 此集合只能操作整数类型的元素
①可调用ArrayList的所有方法
ArrayList
arrayList = new ArrayList<>();
②向上转型,List是父类,发生了动态绑定,只能调用子类重写父类List接口的方法,不能调用子类特有的方法
List
arraylist = new ArrayList<>();
实例化的时候推荐使用泛型<>;指定一下存放的数据类型,避免数据类型杂乱无章
①ArrayList():无参构造方法
(当调用无参构造方法时,默认数组长度为0;只有第一次调用add方法时才会分配内存,此时数组长度为10,满了按1.5倍扩容)
②ArrayList(int initialCapacity):指定初始容量
1.当指定的容量>0,数组长度为指定值
2.当指定的容量=0,默认数组长度为0
3.当指定的容量<0,抛出异常
(数组满了就按1.5倍扩容)
③ArrayList(Collection extends E> c):利用其他 Collection构建 ArrayList
1.必须是实现了Collection接口的
2. extends E>:带有泛型;说明你要传入的数据,它的泛型参数必须是E或E的子类
①boolean add(E e):数组尾部添加元素e
②void add(int index, E e):将元素e插入到指定的index位置
③boolean addAll(Collection extends E> c):数组尾部批量添加Collection extends E>类c的元素
import java.util.ArrayList; import java.util.List; //使用addAll方法 public class Demo1 { public static void main(String[] args) { ArrayList
arrayList = new ArrayList<>(); arrayList.add("handsome boy"); List list = new ArrayList<>(); list.add("hlizoo"); list.add("is a"); list.addAll(arrayList); for (String x:list) { System.out.print(x+" "); } } }
①E remove(int index):删除 index 位置元素
(返回值是删除的元素,因此我们可以接收它)
②boolean remove(Object o):删除遇到的第一个o元素
③只要你删除了元素,那么后面的元素就会往前移import java.util.ArrayList; import java.util.Iterator; public class Demo3 { public static void main(String[] args) { ArrayList
arrayList = new ArrayList<>(); arrayList.add("a"); arrayList.add("bb"); arrayList.add("ccc"); System.out.println(arrayList); String ret = arrayList.remove(1); System.out.println(arrayList); } }
import java.util.ArrayList; import java.util.List; //使用remove public class Demo2 { public static void main(String[] args) { List
list = new ArrayList<>(); list.add("a"); list.add("b"); list.add("c"); String ret1 = list.remove(1); //删除1下标的元素 System.out.println("ret1="+ret1); Boolean ret2 = list.remove("a"); //删除元素a System.out.println("ret2="+ret2); } }
void clear():清空数组
(引用类型元素全部置为null,数组长度置为0)
List
subList(int fromIndex, int toIndex): 截取部分list
①返回类型是List<>,接收时记得要用List接口接收
②截取的范围是[开始下标,结束下标),采用前闭后开区间
③sublist截取的是原本数组的地址,你改变任何值都会影响原数组
import java.util.ArrayList; import java.util.List; //使用sublist public class Demo2 { public static void main(String[] args) { ArrayList
arraylist = new ArrayList<>(); arraylist.add("aaa"); arraylist.add("bbbb"); arraylist.add("ccccc"); arraylist.add("dddddd"); arraylist.add("123456789"); List ret = arraylist.subList(1,3); //截取下标[1,3) System.out.println(ret); System.out.println(arraylist); System.out.println("================================"); ret.set(0,"what"); //ret此时存储的是bbbb和ccccc,把0下标改为what就是把bbb改成what System.out.println(ret); System.out.println(arraylist); } }
System.out.println( )
(原因在于ArrayList父类重写了toString方法)
import java.util.ArrayList; public class Demo3 { public static void main(String[] args) { ArrayList
arrayList = new ArrayList<>(); arrayList.add("hlizoo"); arrayList.add("777777"); System.out.println(arrayList); } }
①区分length和size
(1)Java中的length()方法是针对字符串String
length()方法:用于获取字符串长度
(2)Java中的size()方法是针对泛型集合
size()方法:用于获取泛型集合有多少个元素
②使用get方法获取对应下标元素打印
import java.util.ArrayList; public class Demo3 { public static void main(String[] args) { ArrayList
arrayList = new ArrayList<>(); arrayList.add("I"); arrayList.add("like"); arrayList.add("java"); for (int i = 0; i < arrayList.size(); i++) { System.out.print(arrayList.get(i)+" "); } System.out.println(); } }
①冒号的右边是要遍历的集合/数组
②冒号的左边是遍历集合/数组中的类型+元素
import java.util.ArrayList; public class Demo3 { public static void main(String[] args) { ArrayList
arrayList = new ArrayList<>(); arrayList.add("I"); arrayList.add("like"); arrayList.add("java"); for (String x:arrayList) { System.out.print(x+" "); } } }
①通过调用ArrayList集合的iterator或listIterator方法获取迭代器对象
②boolean hasNext():
hasNext
方法用于检查是否还有下一个元素
③E next():
next
方法用于获取下一个元素的值
import java.util.ArrayList; import java.util.Iterator; public class Demo3 { public static void main(String[] args) { ArrayList
arrayList = new ArrayList<>(); arrayList.add("I"); arrayList.add("like"); arrayList.add("java"); Iterator iterator = arrayList.listIterator(); while(iterator.hasNext()){ System.out.print(iterator.next()+" "); } System.out.println(); } }
前面我们提到过ArrayList是一个动态类型的顺序表,可以默认按1.5倍自动扩容
①当ArrayList中的元素个数达到容量大小时,会调用grow(int minCapacity)方法进行扩容
②grow方法首先会对当前容量进行扩大,再创建一个新的数组,将原来的元素复制到新的数组中,最后将引用指向新的数组
③grow方法预估扩容的大小
(1)初步按照1.5倍扩容
(2)如果用户输入的参数其扩容倍数需求大于1.5,则按用户需求来
(3)在真正扩容之前会检测扩容能否成功,防止太大导致扩容失败
①可根据下标去查找元素,效率极高
②可指定下标去更新元素,效率也高
①每次插入数据都要往后移动元素,极端情况下如果要插入到0下标,那么效率会很低
②每次删除元素都要后的往前覆盖,极端情况下如果要删除0下标元素,效率也会很低
③当数组满了之后可以进行1.5倍扩容,那万一扩容完了却只添加1个元素,就浪费空间
3.总结
ArrayList更适合去查找数据或者更新数据
(至于删除数据和插入数据,更推荐使用链表或LinkedList)
CVTE面试题:删除第一个字符串中出现的第二个字符串当中的字符
import java.util.ArrayList; import java.util.List; //题目:删除第一个字符串中出现的第二个字符串当中的字符 public class Question1 { public static void func(String str1, String str2){ List
list = new ArrayList<>(); for (int i = 0; i < str1.length(); i++) { //得到str1中的每一个字符 char ch = str1.charAt(i); if(!str2.contains(ch+"")){ list.add(ch); //将字符放进集合ArrayList } } for (char x:list) { System.out.print(x+""); } System.out.println(); } public static void main(String[] args) { func("welcome to bit","come"); } }
①定义一个Card类来表示一张牌当中的元素
//表示一张牌中有什么
//一张牌就有花色和数字
public class Card {
private String suit;
private int rank;
public Card(String suit, int rank) {
this.suit = suit;
this.rank = rank;
}
public String getSuit() {
return suit;
}
public void setSuit(String suit) {
this.suit = suit;
}
public int getRank() {
return rank;
}
public void setRank(int rank) {
this.rank = rank;
}
@Override
public String toString() {
return suit +" " + rank;
}
}
②定义一个Get类来实现抓取
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class Get {
//花色
private static final String[] SUITS = {"♥","♠","♣","♦"};
//生成一副牌(牌有52张,不计大小王)
public static List buyCard() {
List cards = new ArrayList<>();
//外层for表示花色
for (int i = 0; i < SUITS.length; i++) {
//内层for表示一个花色有13张牌
for (int j = 1; j <= 13; j++) {
//初始化一张牌card
Card card = new Card(SUITS[i],j);
//将生成的牌都放进ArrayList对象cards中
cards.add(card);
}
}
return cards;
}
//洗牌(洗牌才能保证公平性)
//思路:从后往前,生成一个随机下标与当前下标元素交换
public static void shuffle(List cards) {
Random random = new Random();
for (int i = cards.size()-1; i > 0 ; i--) {
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) {
List cards = buyCard(); //获得一副牌
System.out.println(cards); //展示原始牌
System.out.println("洗牌:");
shuffle(cards); //牌交换顺序
System.out.println(cards); //展示洗牌后的牌
//3个人,每个人轮流抓5张牌
//是轮流,A一张B一张C一张,然后5个轮次,而不是一次性抓5张
//因为每个人的牌都是独立的,因此每个人都安排一个List对象,抓到的牌放进对应的List
List hand1 = new ArrayList<>();
List hand2 = new ArrayList<>();
List hand3 = new ArrayList<>();
//此时就类似一个二维数组
//hands里面就存放三个List对应三个人
//这样就可以方便指定人去抓牌了
List> hands = new ArrayList<>();
hands.add(hand1);
hands.add(hand2);
hands.add(hand3);
//每个人轮流抓5张牌,因此i<5,j<3
//而不是i<3,j<5,这样就变成了一个人连续抓5张牌
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 3; j++) {
//抓牌的动作
//从牌组里面拿牌就相当于此牌被拿走没有了
//那么每次拿的都是0下标,是因为牌被拿走后它的下一张牌就相当于0下标的牌了
Card card = cards.remove(0);
//如何抓到指定的人的手里呢??
//比如hands.get(0)就是hand1,第一个人抓牌
hands.get(j).add(card);
}
}
System.out.println("第1个人牌:");
System.out.println(hand1);
System.out.println("第2个人牌:");
System.out.println(hand2);
System.out.println("第3个人牌:");
System.out.println(hand3);
System.out.println("剩下的牌:");
System.out.println(cards);
}
}
③部分代码分析
④代码运行结果
杨辉三角题目
①代码整体分析:
②代码
import java.util.ArrayList;
import java.util.List;
//杨辉三角
public class Question3 {
public List> generate(int numRows) {
//相当于二维数组,把所有list放入ret
List> ret = new ArrayList<>();
//相当于每一行的list
List list = new ArrayList<>();
//将第一行放入list,因为第一行只有一个1
list.add(1);
ret.add(list);
//从第二行开始,以此类推
//i从下标1开始
for (int i = 1; i < numRows; i++) {
//每一行定义一个curRow
List curRow = new ArrayList<>();
//第一个数字1
curRow.add(1);
//处理中间的数字
//preRow获取上一行
List preRow = ret.get(i-1);
for (int j = 1; j < i; j++) {
int val = preRow.get(j) + preRow.get(j-1);
curRow.add(val);
}
//最后一个数字1
curRow.add(1);
ret.add(curRow);
}
return ret;
}
}