目录
(2)从第二行开始算,把数据一行一行塞到ret中,i代表行,j代表列
线性表是n个具有相同特性的数据有限序列,包括顺序表,链表,栈,队列
ArrayList底层是一个数组,可以进行随机访问O(1),当使用随机访问进行读写的时候,速度是比较快的。
随机访问不是查找,随机访问是按照下标访问;而查找使用的是indexOf这样的方法按照元素的值进行查找,这个过程要遍历ArrayList,开销是O(N)
问题:这里面有多少个有效数据?怎么用程序去算?
按照数组的思想,就是一个for循环然后遍历到0停止。但是万一这里面就有一个0,那不是直接就漏掉了。所以单纯用一个数组去做肯定不行。所以我们要结合一个方法来做。
现在我们要来自己写一个顺序表底层逻辑和方法
设置一个usedSize,每次往数组加进去一个元素,usedSize就++
- public class MyArrayList implements IList{
- public int[] elem;
- public int usedSize;//0
- //顺序表默认大小
- public static final int DEFAULT_SIZE = 10;
- //给数组分配内存
- public MyArrayList(){
- this.elem = new int[DEFAULT_SIZE];
- }
- //更灵活的构造方法
- public MyArrayList(int capacity){
- this.elem = new int[capacity];
- }
第一步的初始化完成了,接下来要思考顺序表中一存储的数据要怎么操作?没有数据的话要怎么加入数据?
我们设置一个接口IList,把可能用到的方法都放到接口中
- //IList
- public interface IList {
- // 新增元素,默认在数组最后新增
- public void add(int data);
- // 在 pos 位置新增元素
- public void add(int pos, int data);
- // 判定是否包含某个元素
- public boolean contains(int toFind);
- // 查找某个元素对应的位置
- public int indexOf(int toFind);
- // 获取 pos 位置的元素
- public int get(int pos);
- // 给 pos 位置的元素设为 value
- public void set(int pos, int value);
- //删除第一次出现的关键字key
- public void remove(int toRemove);
- // 获取顺序表长度
- public int size();
- // 清空顺序表
- public void clear();
- // 打印顺序表,注意:该方法并不是顺序表中的方法,为了方便看测试结果给出的
- public void display();
-
- boolean isFull();
- }
接着在MyArrayList里面重写接口方法
- @Override
- public void display() {
- for (int i = 0; i < this.usedSize; i++) {
- System.out.print(this.elem[i] + " ");
- }
- System.out.println();
- }
现在我们要添加元素,首先我们要判断顺序表里面元素是不是满的,所以我们添加多一个方法isFull()
- @Override
- public boolean isFull() {
- // if(usedSize == elem.length){
- // return true;
- // }
- // return false;
- return usedSize == elem.length;
- }
- @Override
- public void add(int data) {
- if(isFull()){
- //扩容
- elem = Arrays.copyOf(elem, elem.length*2);
- }
- this.elem[this.usedSize] = data;
- this.usedSize++;
- }
copyOf就是把elem列表拷贝一份再进行长度的扩容
代码优化:
我们可以把检查容量的过程封装到一个方法里面,我们再上面的add方法使用就只需要调用这个封装方法就行
- private void checkCapacity(){
- if(isFull()){
- //扩容
- elem = Arrays.copyOf(elem, elem.length*2);
- }
- }
但为什么是private呢? 因为这个检查容量的方法是我们在做功能的时候使用的,只服务与当前类,而不是提供给用户用的
现在顺序表能添加元素了,但是它还不知道要把这个元素加到哪里,我们搞了另一个add方法,传入两个参数pos和data
小问题:这里的pos可以放到5的位置上吗?
答案是不能。因为数据结构当中,每次存储数据的时候一定记住,必须要有一个前驱信息,如果4位置没有放置任何东西,5位置是肯定不能存东西的
所以要存放的pos∈[0, usedSize]
那我们可以封装一个方法来检查pos值得合法性
- private void checkPosOnAdd(int pos) throws PosIlleagaly{
- if(pos < 0|| pos > usedSize){
- System.out.println("不合法");
- //return;void要return点东西,我们可以抛一个异常
- throw new PosIlleagaly("插入元素下标异常"+pos);
- }
- }
- //PosIlleagaly.java
- package myList;
-
- public class PosIlleagaly extends RuntimeException{
- public PosIlleagaly(String msg){
- super(msg);
- }
- }
add方法里面也要加入异常语句
- @Override
- public void add(int pos, int data) {
- try{
- checkPosOnAdd(pos);
- }catch (PosIlleagaly e){
- e.printStackTrace();
- }
- checkCapacity();
-
- }
测试一下
处理完异常,我们看看怎么个插入法
定义一个i,从顺序表最末端元素的位置开始,把最后一个元素往后移elem[i+1] = elem[i],然后让i往前面去遍历(i--),直到i找到pos也就是i 测试效果: 如果toFind是一个字符串,那就得用equals,然后重写方法 查找元素下标 获取指定下标的元素 还是得判断pos有没有越界,这里0<=pos<=usedSize-1 给pos位置的元素进行更新 删除元素 1.找到要修改的数字 2.挪动数据(添加元素的逆过程),挪到最后一个 3.修改size,删掉最后一个格子,也就直接把刚挪到最后一个位置的元素删除 为什么要usedSize-1呢? 假设i在4的位置,往后挪一位到5的位置,刚好就是usedSize-1 = 6-1 到clear就更简单了,直接把usedSize置为0就行了 如果elem是Person类型,因为是引用数据类型,当我们把usedSize置为0的时候,引用的地址还不能被回收,这会造成内存泄漏 那JVM为什么不会自动回收对象呢?因为对象在被回收的时候有一个前提:对象没有被引用,而这里的0和1下标确确实实在引用那两个对象 那我们就要改一下代码(针对引用类型) 暴力方法:elem = null 温柔方法: 自己手搓了一个自己的顺序表代码后,我们不妨看看官方是怎么实现ArrayList的 整个ArrayList结构 看看代码(部分): 默认容量 数组和大小 空数组 当等于0的时候就分配给它一个空的数组(EMPTY_ELEMNTDATA) 这个数组用来表示内存分配的 当我们看到new那一行代码的时候,认为其实没有分配内存 那这些add怎么存储到ArrayList当中的呢? 从上面的分析可以得出结论1:第一次add的时候会分配10的内存 10个放满之后才会调用这个grow oldCapacity>>1 相当于除以2,换句话说这里的扩容标准1+0.5=1.5倍 再来看另一个构造方法 ?代表通配符,是E的子类或者E本身 举一反三:我们也可以用LinkedList,因为链表同样实现了collection接口 同理,只要实现了collection接口的,都可以传递 注意这里的第二个remove参数一定得是个对象,而不能光填数字 细说一下subList 初始化一个列表 分割下标1到2的元素(下标3取不到) 现在我要把list1的元素2改为99,用set方法后再打印 我们诧异地发现list的值也被改了??! 理论来说截取出来后进行修改不会动到原来的列表啊 其实这里的截取不是产生一个新对象,list1只是截取了list从1位置开始的地址,换句话说,list1 0位置的地址和list 1位置的地址一样,改了一个地方的值另一个也会被改变 两种打印列表元素的方式:for each和迭代器方式 题目一开始使用嵌套调用说明这段代码是实现了List这个接口的 我们拿ArrayList来测试一下这个嵌套调用 list3每次添加的元素都是列表 而杨辉三角的每一行都可以当作一个list,这就相当于一个二维数组了 设置i从后往前遍历(从前往后可能要包含到自己,不方便洗牌),再生成随机坐标index,然后拿这两个交换就行了 每个人抓的牌组合在一起相当于一个列表 因为三个人的牌是没有关系的,所以三个人每个人相当于又一个列表的元素,也就是说三个人每人就是一行,每一行都有他们独立的牌,这就形成了一个二维数组 3个人每人轮流抓5张牌,每次揭牌1张 测试效果
第三部分:找元素和更新元素
第四部分:删除元素+有的没的
@Override
public void clear() {
this.usedSize = 0;
}
for (int i = 0; i < usedSize; i++) {
this.elem[i] = null;
}
ArrayList 深度剖析
list1.set(0, 99);
System.out.println(list1);
System.out.println(list);
ArrayList的应用
1.杨辉三角
> list3 = new ArrayList<>();
(1)先处理第一行
> ret = new ArrayList<>();
(2)从第二行开始算,把数据一行一行塞到ret中,i代表行,j代表列
(3)观察杨辉三角的特点
(4)最后再把结果返回就行
(5)整个的代码
> generate(int numRows) {
> ret = new ArrayList<>();
2.简单的洗牌算法
1.要买一副拍,也就是要生成一副扑克牌
2.洗牌
3.揭牌,每人轮流抓5张牌
> hands = new ArrayList<>();