Collection接口之 List和Set

在这里插入图片描述
1 List 接口继承自 Collection 接口, 允许定义一个 重复的 有序集合.
实现List接口的实现类主要有: ArrayList、LinkedList.
在这里插入图片描述

public static void main(String[] args) {
        //1.创建List实例
        List<String> list = new ArrayList<>();
        //2.新增元素
        list.add("C++");
        list.add("Java");
        list.add("Python");
        //3.打印整个list
        System.out.println(list);   // [C++,Java,Python]
        //4.按照下标来访问元素
        System.out.println(list.get(2));
        //5.跟据下标来修改元素
        list.set(0, "PHP");
        System.out.println(list);
        //6.循环遍历list
        for (String x : list) {
            System.out.println(x);
        }
        for(int i=0;i<list.size();i++){
            System.out.println(list.get(i));
        }
        //7.使用subList获取子序列
        System.out.println("获取子序列");
        System.out.println(list.subList(1,2)); // [Java] 左闭右开
        //8.可以使用构造方法构造出新的List对象
        List<String> list1=new ArrayList<>(list);
        System.out.println("复制了一份list1:");
        System.out.println(list1);
        list.set(0,"C#");
        //设置下标为index的元素为指定值
        System.out.println(list1);
        //list1 也可以直接输出.
    }

2 ArrayList(顺序表):

public class SeqList {
    private int[] datas = new int[100];
    private int size = 0;
    public int getSize() {
        // size 这个成员只能提供 get 方法, 不能提供 set
        // size 是通过后面是增删方法来进行维护的.
        return size;
    }
    public void display() {
        // 依次打印出每个元素
        // 形如: [1, 2, 3, 4]
        String result = "[";
        for (int i = 0; i < size; i++) {
            result += datas[i];
            if (i < size - 1) {
                result += ",";
            }
        }
        result += "]";
        System.out.println(result);
    }
    // pos 表示新元素要插入的位置(下标).
    // data 表示新元素的值
    
    public  void add(int pos, int data) {
        // 判定 pos 是否是有效的值
        // 写代码的时候要时刻关注参数的有效性
        if (pos < 0 || pos > size) {
            return;
        }
        // 扩容的支持, 顺序表如果容量不够了, 就能自动扩容
        if (size >= datas.length) {
    扩容操作:
            int[] newDatas = new int[2 * datas.length];
            for (int i = 0; i < datas.length; i++) {
                newDatas[i] = datas[i];
            }
            datas = newDatas;
        }
        // 1. 尾插的情况(比较简单的情况)
        //    把这个新元素放到下标为 size 的位置上.
        if (pos == size) {
            datas[pos] = data;
            size++;
            return;
        }
        // 2. 普通位置的插入
        for (int i = size - 1; i >= pos; i--) {
            datas[i + 1] = datas[i];
        }
        datas[pos] = data;
        size++;
    }
    
    public boolean contains(int toFind) {
        // 循环访问每个元素并进行比较.
        // 如果发现某个元素和 toFind 相等, 就找到了, 返回 true
        // 如果所有元素都找完了, 也没找到相等的, 就返回 false
        for (int i = 0; i < size; i++) {
            if (datas[i] == toFind) {
                return true;
            }
        }
        return false;
    }
    
    public int search(int toFind) {
        for (int i = 0; i < size; i++) {
            if (datas[i] == toFind) {
                return i;
            }
        }
        return -1;
    }
    
    public int getPos(int pos) {
        return datas[pos];
    }
    public void setPos(int pos, int data) {
        datas[pos] = data;
    }
    
    // toRemove 表示要被删除的元素
     void remove(int toRemove) {
        // 1. 先找到 toRemove 对应的下标
        int pos = search(toRemove);
        if (pos == -1) {
            // 没找到, 要删除的元素不存在
            return;
        }
        // 2. 如果下标是最后一个元素, 直接尾删即可.
        if (pos == size - 1) {
            size--;
            return;
        }
        // 3. 如果下标是中间元素, 需要先搬运, 再删除
        for (int i = pos; i < size - 1; i++) {
            datas[i] = datas[i + 1];
        }
        size--;
    }
    public void clear() {
        size = 0;
    }
}

(1) ArrayList 底层实现是 动态数组. 在内存中是连续存在的, 有序的.(能保证取出的顺序和插入的顺序一致).
(2) 增容代价较大. 也是我们最常用的集合, 允许任何符合规则的元素插入.(包括null)
(3) ArrayList 提供了 索引机制 (即下标). 可以通过索引迅速查找元素, 查找效率高. 增加 或 删除元素时身后的元素都要移动, 增删率效低.

根据下标 获取元素/修改元素 O(1).
任意位置增加/删除元素 O(N).
尾删O(1), 尾插O(1)(前提是没有触发扩容).

(4) ArrayList: 线程不安全, 查询速度快.
注意: Vector: 线程安全, 但速度慢.
(5) ArrayList list = new ArrayList(), 不指定容量大小默认为 10, 每次扩容容量为原来 size的 0.5倍.

3 LinkedList(链表):

// 一个节点
class Node {
    public int data;   // 数据
    public Node next = null;  // 下一个节点的位置
    public Node(int data) {
        this.data = data;
    }
}

我们写了一个 不带傀儡节点的 单向链表.
public class LinkedList {
    // 管理所有的链表节点. 只需要记录头结点的位置即可
    // 初始情况下 head 为 null, 此时表示空链表(不带傀儡节点)
    private Node head = null;
    
        // 头插
    public void addFirst(int data) {
        // 1. 根据 data 值构建一个链表节点(Node对象)
        Node node = new Node(data);
        // 2. 如果链表为空链表
        if (head == null) {
            head = node;
            return;
        }
        // 3. 如果链表不是空链表
        node.next = head;
        head = node;
    }
    
        // 尾插
    public void addLast(int data) {
        // 1. 根据 data 构造一个 Node 对象
        Node node = new Node(data);
        // 2. 如果链表为空链表
        if (head == null) {
            head = node;
            return;
        }
        // 3. 如果链表非空, 需要先找到这个链表末尾的最后一个节点
        Node tail = head;
        while (tail.next != null) {
            tail = tail.next;
        }
        // 循环结束之后, tail 就对应到最后一个节点了
        tail.next = node;
    }
    
    public void display() {
        // 把链表中的每个元素都打印出来
        for (Node cur = head; cur != null; cur = cur.next) {
            System.out.print(cur.data + " ");
        }
        System.out.println();
    }
    
    public int getSize() {
        int size = 0;
        for (Node cur = head; cur != null; cur = cur.next) {
            size++;
        }
        return size;
    }
    
    指定位置插, 插入成功, 返回 true, 否则 false
    public boolean addIndex(int index, int data) {
        int size = getSize();
        // 1. 判定 index 是否有效
        if (index < 0 || index > size) {
            // index 无效, 插入失败
            return false;
        }
        // 2. 如果 index 为 0, 相当于头插
        if (index == 0) {
            addFirst(data);
            return true;
        }
        // 3. 如果 index 为 size, 相当于尾插
        if (index == size) {
            addLast(data);
            return true;
        }
        Node node = new Node(data);
        // 4. 如果 index 是一个中间的位置
        //  a) 先找到 index 的前一个节点 index - 1
        Node prev = getPos(index - 1);
        //  b) 接下来就把新节点插入到 prev 之后
        // 注释是头插的代码
        // node.next = head;
        // head = node;
        node.next = prev.next;
        prev.next = node;
        return true;
    }
    
    // 给定 index 下标, 找到对应的节点
    private Node getPos(int index) {
        Node cur = head;
        for (int i = 0; i < index; i++) {
            // cur.next 操作之前必须要保证
            // cur 是非 null 的
            cur = cur.next;
        }
        return cur;
    }
    
    public boolean contains(int toFind) {
        for (Node cur = head; cur != null; cur = cur.next) {
            if (cur.data == toFind) {
                return true;
            }
        }
        return false;
    }
    
    public void remove(int toRemove) {
         1. 如果要删除元素是头结点, 特殊处理一下
        if (head.data == toRemove) {
            // 头结点要被删掉
            head = head.next;
            return;
        }
         2. 如果要删除元素不是头结点, 找到要删除节点的前一个位置
        Node prev = searchPrev(toRemove);
        // 3. 修改引用的指向, 完成删除
        // prev.next = prev.next.next;
        Node toDelete = prev.next;
        prev.next = toDelete.next;
    }
    
    private Node searchPrev(int toRemove) {
        // 找到 toRemove 的前一个节点
        for (Node cur = head; cur != null
                && cur.next != null; cur = cur.next) {
            if (cur.next.data == toRemove) {
                return cur;
            }
        }
        return null;
    }
}

(1) LinkedList 底层实现是 循环双向链表结构, 有序的. 因为不是数组结构(数据并不是在连续的内存空间上), 所以 不存在扩容机制, 插入一个开辟一个空间.

(2) LinkedList通过 修改前后两个元素的链接指向 实现增加和删除操作. 增删效率高.

对链表 头结点 进行添加操作, 删除操作, 查找操作时间复杂度都为 O(1).
在链表 任意位置及尾部 添加, 删除, 查找, 因为要从头遍历链表,时间复杂度都为 O(N).

(3) LinkedList是 线程不安全的.
ConcurrentLinkedQueue 线程安全.

注意: Concurrent包 下先用 volatile 声明一个变量, 然后使用 CAS的原子条件 更新来实现线程之间的同步, 保证了线程安全.

什么情况使用 ArrayList, 什么情况使用 LinkedList?
(1) ArrayList. 当操作是在一列数据的 后面添加数据 而不是在前面或中间, 并且需要 随机地 访问其中的元素.
(2) LinkedList. 当你的操作是在一列数据的 前面或中间添加或删除数据, 并且 按照顺序 访问其中的元素.

Set 接口继承自 Collection 接口, 允许定义一个 不重复的 无序集合.
(1) 元素不可以重复, 最多只能插入一个null.
(2) 无序, 不能保证 插入的顺序和 输出的顺序一致.
(3) 没有索引.
(4) 实现Set接口的实现类主要有:HashSet 和 TreeSet.

Set 底层是由Map封装的.

一: HashSet 特点:

1 底层实现是 HashMap(保存数据).
HashSet的值都是存储在HashMap中的, HashSet的值是 作为HashMap的 key存储 在HashMap中的, 当存储的值已经存在时返回false.

2 线程不安全. 存取速度快. 默认初始容量为 16, 加载因子为 0.75, 扩容为原来容量的 2倍.
3 HashSet如何 保证元素不重复?
HashMap中的key不能重复, 因为计算出相同的 Hash值后, 会在对应存储位置的链表上操作, 并不能出现两个相同的key. 而HashSet的元素又是作为HashMap的key存入的, 当然也不能重复.

4 LinkedHashSet 特点:
具有 HashSet 的查找效率, 并且内部使用的是 双向链表 维护元素的插入顺序, 保证有序.

二: TreeSet 特点:

1 TreeSet 底层实现是 TreeMap.
TreeMap: 基于 红黑树 实现, 可以实现 自然排序, 把记录的 key值按照 升序排序,.
2 线程不安全.
3 不能添加 key为 null 的数据, 只能插入同一类型的数据.

List 练习题: 杨辉三角.

public class Yanghui {
    public static List<List<Integer>> genarate(int numRows) {
        // 杨辉三角的特点:
        // 1. 第一行固定, 就是一个 1
        // 2. 第二行也固定, 就是两个 1
        // 3. 第 i 行, 收尾元素都固定是 1
        // 4. 第 i 行有 i 个元素
        // 5. 对于第 i 行来说, 第 j 列的值是 i - 1 行 j - 1 列值 加上 i - 1 行 j 列值
        //    例如, 第 2 行第 2 列 值是 2. 就是 第 1 行第 1 列 + 第 1 行第 2 列
        if (numRows <= 0) {
            return new ArrayList<>();
        }
        List<List<Integer>> result = new ArrayList<>();
        //1.先准备第一行,只有1个元素
        List<Integer> firstLine = new ArrayList<>();
        firstLine.add(1);
        result.add(firstLine);
        if (numRows == 1) {
            return result;
        }
        // 2. 再插入第二行. 就只有 2 个元素
        List<Integer> secondLine = new ArrayList<>();
        secondLine.add(1);
        secondLine.add(1);
        result.add(secondLine);
        if (numRows == 2) {
            return result;
        }
        // 3. 就要处理第 i 行的情况了
        //  a) 第 i 行有 i 列
        //  b) (i, j) = (i - 1, j) + (i - 1, j - 1);
        //  c) 第一个元素和最后一个元素都固定是 1
        //  arr[row][col];
        //  result.get(row).get(col);
        //  List<Integer> line = result.get(row);
        //  line.get(col);
        //注意这是双层循环
        for(int row=3;row<=numRows;row++){
            // 如果要想知道第 row 行的情况, 就得先知道 row - 1 行的内容
            List<Integer> prevLine=result.get((row-1)-1);//难点一
            List<Integer> curLine=new ArrayList<>();
            curLine.add(1);
            // 第 row 行应该有 row 列, 下面这个循环相当于循环了 row - 2 次
            // 因为第一列和最后一列都固定是1, 不参与循环
            // 针对这种 "差一" 问题, 最好的办法就是套入具体的数字来验证下是否合理.
            // 如果 row 为 5 的话, 第 5 行应该有 5 列. 下面的循环应该就要执行 3 次
            for(int col=2;col<row;col++){//难点2 (循环到底要执行几次)
                // 此处 col 也是从 1 开始计算的. 转换成下标需要继续再 - 1
                int curNum=prevLine.get(col-1-1)+prevLine.get(col-1);
                // 难点3(col - 1 - 1)
                curLine.add(curNum);
            }
            //最后处理该行最后一个1
            curLine.add(1);
            result.add(curLine);
        }
        return result;
    }
    public static void main(String[] args) {

        System.out.println(genarate(5));
    }
}

6 综合练习 扑克牌.

class Card{
    private String rank;//点数
    private String suit;//花色
    public Card(String rank,String suit){
        this.rank=rank;
        this.suit=suit;
    }
    public String getRank() {
        return rank;
    }
    public void setRank(String rank) {
        this.rank = rank;
    }
    public String getSuit() {
        return suit;
    }
    public void setSuit(String suit) {
        this.suit = suit;
    }
    @Override
    public String toString() {
//        return "Card{" +
//                "rank='" + rank + '\'' +
//                ", suit='" + suit + '\'' +
//                '}';
        return "["+this.suit+this.rank+"]";
    }
}
public class Poker {
    public static void main(String[] args) {
        //1.创建一副牌
        List<Card> poker=buyPoker();
        //2.洗牌, 最简单的办法, 可以直接使用 Collections.shuffle 方法
        //    把 List 中的元素随机打乱顺序
        //    shuffle 如果想自己实现, 也可以. 办法有很多.
        //    一种简单的办法: 从后往前遍历 List, 取出当前元素, 再生成一个随机位置.
        //    把当前元素和随机位置的元素交换即可
        Collections.shuffle(poker);
        // System.out.println(poker);
        // 3. 发牌. 假设有三个玩家, 每人发五张牌(梭哈)
        //    每个玩家就是一个 List, 就可以把每个人的手牌放到这个 List 中.
        //    可以把这多个玩家的信息再放到一个 List 中
        //    players 这就表示所有的玩家手牌.
        //    每个元素就是一个玩家的所有手牌(也是一个 List)
        List<List<Card>> players=new ArrayList<>();
        players.add(new ArrayList<Card>());
        players.add(new ArrayList<Card>());
        players.add(new ArrayList<Card>());
        // 给三个玩家发牌, 没人发五张牌
        // i 表示牌的编号, j 表示玩家编号
        for(int i=0;i<5;i++){
            for(int j=0;j<3;j++){
                List<Card> player=players.get(j);
                // remove 表示删除 List 中指定下标的元素
                // remove 返回值表示删除的这个元素是啥
                Card card=poker.remove(0);
                player.add(card);
            }
        }
        //到这一步, 玩家发牌结束, 可以查看玩家的手牌了
        for(int i=0;i<3;i++){
            System.out.println("玩家"+i+": "+players.get(i));
        }
    }
    //买一副牌
    private static List<Card> buyPoker(){
        List<Card> poker=new ArrayList<>();
        String[] suits={"♥","♠","♣","♦"};
        for(int i=0;i<4;i++){
            //处理四种花色,每种花色13张牌
            for(int j=2;j<=10;j++){
                // j 表示点数. int => String
                // "" + j
                // 也可以用String.valueOf(j)
                poker.add(new Card(""+j,suits[i]));
            }
            poker.add(new Card("J",suits[i]));
            poker.add(new Card("Q",suits[i]));
            poker.add(new Card("K",suits[i]));
            poker.add(new Card("A",suits[i]));
        }
        return poker;
    }
}
        //运行结果
		玩家0: [[♠K], [2], [5], [♣A], [6]]
		玩家1: [[8], [♣K], [6], [4], [♠Q]]
		玩家2: [[5], [6], [10], [♦A], [2]]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值