蓝桥杯第十五届抱佛脚(二)竞赛中的数据结构

蓝桥杯第十五届抱佛脚(二)内置数据结构

文章目录

  • 蓝桥杯第十五届抱佛脚(二)内置数据结构
    • 在竞赛中常见的数据结构
      • 数组(Array)
      • 链表(Linked List)
      • 栈(Stack)
      • 队列(Queue)
      • 树(Tree)
      • 映射(Map)
    • 内置数据结构的快速使用
      • 迭代器(Iterator)
      • Vector 容器(类)
      • 线性表基本使用
        • 引入
        • 创建List
        • 添加元素
        • 访问元素
        • 修改元素
        • 删除元素
        • 遍历List
        • List大小
        • List实现类
      • 队列 Queue
        • 定义方式
        • 基本使用
      • Map 映射
        • 定义方法
        • 基本使用
        • 成员方法
      • Set 集合使用
        • 定义方法
        • 基本使用
      • 自定义排序准则
        • 使用Comparator进行自定义排序
    • 例题练习
      • Vector 的使用例题
      • 队列使用例题
      • Map使用例题

在竞赛中常见的数据结构

数组(Array)

  • 数组是一种线性数据结构,在内存中是一块连续的存储空间。可以通过索引随机访问元素,查找效率很高。但数组大小固定,插入和删除操作效率低下。

    int[] arr = new int[10]; // 声明一个长度为10的整型数组
    arr[0] = 1; // 访问和赋值
    
  • 数组支持随机访问,查找效率高,适合存储基础数据类型和需要频繁查询的数据。但插入和删除效率低。

    例子:给定一个数组,求数组中是否有两个数之和等于目标值(两数之和)。我们可以先排序,然后使用双指针从首尾向中间扫描,时间复杂度O(nlogn)。

    int[] twoSum(int[] nums, int target) {
      Arrays.sort(nums);
      int i = 0, j = nums.length - 1;
      while (i < j) {
        int sum = nums[i] + nums[j];
        if (sum == target) return new int[]{i, j}; 
        else if (sum < target) i++;
        else j--;
      }
      return new int[]{-1, -1};
    }
    

链表(Linked List)

  • 链表是一种动态数据结构,每个节点包含数据和指向下一个节点的引用。优点是内存动态分配,插入删除效率高,缺点是访问元素需要遍历,查找效率低。

    // 单链表节点定义
    class ListNode {
    int val;
    ListNode next;
    ListNode(int x) { val = x; }
    }
    
  • 链表适合需要频繁插入和删除的场景。单链表无法高效查找和访问指定位置的元素,双向链表可以有效解决这个问题。

    例子:反转一个单链表(链表反转)。我们可以使用迭代或递归的方式进行反转。

    // 迭代反转单链表
    ListNode reverseList(ListNode head) {
      ListNode prev = null, cur = head;
      while (cur != null) {
          ListNode next = cur.next;
          cur.next = prev;
          prev = cur;
          cur = next;
      }
      return prev;
    }
    

栈(Stack)

  • 栈是一种先进后出(LIFO)的线性数据结构。Java中可以用数组或链表实现。

    // 基于数组实现的栈
    class ArrayStack {
      int[] arr = new int[100]; // 存储元素的数组
      int top = -1; // 栈顶指针
      void push(int x) { ... }
      int pop() { ... }
    }
    
  • 栈的特殊访问顺序可以借助递归的性质来模拟。常用于各种括号匹配、中缀表达式转后缀表达式等问题。

    例子:给定一个只包含'('')'的字符串,求最长有效括号子串的长度(最长有效括号)。我们利用栈去匹配括号,遇到'('入栈,')'出栈,最后计算长度。

    int longestValidParentheses(String s) {
      Stack<Integer> stack = new Stack<>();
      stack.push(-1); // 哨兵,避免corner case
      int maxLen = 0;
      for (int i = 0; i < s.length(); i++) {
          if (s.charAt(i) == '(') 
              stack.push(i); 
          else {
              stack.pop(); // 出栈匹配
              if (stack.isEmpty()) stack.push(i); // 直接入栈
              else maxLen = Math.max(maxLen, i - stack.peek());
          }
      }
      return maxLen;
    }
    

队列(Queue)

  • 队列是一种先进先出(FIFO)的线性数据结构。Java提供了Queue接口及ArrayDeque等实现。

    Queue<Integer> queue = new LinkedList<>();
    queue.offer(1); // 入队
    int front = queue.poll(); // 出队
    
  • 队列常用于遍历或拓扑排序等操作。在广度优先搜索(BFS)算法中也经常用到队列保存待遍历的元素。

    例子:打开转盘锁(锁的最小旋转次数)。我们可以利用BFS和队列,从初始状态出发,计算到达目标状态的最短路径。

    int openLock(String[] deadends, String target) {
      Queue<String> queue = new LinkedList<>();
      Set<String> visited = new HashSet<>(Arrays.asList(deadends));
      queue.offer("0000");
      visited.add("0000");
      int step = 0;
      while (!queue.isEmpty()) {
          int size = queue.size();
          for (int i = 0; i < size; i++) { //控制层次遍历,一次遍历所有当前队列的值
              String curr = queue.poll();
              if (target.equals(curr)) return step; //到达目标,返回步数
              if (!visited.contains(curr)) {
                  for (int j = 0; j < 4; j++) {
                      String up = upOne(curr, j);
                      String down = downOne(curr, j);
                      if (!visited.contains(up)) {
                          queue.offer(up);
                          visited.add(up);
                      }
                      if (!visited.contains(down)) {
                          queue.offer(down);
                          visited.add(down);
                      }
                  }
              }
          }
          step++;
      }
      return -1;
    }
    

树(Tree)

  • 树是一种分层数据的递归数据结构,常用的有二叉树、二叉查找树等。

    // 二叉树节点定义
    class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode(int x) { val = x; }
    }
    
  • 树形结构广泛应用于文件系统、计算机网络、抽象语法树等,是一种非常重要的数据模型。二叉树、二叉查找树在算法竞赛中使用也非常普遍。

    例子:给定两棵二叉树,编写一个函数来合并它们(合并二叉树)。

    TreeNode mergeTrees(TreeNode t1, TreeNode t2) {
      if (t1 == null) return t2; 
      if (t2 == null) return t1;
      TreeNode root = new TreeNode(t1.val + t2.val);
      root.left = mergeTrees(t1.left, t2.left);
      root.right = mergeTrees(t1.right, t2.right);
      return root;
    }
    

映射(Map)

  • 映射是存储键值对数据的数据结构。Java中的HashMap基于散列实现,TreeMap基于红黑树实现。

    Map<String, Integer> map = new HashMap<>();
    map.put("A", 1);
    int val = map.get("A"); // 获取键对应的值
    
  • 映射可以高效地存储键值对,常用于统计频率、建立关联关系等操作。

    例子:给定两个字符串,找出其中最长的公共子序列长度(最长公共子序列)。我们可以利用动态规划和map存储计算过的结果。

    int longestCommonSubsequence(String s1, String s2) {
      Map<String, Integer> memo = new HashMap<>();
      return lcs(s1, 0, s2, 0, memo);
    }
    
    int lcs(String s1, int i, String s2, int j, Map<String, Integer> memo) {
      String key = i + "," + j;
      if (memo.containsKey(key)) return memo.get(key);
      int res = 0;
      if (i < s1.length() && j < s2.length() && s1.charAt(i) == s2.charAt(j))
          res = 1 + lcs(s1, i + 1, s2, j + 1, memo);
      else
          res = Math.max(i < s1.length() ? lcs(s1, i + 1, s2, j, memo) : 0, 
                 j < s2.length() ? lcs(s1, i, s2, j + 1, memo) : 0);
      memo.put(key, res);
      return res;
    }
    

内置数据结构的快速使用

  • 迭代器讲解
  • Vector 容器(类)
  • 线性表的使用
  • 队列的使用
  • 映射(map)的使用
  • 集合(set)的使用

迭代器(Iterator)

在Java中,迭代器(Iterator)是一种用于遍历集合(Collection)元素的接口。它提供了一种统一的方式来访问不同集合类中的元素,而不用关心集合的底层实现细节。

迭代器接口定义如下:

public interface Iterator<E> {
    boolean hasNext(); // 判断是否还有下一个元素
    E next(); // 返回下一个元素
    void remove(); // 移除当前元素(可选操作)
}

它主要有以下特点:

  1. 统一遍历方式:Iterator提供了统一的遍历方式,可以遍历各种不同的集合类,包括List、Set等。

  2. 单向移动指针:迭代器是单向移动指针,通过next()方法向集合中的下一个元素移动。

  3. 安全性:在单线程环境下,通过Iterator遍历集合时,不会抛出并发修改异常(ConcurrentModificationException)。

  4. 可移除元素:迭代器可以通过remove()方法移除集合中的当前元素,但前提是在next()之后、next()之前没有调用过remove()

  5. 只能单向遍历:迭代器只能单向遍历,不能像List那样双向遍历。

下面是使用迭代器遍历一个List的示例:

List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C", "D"));
Iterator<String> iterator = list.iterator();

while (iterator.hasNext()) {
    String str = iterator.next();
    System.out.println(str);
}

输出结果:

A
B
C
D

在Java中,可以通过Iterator接口遍历任何实现了Iterable接口的集合类。常见的集合类如ArrayListLinkedListHashSetTreeSet等都实现了Iterable接口,因此都可以使用迭代器进行遍历。

此外,Java还提供了ListIterator接口扩展了Iterator接口,增加了向前和向后遍历的能力,常用于List集合的遍历和修改操作。

总之,迭代器为Java集合类提供了一种统一、安全、方便的遍历方式,是Java集合框架的重要组成部分。在算法竞赛中,熟练使用迭代器也可以提高代码的简洁性和可读性。

Vector 容器(类)

线性表中有 Vector 顺序表 和 list 链表,两者作用比较相似。

Vector 的主要作用就是可变长度的数组,就把他当成数组使用即可。

Vector是Java集合框架中一种较老的动态数组实现,它和ArrayList类似,但是Vector是线程安全的(synchronized)。

Vector特点和用法:

  1. 线程安全
    Vector中的大部分方法都是同步的(synchronized),这意味着无论一个线程在读取或修改Vector的时候,它都不会被其他线程所打扰,因此可以在多线程环境中安全地使用。

    Vector<String> vector = new Vector<>();
    vector.add("A");
    vector.add("B");
    vector.add("C");
    
  2. 动态扩容
    ArrayList类似,Vector内部使用数组存储元素,当数组满了时会自动扩容,扩容操作耗费时间和内存。

  3. 允许null值
    Vector允许存储null值。

  4. 迭代器和枚举
    和其它集合一样,可以通过Iterator遍历Vector中的元素,也可以使用旧的Enumeration枚举来遍历。

    Enumeration<String> e = vector.elements();
    while(e.hasMoreElements()) {
        System.out.println(e.nextElement());
    }
    
  5. 增删改查
    Vector提供了一系列增删改查的方法,如add()set()get()remove()等。

    vector.remove(1); // 移除索引为1的元素
    String val = vector.get(0); // 获取索引为0的元素
    
  6. ArrayList相比
    由于Vector是线程安全的,所以在单线程环境下会比ArrayList慢一些。现在通常不直接使用Vector,而是使用Collections.synchronizedList()来包装一个ArrayList使其线程安全:

    List<String> syncList = Collections.synchronizedList(new ArrayList<>());
    

以下为 Java VectorApi

修饰符和类型方法和说明
booleanadd(E e)将指定的元素附加到此 Vector 的末尾。
voidadd(int index, E element)在此 Vector 的指定位置插入指定元素。
booleanaddAll(Collection<? extends E> c)将指定集合中的所有元素追加到末尾 这个向量,按照它们由指定的返回的顺序 集合的迭代器。
booleanaddAll(int index, Collection<? extends E> c)将指定 Collection 中的所有元素插入到此 指定位置的向量。
voidaddElement(E obj)将指定的组件添加到此向量的末尾, 将其大小增加一。
intcapacity()返回此向量的当前容量。
voidclear()从此 Vector 中删除所有元素。
Objectclone()返回此向量的克隆。
booleancontains(Object o)退货 true 如果此向量包含指定的元素。
booleancontainsAll(Collection<?> c)如果此 Vector 包含所有元素,则返回 true 指定的集合。
voidcopyInto Object [] anArray 将此向量的分量复制到指定的数组中。
EelementAt(int index)返回指定索引处的组件。
Enumerationelements()返回此向量的组件的枚举。
voidensureCapacity(int minCapacity)如有必要,增加此向量的容量,以确保它至少可以容纳由指定的组件数量最小容量参数。
booleanequals(Object o)比较指定的 Object 与此 Vector 是否相等。
EfirstElement()返回第一个组件(索引处的项目 0) 的这个向量。
Eget(int index)返回此 Vector 中指定位置的元素。
inthashCode()返回此 Vector 的哈希码值。
intindexOf(Object o)返回指定元素第一次出现的索引 在此向量中,如果此向量不包含该元素,则为 -1。
intindexOf(Object o,int index)返回指定元素第一次出现的索引这个向量,从 index, 或返回 -1 如果 未找到该元素。
voidinsertElementAt(E obj, int index)将指定对象作为组件插入此向量中的 指定的 index.
booleanisEmpty()测试此向量是否没有组件。
Iteratoriterator()以适当的顺序返回此列表中元素的迭代器
ElastElement()返回向量的最后一个组件。
intlastIndexOf(Object o)返回指定元素最后一次出现的索引在此向量中,如果此向量不包含该元素,则为 -1。
intlastIndexOf(Object o, int index)返回指定元素最后一次出现的索引这个向量,从 index, 或返回 -1 如果 未找到该元素。
ListIteratorlistIterator()返回此列表中元素的列表迭代器(在适当的顺序)。
ListIteratorlistIterator(int index)返回此列表中元素的列表迭代器(在适当的序列),从列表中的指定位置开始。
Eremove(int index)移除此 Vector 中指定位置的元素。
booleanremove(Object o)移除此 Vector 中第一次出现的指定元素如果 Vector 不包含该元素,则它保持不变。
booleanremoveAll(Collection<?> c)从此 Vector 中删除其包含在指定的集合。
voidremoveAllElements()从此向量中删除所有组件并将其大小设置为零。
booleanremoveElement(Object obj)删除参数的第一个(最低索引)出现从这个向量。
voidremoveElementAt(int index)删除指定索引处的组件。
protected voidremoveRange(int fromIndex, int toIndex)从此列表中删除索引介于两者之间的所有元素 fromIndex,包括在内,和 toIndex, 独家的。
booleanretainAll(Collection<?> c)仅保留此 Vector 中包含在指定的集合。
Eset(int index, E element)将此 Vector 中指定位置的元素替换为指定的元素。
voidsetElementAt(E obj,int index)将组件设置在指定的位置 index 这个的向量是指定的对象。
voidsetSize(int newSize)设置此向量的大小。
intsize()返回此向量中的组件数。
ListsubList(int fromIndex,int toIndex)返回此列表中 fromIndex 之间的部分的视图
Object[]toArray()返回一个包含此 Vector 中所有元素的数组以正确的顺序。
T[]toArray[] 返回一个包含此 Vector 中所有元素的数组正确的顺序; 返回数组的运行时类型指定数组。
StringtoString()返回此 Vector 的字符串表示形式,包含 每个元素的字符串表示。
voidtrimToSize()将此向量的容量修剪为向量的电流 尺寸。

线性表基本使用

Java中的List接口是java.util包下的一部分,它是一个有序集合,用于存储元素的序列,这些元素可以重复。List接口允许用户根据整数索引访问列表中的元素,提供了丰富的方法来插入、更新、删除和访问元素。

Java提供了ArrayListLinkedList等几种List实现。

顺序表随机访问O(1) ,随机访问事采用ArrayList

引入

在使用List之前,需要导入java.util.List接口,以及对应的实现类(如ArrayListLinkedList):

import java.util.List;
import java.util.ArrayList;
// 或者使用 LinkedList
// import java.util.LinkedList;
创建List

创建一个List实例通常使用其实现类,如ArrayListLinkedList

List<String> list = new ArrayList<>();
// 或者
// List<String> list = new LinkedList<>();
添加元素

使用add方法向List中添加元素:

list.add("Element 1");
list.add("Element 2");
list.add("Element 3");
访问元素

可以通过索引使用get方法访问List中的元素:

String element = list.get(1); // 访问第二个元素
System.out.println(element);
修改元素

使用set方法可以修改List中指定位置的元素:

list.set(1, "New Element 2"); // 将第二个元素修改为 "New Element 2"
删除元素

可以使用remove方法通过索引或直接通过对象来删除元素:

list.remove(1); // 删除第二个元素
// 或者
list.remove("Element 3"); // 删除指定的对象
遍历List

List可以通过for循环或Iterator来遍历:

// 使用 for 循环遍历
for(String item : list) {
    System.out.println(item);
}

// 使用迭代器遍历
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){
    String item = iterator.next();
    System.out.println(item);
}
List大小

使用size方法可以获取List的大小:

int size = list.size();
System.out.println("List size: " + size);
List实现类
  • ArrayList C++ vector: 底层使用动态数组实现,适用于频繁的随机访问和较少的插入、删除操作。
  • LinkedList: C++ list底层使用双向链表实现,适用于频繁的插入、删除操作。

选择哪种实现类取决于具体的使用场景。ArrayList通常是List实现的首选,除非程序特别需要LinkedList的特定功能(如频繁的插入和删除)。

队列 Queue

定义方式
Queue<String> queue = new LinkedList<String>();

部分成员函数(包括继承的):

  • add(): 增加一个元索,如果队列已满,则抛出一个异常
  • remove():移除并返回队列头部的元素,如果队列为空,则抛出一个异常
  • element():返回队列头部的元素,如果队列为空,则抛出一个异常
  • offer():添加一个元素并返回 true,如果队列已满,则返回 false
  • poll(): 移除并返问队列头部的元素,如果队列为空,则返回 null
  • peek(): 返回队列头部的元素,如果队列为空,则返回 null
  • put(): 添加一个元素, 如果队列满,则阻塞
  • take(): 移除并返回队列头部的元素,如果队列为空,则阻塞
  • size(): 返回队列长度。
基本使用
  1. offer(E e)add(E e)
Queue<String> queue = new LinkedList<>();
queue.offer("A"); // 返回true
queue.add("B"); // 不会抛出异常
queue.offer("C"); 
queue.offer("D"); 
System.out.println(queue); // 输出 [A, B, C, D]

Queue<String> queueFull = new LinkedList<>(Arrays.asList("1", "2", "3"));
queueFull.add("4"); // 抛出 IllegalStateException
  1. poll()remove()
Queue<String> queue = new LinkedList<>(Arrays.asList("A", "B", "C"));
System.out.println(queue.poll()); // 输出 A
System.out.println(queue.remove()); // 输出 B
System.out.println(queue.poll()); // 输出 C
System.out.println(queue.poll()); // 输出 null
System.out.println(queue.remove()); // 抛出 NoSuchElementException
  1. peek()element()
Queue<String> queue = new LinkedList<>(Arrays.asList("A", "B", "C"));
System.out.println(queue.peek()); // 输出 A
System.out.println(queue.element()); // 输出 A
System.out.println(queue.peek()); // 输出 A

Queue<String> emptyQueue = new LinkedList<>();
System.out.println(emptyQueue.peek()); // 输出 null
System.out.println(emptyQueue.element()); // 抛出 NoSuchElementException
  1. put(E e)take()
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(2);
blockingQueue.put("A"); // 不会阻塞
blockingQueue.put("B"); // 不会阻塞
blockingQueue.put("C"); // 会阻塞,直到队列有空位

new Thread(() -> {
    try {
        Thread.sleep(1000); // 休眠1秒
        blockingQueue.take(); // 移除队首元素
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}).start();

blockingQueue.put("D"); // 由于上面的take()操作,此处不会阻塞
  1. contains(Object o)size()
Queue<String> queue = new LinkedList<>(Arrays.asList("A", "B", "C", "D"));
System.out.println(queue.contains("A")); // 输出 true
System.out.println(queue.contains("E")); // 输出 false
System.out.println(queue.size()); // 输出 4

Map 映射

map 是一个关联容器,它提供一对一的 hash

  • 第一个可以称为关键字(key),每个关键字只能在 map 中出现一次
  • 第二个可能称为该关键字的值(value)

map 以模板(泛型)方式实现,可以存储任意类型的数据,包括使用者自定义的数据类型。Map 主要用于资料一对一映射(one-to-one)的情況,mapC++ 的內部的实现自建一颗红黑树,这颗树具有对数据自动排序的功能。在 map 内部所有的数据都是有序的。

比如,像是管理班级内的学生,Key 值为学号,Value 放其他信息的结构体或者类。

定义方法
Map m1 = new TreeMap();

这里我们讲的是排序的 map 还有不排序的 mapjava 里面叫 hashmapC++ 里叫 unordered_map,除了不排序,用法和功能都一样。

基本使用

Java中的Map是一种键值对(Key-Value)映射结构的集合接口,它提供了一种存储和检索值的便捷方式。Map中的每个元素都由一个键(Key)和一个值(Value)组成,键用于唯一标识一个值,值则是与该键相关联的数据。Map接口定义了一些常用的方法,用于操作键值对的存储和访问。下面是Map接口的基本使用:

  1. 创建Map实例

    Java提供了多种Map接口的实现类,如HashMapLinkedHashMapTreeMap等。我们可以使用相应的构造方法创建一个Map实例。

    // 创建一个空的HashMap
    Map<String, Integer> map = new HashMap<>();
    
    // 创建一个初始容量为8的HashMap
    Map<String, Integer> map = new HashMap<>(8);
    
    // 使用静态工厂方法创建一个不可修改的Map
    Map<String, Integer> map = Map.of("A", 1, "B", 2, "C", 3);
    
  2. 添加键值对

    使用put()方法向Map中添加新的键值对。如果键已经存在,则会用新值替换旧值。

    map.put("Apple", 3);
    map.put("Banana", 5);
    map.put("Orange", 2);
    
  3. 获取值

    使用get()方法根据键获取对应的值。如果键不存在,则返回null

    Integer count = map.get("Apple"); // 3
    Integer missing = map.get("Grape"); // null
    
  4. 移除键值对

    使用remove()方法根据键移除对应的键值对,并返回被移除的值。

    Integer removed = map.remove("Banana"); // 5
    
  5. 检查是否包含键或值

    使用containsKey()containsValue()方法检查Map中是否包含指定的键或值。

    boolean hasApple = map.containsKey("Apple"); // true
    boolean hasFive = map.containsValue(5); // false
    
  6. 迭代访问

    使用keySet()values()entrySet()方法分别获取Map中所有键、值或键值对的集合视图,然后使用迭代器或增强for循环遍历。

    // 遍历所有键
    for (String key : map.keySet()) {
        System.out.println(key);
    }
    
    // 遍历所有值
    for (Integer value : map.values()) {
        System.out.println(value);
    }
    
    // 遍历所有键值对
    for (Map.Entry<String, Integer> entry : map.entrySet()) {
        System.out.println(entry.getKey() + ":" + entry.getValue());
    }
    
  7. 其他常用方法

    Map接口还提供了一些其他常用方法,如size()获取映射大小、isEmpty()判断是否为空、putAll()将另一个Map的所有映射关系添加到当前Map等。

成员方法
方法名方法描述
void clear( )从此映射中移除所有映射关系(可选操作)。
boolean containsKey(Object k)如果此映射包含指定键的映射关系,则返回 true。
boolean containsValue(Object v)如果此映射将一个或多个键映射到指定值,则返回 true。
boolean equals(Object obj)比较指定的对象与此映射是否相等。
Object get(Object x)返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回 null。
int hashCode( )返回此映射的哈希码值。
boolean isEmpty( )如果此映射未包含键-值映射关系,则返回 true。
Set keySet( )返回此映射中包含的键的 Set 视图。
Object put(Object k, Object v)将指定的值与此映射中的指定键关联
Object remove(Object k)如果存在一个键的映射关系,则将其从此映射中移除(可选操作)。
int size( )返回此映射中的键-值映射关系数。
Collection values( )返回此映射中包含的值的 Collection 视图。

Set 集合使用

定义方法

Set是一种不包含重复元素的集合,它继承自Collection接口。

Java提供了两个主要的Set实现:HashSet和TreeSet。

  1. HashSet

    HashSet是基于哈希表实现的,它不保证元素的顺序,允许使用null元素。HashSet的主要特点是:

    • 不允许存储重复元素
    • 元素无序
    • 非线程安全
    • 允许null元素
    • 性能较好,查找效率高

    示例:

    Set<String> set = new HashSet<>();
    set.add("apple");
    set.add("banana");
    set.add("apple"); // 重复元素被忽略
    System.out.println(set); // 输出 [banana, apple]
    
  2. TreeSet

    TreeSet是基于红黑树实现的,它会自动对元素进行排序。TreeSet的主要特点是:

    • 不允许存储重复元素
    • 元素自动排序
    • 非线程安全
    • 不允许null元素
    • 性能稍逊于HashSet

    示例:

    Set<Integer> set = new TreeSet<>();
    set.add(3);
    set.add(1);
    set.add(2);
    set.add(3); // 重复元素被忽略
    System.out.println(set); // 输出 [1, 2, 3]
    

Set集合常用于需要去除重复元素的场景,如数学集合运算、词频统计等。除了HashSet和TreeSet之外,Java还提供了一些特殊的Set实现,如LinkedHashSet(有序Set)、CopyOnWriteArraySet(线程安全Set)等。

基本使用
  1. 创建Set实例

    // HashSet
    Set<String> set1 = new HashSet<>();
    
    // TreeSet
    Set<Integer> set2 = new TreeSet<>();
    
  2. 添加元素

    使用add()方法添加元素到Set中,添加重复元素时会被忽略。

    set1.add("apple");
    set1.add("banana");
    set1.add("apple"); // 会被忽略
    
    set2.add(3);
    set2.add(1);
    set2.add(2);
    set2.add(3); // 会被忽略
    
  3. 删除元素

    使用remove()方法删除指定元素。

    set1.remove("apple"); // 删除"apple"元素
    
  4. 判断元素是否存在

    使用contains()方法判断Set中是否包含指定元素。

    boolean containsApple = set1.contains("apple");
    
  5. 获取Set大小

    使用size()方法获取Set的元素个数。

    int size = set1.size();
    
  6. Set遍历

    可以使用增强for循环或迭代器遍历Set中的元素。

    // 增强for循环
    for (String s : set1) {
        System.out.println(s);
    }
    
    // 迭代器遍历
    Iterator<Integer> iter = set2.iterator();
    while (iter.hasNext()) {
        System.out.println(iter.next());
    }
    
  7. Set运算

    Set提供了一些方法用于进行集合运算,如 addAll()(并集)、retainAll()(交集)、removeAll()(差集)等。

    Set<String> set3 = new HashSet<>(Arrays.asList("apple", "orange"));
    set1.addAll(set3); // 并集
    set1.retainAll(set3); // 交集  
    set1.removeAll(set3); // 差集
    
  8. Set转换为数组

    使用toArray()方法可以将Set转换为数组。

    String[] arr = set1.toArray(new String[0]);
    

自定义排序准则

要使用自定义排序准则,你可以定义一个比较函数或者更常见的是定义一个比较类(比较函数对象),然后将其作为第二个模板参数传递给set。

使用Comparator进行自定义排序

定义一个比较类通常更灵活,因为它可以持有状态(虽然在大多数排序场景中不需要)。

在Java中,Set接口的实现类(如HashSet)本身不保证有序,但Java提供了SortedSet接口和TreeSet实现类,后者可以根据自然排序或提供的Comparator进行排序。若要对Set中的元素应用自定义排序准则,可以使用TreeSet并提供一个自定义的Comparator

要在TreeSet中实现自定义排序,可以在创建TreeSet时通过构造器传入一个Comparator对象。Comparator接口定义了一个compare方法,你需要在其中实现你的排序逻辑。

下面是一个简单的例子,展示如何根据字符串的长度而不是字典顺序来排序String元素:

import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;

public class CustomSortExample {
    public static void main(String[] args) {
        // 使用Comparator实现自定义排序逻辑
        Comparator<String> stringLengthComparator = new Comparator<String>() {
            @Override
            public int compare(String s1, String s2) {
                return Integer.compare(s1.length(), s2.length());
            }
        };

        // 创建一个TreeSet并传入自定义的Comparator
        Set<String> strings = new TreeSet<>(stringLengthComparator);

        strings.add("Apple");
        strings.add("Fig");
        strings.add("Banana");

        for (String s : strings) {
            System.out.println(s);
        }
        // 输出结果将根据字符串长度排序:Fig, Apple, Banana
    }
}

在这个例子中,Comparator实现了根据字符串长度的比较逻辑,然后我们将这个Comparator传递给TreeSet的构造函数。这样,TreeSet就会根据字符串长度来排序存储的元素。

使用Lambda表达式简化Comparator

如果你使用的是Java 8或更高版本,可以使用Lambda表达式来更简洁地实现Comparator

Set<String> strings = new TreeSet<>((s1, s2) -> Integer.compare(s1.length(), s2.length()));

这行代码与上面的例子实现了相同的功能,但是更简洁。

例题练习

Vector 的使用例题

在这里插入图片描述

  • 输入

10

10124214 北京
12421565 上海
sdafasdg213 天津
fasdfga124 北京
145252 上海
235wtdfsg 济南
3242356fgdfsg 成都
23423 武汉
23423565f 沈阳
1245dfwfs 成都

  • 输出

北京 2
10124214
fasdfga124
上海 2
12421565
145252
天津 1
sdafasdg213
济南 1
235wtdfsg
成都 2
3242356fgdfsg
1245dfwfs
武汉 1
23423
沈阳 1
23423565f

解题思路:

首先我们要知道中国城市肯定在 1000 个以内,但是单号我们不确定,我们不可能每个数组开 1000000 个,那样内存不够,所以这时候我们就用到了我们的vector ,他的容量是动态申请的,在比赛中我们可以理解为无限制。

  • 第一步:我们创建一个 vector 用于保存地址
vector<string> city;
  • 第二步:我们创建一个 vector 组用于存放单号
vector<string> dig[1000];
  • 第三步:我们定义一个映射函数,因为你的城市可能会再次出现,你需要知道之前有没有。
  • 第四步:我们开始读入操作并按照顺序进行存放

解题步骤:

  1. 输入一些城市和对应的车站名称。
  2. 如果城市已存在,则将车站添加到对应城市的列表中;如果城市不存在,则先添加城市,再为该城市创建一个车站列表。
  3. 最后输出每个城市的名称、该城市的车站数量,以及每个车站的名称。

使用了两个Vector对象来存储城市名称和对应的车站列表。Myfind函数用于查找给定城市在city向量中的位置,如果找到则返回索引,否则返回-1。

main函数中,首先输入城市数量n,然后循环输入每个城市及其对应的车站名称。对于每个输入的城市,先调用Myfind查找是否已存在,如果存在则将车站添加到对应列表,否则先添加城市,再为其创建一个新的车站列表。

最后,遍历citydig向量,输出每个城市的名称、车站数量和每个车站的名称。

import java.util.Scanner;
import java.util.Vector;

public class Main {
    static Vector<String> city = new Vector<String>(); // 存储城市
    static Vector<Vector<String>> dig = new Vector<Vector<String>>(); // 用于存储对应城市的快递单号

    static int Myfind(String s) { // 查找给定城市在city中的位置
        for (int i = 0; i < city.size(); i++) {
            if (city.get(i).equals(s)) {
                return i; // 如果找到,返回索引
            }
        }
        return -1; // 如果没找到,返回-1
    }

    public static void main(String[] args) {
        int n;
        Scanner in = new Scanner(System.in);
        n = in.nextInt(); // 输入城市数量n

        for (int i = 0; i < n; i++) {
            String d, c;
            d = in.next(); // 输入快递单号
            c = in.next(); // 输入城市名称

            int flag = Myfind(c); // 查找城市在city向量中的位置
            if (flag == -1) { // 如果城市不存在
                city.addElement(c); // 将城市添加到city向量
                dig.addElement(new Vector<String>()); // 为新城市创建一个快递单号列表
                dig.get(city.size() - 1).addElement(d); // 将快递单号添加到对应城市的列表
            } else {
                dig.get(flag).addElement(d); // 如果城市存在,将单号添加到对应城市的列表
            }
        }

        for (int i = 0; i < city.size(); i++) {
            System.out.println(city.get(i) + " " + dig.get(i).size()); 
            for (int j = 0; j < dig.get(i).size(); j++) {
                System.out.println(dig.get(i).get(j));
            }
        }
    }

}

队列使用例题

在这里插入图片描述

  • 输入

5
IN xiaoming N
IN Adel V
IN laozhao N
OUT N
IN CLZ V

  • 输出

Adel
CLZ
laozhao

解题思路:

  1. 用两个队列分别存储VIP用户和普通用户。
  2. 根据输入的指令操作两个队列,IN指令表示入队,OUT指令表示出队。
  3. 最后分别遍历两个队列,按顺序输出用户名字。

解题步骤:

  1. 创建两个队列V和N,分别用于存储VIP用户和普通用户。
  2. 读入指令的个数M。
  3. 对于每个指令:
    • 如果指令是"IN name V",则将name入队到V队列。
    • 如果指令是"IN name N",则将name入队到N队列。
    • 如果指令是"OUT V",则将V队列队首元素出队。
    • 如果指令是"OUT N",则将N队列队首元素出队。
  4. 遍历V队列,依次输出其中的元素。
  5. 遍历N队列,依次输出其中的元素。
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;

public class Main {
    static Queue<String> V = new LinkedList<String>(); // 创建VIP队列
    static Queue<String> N = new LinkedList<String>(); // 创建普通队列

    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int M = in.nextInt(); // 读入指令个数

        while (M > 0) { // 处理每个指令
            M--;
            String op, name, type;
            op = in.next(); // 读入指令

            if (op.contains("IN")) { // 如果是入队指令
                name = in.next(); // 读入用户名
                type = in.next(); // 读入用户类型
                if (type.contains("V")) {
                    V.offer(name); // 将用户名入队到VIP队列
                } else {
                    N.offer(name); // 将用户名入队到普通队列
                }
            } else { // 如果是出队指令
                type = in.next(); // 读入用户类型
                if (type.contains("V")) {
                    V.poll(); // 将VIP队列队首元素出队
                } else {
                    N.poll(); // 将普通队列队首元素出队
                }
            }
        }

        while (V.size() != 0) { // 输出VIP队列
            System.out.println(V.poll());
        }

        while (N.size() != 0) { // 输出普通队列
            System.out.println(N.poll());
        }
    }
}

这段代码首先创建了两个队列V和N,用于分别存储VIP用户和普通用户。然后读入指令的个数M,进入循环处理每个指令。对于入队指令(“IN name V"或"IN name N”),将用户名字入队到对应的队列;对于出队指令(“OUT V"或"OUT N”),将对应队列的队首元素出队。最后,分别遍历V和N队列,依次输出其中的元素。

Map使用例题

在这里插入图片描述

  • 输入

6
1fagas
dsafa32j
lkiuopybncv
hfgdjytr
cncxfg
sdhrest

  • 输出

NO

  • 输入

5
sdfggfds
fgsdhsdf
dsfhsdhr
sdfhdfh
sdfggfds

  • 输出

sdfggfds

解题思路:

import java.util.Map;
import java.util.Scanner;
import java.util.TreeMap;

public class Main {

    // 声明一个静态的 TreeMap 来存储已经遇到的唯一单词
    static Map<String, Boolean> mp = new TreeMap<>();

    public static void main(String[] args) {

        int n;
        boolean flag = false; // 标志变量,用于指示是否发现重复单词
        Scanner in = new Scanner(System.in);
        String ans = "NO"; // 如果没有发现重复单词,则默认答案为 "NO"
        n = in.nextInt(); 

        // 遍历每个输入单词
        for (int i = 0; i < n; i++) {
            String word;
            word = in.next(); // 读取下一个单词

            // 如果已经发现了重复单词,则跳过后续处理
            if (flag)
                continue;

            // 检查单词是否已经遇到过
            if (mp.containsKey(word)) {
                flag = true; // 将标志设置为 true,表示已经发现重复单词
                ans = word; // 存储重复的单词
            } else {
                mp.put(word, true); // 将单词添加到 map 中,值为 true 表示已经遇到过
            }
        }

        // 输出结果(要么是重复的单词,要么是 "NO" 如果没有发现重复)
        System.out.println(ans);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/483628.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

综合知识篇20-基于中间件的开发新技术考点(2024年软考高级系统架构设计师冲刺知识点总结系列文章)

专栏系列文章: 2024高级系统架构设计师备考资料(高频考点&真题&经验)https://blog.csdn.net/seeker1994/category_12593400.html案例分析篇00-【历年案例分析真题考点汇总】与【专栏文章案例分析高频考点目录】(2024年软考高级系统架构设计师冲刺知识点总结-案例…

强化学习之父Richard Sutton:通往AGI的另一种可能

2019年&#xff0c;强化学习之父、阿尔伯塔大学教授Richard Sutton发表了后来被AI领域奉为经典的The Bitter lesson&#xff0c;这也是OpenAI研究员的必读文章。 在这篇文章中&#xff0c;Richard指出&#xff0c;过去 70 年来&#xff0c;AI 研究的一大教训是过于重视人类既有…

Python学习从0到1 day18 Python可视化基础综合案例 1.折线图

我默记这段路的酸楚&#xff0c;等来年春暖花开之时再赏心阅读 —— 24.3.24 python基础综合案例 数据可视化 — 折线图可视化 一、折线图案例 1.json数据格式 2.pyecharts模块介绍 3.pyecharts快速入门 4.数据处理 5.创建折线图 1.json数据格式 1.什么是json 2.掌握如何使用js…

用kimichat炒股:查找比亚迪、特斯拉等电动车产业链相关股票

kimichat可以联网检索&#xff0c;搜索结果更加准确、智能。 在kimichat中输入提示词&#xff1a;找出同时在比亚迪产业链和特斯拉产业链的企业 根据您提供的搜索结果中的信息&#xff0c;我们可以找出同时在比亚迪产业链和特斯拉产业链中的企业。以下是从搜索结果中提取的相关…

【动态规划】Leetcode 70. 爬楼梯

【动态规划】Leetcode 70. 爬楼梯 解法1 ---------------&#x1f388;&#x1f388;题目链接&#x1f388;&#x1f388;------------------- 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; 输入…

先进电机技术 —— 长线缆驱动电机面临哪些问题?

一、长线驱动问题简述 电机变频驱动器&#xff08;VFD&#xff09;输出侧采用长线缆驱动电机运行时&#xff0c;将会面对多种问题&#xff0c;主要包括但不限于&#xff1a; 此图片来源于网络 1. **电压降**&#xff1a; - 长线缆的电阻会导致电压降增大&#xff0c;当电…

智能优化算法 | Matlab实现牛顿-拉夫逊优化算法Newton-Raphson-based optimize(内含完整源码)

文章目录 效果一览文章概述源码设计参考资料效果一览 文章概述 智能优化算法 | Matlab实现牛顿-拉夫逊优化算法Newton-Raphson-based optimize(内含完整源码) 源码设计 % ------------------------------------------------------------------------------------------------…

UNI-APP读取本地JSON数据

首先要把json文件放在static文件夹下 然后在要读取数据的页面导入 import data from ../../static/data.json读取数据&#xff1a; onLoad() {console.log(data, data)}, 打印出来的就是JSON文件里的数据了

【SQL】1527. 患某种疾病的患者(like;通配符)

前述 知识点回顾&#xff1a; MySQL 使用OR在LIKE查询中比较多个字段 %&#xff1a;表示任意字符&#xff08;包括0个或多个&#xff09;_&#xff1a;表示任意单个字符匹配空格&#xff1a;直接用空格就行&#xff0c;例如&#xff0c;% DIAB1%可以匹配字符串ACNE DIAB100 …

利用免费 GPU 部署体验大型语言模型推理框架 vLLM

vLLM简介 vLLM 是一个快速且易于使用的 LLM&#xff08;大型语言模型&#xff09;推理和服务库。 vLLM 之所以快速&#xff0c;是因为&#xff1a; 最先进的服务吞吐量 通过 PagedAttention 高效管理注意力键和值内存 连续批处理传入请求 使用 CUDA/HIP 图快速模型执行 量…

瑞芯微RK3576|触觉智能:开启科技新篇章

更多产品详情可关注深圳触觉智能官网&#xff01; “瑞芯微&#xff0c;创新不止步&#xff01;”——全新芯片RK3576即将震撼登场。指引科技风潮&#xff0c;创造未来无限可能&#xff01;这款芯片在瑞芯微不断创新和突破的道路上&#xff0c;不仅是对过往成就的完美延续&…

Python爬虫-批量爬取星巴克全国门店

前言 本文是该专栏的第22篇,后面会持续分享python爬虫干货知识,记得关注。 本文笔者以星巴克为例,通过Python实现批量爬取目标城市的门店数据以及全国的门店数据。 具体的详细思路以及代码实现逻辑,跟着笔者直接往下看正文详细内容。(附带完整代码) 正文 地址:aHR0cHM…

基于python+vue电影院订票信息管理系统flask-django-php-nodejs

根据此问题&#xff0c;研发一套电影院订票信息管理系统&#xff0c;既能够大大提高信息的检索、变更与维护的工作效率&#xff0c;也能够方便信息系统的管理运用&#xff0c;从而减少信息管理成本&#xff0c;提高效率。 该电影院订票信息管理系统采用B/S架构、前后端分离以及…

Nacos部署(二)Linux部署Nacos2.3.x集群环境

&#x1f60a; 作者&#xff1a; 一恍过去 &#x1f496; 主页&#xff1a; https://blog.csdn.net/zhuocailing3390 &#x1f38a; 社区&#xff1a; Java技术栈交流 &#x1f389; 主题&#xff1a; Nacos部署&#xff08;二&#xff09;Linux部署Nacos2.3.x集群环境 ⏱️…

隐私计算实训营第四讲-SecretFlow 环境安装与部署

SecretFlow 环境安装与部署 SecretFlow环境的安装和部署指南&#xff0c;包括仿真模式和生产模式的配置方法。 1. 环境安装 要安装SecretFlow环境&#xff0c;请按照以下步骤操作&#xff1a; 1.1 创建并激活Conda环境 创建名为 sf 的新Conda环境&#xff0c;并指定Python版…

#Linux(SSH软件安装及简单使用)

&#xff08;一&#xff09;发行版&#xff1a;Ubuntu16.04.7 &#xff08;二&#xff09;记录&#xff1a; &#xff08;1&#xff09;终端键入&#xff08;root权限&#xff09;安装 apt-get install openssh-server 安装时遇到报错 E: Could not get lock /var/lib/dpkg/…

LeetCode---389周赛

题目列表 3083. 字符串及其反转中是否存在同一子字符串 3084. 统计以给定字符开头和结尾的子字符串总数 3085. 成为 K 特殊字符串需要删除的最少字符数 3086. 拾起 K 个 1 需要的最少行动次数 一、字符串及其反转中是否存在同一子字符串 直接暴力枚举即可&#xff0c;代码…

网络行为管理系统招标模板

项目名称&#xff1a;网络行为管理系统招标 一、项目背景 随着信息技术的迅猛发展&#xff0c;网络安全和数据保护已成为企业和组织面临的关键挑战。为了确保网络环境的安全、合规&#xff0c;并实现对网络行为的有效管理和审计&#xff0c;我们特此启动网络行为管理系统的招…

maya打开bvh脚本

目录 maya打开脚本编辑器 运行打开bvh脚本 maya导出bvh脚本 maya打开脚本编辑器 打开Maya软件&#xff0c;点击右下角 “脚本编辑器” 运行打开bvh脚本 https://github.com/jhoolmans/mayaImporterBVH/blob/master/bvh_importer.py import os import re from typing impo…

OD C卷 - 反射计数

反射计数&#xff08;200&#xff09; 给定一个包含0 、1的二维矩阵&#xff1b;一个物体从给定的初始位置出发&#xff0c;在给定的速度下移动&#xff0c;遇到矩阵的边缘则发生镜面反射&#xff0c;无论物体经过0还是1&#xff0c;都不影响其速度&#xff1b;经过t时间单位后…