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

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

文章目录

  • 蓝桥杯第十五届抱佛脚(二)内置数据结构
    • 在竞赛中常见的数据结构
      • 数组(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 caseint 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);elseres = 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()); // 输出 AQueue<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>() {@Overridepublic int compare(String s1, String s2) {return Integer.compare(s1.length(), s2.length());}};// 创建一个TreeSet并传入自定义的ComparatorSet<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(); // 输入城市数量nfor (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);}
}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

(一)基于IDEA的JAVA基础7

关系运算符 运算符 含义 范例 结果 等于 12 false &#xff01; 不等于 1&#xff01;2 true > 大于 1>2 false < 小于 …

谈谈我对 AIGC 趋势下软件工程重塑的理解

作者&#xff1a;陈鑫 今天给大家带来的话题是 AIGC 趋势下的软件工程重塑。今天这个话题主要分为以下四大部分。 第一部分是 AI 是否已经成为软件研发的必选项&#xff1b;第二部分是 AI 对于软件研发的挑战及智能化机会&#xff0c;第三部分是企业落地软件研发智能化的策略…

Android 项目新建问题总结

title: Android 项目新建问题总结 search: 2024-03-24 tags: “#Android 项目新建问题总结” Android 项目新建问题总结 一、gradle 项目每次都自动下载依赖包到C盘 背景&#xff1a;idea 首次打开一个 gradle 项目&#xff0c;都会在 C 盘下载项目所需的依赖包&#xff0c;但…

解读 Xend Finance:向 RWA 叙事拓展,构建更具包容性的 DeFi 体系

在二十世纪后&#xff0c;非洲地区陆续爆发了主权运动&#xff0c;这也让非洲大陆逐渐摆脱“殖民地”的标签。目前&#xff0c;非洲大陆公有 54 个主权国家&#xff0c;接近 15 亿且仍在飙升的人口规模&#xff0c;其 GDP 已经与印度相当&#xff0c;且仍旧处于飞速的发展进程中…

微服务(基础篇-002-Ribbon)

目录 Ribbon负载均衡&#xff08;1&#xff09; 负载均衡的原理&#xff08;1.1&#xff09; 负载均衡策略&#xff08;1.2&#xff09; Ribbon-IRule(1.2.1) 修改负载均衡的方法&#xff08;1.2.2&#xff09; 懒加载&#xff08;1.3&#xff09; 饥饿加载&#xff08;1…

如何打破SAST代码审计工具的局限性?

关键词&#xff1a;白盒测试&#xff1b;代码分析工具&#xff1b;代码扫描工具&#xff1b;静态代码检测工具&#xff1b; 在代码的世界里&#xff0c;安全问题如同潜伏的暗礁&#xff0c;随时可能让航行中的软件项目触礁沉没。SAST代码审计工具如同雷达一样&#xff0c;以其独…

在fstab文件中配置UUID方式自动挂载数据盘、swap、目录(**)

linux如何挂在硬盘&#xff0c;自动挂载和手动挂载&#xff08;详细说明&#xff09;https://gitcode.csdn.net/65eedcea1a836825ed7a06f4.html 解决linux重启后磁盘挂载失效的问题 https://blog.csdn.net/sugarbliss/article/details/107033034 linux /etc/fstab 文件详细说…

我的风采——android studio

目录 实现“我的风采”页面要求理论代码生成apk文件 实现“我的风采”页面 要求 要求利用’java框架的边框布局实现“找的风采 ”页而&#xff0c;其中中间为你的生活照&#xff0c;左右和下面为按钮&#xff0c;上面为标签 理论 Java GUI编程是Java程序设计的重要组成部分…

Unity 背包系统中拖拽物体到指定位置或互换位置效果的实现

在Unity中&#xff0c;背包系统是一种常见的游戏系统&#xff0c;可以用于管理和展示玩家所持有的物品、道具或装备。 其中的拖拽功能非常有意思&#xff0c;具体功能就是玩家可以通过拖拽物品图标来移动物品在背包中的位置&#xff0c;或者将物品拖拽到其他位置或界面中&…

Spring Cloud Gateway Server MVC

之前你如果要用spring cloud gateway &#xff0c;就必须是webflux 的&#xff0c;也就是必须是异步响应式编程。不能和spring mvc 一起使用。现在spring cloud 新出了一个可以不用webflux的gateway。 具体使用mvc的gateway步骤如下 普通的Eureka Client的项目 如果你只是想测…

【前端Vue】HR-saas中台项目开发md文档第1篇:vuex基础-介绍,vuex基础-初始化功能【附代码文档】

HR-saas中台管理项目开发完整教程&#xff08;附代码资料&#xff09;主要内容讲述&#xff1a;vuex基础-介绍,vuex基础-初始化功能,vuex基础-state,vuex基础-mutations,vuex基础-actions,vuex基础-getters。项目课设计&#xff0c;人力资源的环境搭建vue-element-admin的了解和…

游戏引擎中的地形系统

一、地形的几何 1.1 高度图 记录不同定点的高度&#xff0c;对每个网格/顶点应用高度、材质等信息&#xff0c;我们每个顶点可以根据高度改变位移 但是这种方法是不适用于开放世界的。很难直接画出几百万公里的场景 1.2 自适应网格细分 当fov越来越窄的时候&#xff0c;网格…

网络协议栈--传输层--UDP/TCP协议

目录 本节重点一、再谈端口号1.1 再谈端口号1.2 端口号范围划分1.3 认识知名端口号(Well-Know Port Number)1.4 回答两个问题1.5 netstat1.6 pidof 二、UDP协议2.1 UDP协议段格式2.2 UDP的特点2.3 面向数据报2.4 UDP的缓冲区2.5 UDP使用注意事项2.6 基于UDP的应用层协议2.7 UDP…

flutter实现视频播放器,可根据指定视频地址播放、设置声音,进度条拖动,下载等

需要装依赖&#xff1a; gallery_saver: ^2.3.2video_player: ^2.8.3 AndroidManifest.xml <uses-permission android:name"android.permission.INTERNET"/> 实现代码 import dart:async; import dart:io;import package:flutter/material.dart; import pa…

leetcode 15.三数之和 JAVA 双指针法

题目 思路 双指针法 去重 为啥要去重呢&#xff1f;因为题目中说了要返回不重复的三元组。拿示例1来看&#xff0c;&#xff08;-1&#xff0c;0&#xff0c;1&#xff09;和&#xff08;0&#xff0c;1&#xff0c;-1&#xff09;虽然都等于0&#xff0c;但其实它们里面的数…

Java毕业设计-基于springboot开发的网吧管理系统-毕业论文+答辩PPT(附源代码+演示视频)

文章目录 前言一、毕设成果演示&#xff08;源代码在文末&#xff09;二、毕设摘要展示1、开发说明2、需求分析3、系统功能结构 三、系统实现展示1、系统登录2、管理员功能模块3、网管功能模块4、会员功能模块 四、毕设内容和源代码获取总结 Java毕业设计-基于springboot开发的…

【Python从入门到进阶】51、电影天堂网站多页面下载实战

接上篇《50、当当网Scrapy项目实战&#xff08;三&#xff09;》 上一篇我们讲解了使用Scrapy框架在当当网抓取多页书籍数据的效果&#xff0c;本篇我们来抓取电影天堂网站的数据&#xff0c;同样采用Scrapy框架多页面下载的模式来实现。 一、抓取需求 打开电影天堂网站&…

机器学习(27)

文章目录 文献阅读1. 题目2. abstract3. 网络架构3.1 Theoretical Results 4. 文献解读4.1 Introduction4.2 创新点4.3 实验过程4.3.1 数据集4.3.2 参数设置 4.4 结论 三、实现GAN1. 任务要求2. 实验结果3.实验代码3.1数据准备3.2 模型构建3.3 展示函数3.4 训练过程 小结本周内…

C#宿舍信息管理系统

简介 功能 1.发布公告 2.地理信息与天气信息的弹窗 3.学生信息的增删改查 4.宿舍信息的增删改查 5.管理员信息的增删改查 6.学生对宿舍物品的报修与核实 7.学生提交请假与销假 8.管理员对保修的审批 9.管理员对请假的审批 技术 1.采用C#\Winform开发的C\S系统 2.采用MD5对数据…

IDEA Android新建项目基础

title: IDEA Android基础开发 search: 2024-03-16 tags: “#JavaAndroid开发” 一、构建基本项目 在使用 IDEA 进行基础的Android 开发时&#xff0c;我们可以通过IDEA自带的新建项目功能进行Android应用开发基础架构的搭建&#xff0c;可以直接找到 File --> New --> …