Week 5
Lists, Stacks, Queues, and Priority Queues
1) 为什么需要数据结构( Data Structure )
-
- 文档开头用"变量到处都是"引出:当数据很多时,需要更好的组织方式( store data)
- 数据结构( Data Structure ) :
- 以特定方式组织数据的集合(collection of data organized in a specific fashion)
- 不仅存数据(stores data),还提供访问与操作(operations for accessing/manipulating)
- 高性能软件(high-performance software)需要选择合适的数据结构(best data structures)
2) 容器对象( Container Object )与面向对象思维( OOP thinking )
-
- 文档把数据结构比作"口袋/容器(container)"
- 容器对象(Container Object)在 OOP 下有两层:
- 类( Class / design ):定义结构(structure)、字段(fields)、方法(methods:search/insert/delete...)
- 实例( Instance / object ):真正创建出来装具体数据的容器(actual container holding specific data)
3) Java Collections Framework 总览( Grand Hierarchy )

-
- Java Collections Framework 的"大分支":
- Collections:存一组元素(stores a group of elements)
- Maps :存 key/value(stores key/value pairs)
并说明本周聚焦 Collections。
- Collection types(集合类型)列出 4 类:
- Sets:无重复(no duplicates)
- Lists:有序(ordered)
- Queues:先进先出(FIFO)
- PriorityQueues:按优先级(priority)
- Java Collections Framework 的"大分支":
4) 接口与实现( Interfaces vs. Implementation )
-
- 文档用"蓝图/工具/成品"的比喻解释三层:
- 接口( Interface / blueprint ):定义共同操作(common operations)。
- 抽象类( Abstract class / helper ):提供部分实现(partial implementation)。
- 具体类( Concrete class ):可直接使用的数据结构实现(ready-to-use)。
- 图里以 Collection<E> 为核心接口,下面分出:
- Set → HashSet
- List → ArrayList, LinkedList
- Queue → PriorityQueue
- 文档用"蓝图/工具/成品"的比喻解释三层:

5) Collection 接口:通用控制面板( Universal Controls )
文档把 Collection 的方法看成"万能遥控器(universal remote)",适用于 Set/List/Queue。
分类可记成三组=:
5.1 修改类( Modification )
-
- add(e):添加元素(add element)
- remove(o):删除指定元素(remove element)
- clear():清空(clear all)
5.2 查询类( Query )
-
- size():元素数量(count elements)
- isEmpty():是否为空(empty check)
- contains(o):是否包含(membership check)
5.3 批量操作( Bulk Operations )
-
- addAll(c):把集合 c 全部加入(union-like add)
- removeAll(c):移除所有出现在 c 的元素(difference-like remove)
- retainAll(c):只保留共同元素(intersection-like keep common)
5.4 迭代器( Iterator )
-
- iterator():获得迭代器对象(get iterator)
6) 基础操作示例( Basic operations )
6.1 添加( Adding data )
-
- 示例代码用 ArrayList<String> 依次 add("New York")、add("Atlanta")、add("Dallas")、add("Madison")。
- 并说明:add(e) 返回 true 表示集合发生改变(collection changed)

6.2 查询与删除( Query operations )
-
- contains("Dallas") → 判断是否存在
- remove("Dallas") → 删除指定元素
- size() → 数量
- isEmpty() → 是否空
- 并用表格解释这些方法的含义与返回值(returns true / integer count)

6.3 批量操作 = 集合论( Bulk operations = Set theory )
文档用 C1/C2 图示三种批量方法:
-
- C1.addAll(C2):把 C2 的元素加到 C1(并集 union 的感觉)
- C1.removeAll(C2):从 C1 删除所有出现在 C2 的(差集 difference 的感觉)
- C1.retainAll(C2):C1 只保留共同元素(交集 intersection 的感觉)
7) 便利类( Convenience classes ): AbstractCollection
-
- 如果要从零实现接口的全部方法(implement every interface method from scratch)会很繁琐(tedious)
- 解决:AbstractCollection已经替你实现了大部分方法(implements most methods)。
- 你只需实现少数核心:add、size、iterator
8) 不支持的操作( UnsupportedOperationException )
-
- 文档指出:不是所有集合都支持所有操作(not all collections support all operations),例如只读列表(read-only lists)
- 常见处理方式:在不支持的方法里抛出 UnsupportedOperationException(Not supported)。
- 这样保持接口一致(interface consistent),同时又允许不同实现的灵活性(flexibility)

9) 克隆与序列化( Cloning and Serialization )
-
- Cloneable:很多实现(ArrayList, LinkedList)可被克隆(can be cloned)。
- Serializable:很多集合可以保存到文件/流(saved to files/streams)。
- ⚠️ 警告:java.util.PriorityQueue 不实现 Cloneable(does NOT implement Cloneable)
10) 迭代与隐藏实现细节( Iterator Pattern )
10.1 为什么需要迭代器( iterator )
-
- 文档对比 Array vs Collection :
- 数组(array)你知道怎么按下标访问
- 集合(collection)内部结构可能是链表/网格等(unknown structure),但你仍想逐个查看元素而不暴露实现细节(without exposing implementation details)
- 文档对比 Array vs Collection :
10.2 三种遍历方式( Three ways to traverse )
3 种遍历(traverse):
-
- Iterator (手动 manual search ):while(it.hasNext() ){ it.next();}
- foreach 循环( foreach loop / quick scan ):for (Element e : collection){ ...}
- forEach 方法( forEach method / automated action ) :collection.forEach(e -> ...)
并提示:如果遍历时要删除元素(remove while walking),用 iterator 更合适
10.3 Iterable / Collection / iterator()
Collection 继承/依赖 Iterable,并通过 iterator() 提供统一遍历方式(iterator pattern)
10.4 迭代器接口( Iterator interface ):统一遍历工具( uniform traversal )
Iterator 的三个核心方法(core methods):
-
- hasNext():检查是否还有下一个元素(Check: Is there more?)
- 返回 boolean(returns boolean)
- next():获取当前元素(Action: Give me the item!)
- 返回元素(returns element)
- remove():删除"上一次看到的元素"(Edit: Delete the last one I saw)
- 返回 void(returns void)
- hasNext():检查是否还有下一个元素(Check: Is there more?)
Iterator 接口(Iterator interface)提供一种统一方式(uniform way)来遍历(traverse)各种集合(collections)
10.5 手动遍历( Manual operation ) = while + iterator
-
- 创建集合(Collection)并加入数据:
- Collection<String> collection = new ArrayList<>();
- collection.add("New York");
- collection.add("Atlanta");
- 获取迭代器(Obtaining the gadget):
- Iterator<String> iterator = collection.iterator();
- 手动遍历(manual operation):
- 创建集合(Collection)并加入数据:
while (iterator.hasNext()) {
System.out.print(iterator.next().toUpperCase() + " ");
}
图片吐槽点:这样写能工作(It works),但写 while 循环感觉麻烦(feels like a lot of work)。
10. 6 next() 的关键概念( Crucial concept )
-
- next() 会返回当前元素(returns the current element)
- 并且会把迭代器的"游标"向前移动(advances the iterator cursor forward)
这解释了为什么 hasNext()/next() 必须配合使用:
-
- hasNext() 先判断还有没有
- next() 再取出并前进
10.7 foreach 循环( foreach loop ):让你 " 偷懒 " ( do be lazy )
对比:
Before (手动 manual )
while (iterator.hasNext()) { ... }
After ( foreach )
for (String element : collection) { // Read as: "in"
System.out.print(element.toUpperCase() + " ");
}
特点(features):
-
- foreach 更省代码(writes the code for you)
- 适用于数组(arrays)和任何可迭代对象(any Iterable)
10.8 Java 8 : forEach 方法( forEach method )与 Consumer ( Consumer action )
collection.forEach(e ->
System.out.print(e.toUpperCase() + " "));
解释(explanation):
-
- 这是使用 Consumer(消费者动作):把你的逻辑自动应用到每个元素(applies your logic to every element automatically)
- "函数式思维(functional approach)":告诉集合做什么(WHAT),而不是怎么循环(HOW)
10.9 三种循环方式对比( Iterators vs Foreach vs forEach )
对照表(loop comparison):
-
- Iterator ( While Loop )
- 缺点:冗长(verbose)
- 但在"循环中需要 remove()"时必须用(necessary if need remove during loop)
- Foreach Loop
- 最干净语法(cleanest syntax)
- 推荐用于简单遍历(recommended for simple traversal)
- 不能删除元素(cannot remove elements)
- forEach Method
- 函数式风格(functional style)
- 一行很简洁(concise one-liners)
- Iterator ( While Loop )
红色警告( critical producer note ):
如果你需要在遍历时删除元素(remove elements while looping),必须使用显式迭代器(explicit Iterator)。
-
- List
11.1 List 接口( List interface ):有序 + 可重复 + 有下标
图片把 List 描述成更精确的蓝图(precise blueprint),它保证:
-
- 有序(Ordered collection):元素按顺序存储(stored in sequence)
- 允许重复(Duplicates allowed):可以存相同元素多次(store same element twice)
- 位置控制(Positional control):可以在精确位置插入(exact insertion points)
核心方法(core methods):
-
- add(index, element):按位置插入(insert at index)
- get(index):按下标取(get by index)
- indexOf(element):找元素位置(find index)
- subList(from, to):取子列表(sublist)
11.2 ListIterator :双向导航器( bidirectional navigator )
-
- Iterator:只能向前(forward only ->)
- ListIterator:双向(bidirectional <->)
接口定义(interface definition):
-
- public interface ListIterator<E> extends Iterator<E>
关键方法(key methods):
-
- 向前(forward):hasNext(), next(), nextIndex()
- 向后(backward):hasPrevious(), previous(), previousIndex()
- 修改(modify):add(e), set(e)
并提示:可以用 listIterator(startIndex) 从任意位置开始导航(begin navigation anywhere)。
(1) ArrayList ( Gadget #1: ArrayList )
机制(mechanism):
-
- 可变长数组(resizable-array implementation)
行为(behavior):
-
- 容量自动增长(capacity grows automatically)
- 满了会创建更大数组并复制旧数据(new larger array + copy old data)
构造器(constructors):
-
- new ArrayList():默认容量策略
- new ArrayList(capacity):预设容量(pre-sized)
内存提示(memory tip):
-
- 可用 trimToSize() 把容量收缩到当前大小(shrink capacity to fit current size)
(2) LinkedList ( Gadget #2: LinkedList )
机制(mechanism):
-
- 双向链表(doubly-linked list)
"双重身份"(dual nature):
-
- 同时实现 List 和 Deqeue(double ended queue)
Deque 特有方法(exclusive Deque methods):
-
- addFirst(e), addLast(e)
- getFirst(), getLast()
- removeFirst(), removeLast()
图片比喻:像一条链(like a chain),两端操作很容易(snap links on/off ends easily)。
代码演示( In Action: Code Walkthrough )
图片示例步骤:
-
- List<Integer> arrayList = new ArrayList<>();
- arrayList.add(1);:自动装箱(Autoboxing: int -> Integer)
- arrayList.add(2);
- arrayList.add(0, 10);:在 index 0 插入(insert at index 0)
"魔法转换"(magic conversion / copy constructor):
-
- LinkedList<Object> linkedList = new LinkedList<>(arrayList);
说明 LinkedList 可以用"拷贝构造器(copy constructor)"从另一个集合初始化
- LinkedList<Object> linkedList = new LinkedList<>(arrayList);
然后:
-
- linkedList.add(1, "red");
- linkedList.removeLast();
控制台输出(console output)展示:
-
- Step 1: 10, 1, 2
- Step 2 (Conversion): 10, red, 1

12) ArrayList vs LinkedList :效率对决( Efficiency showdown )
12.1 随机访问( Random Access: get/set )
-
- ArrayList:get/set 快(Fast ****O(1)****constant time,像"瞬移 teleportation via index")
- LinkedList:get/set 慢(Slow O(n) linear time,必须沿链走 must walk the chain)
结论(verdict):频繁读/访问(frequent reading/access)用 ArrayList。
12.2 插入 / 删除( Modification: insert/delete )
-
- ArrayList:慢(slow),因为需要移动元素(requires shifting elements)
- LinkedList:快(fast),只改指针(pointer change only)
结论:在开头/结尾大量操作(heavy manipulation at start/end,如 stacks/queues)用 LinkedList。
12.3 效率陷阱( Efficiency trap )
图片警告:
-
- 在 LinkedList 上用 for (i=0; i<size; i++) list.get(i) 非常慢(very slow)
- 因为每次 get(i) 都是 O(n),整体变成 O(n^2)(quadratic time)
推荐(good):
-
- 用 foreach(implicit iterator):for (Integer x : list) { process(x); }
这样整体是 O(n)(linear)。
- 用 foreach(implicit iterator):for (Integer x : list) { process(x); }
13) 快速创建 List ( Quick creation methods )
-
- Arrays.asList(T... a):返回由数组支撑的固定大小列表(fixed-size list backed by an array)
- List.of(T... elements)(Java 9+):不可变列表工厂(immutable list factory),直观简洁(intuitive and concise)

14) 选择速查表( Cheat sheet: which to choose )
-
- 结构(Structure)
- ArrayList:可变长数组(Resizable array)
- LinkedList:链式节点(Linked nodes)
- 随机访问(Random access / get)
- ArrayList:快 O(1)(Winner)
- LinkedList:慢 O(n)
- 开头插入(Insert at start)
- ArrayList:慢(Shifting)
- LinkedList:快 O(1)(Winner)
- 内存开销(Memory overhead)
- ArrayList:低(Low)
- LinkedList:高(High,node objects)
- 结构(Structure)
最终建议(Doraemon's final advice):
-
- 不确定就先用 ArrayList(start with ArrayList)
- 只有当你确实需要在开头高效增删(efficient adding/removing from the beginning)时再换 LinkedList。
15) Comparable vs Comparator ( Comparable 对比 Comparator )
场景:你想排序(sort)geometricObjects,但写:
-
- Collections.sort(geometricObjects);
会报错:Not Comparable(因为 GeometricObject 没有自然顺序)。
- Collections.sort(geometricObjects);
图片解释:
-
- GeometricObject 没有自然顺序( no natural order ) ,需要一个外部裁判(external judge):Comparator。
对比表(table):
-
- Comparable:内部(internal),由类自己实现(implemented by the class),定义自然顺序(defines natural order)。
- Comparator :外部(external),单独的类(a separate class),定义自定义顺序(defines custom order)。
提示:Comparator 更灵活(flexibility)。
16) Comparator 接口( The Comparator Interface )
图片把 Comparator 当成"排序机器(sorting machine)",接口形状是:
-
- interface Comparator<T> { int compare(T o1, T o2); }
比较结果含义(return meanings):
-
- 负数(negative)→ A < B
- 0(zero)→ A == B
- 正数(positive)→ A > B
图片给的数学技巧(math trick):
-
- compare(a, b) ≈ a - b(用差值判断大小)
17) " 经典 " 写法:单独写一个比较器类( Classic implementation )

图片示例文件:GeometricObjectComparator.java(单独一个文件)。
-
- public class GeometricObjectComparator implements Comparator<GeometricObject>, java.io.Serializable
- compare(GeometricObject o1, GeometricObject o2):取面积(area1 = o1.getArea(),area2 = o2.getArea()),然后:
- area1 < area2 → return -1
- area1 == area2 → return 0
- else → return 1
图片标注:Best Practice(最佳实践)------清晰、规范,但写起来比较啰嗦(verbose)。
18) 把 " 裁判 " 插到 max 里( Plugging in the Judge )
图片把 max() 比作机器:它不懂形状(doesn't know shapes),所以把 Comparator 传进去教它怎么判断(teach it how to judge)。
示例:
-
- GeometricObject g1 = new Rectangle(5, 5);
- GeometricObject g2 = new Circle(5);
- GeometricObject g = max(g1, g2, new GeometricObjectComparator());
输出提示:大对象的面积(area of the larger object)是多少。
核心点:
-
- **Comparator 作为参数(parameter)**让通用方法(generic method like max)可以处理"无自然顺序"的对象。
19) 啰嗦陷阱( Verbose Trap ) → Java 8 Lambda ( Lambda Expressions )
图片左边:不想新建文件,就写匿名内部类(anonymous inner class):
new Comparator<String>() {
public int compare(String s1, String s2) {
return s1.length() - s2.length();
}
}
虽然不用新文件,但仍然难看(ugly to read)。
图片右边:解决方案(solution)
-
- Java 8 开始,Comparator 是函数式接口(functional interface),可以用 Lambda(lambda expression)。
升级后的 Lambda 示例(Upgrade):
-
- (o1, o2) -> o1.getArea() > o2.getArea() ? 1 : -1
并强调三点: - 参数可推断(inferred parameters)
- Lambda 运算符(lambda operator ->)
- 更精简的逻辑体(concise logic body)
- (o1, o2) -> o1.getArea() > o2.getArea() ? 1 : -1


20) 自定义排序:字符串按长度( Custom Criteria: Sorting Strings )
图片对比两种排序标准(criteria):
-
- 自然顺序(natural order):按字母表(alphabetical)
- 自定义 Comparator(custom comparator):按长度(by length)
代码示例:
-
- cities 是 String\[\] 数组
- Arrays.sort(..., comparator) 按你给的比较规则排序
-
- Arrays.sort(cities, (s1, s2) -> s1.length() - s2.length());
并提示:
-
- 如果 s1 更短(s1 shorter),s1.length() - s2.length() 为负数(negative)→ s1 排前面。
21) 语法糖:方法引用( Syntactic Sugar: Method References )
图片给等价关系:
-
- (s1, s2) -> s1.compareToIgnoreCase(s2)
等价于 - String::compareToIgnoreCase(method reference)
- (s1, s2) -> s1.compareToIgnoreCase(s2)
示例:
-
- List<String> cities = Arrays.asList("Atlanta", "Savannah", "Dallas");
- cities.sort(String::compareToIgnoreCase);
要点:方法引用让你不用显式写参数传递(let compiler do the work)。
22) " 终极工具 " : Comparator.comparing ( Comparator.comparing )
图片把 Comparator.comparing() 描述成"读起来像英语(reads like English)":
-
- Arrays.sort(cities, Comparator.comparing(String::length));
关键词:
-
- Key Extractor (键提取器) :String::length
意思是:按"某个属性(property)/键(key)"来比较。
- Key Extractor (键提取器) :String::length
23) 真实例子:排序贷款( Real World: Sorting Loans )
图片示例:Loan 有属性 amount、interestRate。
排序代码:
-
- Arrays.sort(list, Comparator.comparing(Loan::getLoanAmount));
解释:Key Extractor 决定比较依据(judge the loans based on this specific property)。
24) 相同键如何打破平局( Breaking ties with thenComparing )
问题:如果两个 loan 金额相同(same amount),谁先?
做法:链式比较(chaining comparisons):
-
- Comparator<Loan> loanComparator = Comparator.comparing(Loan::getLoanAmount)
.thenComparing(Loan::getAnnualInterestRate);
- Comparator<Loan> loanComparator = Comparator.comparing(Loan::getLoanAmount)
逻辑:
-
- 先比金额(compare loan amount)
- 如果相等(equal)再比利率(compare interest rate)
25) 反转排序( Flipping the script: reversed )
想要降序(descending order),不必交换参数或手动取负(no need to swap args / write negative math),直接:
-
- Arrays.sort(list, Comparator.comparing(Loan::getLoanAmount).reversed());
26) 哆啦 A 梦总结表( Doraemon's Cheat Sheet )
图片表格总结"从旧到新"的 comparator 写法:
-
- 老方法(The Old Way):class MyComp implements Comparator → 啰嗦、独立文件(verbose, separate file)
- Lambda(The Lambda):(o1, o2) -> ... → 简洁、内联(concise, inline)
- 现代方法(The Modern Way):Comparator.comparing(Class::prop) → 可读、声明式(readable, declarative)
- 强力工具(Power Tools):.thenComparing(), .reversed() → 链式逻辑(chaining logic)
最后提示:别重复造轮子(don't reinvent the wheel),用 Collections的静态工具方法(static utility methods)。
注意:java.util.Collections (工具类 utility class)不要和 Collection interface(集合接口)搞混。
A) Collections 工具类( java.util.Collections ) "Gadgets" 合集
Gadget #1 :即时排序器( Instant Sorter )
-
- 代码思路:把一组字符串(strings)放进列表(List),然后用 Collections.sort(list) 自动按字母顺序排序(alphabetical / natural order)。
- 图片感叹:居然会自动按字母排序(sorted alphabetically automatically)。
关键词:排序(sort)、自然顺序(natural order)、Collections.sort

Gadget #2 :反转器( Reverser )

-
- 场景:如果想要倒序(reverse order / descending),使用:
- Collections.sort(list, Collections.reverseOrder());
- Collections.reverseOrder():返回一个反转自然顺序的比较器(Comparator that reverses natural ordering)。
- 输出示例显示:列表被按相反顺序排列。
- 场景:如果想要倒序(reverse order / descending),使用:
关键词:反向比较器(reverseOrder)、比较器(Comparator)、倒序排序(descending sort)
Gadget #3 :高速查找器(二分查找 binarySearch )

-
- 方法:Collections.binarySearch(list, key)
- 强制前提( WARNING ):列表必须先排好序(List must be sorted FIRST)。
- 找到时:返回索引(returns index),示例 search(...,7) 返回 index 2。
- 找不到时:返回负数(negative result),并遵循公式:
Not Found Logic :-(insertion point) - 1
图片示例:查 9 的插入点(insertion point)是 index 3 → -(3) - 1 = -4。
关键词:二分查找(binarySearch)、插入点(insertion point)、必须先排序(sorted first)

Gadget #4 :搅拌器( Mixer )

包含两个操作:
4.1 Collections.reverse() (反转 reverse )
只翻转顺序(flips order only),不是随机。
4.2 Collections.shuffle() (洗牌 shuffle )
-
- 随机打乱顺序(randomizes order)。
- 还支持带种子(seeded)的版本:
- Collections.shuffle(list, new Random(20));
图片提示:用种子(seed)可以让结果可复现(reproducible),适合一致性测试(consistent testing)。
- Collections.shuffle(list, new Random(20));
关键词:反转(reverse)、洗牌(shuffle)、随机种子(seed)、可复现(reproducible)
Gadget #5 :克隆器( Cloner / copy )

-
- 方法:Collections.copy(dest, src)
- 图片给了"陷阱(trap)":
- 如果目标列表 dest 的长度比源列表 src 小,会抛 IndexOutOfBoundsException。
- 示例:src size=2,dest size=0(空)→ copy 直接报错。
- 解决(solution):dest 的大小必须 ≥ src(Destination must be >= Source size)。
图片示例用"先创建足够长度的 dest"再 copy 成功。
关键词:拷贝(copy)、目标大小(destination size)、越界异常(IndexOutOfBoundsException)
Gadget #6 :复制器( Replicator )

两种功能:
6.1 Collections.nCopies(n, value) (创建 creation )
-
- 创建一个含 n 个相同元素的列表(n copies)。
- 图片标注:结果是不可变(immutable / read-only)------不能修改返回列表。
6.2 Collections.fill(list, value) (修改 modification )
-
- 把已有列表所有元素替换成同一个值(replaces content of an existing list)。
关键词:nCopies(nCopies)、不可变(immutable)、填充(fill)
Gadget #7 :分析器( Analyzer : max / min )

-
- Collections.max(nums):按自然顺序找最大(find highest value)。
- 自定义比较:用 Comparator 找"最短字符串"(shortest length),例如:
- Collections.min(strings, Comparator.comparing(String::length));
- 图片强调:不用手写循环(No loops),一行代码搞定(one line of code)。
关键词:最大值(max)、最小值(min)、Comparator.comparing(comparing)
Gadget #8 :检查器( Inspector )
8.1 频率扫描( Frequency Scanner )

-
- Collections.frequency(list, "red"):统计元素出现次数(count occurrences)。
- 图片示例显示结果为 2。
8.2 不相交检测( Disjoint Detector )

-
- Collections.disjoint(list1, list2):判断两个集合是否没有共同元素(no common elements)。
- 输出:不相交返回 true(disjoint true),有交集返回 false(disjoint false)。
关键词:频率(frequency)、不相交(disjoint)、交集(overlap)
Gadget Inventory (工具清单)
图片把工具分组:
-
- 排序(SORTING):sort、reverseOrder
- 搜索(SEARCHING):binarySearch(必须先 sort)
- 修改(MODIFYING):shuffle、reverse、copy、fill
- 分析(ANALYZING):max、min、frequency、disjoint
- 创建(CREATING):nCopies
Mission Accomplished (总结)
图片 checklist:
-
- 效率(Efficiency):使用优化过的方法(optimized code)
- 可读性(Readability):标准方法大家都认识(standard methods everyone knows)
- 安全性(Safety):注意 copy 的大小要求(copy size)和 binarySearch 的排序要求(sort requirements)
最后一句:去实验里练(practice in the lab)。
B) 特殊容器: Vector & Stack ( Specialized Containers )
1) Vector (向量 Vector )
-
- 特点:同步(synchronized / thread-safe),即同一时刻只有一个线程能修改(one thread at a time)。
- 代价:更慢(slower than ArrayList)。
- 建议:只有需要时才用(use only when necessary)。
关键词:同步(synchronized)、线程安全(thread-safe)、更慢(slower)
2) Stack (栈 Stack )
-
- 行为:后进先出(LIFO: Last-In, First-Out)
- 常用方法:push()、pop()、peek()
C) 队列与优先队列( Queues & Priority Queues )
1) Queue (队列 Queue )
-
- 行为:先进先出(FIFO: First-In, First-Out)
- 方法:offer()、poll()、peek()
2) PriorityQueue (优先队列 PriorityQueue )
-
- 按重要性排序(ordered by importance)
- 最高优先级先出(highest priority leaves first)
- 与到达顺序无关(regardless of arrival time)
D) 旧时代容器( Legacy ): Vector & Stack 的历史
1) 历史( history )
-
- Vector 和 Stack 在 Java 2 之前就存在(before Java 2)。
- 后来为适配 Java Collections Framework 被重新设计(redesigned)。
- 仍保留旧式方法用于兼容(old-style methods for compatibility)。
- 图示关系:AbstractList → ArrayList 与 Vector;Stack 基于 Vector(extends)。
E) Vector 的 " 同步容器 " 对比( Vector vs ArrayList )
-
- ArrayList:不同步(not synchronized)→ 更快(faster)
- Vector:同步(synchronized)→ 线程安全(thread-safe)但更慢(slower)
- 关键概念(key concept):同步方法防止多线程访问时数据损坏(prevent data corruption in multithreading)。
- 经验法则(rule of thumb):不需要同步就用 ArrayList,更高效(more efficient)。

F) Vector 的构造( Constructing a Vector )
图片列了几种构造方式(constructors):
-
- new Vector<>():默认(initial capacity 10)
- new Vector<>(existingCollection):从现有集合创建(from existing collection / copy)
- new Vector<>(20):指定容量(specific capacity)
- new Vector<>(20, 5):容量 + 增量(capacityIncrement)
- 独特特性:capacityIncrement 控制"满了之后增长多少"(controls growth when full)

G) Vector 的旧式操作( Legacy operations )
图片表格对应:
-
- addElement(o) ↔ add(o)
- insertElementAt(o, index) ↔ add(index, o)
- removeElement(o) ↔ remove(o)
- removeAllElements() ↔ clear()
并指出:老 Vector API 常带 "Element" 后缀(suffix),而这些方法也是同步的(synchronized)。
H) 容量管理( Managing Capacity )
图片说明 capacity(容量)和 size(实际大小)差异会造成"浪费内存"(wasted memory)。
-
- capacity():返回当前总容量(current total space)
- setSize(newSize):手动设置 size(manually sets size)
- trimToSize():把 capacity 缩到当前 size(trim capacity to match size),节省内存(saves memory)
下面按你这组图片内容(主题:Stack 、 Queue 、 PriorityQueue 、 LinkedList 作为 Queue 、 Queue 方法安全性、 PriorityQueue 自然顺序与自定义优先级、 Vector/Stack 关系 )做一份紧贴图片的详细中文笔记,关键词后附英文。
27) Stack 类( The Stack Class )
图片用"煎饼堆"解释栈(Stack):
-
- 定义(Definition):Stack extends Vector(Stack 继承 Vector)。
- 核心概念(Concept):LIFO(Last-In, First-Out)后进先出
- 规则(Rule):只能拿走最后放上去的那个(the top one)。
对话强调:想拿底部(bottom)是不行的,必须先拿顶端(top)------这就是 LIFO。
a) 压栈与出栈( Push and Pop )
图片给出两个最经典操作(core operations):
-
- push(E item):压栈(push),加到栈顶(adds to top)
- pop():出栈(pop),移除并返回栈顶(removes & returns top)
记忆:push 把盘子放上去;pop 把最上面拿走。
b) 查看与检查( Peeking and Checking )
图片列出:
-
- peek():查看栈顶(look at top)但不移除(without removing)
- empty():是否空栈(is empty),空则返回 true(returns true if contains no items)
示例(example):
-
- Stack<String> s = new Stack<>();
- s.push("Dorayaki");
- System.out.println(s.peek());
输出 "Dorayaki",但栈大小仍是 1(stack size still 1)。
c) 栈搜索( Searching the Stack )
图片强调 search(Object o) 的返回规则(return value rule):
-
- 方法(Method):search(Object o)
- 返回值(Return Value):从栈顶开始数的 1-based 位置(1-based position from the TOP)
- 没找到返回 -1(returns -1 if not found)
⚠️ 注意(Note):它不是数组下标(not like array index),不是从 0 开始,而是从 1 开始,并且从"顶端往下数"。
28) 该用哪个容器( Which Gadget to Use? )
-
- ArrayList
- 用途(Use):通用(general purpose)、速度(speed)
- 注意(Note):非线程安全(not thread-safe)
- Vector
- 用途(Use):旧代码(legacy code)、简单线程安全(simple thread safety)
- 注意(Note):更慢(slower)
- Stack
- 用途(Use):LIFO 逻辑(Last-In, First-Out logic)
- 注意(Note):继承 Vector(extends Vector)
- ArrayList
旁边提示:现代 Java 一般优先 ArrayList,但理解 Vector/Stack 有助于读旧代码(legacy code)。
29) Queue 与 PriorityQueue ( Queues and Priority Queues in Java )
图片把队列(Queue)比作排队买铜锣烧:
(1) Queue ( FIFO )
-
- FIFO:First-In, First-Out 先进先出
- 定义(Definition):元素从末尾加入(appended to the end),从开头移除(removed from the beginning)。
- 图上标注操作:
- 插入(Insertion):offer(从队尾进)
- 移除(Removal):poll(从队头出)
直观:先来的先走(first to arrive is first to leave)。
(2) Queue 在 Java 里是接口( Queue is an interface )

-
- Queue 不是一个类(not a class),而是接口(interface)。
- 一般用 LinkedList 来"真正实现"(concrete implementation)一个队列。
- 继承层次(Hierarchy):
- Queue interface extends java.util.Collection
- 它增加了插入/取出/查看(insertion, extraction, inspection)相关操作。
- 常见实现(implementations):LinkedList、PriorityQueue。
(3) Queue 方法安全协议( Gadget Safety Protocols )
把 Queue 方法分成两类:
3.1 安全方法( Safe methods ) --- 空队列不会崩( returns null/false )
-
- offer(e)(insert)
- poll()(remove)
- peek()(examine)
特点:队列为空(empty)时不会抛异常(won't crash),通常返回 null 或 false(returns null/false)。
3.2 风险方法( Risky methods ) --- 可能抛异常( throws exception )
-
- add(e)(insert)
- remove()(remove)
- element()(examine)
图片建议:尽量用 safe 方法(use offer/poll/peek),避免空队列崩溃。
(4) LinkedList 作为 Queue ( LinkedList is special )

图片强调 LinkedList 的"双接口身份":
-
- LinkedList 同时实现 List 和 Queue(implements both List and Queue)。
- 它在两端增删(adding/removing from the ends)很高效(very efficient)。
示例代码(example):
Queue<String> queue = new LinkedList<>();
queue.offer("Oklahoma");
queue.offer("Indiana");
queue.offer("Georgia");
queue.offer("Texas");
(5) 队列处理( Queue Processing / FIFO )

图片用管道演示:元素按加入顺序被取出(retrieved in the exact order they were added)。
示例循环(loop):
-
- while (queue.size() > 0) { System.out.print(queue.remove() + " "); }
输出按 FIFO:Oklahoma → Indiana → Georgia → Texas(先入先出)。
- while (queue.size() > 0) { System.out.print(queue.remove() + " "); }
(6) 转折:优先队列( The Twist: Priority Queues )
图片引入"VIP 插队":
-
- PriorityQueue(优先队列):不是按到达顺序(arrival time),而是按优先级/自然顺序(priority / natural ordering)处理。
- 规则(rule):更小的元素优先(smaller elements come first)
- 对数字:1 比 10 优先
- 对字符串:字母序更靠前(A before Z)优先
图片总结:默认最小元素是 VIP(smallest item removed first)。
(7) PriorityQueue 示例:按字母序( Alphabetical priority )
示例(example):
PriorityQueue<String> queue1 = new PriorityQueue<>();
queue1.offer("Oklahoma");
queue1.offer("Indiana");
queue1.offer("Georgia");
queue1.offer("Texas");
while (queue1.size() > 0) System.out.print(queue1.remove() + " ");
输出变成按字母序(alphabetical order):Georgia, Indiana, Oklahoma, Texas(图片里强调 "Alphabetically first = Highest Priority")。
(8) 自定义优先级( Customizing Priority )
如果你希望"最大的数字(largest number)"或"Z 优先"(highest number / Z first),图片说:
-
- 把一个 Comparator(比较器)传给 PriorityQueue 的构造器(constructor)。
- 示例使用 Collections.reverseOrder() 反转规则(reverse the order)。
代码思路(picture idea):
PriorityQueue<String> queue2 = new PriorityQueue<>(4, Collections.reverseOrder());
效果:优先级反转(priority reversed),最大的/字母序最后的先出来(largest comes out first)。
(9) 选择: LinkedList vs PriorityQueue
-
- LinkedList (as Queue):逻辑 FIFO(fairness),适合普通排队(standard lines)。
- PriorityQueue:按重要性(importance),适合 VIP 任务/调度(VIP tasks / scheduling)。
(10) PriorityQueue 重要安全规则( Important Safety Rules )
图片列出 4 条(非常适合背):
-
- 不允许 null(No nulls):PriorityQueue 不允许 null 元素(does not allow null elements)。
- 必须可比较(Comparable required):元素必须实现 Comparable,或你提供 Comparator。
- 不满足会崩(The crash):如果不可比较,会抛 ClassCastException。
- 平局无保证(Ties arbitrary):优先级相同,顺序可能是任意的(order is arbitrary)。
TLDR
0. 文档定位( TL;DR purpose )
- 这份 TL;DR(TL;DR)用条目方式总结第5周核心概念(core concepts)、常用方法(common methods)与选型建议(choosing data structures)。
- 覆盖:集合层次(hierarchy)、遍历(traversal)、List/Queue/Stack/PriorityQueue 特性、比较与排序(comparison & ordering)、Collections 工具方法(utility methods)、以及快速提醒(quick reminders)。
1. 核心概念与层次( Core Concepts and Hierarchy )
- Collection(Collection)是"元素集合"的根接口(root interface),覆盖 List/Queue/PriorityQueue/Stack/Set 共享的通用操作(common operations)。
- Map(Map)与 Collection 分离(separate),存储键值对(key/value pairs)。
- List(List):有序(ordered)且允许重复(allows duplicates)。
- Stack(Stack):后进先出(LIFO, last-in first-out)。
- Queue(Queue):先进先出(FIFO, first-in first-out)。
- PriorityQueue(PriorityQueue):按优先级移除(removes by priority),不是按插入顺序(not insertion order)。
- 框架结构(framework structure):接口(interfaces)+ 便利抽象类(convenience abstract classes)+ 具体类(concrete classes),大致:
Collection → List / Queue → ArrayList / LinkedList / Vector / Stack / PriorityQueue。 - 多数具体集合类(concrete collection classes)是可克隆(Cloneable)且可序列化(Serializable);PriorityQueue 是 Cloneable 的例外(exception)。
2. 遍历机制( Traversal Mechanisms )
2.1 iterator ( iterator() )
- iterator():获取迭代器(gets an iterator)。
- hasNext():判断是否还有元素(checks whether more elements remain)。
- next():返回下一个元素(returns the next element)。
- remove():删除"上一次 next() 返回的元素"(removes the last element returned by next)

2.2 foreach ( Foreach loop )
- foreach 循环(foreach loop):最简单的只读遍历(simplest read-only traversal),适用于
任何 Iterable(any Iterable)。

2.3 forEach(action) ( Java 8 )
- forEach(action):对每个元素执行动作(performs an action on each element)。
- 例:list.forEach(s -> System.out.print(s.toUpperCase() + " "));

3. 常见集合操作( Common Collection Operations )
- add(e):添加一个元素(adds one element)。
- addAll(c):添加另一个集合所有元素(adds all elements from another collection)。
- contains(o):判断是否存在(tests whether an element exists)。
- containsAll(c):判断是否包含另一个集合的全部元素(tests whether all elements exist)。
- remove(o):删除匹配元素(removes a matching element)。
- removeAll(c):删除所有也出现在 c 的元素(removes elements also found in another collection)。

- retainAll(c):只保留共同元素(keeps only common elements)。
- clear():清空(removes everything)。
- size():元素数量(returns number of elements)。
- isEmpty():是否为空(checks whether collection has no elements)。
- toArray():返回 Object 数组(returns an Object\[\])。
- toArray(T\[\]):返回指定类型数组(returns a typed array)。


- new String0:让 toArray 自己创建合适大小
- new Stringlist.size():你提前创建好,toArray 直接填充
4. List 专有特性( List-Specific Features )
- List 是有序(ordered)、允许重复(duplicates)、支持基于索引访问(index-based access)。
- add(index, e):在位置插入(inserts at a position)。
- addAll(index, c):在位置插入集合(inserts a collection at a position)。
- get(index):按下标读取(reads by index)。
- set(index, e):替换并返回旧值(replaces and returns old element)。
- remove(index):按下标删除(removes by index)。
- indexOf(o) / lastIndexOf(o):首次/最后一次出现位置(first/last matching index)。
- subList(from, to):返回视图(view),范围到 to-1(to - 1)。



- ListIterator:从末尾开始可反向遍历(backward traversal),用 hasPrevious()/previous()。
- trimToSize():缩减 ArrayList 容量到当前 size(shrinks capacity)。
- LinkedList 支持两端操作(both ends efficiently):addFirst/addLast/getFirst/getLast/removeFirst/removeLast。
- Arrays.asList(...):快速创建列表(quick create list)。


5. Queue 、 Stack 、 PriorityQueue ( Queue, Stack, PriorityQueue )
5.1 Stack ( Stack )
- Stack 是旧式 LIFO 结构(legacy LIFO),建立在 Vector 上(built on Vector)。
- push(e):压栈(adds to top)。
- peek():看栈顶不删除(reads top without removing)。
- pop():删除并返回栈顶(removes & returns top)。
- empty():是否空栈(checks whether empty)。
- search(o):返回元素在栈中的位置(position)。

5.2 Queue ( Queue )
- Queue 是 FIFO(FIFO);LinkedList 可实现 Queue(LinkedList implements queue)。
- offer(e):推荐插入方式(preferred insert)。
- poll():移除队首,空则返回 null(returns null if empty)。
- remove():移除队首,空则抛异常(throws exception if empty)。
- peek():读队首,空则返回 null(returns null if empty)。
- element():读队首,空则抛异常(throws exception if empty)。


5.3 PriorityQueue ( PriorityQueue )
- PriorityQueue 先移除"最高优先级"(highest-priority)元素;默认用自然顺序(natural order),所以最小元素先出(least element comes out first)。
- 自定义优先级:PriorityQueue(initialCapacity, comparator)(custom priority order)。
- 例:new PriorityQueue<>(4, Collections.reverseOrder())(反向顺序 reversed order)。

6. 比较与排序( Comparison and Ordering )
- Comparable 定义自然顺序(natural order);Comparator 定义自定义顺序(custom order)。
- compareTo(...):自然排序(natural ordering)。例:"Atlanta".compareTo("Dallas")。
- compare(a,b):用 comparator 自定义排序(custom ordering)。
- 例:Comparator<String> c = (s1, s2) -> s1.length() - s2.length();

- sort(comparator):用指定 comparator 排序(sort with comparator)。例:list.sort(String::compareToIgnoreCase)。
- sort(null):按自然顺序排序(natural order)。
- Comparator.comparing(...):从 key 构建 comparator(build comparator from key)。例:Comparator.comparing(String::length)。


- thenComparing(...):添加第二比较规则(secondary rule)。
- reversed():反转 comparator 顺序(reverse comparator order)。

7. Collections 工具方法( Collections Utility Methods )
- Collections.sort(list):按自然顺序排序(natural order)。
- Collections.sort(list, c) 与 reverseOrder():用 comparator 排序,常用于降序(descending)。
- binarySearch(list, key):在已排序列表中二分查找(search a sorted list)。

- binarySearch(list, key, c):用同一个 comparator 查找(same comparator as sorting)。
- reverse(list):反转当前顺序(reverse current order)。
- shuffle(list):随机打乱(random reordering)。
- shuffle(list, random):固定 Random 可复现(reproducible shuffle)。
- copy(dest, src):按索引拷贝(copies by index),dest 必须足够长(destination must be long enough)。 用 src 的元素去覆盖 dest 相同索引位置的元素
- nCopies(n, o):生成不可变重复列表(immutable list of repeated references)。
- fill(list, o):把列表全部替换成 o(replaces all elements)。
- max(c) 与 min(c, comparator):最大/最小(largest/smallest)。

- disjoint(c1, c2):是否无交集(no shared elements)。
- frequency(c, o):出现次数(count occurrences)。

8. 如何选数据结构( Choosing the Right Data Structure )
- ArrayList:当索引随机访问(random access by index)很重要,且不频繁在前端插入/删除(not frequently insert/remove at front)。
- LinkedList:当经常在开头或两端插入/删除(frequent insertion/removal at beginning or both ends)。
- Vector:主要用于旧代码或需要同步(legacy / synchronized);否则 ArrayList 更快(faster)。
- Stack:用于 LIFO 场景(LIFO tasks),如表达式求值(expression evaluation)。
- Queue:用于 FIFO 处理(FIFO processing)。
- PriorityQueue:移除顺序由优先级决定(removal by priority),不是到达顺序(arrival order)。
- 若不需要插入/删除(no insertion/deletion),普通数组(plain array)比 list 更高效(more efficient)。
9. 快速提醒( Quick Reminders )
- List 允许重复(duplicates);Set 不允许(no duplicates)。
- Collection 是元素集合的根(root);Map 是分离的(separate)。
- LinkedList get(i) 能用但遍历很慢(inefficient traversal),应优先 foreach/forEach/iterator。
- Queue:poll()/peek() 空时返回 null;remove()/element() 空时抛异常(throws exceptions)。
- 默认 PriorityQueue:最小元素优先级最高(least element highest priority)。
- PriorityQueue 不是 FIFO,除非优先级顺序刚好等于插入顺序(not FIFO unless matches)。
- Collections.binarySearch 只在"已按相同顺序排序"的列表才有意义(must be sorted in matching order)。
- Collections.copy 是浅拷贝(shallow),且 dest 需要足够槽位(enough slots)。
- Collections.nCopies 返回不可变列表(immutable),且所有元素引用同一个对象(same object reference)。
- 某些具体子类可能不支持某些操作(unsupported operations),会抛 UnsupportedOperationException。
- Vector 方法是同步的(synchronized);若不需要同步,通常选 ArrayList(preferred)。
Lab Sheet
文档概览( Overview )

- 本实验单对应第5周主题:List、Queue、Stack、PriorityQueue。
This lab matches Week 5 topics: List, Queue, Stack, PriorityQueue. - 学生重点:为每个问题选择合适的 Java 集合,并使用本周笔记介绍的核心方法
- 四个任务是累积式(cumulative):要在同一个 todo 系统里从 Task1 扩展到 Task4,而不是写四个程序。
Task 1 :用 List 做核心任务列表( Core Task List with List )
- 场景:学生一天有许多小任务,需要按清晰顺序存储,方便查看和后续更新。
Scenario: A student has many small tasks and needs them stored in a clear order for viewing and later updates.
必做功能( Required Features )
- 添加任务:用户输入标题(title),任务加入 todo list。
- 查看全部任务:按顺序显示(in order),每条显示索引(index)、标题(title)、完成状态(completion status)。
- 编辑任务:按索引选择(select by index),可更新标题(update title)。
- 删除任务:按索引选择并从列表移除(removed from list)。
- 标记完成:按索引选择并更新状态(status updated)。
- 按状态显示:只看已完成(completed)或只看未完成(incomplete)。
- 清晰遍历列表:显示任务时用 foreach / forEach / iterator 三者之一。
实现约束( Implementation Constraints )
- 任务必须用 List<Task> 存储,建议实现类如 ArrayList<Task>。
Tasks must be stored using List<Task>; use a suitable implementation such as ArrayList<Task>. - 自定义 Task 类至少包含:title 与 completed。
Define a custom Task class with at least: title and completed. - 编辑/删除等操作要用"基于索引"(index-based)的方法。
预期结果( Expected Outcome )
- 完成 Task1 后应能展示:有序存储(ordered storage)、索引访问与更新(index-based access/update)、遍历(traversal)、可扩展的基础结构(base structure)。
Task 2 :用 Queue 做任务处理流程( Queue-Based Task Processing )
- 场景:学生不会一次做完所有任务,日常更适合先进先出(FIFO)处理。
Scenario: students don't do all tasks at once; FIFO processing suits day-to-day handling. - 目标:在保留 Task1 主列表的基础上,加一个处理队列(processing queue)。
Goal: keep the original task list and add a processing queue on top.
必做功能( Required Features )
- 从主列表移入队列:用户从 todo list 选一个任务加入 queue,但任务仍属于整个系统(不丢失主列表结构)。
Move from main list to queue: select a task, add to the queue, and keep it as part of the overall system. - 查看队列:按 FIFO 顺序显示所有排队任务。
View queue: display all queued tasks in FIFO order. - 查看下一项:查看队首任务但不移除(without removing)。
View next: display the front task without removing it. - 处理下一项:移除队首并显示刚处理了哪个任务。
Process next: remove the front and display which task was processed. - 空队列处理:队列为空时给提示信息,不要崩溃(instead of crashing)。
Handle empty queue: if empty, show a message instead of crashing. - 明确角色:主列表负责存储(stores tasks),队列负责 FIFO 处理(processes selected tasks)。
Keep roles clear: list stores tasks; queue processes selected tasks in FIFO order. - 复用 Task1 菜单与数据模型:把队列功能加进原程序,不要新开项目。
Reuse Task 1 menu and model: add queue options to the existing program, don't start a new one.
实现约束( Implementation Constraints )
- 队列必须用 Queue<Task>,建议具体类(concrete class)用 LinkedList<Task>。
Queue must use Queue<Task>; use a concrete class such as LinkedList<Task>. - 使用 Week5 的队列操作:offer() 添加,peek() 查看队首,poll() 安全移除队首。
Use Week 5 queue ops: offer() add, peek() inspect front, poll() safely remove front. - 不要替换 Task1 的 List<Task>,Queue 是额外组件(additional component)。
Do not replace Task 1's List<Task>; the queue is an additional component.
预期结果( Expected Outcome )
- 能清楚展示 List 存储(storage)与 Queue 处理(processing)的差别。
Demonstrate the difference between storing tasks in a List and processing tasks in a Queue. - 系统更像完整应用:既有存储也有工作流(workflow management)。
System should feel more complete with both storage and workflow management.
Task 3 :用 Stack 记录最近操作( Stack-Based Recent Activity History )
- 场景:用户往往最想查看最近做了什么,符合后进先出(LIFO)模式。
Scenario: users want to inspect most recent actions first; this matches LIFO and a stack. - 目标:在 Task1+Task2 的系统上新增"最近操作历史"(recent activity history)。
Goal: extend the existing system by recording recent actions in a stack.
必做功能( Required Features )
- 记录操作:每次执行会改变状态的操作都记录简短描述(action description)。
Record actions: whenever a state-changing operation occurs, record a short description. - 示例动作:ADD/EDIT/REMOVE/COMPLETE/MOVE_TO_QUEUE/PROCESS_QUEUE。
Example actions: ADD, EDIT, REMOVE, COMPLETE, MOVE_TO_QUEUE, PROCESS_QUEUE. - 查看最近动作:查看栈顶但不移除(without removing)。
View most recent: show latest action without removing it. - 移除最近动作:弹出(pop)并显示被移除的动作,体现 LIFO。
Remove most recent: pop and display it to demonstrate LIFO behaviour. - 显示历史:从最新到最旧显示(most recent to oldest)。
Display history: show recorded actions from most recent to oldest. - 空栈处理:无记录时给提示信息,不要崩溃。
Handle empty stack: if none stored, show a message instead of crashing. - 解释理由:能说明为什么栈适合"最近动作历史"。
Explain behaviour: describe why a stack suits recent-action history. - 保持前面功能可用:List 与 Queue 仍要正常工作,History 是新增不是替代。
Keep previous features working: list and queue must continue; history is added, not a replacement.
实现约束( Implementation Constraints )
- 最近历史必须用 Stack 实现(implemented using Stack)。
Recent action history must be implemented using Stack. - 可选:用 Stack<String> 存描述,或自定义 ActionRecord(含 action type、task title)。
Options: Stack<String> or a small ActionRecord class (action type, task title). - 使用 Week5 栈操作:push() / peek() / pop()。
Use Week 5 stack ops: push(), peek(), pop().
预期结果( Expected Outcome )
- 能展示 LIFO、peek vs pop 的差别,以及 stack 与 queue 处理差异。
Demonstrate LIFO, the difference between viewing/removing the top, and how stack differs from queue. - 系统应包含:List 存储、Queue FIFO 处理、Stack 最近历史。
System should include: List storage, FIFO Queue processing, Stack recent history.
Task 4 :优先级调度( Priority-Based Scheduling )
- 场景:任务紧急程度不同,FIFO 不够,需要优先级(priority)。
Scenario: not all tasks are equally urgent; FIFO is insufficient when urgent tasks should be first. - 目标:在同一系统里保留 List、FIFO Queue、Stack,再新增 PriorityQueue 作为第二种处理策略。
Goal: keep list, FIFO queue, and stack, then add a PriorityQueue as a second processing strategy.
必做功能( Required Features )
- 每个任务要有优先级(priority):如 HIGH/MEDIUM/LOW。
Assign a priority to each task, e.g., HIGH, MEDIUM, LOW. - 把任务加入优先队列:用户选择任务加入 priority queue,并且要与 FIFO queue 并存(alongside)。
Add tasks to priority queue alongside the normal FIFO queue. - 查看优先队列任务:显示当前在 priority queue 里的任务,并清晰显示优先级。
View priority tasks: display tasks in the priority queue and show priority levels clearly. - 查看最高优先任务:查看下一次会被处理的任务但不移除。
View highest-priority task: display next task without removing it. - 处理最高优先任务:移除并显示最高优先级任务。
Process highest-priority task: remove and display the highest-priority task. - 对比 FIFO:能解释普通队列(normal queue)和优先队列(priority queue)的区别。
Compare FIFO: explain the difference between a normal queue and a priority queue. - 空优先队列处理:空时显示提示信息。
Handle empty priority queue: display an appropriate message if empty. - 系统概览:能查看主列表、FIFO 队列、优先队列、最近 10 条历史记录。
Provide overview: view main list, FIFO queue, priority queue, and most recent 10 activities.
实现约束( Implementation Constraints )
- 使用 PriorityQueue<Task> 做优先级处理。
Use PriorityQueue<Task> for priority-based processing. - 任务排序规则用 Week5 方法之一:Comparable<Task> 或 Comparator<Task>。
Define ordering using Comparable<Task> or Comparator<Task>. - 若优先级相同:不要求按插入顺序取出(removal order need not match insertion order);若想加第二规则,可用标题字母序(alphabetical title order)。
If same priority: removal order need not match insertion; optional secondary rule like alphabetical title order. - 继续使用同一个 Task 类,只是新增 priority 字段(priority field)。
Continue using the same Task class, extended with a priority field. - 保持三种结构同程序共存:List<Task> 存储、Queue<Task> FIFO、Stack 历史。
Keep structures in same program: List<Task> storage, Queue<Task> FIFO, Stack history.
预期结果( Expected Outcome )
- 能展示 PriorityQueue 的自动排序(automatic ordering)、Comparable/Comparator 的使用、FIFO 与优先级处理的差别。
Demonstrate automatic ordering with PriorityQueue, use of Comparable/Comparator, and FIFO vs priority processing. - 最终系统应更完整:List 存、两种方式处理(FIFO 或 priority)、Stack 回看历史,且所有新功能都基于 Task1 的同一程序扩展。
Final system: tasks stored in a list, processed by FIFO or priority, history via stack, and all features build on the same core program.
总结( Summary )
- 完成本实验后应理解:不同集合类型解决不同问题。
- List:有序存储 + 索引访问(ordered storage & index-based access)。
List: ordered storage and index-based access. - Queue:FIFO 处理(FIFO processing)。
- Stack:LIFO 历史(LIFO history)。
- PriorityQueue:按优先级处理(priority-based processing)。
- 核心目标:做一个逐步扩展(progressively extended)的应用,并为每个新需求选择正确的数据结构(correct data structure)与方法(methods)。
Main goal: build one progressively extended app and apply the correct data structure and methods for each requirement. - import java.util.ArrayList;
- import java.util.Comparator;
- import java.util.LinkedList;
- import java.util.List;
- import java.util.PriorityQueue;
- import java.util.Queue;
- import java.util.Scanner;
- import java.util.Stack;
- /**
- * Week 5 Complete Todo System
- * - List: store tasks (ordered, index-based)
- * - Queue: FIFO processing
- * - Stack: recent action history (LIFO)
- * - PriorityQueue: priority-based processing
- */
- public class TodoSystemComplete {
- private final Scanner input = new Scanner(System.in);
- // Task 1: Core list storage (List)
- private final List<Task> tasks = new ArrayList<>();
- // Task 2: FIFO processing (Queue)
- private final Queue<Task> processingQueue = new LinkedList<>();
- // Task 3: Recent actions (Stack, LIFO)
- private final Stack<ActionRecord> recentActions = new Stack<>();
- // Task 4: Priority tasks (PriorityQueue)
- // NOTE: PriorityQueue iteration order is NOT sorted order; only peek/poll are ordered by priority.
- private final PriorityQueue<Task> priorityQueue = createPriorityQueue();
- public static void main(String\[\] args) {
- TodoSystemComplete system = new TodoSystemComplete();
- system.run();
- }
- public void run() {
- boolean running = true;
- while (running) {
- printMainMenu();
- int choice = readInt("Choose an option: ");
- switch (choice) {
- case 1 -> addTask();
- case 2 -> viewAllTasks();
- case 3 -> editTask();
- case 4 -> removeTask();
- case 5 -> markTaskCompleted();
- case 6 -> viewTasksByStatus();
- case 7 -> moveTaskToQueue();
- case 8 -> viewProcessingQueue();
- case 9 -> viewNextQueueTask();
- case 10 -> processNextQueueTask();
- case 11 -> viewMostRecentAction();
- case 12 -> removeMostRecentActionFromHistory();
- case 13 -> viewRecentActionHistory();
- case 14 -> assignPriorityToTask();
- case 15 -> addTaskToPriorityQueue();
- case 16 -> viewPriorityQueue();
- case 17 -> viewHighestPriorityTask();
- case 18 -> processHighestPriorityTask();
- case 19 -> viewSystemOverview();
- case 0 -> {
- running = false;
- System.out.println("Exiting Todo System.");
- }
- default -> System.out.println("Invalid option. Please try again.");
- }
- }
- }
- private void printMainMenu() {
- System.out.println();
- System.out.println("=== Todo System ===");
- System.out.println("Task 1 - Core List Features");
- System.out.println("1. Add task");
- System.out.println("2. View all tasks");
- System.out.println("3. Edit task");
- System.out.println("4. Remove task");
- System.out.println("5. Mark task as completed");
- System.out.println("6. View tasks by status");
- System.out.println();
- System.out.println("Task 2 - Queue Features");
- System.out.println("7. Move task to processing queue");
- System.out.println("8. View processing queue");
- System.out.println("9. View next queue task");
- System.out.println("10. Process next queue task");
- System.out.println();
- System.out.println("Task 3 - Stack Features");
- System.out.println("11. View most recent action");
- System.out.println("12. Remove most recent action from history");
- System.out.println("13. View recent action history");
- System.out.println();
- System.out.println("Task 4 - Priority Queue Features");
- System.out.println("14. Assign priority to a task");
- System.out.println("15. Add task to priority queue");
- System.out.println("16. View priority queue");
- System.out.println("17. View highest-priority task");
- System.out.println("18. Process highest-priority task");
- System.out.println("19. View system overview");
- System.out.println("0. Exit");
- }
- // -------------------------
- // Task 1: List features
- // -------------------------
- private void addTask() {
- String title = readNonEmptyLine("Enter task title: ");
- Task task = new Task(title);
- tasks.add(task);
- recordAction(ActionType.ADD, task, "Added a new task.");
- System.out.println("Task added.");
- displayTask(task, tasks.size() - 1);
- }
- private void viewAllTasks() {
- System.out.println("=== All Tasks ===");
- if (tasks.isEmpty()) {
- System.out.println("No tasks available.");
- return;
- }
- int index = 0;
- for (Task task : tasks) {
- displayTask(task, index);
- index++;
- }
- }
- private void editTask() {
- int index = chooseTaskIndex();
- if (index == -1) return;
- Task task = tasks.get(index);
- String oldTitle = task.getTitle();
- String newTitle = readNonEmptyLine("Enter the new title: ");
- task.setTitle(newTitle);
- // IMPORTANT: if a task is already in priorityQueue, changing title/priority can break heap order.
- refreshPriorityQueueEntry(task);
- recordAction(ActionType.EDIT, task,
- "Changed title from '" + oldTitle + "' to '" + newTitle + "'.");
- System.out.println("Task updated.");
- displayTask(task, index);
- }
- private void removeTask() {
- int index = chooseTaskIndex();
- if (index == -1) return;
- Task task = tasks.remove(index);
- // Remove from other structures too (keep system consistent)
- boolean removedFromQueue = processingQueue.remove(task);
- boolean removedFromPriorityQueue = priorityQueue.remove(task);
- String details = "Removed task from main list.";
- if (removedFromQueue) details += " Also removed from processing queue.";
- if (removedFromPriorityQueue) details += " Also removed from priority queue.";
- recordAction(ActionType.REMOVE, task, details);
- System.out.println("Task removed.");
- }
- private void markTaskCompleted() {
- int index = chooseTaskIndex();
- if (index == -1) return;
- Task task = tasks.get(index);
- task.setCompleted(true);
- recordAction(ActionType.COMPLETE, task, "Marked task as completed.");
- System.out.println("Task marked as completed.");
- displayTask(task, index);
- }
- private void viewTasksByStatus() {
- System.out.println("1. View completed tasks");
- System.out.println("2. View incomplete tasks");
- int choice = readInt("Choose an option: ");
- boolean targetCompleted;
- if (choice == 1) targetCompleted = true;
- else if (choice == 2) targetCompleted = false;
- else {
- System.out.println("Invalid option.");
- return;
- }
- boolean found = false;
- System.out.println(targetCompleted ? "=== Completed Tasks ===" : "=== Incomplete Tasks ===");
- for (int i = 0; i < tasks.size(); i++) {
- Task task = tasks.get(i);
- if (task.isCompleted() == targetCompleted) {
- displayTask(task, i);
- found = true;
- }
- }
- if (!found) System.out.println("No matching tasks found.");
- }
- // -------------------------
- // Task 2: Queue features
- // -------------------------
- private void moveTaskToQueue() {
- int index = chooseTaskIndex();
- if (index == -1) return;
- Task task = tasks.get(index);
- if (processingQueue.contains(task)) {
- System.out.println("That task is already in the processing queue.");
- return;
- }
- // Use offer/peek/poll (safe Queue methods)
- processingQueue.offer(task);
- recordAction(ActionType.MOVE_TO_QUEUE, task, "Moved task into FIFO processing queue.");
- System.out.println("Task added to processing queue.");
- }
- private void viewProcessingQueue() {
- System.out.println("=== Processing Queue ===");
- if (processingQueue.isEmpty()) {
- System.out.println("The processing queue is empty.");
- return;
- }
- int position = 1;
- for (Task task : processingQueue) {
- System.out.print(position + ". ");
- displayTask(task);
- position++;
- }
- }
- private void viewNextQueueTask() {
- Task task = processingQueue.peek(); // does not remove
- if (task == null) {
- System.out.println("The processing queue is empty.");
- return;
- }
- System.out.println("Next queue task:");
- displayTask(task);
- }
- private void processNextQueueTask() {
- Task task = processingQueue.poll(); // removes head, returns null if empty
- if (task == null) {
- System.out.println("The processing queue is empty.");
- return;
- }
- recordAction(ActionType.PROCESS_QUEUE, task, "Processed the next task from FIFO queue.");
- System.out.println("Processed task:");
- displayTask(task);
- }
- // -------------------------
- // Task 3: Stack features
- // -------------------------
- private void viewMostRecentAction() {
- if (recentActions.isEmpty()) {
- System.out.println("No recent actions recorded.");
- return;
- }
- System.out.println("Most recent action:");
- System.out.println(recentActions.peek()); // peek = view top without removing
- }
- private void removeMostRecentActionFromHistory() {
- if (recentActions.isEmpty()) {
- System.out.println("No recent actions to remove.");
- return;
- }
- ActionRecord removed = recentActions.pop(); // pop = remove top
- System.out.println("Removed action from history:");
- System.out.println(removed);
- }
- private void viewRecentActionHistory() {
- System.out.println("=== Recent Action History ===");
- if (recentActions.isEmpty()) {
- System.out.println("No recent actions recorded.");
- return;
- }
- // Print most-recent to oldest (top to bottom)
- for (int i = recentActions.size() - 1; i >= 0; i--) {
- System.out.println(recentActions.get(i));
- }
- }
- private void recordAction(ActionType actionType, Task task, String details) {
- recentActions.push(new ActionRecord(actionType, task, details));
- }
- // -------------------------
- // Task 4: PriorityQueue features
- // -------------------------
- private void assignPriorityToTask() {
- int index = chooseTaskIndex();
- if (index == -1) return;
- Task task = tasks.get(index);
- PriorityLevel oldPriority = task.getPriority();
- PriorityLevel newPriority = choosePriorityLevel();
- task.setPriority(newPriority);
- // IMPORTANT: If task already inside priorityQueue, reinsert to restore heap ordering.
- refreshPriorityQueueEntry(task);
- recordAction(ActionType.ASSIGN_PRIORITY, task,
- "Changed priority from " + oldPriority + " to " + newPriority + ".");
- System.out.println("Priority updated.");
- displayTask(task, index);
- }
- private void addTaskToPriorityQueue() {
- int index = chooseTaskIndex();
- if (index == -1) return;
- Task task = tasks.get(index);
- if (priorityQueue.contains(task)) {
- System.out.println("That task is already in the priority queue.");
- return;
- }
- priorityQueue.offer(task);
- recordAction(ActionType.ADD_TO_PRIORITY_QUEUE, task, "Added task to priority queue.");
- System.out.println("Task added to priority queue.");
- }
- private void viewPriorityQueue() {
- System.out.println("=== Priority Queue ===");
- if (priorityQueue.isEmpty()) {
- System.out.println("The priority queue is empty.");
- return;
- }
- // NOTE: PriorityQueue iterator is NOT sorted. Copy to list then sort for display.
- List<Task> orderedView = new ArrayList<>(priorityQueue);
- orderedView.sort(
- Comparator.comparingInt(Task::getPriorityValue).reversed()
- .thenComparing(Task::getTitle, String.CASE_INSENSITIVE_ORDER));
- int position = 1;
- for (Task task : orderedView) {
- System.out.print(position + ". ");
- displayTask(task);
- position++;
- }
- }
- private void viewHighestPriorityTask() {
- Task task = priorityQueue.peek();
- if (task == null) {
- System.out.println("The priority queue is empty.");
- return;
- }
- System.out.println("Highest-priority task:");
- displayTask(task);
- }
- private void processHighestPriorityTask() {
- Task task = priorityQueue.poll();
- if (task == null) {
- System.out.println("The priority queue is empty.");
- return;
- }
- recordAction(ActionType.PROCESS_PRIORITY_QUEUE, task, "Processed the highest-priority task.");
- System.out.println("Processed highest-priority task:");
- displayTask(task);
- }
- private void viewSystemOverview() {
- System.out.println("=== System Overview ===");
- System.out.println("Task count: " + tasks.size());
- System.out.println("Queue size: " + processingQueue.size());
- System.out.println("Recent action count: " + recentActions.size());
- System.out.println("Priority queue size: " + priorityQueue.size());
- int completedCount = 0;
- for (Task task : tasks) {
- if (task.isCompleted()) completedCount++;
- }
- System.out.println("Completed tasks: " + completedCount);
- System.out.println("Incomplete tasks: " + (tasks.size() - completedCount));
- if (!tasks.isEmpty()) {
- System.out.println();
- System.out.println("Main task list:");
- viewAllTasks();
- }
- if (!processingQueue.isEmpty()) {
- System.out.println();
- System.out.println("FIFO queue:");
- viewProcessingQueue();
- }
- if (!priorityQueue.isEmpty()) {
- System.out.println();
- System.out.println("Priority queue:");
- viewPriorityQueue();
- }
- if (!recentActions.isEmpty()) {
- System.out.println();
- System.out.println("Most recent activities (up to 10):");
- int shown = 0;
- for (int i = recentActions.size() - 1; i >= 0 && shown < 10; i--) {
- System.out.println(recentActions.get(i));
- shown++;
- }
- }
- }
- // -------------------------
- // Helpers
- // -------------------------
- private PriorityQueue<Task> createPriorityQueue() {
- // Higher priority value first; tie-break by title (case-insensitive)
- return new PriorityQueue<>(
- Comparator.comparingInt(Task::getPriorityValue).reversed()
- .thenComparing(Task::getTitle, String.CASE_INSENSITIVE_ORDER));
- }
- private void refreshPriorityQueueEntry(Task task) {
- // If task exists in heap and its comparison fields changed, reinsert to maintain heap property.
- if (priorityQueue.remove(task)) {
- priorityQueue.offer(task);
- }
- }
- private int readInt(String prompt) {
- while (true) {
- System.out.print(prompt);
- String line = input.nextLine().trim();
- try {
- return Integer.parseInt(line);
- } catch (NumberFormatException ex) {
- System.out.println("Please enter a valid integer.");
- }
- }
- }
- private String readNonEmptyLine(String prompt) {
- while (true) {
- System.out.print(prompt);
- String line = input.nextLine().trim();
- if (!line.isEmpty()) return line;
- System.out.println("Input cannot be empty.");
- }
- }
- private void displayTask(Task task, int index) {
- System.out.printf("%d %s | completed=%s | priority=%s%n",
- index, task.getTitle(), task.isCompleted(), task.getPriority());
- }
- private void displayTask(Task task) {
- System.out.printf("%s | completed=%s | priority=%s%n",
- task.getTitle(), task.isCompleted(), task.getPriority());
- }
- private int chooseTaskIndex() {
- if (tasks.isEmpty()) {
- System.out.println("No tasks available.");
- return -1;
- }
- viewAllTasks();
- int index = readInt("Enter task index: ");
- if (index < 0 || index >= tasks.size()) {
- System.out.println("Invalid task index.");
- return -1;
- }
- return index;
- }
- private PriorityLevel choosePriorityLevel() {
- System.out.println("1. HIGH");
- System.out.println("2. MEDIUM");
- System.out.println("3. LOW");
- int choice = readInt("Choose a priority: ");
- return switch (choice) {
- case 1 -> PriorityLevel.HIGH;
- case 2 -> PriorityLevel.MEDIUM;
- case 3 -> PriorityLevel.LOW;
- default -> {
- System.out.println("Invalid choice. Defaulting to LOW.");
- yield PriorityLevel.LOW;
- }
- };
- }
- // -------------------------
- // Nested model types
- // -------------------------
- private enum ActionType {
- ADD, EDIT, REMOVE, COMPLETE,
- MOVE_TO_QUEUE, PROCESS_QUEUE,
- ASSIGN_PRIORITY, ADD_TO_PRIORITY_QUEUE, PROCESS_PRIORITY_QUEUE
- }
- private enum PriorityLevel {
- HIGH(3), MEDIUM(2), LOW(1);
- private final int value;
- PriorityLevel(int value) { this.value = value; }
- public int getValue() { return value; }
- }
- private static class Task {
- private String title;
- private boolean completed;
- private PriorityLevel priority;
- public Task(String title) {
- this.title = title;
- this.completed = false;
- this.priority = PriorityLevel.LOW;
- }
- public String getTitle() { return title; }
- public void setTitle(String title) { this.title = title; }
- public boolean isCompleted() { return completed; }
- public void setCompleted(boolean completed) { this.completed = completed; }
- public PriorityLevel getPriority() { return priority; }
- public void setPriority(PriorityLevel priority) { this.priority = priority; }
- public int getPriorityValue() { return priority.getValue(); }
- @Override
- public String toString() {
- return title + " | completed=" + completed + " | priority=" + priority;
- }
- }
- private static class ActionRecord {
- private final ActionType actionType;
- private final Task task;
- private final String details;
- public ActionRecord(ActionType actionType, Task task, String details) {
- this.actionType = actionType;
- this.task = task;
- this.details = details;
- }
- @Override
- public String toString() {
- String taskTitle = task == null ? "n/a" : task.getTitle();
- return actionType + " | task=" + taskTitle + " | details=" + details;
- }
- }
- }
Week 6
Mastering Sets and Maps
1 主题与目标( Title & Theme )
-
- 主题:掌握集合 Set(Sets)与映射 Map(Maps),用于"唯一元素(unique elements)"与"键值对(key-value pairs)"的高性能数据结构(high-performance data structures)。
2 为什么 List 不适合 " 查存在 " ( List is the wrong tool )
-
- 场景:用 Java 列表(List)去查"禁飞名单(No-Fly list)"是否存在某人,需要一个个顺序查(sequential search / linear search),在几百万条数据上会太慢(too slow)。
- 结论:List 允许重复(duplicates)且查找依赖顺序扫描(sequential searching),不适合做"纯存在性检查(pure existence check)"。
3 Set 数据结构( The Set Data Structure )
-
- 核心规则:零重复( zero duplicates )------Set 严格保证不能加入重复元素(no duplicate elements)。
- 目的:高速验证( high-speed verification )------专门为"高效存储与处理不重复元素(non-duplicate elements)"设计。
- 对应场景:禁飞名单这种"只关心在不在"(existence only),用 Set 比 List 更合适(perfect fit)。
4 Map 数据结构( The Map Data Structure )
-
- 需求升级:如果不仅要知道"在不在",还要存储详细信息(detailed intel / profile data)。
- Map 的字典式方法(dictionary approach):用键(key)快速查到值(value),实现快速检索(quick lookup)。
- 强调:Map 是配对相关数据(pair related data)且无需顺序查找(without sequential searching)的高效结构(maximum efficiency)。
5 选型对比表( List vs Set vs Map )
-
- List(List)
- 优势:按索引有序访问(ordered access by index)
- 是否允许重复:是(allows duplicates: yes)
- 最佳用途:存储有顺序的序列(storing sequences where order matters)
- Set(Set)
- 优势:无序但元素唯一(unordered, unique elements)
- 是否允许重复:否(allows duplicates: no)
- 最佳用途:快速检查是否存在(checking pure existence)
- Map(Map)
- 优势:键值字典查找(key-value dictionary lookup)
- 是否允许重复:键唯一(unique keys only)
- 最佳用途:用 ID/姓名查"详细档案"(retrieve profiles by key)
- List(List)
6 学习目标( Target Objectives )
-
- 掌握 Set(Master the Set):理解何时使用 HashSet、LinkedHashSet、TreeSet。
- 掌握 Map(Master the Map):区分 Collection 与 Map,掌握 HashMap、LinkedHashMap、TreeMap。
- 性能分析(Performance analytics):比较 Set vs List 的真实执行速度(execution speed)。
- 实作应用(Practical applications):
- 用 Set 统计 Java 关键字(count Java keywords using Sets)
- 用 Map 统计文本词频(count word occurrences using Maps)
- 高级工具(Advanced utility):使用 Collections 获取单例/不可变集合(singleton / unmodifiable)。
- 单例(singleton):只有 1 个元素(one element)
- Collections.singleton(x) 会返回一个 Set,里面只有 x
7 Set 实现: HashSet / LinkedHashSet / TreeSet ( Set Implementations )
-
- 本节聚焦:如何选择不同 Set 实现来获得最优性能(optimal performance)。
(1) Set 层次结构( Set Hierarchy )

-
- 结构:Set → AbstractSet → 三大实现:
- HashSet:主打原始速度(raw speed),顺序不可预测(unpredictable order)
- LinkedHashSet:主打"可追踪"(tactical tracking),保持插入顺序(insertion order)
- TreeSet:主打"精确排序"(perfect precision),保持排序顺序(sorted order)
- 共同核心:Set 的绝对规则是"不允许重复"(no duplicates allowed)。
- 结构:Set → AbstractSet → 三大实现:
(2) AbstractSet 的作用( AbstractSet tech specs )
-
- AbstractSet 继承 AbstractCollection(extends AbstractCollection),提供基础功能(base functionalities)。
- 它提供了 equals() 与 hashCode() 的具体实现(concrete implementations)。
- 技术点(tech fact):一个 set 的 hashCode 是其所有元素 hashCode 的"总和"(sum of hash codes)
- 同时把 size() 与 iterator() 留给具体实现去做(left abstract for implementations)
(3) HashSet :去重靠 equals + hashCode ( dedup via equals/hashCode )
-
- HashSet 用 equals() + hashCode() 来判断重复(duplicate detection)。
- 示例:往 HashSet<String> 连续 add 两次 "New York",最终只保存一份(only
one is stored)。

-
- 重要结论:HashSet 去重规则取决于对象的 equals/hashCode 是否正确实现(correct equals/hashCode)
(4) HashSet 容量、负载因子与扩容( capacity, load factor, threshold )
-
- 默认初始容量(capacity)= 16(default 16)。
- 默认负载因子(load factor)= 0.75(default 0.75)。
- 阈值(threshold)= capacity × loadFactor = 16 × 0.75 = 12。
- 当元素个数达到阈值(size reaches threshold),容量翻倍(capacity doubles),例如扩到 32。
- 负载因子越低(lower load factor)→ 查找更快(faster searches),但更耗空间(more space used)。
- 桶更空 → 冲突更少(fewer collisions) → 每个桶里链/树更短 → contains / get 平均比较次数更少 → 查找更快
(5) String 的 hashCode 公式( String hashCode formula )
-
- 为减少碰撞(avoid collisions),需要更均匀散列(scatter evenly)。
- 文档给出 String hashCode 经典公式:
s0 * 31^(n-1) + s1 * 31^(n-2) + ... + s(n-1)(多项式 rolling hash) - 注释:不同对象也可能 hash 相同(hash collision possible),但设计目标是最小化(minimize collisions)以提升性能(optimal performance)。
(6) 集合战术: addAll / removeAll / retainAll ( Collection tactics )
-
- set1.addAll(set2):合并(merge / union-like)
- set1.removeAll(set2):清除重叠(remove overlapping / difference-like)
- set1.retainAll(set2):只保留共有(keep common / intersection-like)
(7) LinkedHashSet :保持插入顺序( insertion order )
-
- LinkedHashSet 使用链表结构记住插入顺序(linked-list to remember insertion order)。
- 关键规格(key specs):
- 继承 HashSet(extends HashSet)
- 比 HashSet 稍慢(slightly slower)
- 输出顺序 = 添加顺序(exact order they were added)
(8) TreeSet :保持排序顺序( sorted order )
-
- TreeSet 让一切"完美排序"(perfectly sorted)。
- 关键规格(key specs):
- 实现 SortedSet 和 NavigableSet(implements SortedSet, NavigableSet)
- 元素必须可相互比较(mutually comparable)
- 可以把"混乱的 set"直接转成 TreeSet:new TreeSet<>(hashSet)(convert from chaotic set)
(9) SortedSet 常用方法( SortedSet methods )

-
- first():返回最小元素(returns first / smallest),图示例返回 "Beijing"
- last():返回最大元素(returns last / largest),图示例返回 "San Francisco"
- headSet("New York"):获取目标前面的所有元素(everything before target),例:Beijing, London
- tailSet("New York"):获取目标及之后的元素(target and after),例:New York, Paris, San Francisco
(10) NavigableSet 精准边界查找( NavigableSet methods )

-
- lower("P"):严格小于 P 的最大元素(largest < P),示例指向 "New York"
- floor("P"):小于等于 P 的最大元素(largest ≤ P)
- ceiling("P"):大于等于 P 的最小元素(smallest ≥ P)
- higher("P"):严格大于 P 的最小元素(smallest > P),示例指向 "Paris"
- pollFirst() / pollLast():移除并返回两端元素(remove & retrieve ends)
(11) TreeSet 自定义排序:注入 Comparator ( inject Comparator )
-
- 默认 TreeSet 用 Comparable(Comparable)排序(natural ordering)。
- 如果要自定义规则(custom sorting rules),例如按几何对象面积(area)排序,就传入 Comparator(Comparator):
- new TreeSet<>(new GeometricObjectComparator())
- 关键提醒(Result / warning):如果比较器认为两个对象"相等"(comparator sees as equal),Set 会当成重复并拒绝(reject duplicate)。
例如两个圆半径相同(same radius)→ comparator 返回 0 → TreeSet 只保留一个。
( 12) 三种 Set 总结矩阵( Tactical summary matrix )
-
- HashSet(HashSet)
- 性能(performance):插入/删除最快(fastest insertion/removal)
- 顺序(order):不维护顺序(no order maintained)
- 适用(best for):通用(general use)
- LinkedHashSet(LinkedHashSet)
- 性能:稍慢(slightly slower)
- 顺序:精确插入顺序(exact insertion order)
- 适用:按时间/加入顺序跟踪(chronological tracking)
- TreeSet(TreeSet)
- 性能:插入/删除较慢(slower insertion/removal)
- 顺序:完美排序(perfect sorted order)
- 适用:有序取数与范围查询(ordered retrieval & range searches)
- HashSet(HashSet)
8. 性能模拟( Set vs List performance simulation )
-
- 主题:Set vs List 的性能对比模拟(performance simulation)。
- 场景:处理大规模数据集(massive dataset),需要选择最快检索/清理方式(retrieve/clear fastest)。
- 强调:要做"只留有用结果"的模拟(run simulation and see which leaves rest in dust),以验证选择的数据结构是否正确(choose correct data structure).
9. 模拟参数:生成数据集( Simulation Parameters: Generating the Dataset )
-
- 用 final int N = 50000; 生成 50,000 个连续整数(sequential integers)。
- 用 List<Integer> list = new ArrayList<>();,循环 for (i=0;i<N;i++) list.add(i); 填充。
- 用 Collections.shuffle(list) 打乱顺序(shuffle / randomize)。
- 产生"打乱的主列表"(Shuffled Master List, N=50,000),并用同一份打乱数据 初始化五种结构(fair fight / same dataset):
- HashSet
- LinkedHashSet
- TreeSet
- ArrayList
- LinkedList
核心:统一输入(same scrambled list)保证测试公平(fair comparison)。
10. 协议 Alpha :成员测试( Protocol Alpha: Membership Testing )
-
- 目的:测"查找是否存在"(membership test / contains)。
- 做法:用 contains() 查找随机目标(random intel),目标范围到 2N(up to 2N = 100,000)。
- 计时方式:
- long startTime = System.currentTimeMillis();
- 循环 for (i=0;i<N;i++) c.contains((int)(Math.random()*2*N));
- 返回 System.currentTimeMillis() - startTime(elapsed time)。

11. 协议 Beta :数据移除( Protocol Beta: Data Removal )
-
- 目的:测"逐个删除元素"(removal test / remove)。
- 做法:计时并循环删除:
- long startTime = System.currentTimeMillis();
- for (i=0;i<N;i++) c.remove(i);
- 返回耗时(elapsed time)。

12. 模拟结果:性能对决( Simulation Results: Performance Showdown )
表格给出两类测试:成员测试(Membership Test / contains)与移除测试(Removal Test / remove)。
-
- HashSet:contains 约 20 ms;remove 约 27 ms
- LinkedHashSet:contains 约 27 ms;remove 约 26 ms
- TreeSet:contains 约 47 ms;remove 约 34 ms
- ArrayList:contains 约 39,802 ms;remove 约 16,196 ms
- LinkedList:contains 约 52,197 ms;remove 约 14,870 ms
Set 组快到夸张(finished before snapping fingers),List 组接近"整整一分钟"(almost a whole minute)。
结论(takeaway):在"查存在 + 删除"这类任务上,Set 明显更快(Sets dominate)。
13. 速度机制:为什么 Set 快( Mechanics of Speed )
13.1 Set 优势( Set Advantage )
-
- Set 不用索引(don't use indexes)。
- HashSet 通过哈希(hash / hash code)直接定位数据"应该在哪里"(computes exactly where the data lives)。
- contains() 像"瞬移到房间"(teleporting to the exact room)。
核心:哈希查找接近 O(1)(near O(1) lookup)。
13.2 List 困境( List Struggle )
-
- List 强依赖索引/顺序(heavily indexed / sequential)。
- 查找或删除往往要顺序检查(sequential check)或移动大量元素(shift massive amounts)。
- 类比:走廊逐个开门(checking every single door)。
关键词:线性时间(O(n) linear)、移动元素(shifting)。
13.3 " 黄金法则 " ( Golden Rule )
-
- Set 在测试成员关系(membership)和删除无重复元素(removing non-duplicate elements)时,比 List"指数级更高效"(exponentially more efficient)。
(严格算法角度通常说"数量级差异/大幅更高效",但图片用词是 exponentially)
- Set 在测试成员关系(membership)和删除无重复元素(removing non-duplicate elements)时,比 List"指数级更高效"(exponentially more efficient)。
14. 选型:选择你的 Set ( Choosing Your Set )
根据任务需求(mission requirement)选择工具(weapon of choice):
-
- 需求:存无序且不重复,追求最大效率(unordered, non-duplicate, maximum efficiency)
→ HashSet - 需求:不重复,但必须保留插入顺序(preserve exact insertion order)
→ LinkedHashSet - 需求:不重复,并且按递增/字母序排序(sorted increasing/alphabetical order)
→ TreeSet
- 需求:存无序且不重复,追求最大效率(unordered, non-duplicate, maximum efficiency)
15. 选型:选择你的 List ( Choosing Your List )
强调:不是不用 List,而是在需要时用(we don't discard Lists)。
-
- 需求:固定数量元素且允许重复(fixed number, duplicates allowed)
→ Array / ArrayList - 需求:经常在末尾追加/删除(frequent append/delete at end)
→ ArrayList - 需求:经常在开头插入/删除(frequent insert/delete at beginning)
→ LinkedList
- 需求:固定数量元素且允许重复(fixed number, duplicates allowed)
16. 任务完成总结( Mission Accomplished )
-
- List 擅长精确索引访问(precise indexed access)。
- 但当目标是"唯一性 + 快速查询"(uniqueness + rapid lookups),应信任 Set
战术速查表(Tactician's Cheat Sheet):
-
- HashSet:纯速度 + 唯一(pure speed & uniqueness)
- LinkedHashSet:唯一 + 插入顺序(uniqueness + insertion order)
- TreeSet:唯一 + 排序顺序(uniqueness + sorted order)
- ArrayList:索引 + 尾部修改(indexing & end-modifications)
- LinkedList:头部修改(head-modifications)
17. 案例:战术关键词提取( Tactical Keyword Extraction )
17.1 任务参数( Mission Parameters )
-
- 目标文件(Target):标准 Java 源文件(Java source file)。
- 目标(Objective):扫描整个文件并统计保留字数量(scan & count reserved Java keywords)。
- 约束(Constraint):高效率(high-efficiency execution);要把 53 个关键词与文件中可能成千上万词进行匹配(cross-reference 53 targets vs thousands of words)。
- 强调:暴力法(brute force)不行,需要战术数据结构(tactical data structure)。
17.2 为什么选 HashSet ( Why HashSet? )
三点理由:
-
- O(1) 查找速度( O(1) lookup speed ):用哈希码分散元素,实现近乎瞬时成员测试(near-instant membership testing)。
- 不需要重复( no duplicates needed ):Java keywords 唯一,Set 天然拒绝重复(reject duplicates)。
- 无序没关系( unordered architecture ):只关心是不是关键字,不关心存储顺序(don't care order)。
-
- 关键词提取四阶段(Phases)
Phase 1 :定义目标( Defining the Targets )
|---|------------------------------------------------------------|
| |
|
-
- 先把所有 Java 关键字 + 字面量(keywords + literals)硬编码进字符串数组(String\[\])。
- 图片特别提到包含 "true", "false", "null":虽然是字面量(literals),但在语法扫描时像关键字一样处理(act like keywords)。
Phase 2 :武装数据( Weaponizing the Data )

-
- 一行核心构造:
- Set<String> keywordSet = new HashSet<>(Arrays.asList(keywordString));
- 步骤含义:
- Arrays.asList() 把数组转成 List(Array → List)。
- HashSet 构造器接收 Collection,并把 53 个词哈希进内存以便 O(1) 查询(hash into memory for O(1) retrieval)。
- 图片吐槽:不必手写循环(manual loop),API 更优雅(elegance)。
- 一行核心构造:
Phase 3 :建立输入链路( Establishing the Uplink )

-
- 用 Scanner input = new Scanner(System.in); 读用户输入文件名(filename)。
- File file = new File(filename); 创建文件对象(file object)。
- if (file.exists()) { countKeywords(file); }
- 提示:不要在没确认文件存在(exists check)前就扫描(never deploy without confirming target exists)。
Phase 4 :执行扫描( Executing the Sweep )

-
- 流程图:Scanner.next() 提取单词(extract word) → keywordSet.contains(word) O(1) 查询 → 若 true 则 count++。
- 代码框架:
- Scanner input = new Scanner(file); int count = 0;
- while (input.hasNext()) { String word = input.next(); if (keywordSet.contains(word)) count++; }
- return count;
- 图片总结:Scanner 把每个词分离(isolated),contains 命中就自增(increment),干净高效(clean & efficient)。
19. 行动后报告:替代方案对比( After Action Report: Tactical Alternatives )
表格比较:
-
- LinkedHashSet:能用(Yes),查找快(Fast),但"没必要"(Unnecessary):我们不需要插入顺序(don't need insertion order)。
- ArrayList :能用(Yes),但查找慢(Slow O(n)),因为对每个词都要扫描整个列表(scan whole list for every word)。
结论:标准 HashSet 最优(optimal solution)。
20. 输出验证( Output Verification )
-
- 示例输出 1:输入 Welcome.java,输出关键字数量为 5。
- 示例输出 2:输入 GhostFile.java,提示文件不存在(does not exist)。
- 关键结论(Key takeaway):数据结构要对齐主要操作(align data structure with primary operation);对大规模频率/存在查询(frequency/existence queries),HashSet 是最佳(undisputed champion)。
21 战术数据管理: Java 的映射( Tactical Data Management: Mapping Intel in Java )
-
- 强调:原始数据(raw data)本身没用,关键是把情报映射到目标(map intel to targets),要高效(efficiently)且无重复(without duplication)。
- 本节主题:用于快速检索(fast retrieval)的终极数据结构:Java Maps(Java Maps)。
22. Map 的核心概念( Core Concept of Maps )
-
- Map 是存储键值对(key/value pairs)的容器(container)。
- key 类似 List 的索引(indices),但 Map 的 key 可以是任何对象(any object),不只是整数(not just integers)。
- 关键规则(vital rule):Map 不能有重复键(no duplicate keys)。
- 每个键映射到且只映射到一个值(each key maps to exactly one value)。
- 一对 key+value 称为条目(entry)。
- 图示:Search key(如社保号)→ Corresponding value(姓名)。
23. Map 接口:更新与查询( Map Interface: Updating & Querying )
图中把 java.util.Map<K,V> 的常用方法分两类:
(1) 更新方法( Update Methods )
-
- clear():清除所有条目(removes all entries)。
- put(key: K, value: V):添加/替换条目(adds/replaces an entry)。
- putAll(m: Map):把 m 的所有条目加入(adds all entries from m)。
- remove(key: Object):删除指定 key 的条目(removes entry for key)。
(2) 查询方法( Query Methods )
-
- containsKey(key: Object):若 key 存在返回 true(returns true if key exists)。
- containsValue(value: Object):若 value 存在返回 true(returns true if value exists)。
- isEmpty():是否空 map(checks if map has no entries)。
- size():条目数量(returns number of entries)。
24. Map 接口:集合视图与迭代( Collection Views & Iteration )
-
- 重点:你不能直接遍历 Map(can't iterate over a Map directly)。
- 必须请求某种"集合视图"(collection view):
(1) keySet() ( keys view )
-
- keySet():返回所有 key 的集合(returns a Set<K> of all keys)。
(2) values() ( values view )
-
- values():返回所有 value 的集合(returns a Collection<V> of all values)。
(3) entrySet() ( entries view )
-
- entrySet():返回所有条目(returns a Set<Map.Entry<K,V>>)。
(4) Java 8 forEach ( cleaner iteration )
-
- 图片给出更简洁遍历:
map.forEach((k, v) -> System.out.println(k + ": " + v)); - 关键词:forEach、lambda
- 图片给出更简洁遍历:
25. 解剖节点: Map.Entry ( Dissecting the Node: Map.Entry )
-
- 每一对 key/value 是内部接口 java.util.Map.Entry<K,V> 的实例(instance)。
- Map.Entry 关键方法(key methods):
- getKey():返回 key(returns the key)。
- getValue():返回 value(returns the value)。
- setValue(value: V):替换 value(replaces the value)。
- 战术注记(tactical note):AbstractMap 是便利抽象类(convenience abstract class),实现大多数 Map 方法(implements Map methods),但 entrySet() 例外(except entrySet)。
三大实现概览( The Big Three: Implementation Overview )
Map 的三大常用实现(big three):
-
- HashMap
- LinkedHashMap
- TreeMap
26. HashMap :原始速度( HashMap: Raw Speed and Efficiency )
-
- 适用:不关心顺序(don't care about order)。
- 条目顺序随机/不可预测(order random/unpredictable)。
- 最有效率(most efficient)用于定位/插入/删除(locating, inserting, deleting)。
- 关键规格(key specs):
- 默认容量(default capacity)16
- 默认负载因子(load factor)0.75
- 可自定义构造(custom constructor):HashMap(initialCapacity, loadFactor)
27. LinkedHashMap :维持顺序( LinkedHashMap: Maintaining Order )
-
- 基于 HashMap 并加链表结构(extends HashMap + linked-list implementation)。
- 两种顺序模式(order modes):
(1) 插入顺序( Insertion order, default )
-
- 按添加顺序取出(retrieved in the exact order items were added)。
(2) 访问顺序( Access order )
-
- 按访问从"最久未访问 → 最近访问"(least recently accessed → most recently accessed)。
- 构造器(constructor)示例:
LinkedHashMap(initialCapacity, loadFactor, true)
其中 true 表示 access order(访问顺序)。
28. TreeMap :排序情报( TreeMap: Sorted Intelligence )
-
- TreeMap 保证 key 完美排序(keys perfectly sorted)。
- 插入更慢(takes more time to insert),但允许精确遍历(precise traversal)。
- 排序机制(sorting mechanisms):
- 默认:使用 compareTo(requires keys implement Comparable)。
- 自定义:构造器 TreeMap(Comparator c) 定义排序逻辑(custom sorting logic)。
- 关系说明(hierarchy note):TreeMap 实现 SortedMap 与 NavigableMap。
29. TreeMap 导航:高级战术( Navigating the TreeMap: Advanced Tactics )

用"范围尺"(range number line)展示按目标 key(target key)定位:
-
- lowerKey:严格小于(strictly less)
- floorKey:小于等于(<=)
- ceilingKey:大于等于(>=)
- higherKey:严格大于(strictly greater)
SortedMap 方法(SortedMap methods):
-
- firstKey() / lastKey():最小/最大 key(first/last)。
- headMap(toKey) / tailMap(fromKey):返回 map 的一部分(returns portions)。
NavigableMap 额外方法(additional navigable methods):
-
- pollFirstEntry():移除并返回第一个条目(removes and returns first)。
- pollLastEntry():移除并返回最后一个条目(removes and returns last)。
30. 真实执行: Hash vs Tree ( Real-World Execution: Hash vs Tree )

-
- 示例:先建 HashMap 放入若干键值(Smith/Anderson/Lewis/Cook → ages)。
- 输出(output)是随机顺序(random)。
- 再 new TreeMap<>(hashMap):key 会立刻按字母升序排序(sorted ascending alphabetically)。
- 图片强调:HashMap 输出是乱的;TreeMap 输出自动排序。
31. 真实执行:访问顺序( Real-World Execution: Access Order )

-
- 建 LinkedHashMap<>(16, 0.75f, true)(true=access order)。
- 当调用 get("Lewis") 后,Lewis=29 被移动到输出的最后(moved to the very end),表示"最近访问"(most recently accessed)。
- 这就是 access order 的效果(access-order behavior)。
32. 遗留情报: Hashtable ( Legacy Intel: Hashtable )
-
- 在现代集合框架(modern Collections Framework)之前,使用 java.util.Hashtable。
- 它像 HashMap(works like HashMap),但更新方法是同步的(update methods synchronized)。
- 关键结论(key takeaways):
- 后来被重新设计以适配框架(redesigned)。
- 保留仅为向后兼容(backwards compatibility)。
- 建议(tactical advice):除非你确实需要老系统上的同步线程安全更新(synchronized thread-safe updates on legacy systems),否则用现代 Maps(modern Maps)。
33. 战术决策矩阵( Tactical Decision Matrix )
问:更新 Map 时需要维护顺序吗(maintain order when updating the map)?
-
- 不需要(No)→ HashMap:最快、无序(fastest, unordered, default)。
- 需要插入顺序或访问历史(Yes, insertion or access history)→ LinkedHashMap:可追踪历史(tracks history),但内存成本略高(slightly higher memory cost)。
- 需要按 key 排序(Yes, sorted by key)→ TreeMap:按 Comparable/Comparator 排序(sorted via Comparable/Comparator),支持分析式导航(analytical navigation)。
34. 文本词频案例:解析频率模式( Parsing Frequency Patterns )
目标:把原始文本(raw, unstructured text)中的每个词计数(count occurrences),并按字母顺序输出(output alphabetically)。
-
- 数组做会很乱(messy),需要高效结构(efficient data structure)。
(1) 选择装备用什么 Map ( Choosing the Right Loadout )
对比表(table):
-
- HashMap:插入/查找快 O(1),但无顺序(unpredictable/random)。
- LinkedHashMap:插入/查找快 O(1),但顺序是插入或访问(insertion/access order)。
- TreeMap:插入/查找 O(log n),但 key 升序(ascending key order)。
图片结论:虽然 HashMap 最快,但任务需要按字母排序输出,所以 TreeMap最适合(perfect tool)。
(2) 数据破译:分词( Tokenization )

-
- 原始文本噪声大(noisy),不能只按空格切(split by spaces)。
- 使用正则分隔符(regex delimiter):"\\\\s+\\\\p{P}"
- 去掉空白(whitespaces ++++\\s++++)
- 去掉标点(punctuation ++++\\p{P}++++)
- 然后统一转小写(normalize to lowercase):toLowerCase()
- 让 "Good" 与 "good" 同等(treated the same)。
代码示例:
-
- String\[\] words = text.split("\\\\s+\\\\p{P}");
(3) 核心逻辑循环( Core Logic Loop )

流程图逻辑:
-
- 取 token:key = wordsi
- 丢空串(drop token):如果 key.length() == 0
- 判断是否已出现:map.containsKey(key)
- 如果没有(No)→ map.put(key, 1)
- 如果有(Yes)→ value = map.get(key);value++;map.put(key, value)
图片提示:put() 有"双重作用"(double duty):
-
- key 不存在时建立初值(establish baseline count)
- key 存在时覆盖旧值(overwrite old value)为递增结果。
35. 情报存储协议:包装类( Wrapper Classes )
-
- 词频统计用 Map<String, Integer> map = new TreeMap<>();:key 是单词(String),value 是次数(Integer)。
- 关键点:Java Collections Framework 只能存对象(stores Objects),不能直接用基本类型 int(primitive int)作为 Map 的 value。
- 所以用包装类(wrapper class)Integer 来装计数。
- 当我们做 value++ 或 map.put(key, value+1) 时,Java 会自动装箱/拆箱(autoboxing/unboxing)。
36. 执行与输出( Execution & Output )
-
- 图片表示:数据已经映射好了(data mapped),不用"外部迭代器"(external iterator)那种笨重方式(clunky)。
- 用 Java 8 的 forEach(forEach)+ lambda(lambda)输出:
map.forEach((k, v) -> System.out.println(k + "\t" + v)); - 输出面板示例(Output):
- a 2
- class 1
- good 3
- 注意:TreeMap 默认按 key 排序(sorted by keys, alphabetical),所以输出顺序是按字母(alphabetical)。
37. 改任务:要按频率排序怎么办( Changing Mission Parameters )
-
- TreeMap 能按 key 排序(sort by keys),但如果需求改成"最高频率优先"(most frequent first / sort by values),Map 本身做不到(a Map inherently cannot sort by its values)。
- Map 只保证 key 唯一(uniqueness on keys),不保证 value 唯一(values can repeat)。
- 所以必须把 Map 条目抽出来(extract entries),放到一个可以排序的结构里(structure that can be sorted)。
38. 战术数据抽取: entrySet → List ( Tactical Data Extraction )
-
- 抽取流程(pipeline):
- map.entrySet() 得到 Set<Map.Entry<K,V>>(entrySet view)
- new ArrayList<>(entrySet) 变成 List<Map.Entry<K,V>>(ready for sorting)
- 解释:从 Map 的"刚性结构"(rigid structure)里把 key/value 对转换成标准 List 序列(standard list sequence),然后就能用自定义 Comparator(custom comparator)排序。
- 抽取流程(pipeline):
39. 执行按 value 排序( Executing the Value Sort )
-
- 用 Collections.sort(Collections.sort)对 entries 列表排序:
Collections.sort(entries, (entry1, entry2) -> entry1.getValue().compareTo(entry2.getValue())); - 关键点:
- 比较的是 value(compare values),不是 key(not keys)。
- getValue() 返回 Integer(wrapper),可用 compareTo(Comparable.compareTo)。
- 这会得到按频率升序(ascending by frequency)的结果;若要降序(descending),可以交换 entry1/entry2 或用 reversed()。
- 用 Collections.sort(Collections.sort)对 entries 列表排序:
40. 任务复盘:为什么要用 Map ( Mission Debrief: Why Maps? )
-
- 如果不用 Map,需要并行数组(parallel arrays)或自定义对象列表(custom object list),并且每次更新前都要手动遍历查存在(manual iteration to check existence)。
- Map 的优势(advantages):
- 查找(lookups):O(1) 到 O(log n)(取决于 HashMap/TreeMap)→ 频率验证很快(instant frequency verification)。
- 自动排序(automated sorting):TreeMap 把 key 排序交给数据结构(delegates sorting to the structure)。
- 流畅 API(fluid API):forEach 与 entrySet 支持顺滑的数据转换(seamless transformation)。
- 图片总结:Map 把"索引与计数的复杂度"(complexity of indexing and non-integer keys)抽象掉(abstract away)。
41. 战术数据封锁:不可变与只读( Immutable & Unmodifiable )
主题:保护数据不被篡改(tampered),把数据"锁起来"(lock down)。
静态工具腰带( Static Utility Toolbelt )
-
- 用 java.util.Collections 的静态方法(static methods)给现有结构加"访问规则"(access rules),但不改变底层接口(without changing underlying interface)。
- 它适用于 List/Set/Map(operates on standard Lists, Sets, Maps)。
42. 基线:空常量( Empty Constants )
-
- 当你要返回"空结果"(empty result)时,用空常量(empty constants)避免创建对象开销(avoid object creation overhead):
- Collections.EMPTY_SET
- Collections.EMPTY_LIST
- Collections.EMPTY_MAP
- 关键词:空对象模式(empty object pattern)、减少开销(reduce overhead)。
- 当你要返回"空结果"(empty result)时,用空常量(empty constants)避免创建对象开销(avoid object creation overhead):
43. 单例集合:一次性集合( Singletons: One-shot Collections )
-
- 单例(singleton)= 只包含一个不可变元素(single, unchangeable item),内存高效(memory-efficient)。
- 三类方法(arsenal matrix):
- Collections.singleton(o) → 返回只含一个元素的不可变 Set(immutable Set)。
- Collections.singletonList(o) → 不可变 List(immutable List)。
- Collections.singletonMap(key, value) → 只含一个键值对的不可变 Map(immutable Map)。
修改单例会怎样( Modifying a Singleton )
-
- 示例:
Set<String> set = Collections.singleton("Chicago");
set.add("Dallas"); - 结果:立刻抛 UnsupportedOperationException(breach detected)。
- 结论:单例集合结构不可改(structural modifications forbidden)。
- 示例:
44. 不可修改视图:安全摄像头( Unmodifiable Views )

-
- unmodifiableList 不是拷贝(not a copy),而是"实时视图"(live feed)。
- 外部程序能看(can watch),但不能改(cannot touch)。
- 三个要点:
- 原集合仍可在内部被修改(mutable collection can change internally)。
- 视图与原集合动态连接(dynamic link),原集合变了视图立刻更新(updates instantly)。
- 通过只读视图对外暴露更安全(safe exposure)。
(1) 不可修改包装器清单( Unmodifiable Wrapper Arsenal )

-
- unmodifiableCollection(c)
- unmodifiableList(list)
- unmodifiableMap(m)
- unmodifiableSet(s)
- unmodifiableSortedMap(sm)
- unmodifiableSortedSet(ss)
用途:给现有集合加"只读护盾"(read-only shield)。
(2) 尝试绕过视图( Bypassing the camera )

-
- 对只读视图调用 remove("Dallas") 仍会抛 UnsupportedOperationException(breach detected)。
45. 现代捷径: JDK 9+ 工厂方法( JDK 9+ Factory Methods )

-
- 老方法(old way):Collections.unmodifiableSet(new HashSet<>(Arrays.asList("A","B")))(需要 wrapper)。
- 快方法(fast way):
- Set.of("A","B")
- Map.of("Key1","Val1","Key2","Val2")
- 这些 of() 直接返回完全不可变集合(fully immutable collections),没有 wrapper 开销(without wrapper overhead)。
46. 选择你的 " 封锁策略 " ( Choosing your Lockdown Strategy )
表格总结四种策略(strategy):
-
- 空常量(Empty Constants)
- 用途:返回安全的空对象(safe null-object patterns)
- 底层可变?否(No)
- 执行:Collections.EMPTY_LIST
- 单例(Singletons)
- 用途:只存一个元素,最省内存(exactly 1 item, minimal memory)
- 底层可变?否(No)
- 执行:Collections.singleton(o)
- 不可修改视图(Unmodifiable Views)
- 用途:安全对外暴露内部数据(exposing internal data safely)
- 底层可变?是(Yes, by internal code)
- 执行:Collections.unmodifiableList(list)
- JDK 9 工厂(JDK 9 Factory)
- 用途:快速硬编码固定数据集(rapidly hardcoding fixed datasets)
- 底层可变?否(No)
- 执行:Set.of(e1, e2)

-
- 战术数据结构(Tactical Data Structures)
集合( Sets )与映射( Maps )
目标:为高性能 Java(High-performance Java)选择合适框架(right framework)
1) 为什么不只用 ArrayList ( Why not just ArrayList )
-
- 学生疑问:去年什么都用 ArrayList(ArrayList for everything),为什么现在要学新集合(new collections)?
- 回答要点:
- 如果需要保证无重复(no duplicates guarantee)→ List 不擅长。
- 如果需要用唯一 ID 作为键快速查详情(instant lookup by unique ID as a key)→ 需要 Map。
- 做高性能操作(high-performance operations)要用专门装备(specialized loadout):Set 和 Map。
2) 关键概念( Key Concept )
-
- Set(Set):高效处理不重复元素(efficient for processing non-duplicate elements)。
- Map(Map):字典结构(dictionary structure),用键(Key)快速取值(retrieve a Value quickly)。
3) Set 像 " 会员俱乐部 " ( Set as exclusive club )
-
- Set 不允许重复(no duplicates allowed)。
- 规则:如果两个对象 equals() 相等(objects are equals),Set 只保留一个(keeps one)。
- 示例代码(HashSet):重复 add "B-Site"、"A-Site" 会被忽略(ignored),size 不变(size remains 2)。
- 图示:输入流(Input stream)里重复元素被过滤(filtered),输出(Output)只有唯一项。
4) Set 武器库( The Set Arsenal )
表格总结三种 Set:
4.1 HashSet ( HashSet )
-
- 顺序(Ordering):不可预测(unpredictable)。
- 底层技术(Underlying tech):哈希表(hash table)。
- 最适合(Best for):追求最大速度(maximum speed),不关心顺序(order doesn't matter)。
4.2 LinkedHashSet ( LinkedHashSet )
-
- 顺序:插入顺序(insertion order)。
- 底层:链表 + 哈希(linked list + hash)。
- 最适合:保持添加顺序(preserve exact order added)。
4.3 TreeSet ( TreeSet )
-
- 顺序:排序(sorted,基于自然顺序 Comparable 或自定义 Comparator)。
- 底层:红黑树(red-black tree)。
- 最适合:实时按字母/数值排序(alphabetical/numerical sorting on the fly)。
5) HashSet 为什么能快速判重( Hashing Engine )
-
- 合同(Contract):若 o1.equals(o2) 为 true,则 hashCode() 必须相同(MUST be the same)。
- 提示:通常要同时覆写(override both)equals 和 hashCode。
- 容量与负载因子(Capacity & Load Factor):
- 默认容量(default capacity)16
- 默认负载因子(load factor)0.75
- 负载因子决定何时扩容(dictates when it resizes)。
- 计算(The Math):当元素数超过 capacity * loadFactor(如 16 * 0.75 = 12)时,会动态扩容(dynamically doubles)到 32,以保持接近 O(1) 查找速度(maintain O(1) search speeds)。
- 合同(Contract):若 o1.equals(o2) 为 true,则 hashCode() 必须相同(MUST be the same)。
6) TreeSet 的可导航能力( NavigableSet Arsenal )
-
- TreeSet 会自动排序(automatically sorts)。
- 还提供相对某个值的精确定位方法(precise methods relative to a value):
常用方法(methods):
-
- first() / last():取最小/最大元素(absolute extremes)。
- headSet(E toElement):取目标之前所有元素(everything before)。
- tailSet(E fromElement):取目标及之后所有元素(everything after including)。
- lower(E) / higher(E):找严格小于/严格大于目标的最近元素(closest strictly less/greater)。
- 注:排序依赖 Comparable(Comparable)或构造器传入 Comparator(custom Comparator)。
7) 性能对决( Performance Showdown, N=50,000 )
-
- 测试:50,000 元素的成员测试(membership test)contains()。
- Set 组耗时(ms):
- HashSet 约 20 ms
- LinkedHashSet 约 27 ms
- TreeSet 约 47 ms
- List 组耗时(ms):
- ArrayList 约 39,802 ms
- LinkedList 约 52,197 ms
- 结论(Takeaway):
- Set 用哈希/树实现近乎瞬时查找(near-instant lookups)。
- List 必须逐个扫描(scan element-by-element),时间复杂度 O(N)。
8) Map 的解剖( Anatomy of a Map )
-
- 当你不仅想知道"存不存在"(exists),还想取"附带情报/资料"(intel attached),就用 Map。
- Keys(keys):唯一集合(unique set),不允许重复(no duplicates allowed),可为任何对象(any object type)。
- Values(values):与 key 关联的数据(data attached to key),可以重复(can be duplicated)。
- Entry(entry):key/value 合体(combined pair),由 Map.Entry<K,V> 表示。
9) Map 武器库( The Map Arsenal )
三种 Map:
9.1 HashMap ( HashMap )
-
- 顺序:随机(random)。
- 最适合:插入/删除/定位值的纯速度(pure speed for inserting, deleting, locating values)。
9.2 LinkedHashMap ( LinkedHashMap )
-
- 顺序:插入顺序或访问顺序(insertion OR access order)。
- 最适合:做 LRU 缓存(LRU, least recently used)或时间线(chronologies)。
9.3 TreeMap ( TreeMap )
-
- 顺序:按 key 排序(sorted by key)。
- 最适合:字母目录(alphabetical directories)或按 key 升序遍历(iterating keys ascending)。
10) Map 交互武器库( Map Interaction Arsenal )
10.1 更新方法( Update methods )
-
- put(K key, V value):插入条目(inserts entry);如果 key 已存在会覆盖旧值(overrides old value)。
- remove(Object key):删除 key 对应条目(deletes entry associated with key)。
10.2 查询方法( Query methods )
-
- get(Object key):取 value(retrieves value);key 不存在返回 null(returns null)。
- containsKey / containsValue:快速布尔检查(fast boolean checks)。
10.3 遍历 Map 的方式( How to iterate )
-
- 不能直接遍历 Map(cannot iterate directly)。
- 必须提取本质(extract essence):
- keySet() → 所有 keys 的 Set(Set of keys)
- values() → 所有 values 的 Collection(Collection of values)
- entrySet() → 所有 entry 的 Set(Set of Map.Entry)
10.4 Java 8+ 现代写法( Modern way )
-
- agents.forEach((name, level) -> System.out.println(name + " is level " + level));
- 关键词:forEach(forEach)、lambda(lambda)。
11) 高级工具( Advanced Utilities )
-
- 只读视图(Read-only views):
- Collections.unmodifiableSet(set) / Collections.unmodifiableMap(map)
- 返回引用(reference),若尝试修改会抛 UnsupportedOperationException。
- 单例集合(Singletons):
- Collections.singleton(Object) 或 Collections.singletonMap(Key, Value)
- 创建高度优化且不可变(immutable)的"一项集合"(exactly one item)。
- 备注:JDK 9+ 可以用 Set.of() / Map.of() 快速创建不可变集合(instant immutable structures)。
- 只读视图(Read-only views):
12) 战术决策树( Tactical Decision Tree )
-
- 第一步:你需要键值对吗(need key-value pairs)?
- Yes → Map
- 需要 key 排序(need keys sorted)→ TreeMap
- 需要插入/访问顺序(need insertion/access order)→ LinkedHashMap
- 只要速度(just need pure speed)→ HashMap
- No → 看是否允许重复(are duplicates allowed)?
- Yes → List(ArrayList / LinkedList)
- No → Set
- 需要排序(need elements sorted)→ TreeSet
- 需要插入顺序(need insertion order)→ LinkedHashSet
- 只要速度(just need pure speed)→ HashSet
- Yes → Map
- 第一步:你需要键值对吗(need key-value pairs)?
TLDR: Sets and Maps
1. 核心概念: Set vs Map ( Core Concepts: Set vs Map )
- 集合(Set)存储无序且不重复元素(unordered, nonduplicate elements)。
- 映射(Map)存储键/值对(key/value pairs),并支持按键快速查找(fast lookup by key)。
- Set 继承 Collection(Set extends Collection);Map 是独立层级(separate hierarchy)。
- List 的索引(index)是整数(integers);Map 的键(key)可以是对象(objects)。
- Map 不能有重复键(no duplicate keys);一个键最多映射一个值(one key maps to at most one value)。
- AbstractSet / AbstractMap 是便利抽象类(convenience abstract classes),提供部分实现(partial implementations)。
2. Set 接口与常用操作( Set Interface and Common Operations )
- Set 不新增方法(adds no new methods beyond Collection),核心规则是不重复(no duplicates)。
- add(e):若元素不存在才插入(inserts if not already present)。
- remove(o):删除元素(removes an element)。
- contains(o):成员测试(membership test)。
- size():元素数量(number of elements)。
- isEmpty():是否为空(has no elements)。
- clear():清空(removes all elements)。

- 批量操作(bulk updates):
- addAll(c):并集式更新(union-style)
- removeAll(c):差集式更新(difference-style)
- retainAll(c):交集式更新(intersection-style)
- forEach(action):对每个元素执行操作(process each element)。

3. HashSet / LinkedHashSet / TreeSet ( Set Implementations )
- HashSet:不保证顺序(no ordering);只需要"快速+不重复"(fast duplicate-free storage)时最佳。
- LinkedHashSet:保持插入顺序(keeps insertion order)。
- TreeSet:保持排序(keeps elements sorted),依赖 Comparable 或 Comparator(Comparable or Comparator)。
常见构造(constructors):
- new HashSet<>():默认空集合(default empty set)
- new HashSet<>(collection):从已有集合构建(build from existing collection)
- new HashSet<>(initialCapacity, loadFactor):已知规模时调参(tune storage)
- new LinkedHashSet<>():插入有序(insertion-ordered)
- new TreeSet<>():自然顺序排序(natural order)
- new TreeSet<>(collection):把无序数据变成有序副本(sorted copy)
- new TreeSet<>(comparator):自定义排序(custom order)


TreeSet 相关方法(SortedSet/NavigableSet methods):
- first()/last():最小/最大元素(smallest/largest)
- headSet(to)/tailSet(from):有序子集视图(sorted subsets)
- lower/higher/floor/ceiling:邻近元素(nearest neighbors)
- pollFirst()/pollLast():移除并返回两端元素(remove and return ends)

4. 性能: Set vs List ( Performance: Sets vs Lists )
- 文档指出:在成员测试和删除(membership testing and removal)实验里,Set 比 List 高效得多(much more efficient)。
- List 仍然有用:当你需要基于索引访问(index-based access)时。
- 经验:无序且不重复(unordered, nonduplicate)→ 用 Set;需要重复或索引(duplicates or index)→ 用 List。
- "关键字是否存在"这类任务(keyword lookup / "is item present?")优先选 HashSet。
5. Map 接口与核心方法( Map Interface and Core Methods )
- Map 存储 entry(entries)= key/value 对(key/value)。
- 核心工作(core jobs):查询(query)、更新(update)、取 keys/values/entries(retrieve views)。
常用方法(core methods):
- put(key, value):添加或更新(adds or updates)
- get(key):取值(retrieves value)
- containsKey(key):是否有键(key exists)
- containsValue(value):是否有值(value exists)
- remove(key):删除条目(removes entry)
- size()/isEmpty()/clear():数量/空/清空(count/empty/clear)
- putAll(m):复制另一个 map 的所有条目(copies all entries)


集合视图(views):
- keySet() → Set<K>
- values() → Collection<V>
- entrySet() → Set<Map.Entry<K,V>>
- forEach((k,v)->...):遍历 key/value(process each pair)

6. HashMap / LinkedHashMap / TreeMap ( Map Implementations )
- HashMap:查找/插入/删除高效(efficient for lookup, insertion, deletion),不保证顺序
(no order)

- LinkedHashMap :保持插入顺序或访问顺序(insertion order or access order)。
- new LinkedHashMap<>(16, 0.75f, true):true 表示访问顺序(access-ordered)。
- TreeMap :按 key 排序(entries sorted by key)。
- 可用自然顺序(natural order)或比较器(comparator)排序。


TreeMap 相关方法(SortedMap/NavigableMap):
- firstKey()/lastKey():最小/最大键(smallest/largest key)
- headMap(to)/tailMap(from):范围视图(range views)

- headMap(toKey):左半边 (< toKey)
- tailMap(fromKey):右半边 (>= fromKey)
- lowerKey/higherKey/floorKey/ceilingKey:邻近键(nearest keys)
- pollFirstEntry()/pollLastEntry():移除并返回两端 entry(remove & return ends)

7. 遍历 Map 的方法( Traversing Maps )
- 遍历途径:keySet(keys)、values(values)、entrySet(entries)、forEach(lambda)。
- Map.Entry 常用:getKey()/getValue() 读取;setValue(value) 通过 entry 更新。
- lowerKey("M"):返回 严格小于 "M" 的最大 key
�� "M 左边最近的一个"(<) - floorKey("M"):返回 小于等于 "M" 的最大 key
�� "M 左边最近,若刚好有 M 就返回 M"(<=) - ceilingKey("M"):返回 大于等于 "M" 的最小 key
�� "M 右边最近,若刚好有 M 就返回 M"(>=) - higherKey("M"):返回 严格大于 "M" 的最小 key
�� "M 右边最近的一个"(>)
- lowerKey("M"):返回 严格小于 "M" 的最大 key


8. 常见高分用法( Common Use Patterns )
- HashSet 做快速成员测试(fast membership test)------关键字查找(keyword lookup)。
- TreeMap 做计数并按 key 排序输出(count occurrences with sorted output by key)。
- 需要排序时再转换(convert unsorted → sorted only when needed):
- new TreeSet<>(set),new TreeMap<>(map)。
- 保持插入顺序(preserve insertion order)用 LinkedHashSet/LinkedHashMap。

9. 不可变与只读( Immutable and Unmodifiable Collections )
- 单例集合/映射(singleton collections/maps)是不可变(immutable)。
- unmodifiable... 返回只读视图(read-only views),修改会抛 UnsupportedOperationException。
- 例子:
- Collections.singleton(o) / singletonList(o) / singletonMap(k,v)
- Collections.unmodifiableSet/ List/ Map
- Collections.unmodifiableSortedSet/ unmodifiableSortedMap
- JDK 9:Set.of(...) / Map.of(...) 创建不可变集合(immutable set/map)。



10. 选型总结( Choosing the Right Data Structure )
- HashSet:快速、无重复、无顺序需求(fast duplicate-free membership, order doesn't matter)。
- LinkedHashSet:无重复 + 插入顺序(duplicate-free + insertion order)。
- TreeSet:无重复 + 排序(duplicate-free + sorted)。
- HashMap:按 key 快查,顺序不重要(fast key lookup, no order)。
- LinkedHashMap:插入/访问顺序(insertion order or access order)。
- TreeMap:按 key 排序(sorted by key)。
- 需要重复或索引(duplicates or index access)→ List。
11. 常见考试陷阱( Common Exam Traps and Quick Reminders )
- Set 不允许重复;List 允许重复(duplicates)。
- Map 不允许重复 key;同 key 的后一次 put 会替换旧值(replaces old value)。
- HashSet/HashMap 不保证插入顺序(do not preserve insertion order)。
- LinkedHashSet 保留插入顺序;LinkedHashMap 可保留插入或访问顺序。
- TreeSet/TreeMap 需要元素/键可比较(Comparable),或提供 Comparator。
- TreeSet 中比较结果为 0 会被当作重复(comparison returns 0 ⇒ duplicates)。
- HashSet 的重复处理依赖 equals 与一致的 hashCode(consistent hashing)。
- Set 不支持下标(no indexing);用 foreach 遍历。
- 泛型要用包装类型(wrapper types):Map<String,Integer> ✅,Map<String,int> ❌。
- unmodifiable... 是只读视图(view),不是深拷贝(deep copy)。
- singleton 与 Set.of/Map.of 是不可变(immutable)。
lab sheet
0) 实验目标与总体要求( Lab goals & overall requirements )
- 本实验用于在 Week 5 的 todo 系统基础上扩展(extend Week 5 system),引入:
- Set:处理不重复数据(nonduplicate data)与成员测试(membership testing)
- Map:键值关系(key-value relationships)与快速查找(fast lookup)
- TreeSet / TreeMap(TreeSet & TreeMap):有序数据表示(sorted data representation)
- 必须继续在同一个 todo 管理系统上开发(same todo management system),不要新建程序(do not create a new program)。每个任务(task)都是在原系统上叠加新能力(adds new capabilities)。
Task 1 :用 Set 做任务标签系统( Task Tagging System with Set )
场景( Scenario )
- 任务通常会用标签(tags)分类,如 study / urgent / email。一个任务不应该有重复标签(no duplicate tags)。
必做功能( Required features )
- 给任务添加标签(add tags to a task)。
- 阻止重复标签(prevent duplicate tags)。
- 显示每个任务的标签(display tags for each task)。
- 检查标签是否存在(check tag membership)。
实现约束( Implementation constraints )
- 使用集合:Set tags = new HashSet<>();(HashSet)
Task 2 :全局标签管理与过滤( Global Tag Management and Filtering )
场景( Scenario )
- 用户想查看所有标签(explore all tags)并按标签过滤任务(filter tasks)。
必做功能( Required features )
- 收集全部唯一标签(collect all unique tags)。
- 显示所有标签(display all tags)。
- 按标签过滤任务(filter tasks by tag)。
- 处理空结果(handle empty results)。
实现约束( Implementation constraints )
- 使用集合:Set allTags = new HashSet<>();(HashSet)
Task 3 :用 Map 做任务查找系统( Task Lookup System with Map )
场景( Scenario )
- 基于 List 的查找效率低(list-based lookup is inefficient)。
必做功能( Required features )
- 给每个任务分配唯一 ID(assign unique ID)。
- 用 Map 存任务(store tasks in a map)。
- 通过 ID 检索任务(retrieve by ID)。
- 保持一致性(maintain consistency)------说明你的主列表与 map 的数据需要一致更新。
实现约束( Implementation constraints )
- 使用:Map<Integer, Task> taskMap = new HashMap<>();(HashMap)
Task 4 :用 Map 做标签频率分析( Tag Frequency Analysis with Map )
场景( Scenario )
- 分析标签出现频率(analyse how often tags appear)。
必做功能( Required features )
- 统计标签出现次数(count tag occurrences)。
- 显示频率表(display frequencies)。
- 找出出现最多的标签(find most frequent tag)。
- 处理空情况(handle empty cases)。
实现约束( Implementation constraints )
- 使用:Map<String, Integer> tagFrequency = new HashMap<>();(HashMap)
Task 5 :用 TreeSet / TreeMap 输出有序洞察( Sorted Insights with TreeSet and TreeMap )
场景( Scenario )
- 需要"有序输出"(ordered outputs)。
必做功能( Required features )
- 显示排序后的标签(display sorted tags)。
- 显示排序后的频率(display sorted frequencies)。
- 比较结构(compare structures)------对比 HashSet/HashMap 与 TreeSet/TreeMap 在"顺序/性能/用途"上的差别。
- 可选:高级排序(optional advanced sorting)------如按频率、按字母序等组合规则。
实现约束( Implementation constraints )
- 使用 TreeSet(TreeSet)和 TreeMap(TreeMap<String, Integer>)。
总结( Summary )
- 你应理解:
- Set 用于唯一性(uniqueness)
- Map 用于查找(lookup)
- Tree 结构用于有序(ordering)
- 最终系统整合 Week 5 + Week 6 概念(integrates Week 5 and Week 6 concepts)。
TodoSystemWeek6Complete.java
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Scanner;
import java.util.Set;
import java.util.Stack;
import java.util.TreeMap;
import java.util.TreeSet;
/**
* Week 6 Complete Todo System (built on Week 5)
* Adds:
* - Per-task tags using Set<String> (no duplicates)
* - Global unique tag set (HashSet)
* - ID lookup map: Map<Integer, Task> (HashMap)
* - Tag frequency map: Map<String, Integer> (HashMap), plus Tree views (TreeSet/TreeMap)
*/
public class TodoSystemWeek6Complete {
private final Scanner input = new Scanner(System.in);
// Week 5: core structures
private final List<Task> tasks = new ArrayList<>();
private final Queue<Task> processingQueue = new LinkedList<>();
private final Stack<ActionRecord> recentActions = new Stack<>();
private final PriorityQueue<Task> priorityQueue = createPriorityQueue();
// Week 6: new structures
private final Set<String> allTags = new HashSet<>(); // all unique tags (unordered)
private final Map<Integer, Task> taskMap = new HashMap<>(); // ID -> Task lookup
private final Map<String, Integer> tagFrequency = new HashMap<>(); // tag -> count
private int nextTaskId = 1;
public static void main(String\[\] args) {
TodoSystemWeek6Complete system = new TodoSystemWeek6Complete();
system.run();
}
public void run() {
boolean running = true;
while (running) {
printMainMenu();
int choice = readInt("Choose an option: ");
switch (choice) {
case 1 -> addTask();
case 2 -> viewAllTasks();
case 3 -> editTask();
case 4 -> removeTask();
case 5 -> markTaskCompleted();
case 6 -> viewTasksByStatus();
case 7 -> moveTaskToQueue();
case 8 -> viewProcessingQueue();
case 9 -> viewNextQueueTask();
case 10 -> processNextQueueTask();
case 11 -> viewMostRecentAction();
case 12 -> removeMostRecentActionFromHistory();
case 13 -> viewRecentActionHistory();
case 14 -> assignPriorityToTask();
case 15 -> addTaskToPriorityQueue();
case 16 -> viewPriorityQueue();
case 17 -> viewHighestPriorityTask();
case 18 -> processHighestPriorityTask();
// Week 6
case 19 -> addTagToTask();
case 20 -> checkTaskTagMembership();
case 21 -> viewAllTags();
case 22 -> filterTasksByTag();
case 23 -> lookupTaskById();
case 24 -> viewTagFrequency();
case 25 -> viewMostFrequentTag();
case 26 -> viewSortedTags();
case 27 -> viewSortedTagFrequencies();
case 28 -> compareTagViews();
case 29 -> viewSystemOverview();
case 0 -> {
running = false;
System.out.println("Exiting Todo System.");
}
default -> System.out.println("Invalid option. Please try again.");
}
}
}
private void printMainMenu() {
System.out.println();
System.out.println("=== Todo System ===");
System.out.println("Week 5 - Core List Features");
System.out.println("1. Add task");
System.out.println("2. View all tasks");
System.out.println("3. Edit task");
System.out.println("4. Remove task");
System.out.println("5. Mark task as completed");
System.out.println("6. View tasks by status");
System.out.println();
System.out.println("Week 5 - Queue Features");
System.out.println("7. Move task to processing queue");
System.out.println("8. View processing queue");
System.out.println("9. View next queue task");
System.out.println("10. Process next queue task");
System.out.println();
System.out.println("Week 5 - Stack Features");
System.out.println("11. View most recent action");
System.out.println("12. Remove most recent action from history");
System.out.println("13. View recent action history");
System.out.println();
System.out.println("Week 5 - Priority Queue Features");
System.out.println("14. Assign priority to a task");
System.out.println("15. Add task to priority queue");
System.out.println("16. View priority queue");
System.out.println("17. View highest-priority task");
System.out.println("18. Process highest-priority task");
System.out.println();
System.out.println("Week 6 - Set and Map Features");
System.out.println("19. Add tag to a task");
System.out.println("20. Check task tag membership");
System.out.println("21. View all unique tags");
System.out.println("22. Filter tasks by tag");
System.out.println("23. Look up task by ID");
System.out.println("24. View tag frequencies");
System.out.println("25. View most frequent tag");
System.out.println("26. View sorted tags");
System.out.println("27. View sorted tag frequencies");
System.out.println("28. Compare hash-based and tree-based views");
System.out.println("29. View system overview");
System.out.println("0. Exit");
}
// ------------------------------------------------------------
// Week 5 core functionality (kept complete)
// ------------------------------------------------------------
private void addTask() {
String title = readNonEmptyLine("Enter task title: ");
Task task = new Task(nextTaskId, title);
nextTaskId++;
tasks.add(task);
// Keep map consistent (ID lookup)
taskMap.put(task.getId(), task);
recordAction(ActionType.ADD, task, "Added a new task.");
System.out.println("Task added.");
displayTask(task, tasks.size() - 1);
}
private void viewAllTasks() {
System.out.println("=== All Tasks ===");
if (tasks.isEmpty()) {
System.out.println("No tasks available.");
return;
}
int index = 0;
for (Task task : tasks) {
displayTask(task, index);
index++;
}
}
private void editTask() {
int index = chooseTaskIndex();
if (index == -1) return;
Task task = tasks.get(index);
String oldTitle = task.getTitle();
String newTitle = readNonEmptyLine("Enter the new title: ");
task.setTitle(newTitle);
// If task is in priorityQueue, reinsert to fix heap after title change
refreshPriorityQueueEntry(task);
recordAction(ActionType.EDIT, task, "Changed title from '" + oldTitle + "' to '" + newTitle + "'.");
System.out.println("Task updated.");
displayTask(task, index);
}
private void removeTask() {
int index = chooseTaskIndex();
if (index == -1) return;
Task task = tasks.remove(index);
boolean removedFromQueue = processingQueue.remove(task);
boolean removedFromPriorityQueue = priorityQueue.remove(task);
// Remove ID mapping and rebuild tag summaries
taskMap.remove(task.getId());
refreshTagData();
String details = "Removed task from main list.";
if (removedFromQueue) details += " Also removed from processing queue.";
if (removedFromPriorityQueue) details += " Also removed from priority queue.";
recordAction(ActionType.REMOVE, task, details);
System.out.println("Task removed.");
}
private void markTaskCompleted() {
int index = chooseTaskIndex();
if (index == -1) return;
Task task = tasks.get(index);
task.setCompleted(true);
recordAction(ActionType.COMPLETE, task, "Marked task as completed.");
System.out.println("Task marked as completed.");
displayTask(task, index);
}
private void viewTasksByStatus() {
System.out.println("1. View completed tasks");
System.out.println("2. View incomplete tasks");
int choice = readInt("Choose an option: ");
boolean targetCompleted;
if (choice == 1) targetCompleted = true;
else if (choice == 2) targetCompleted = false;
else {
System.out.println("Invalid option.");
return;
}
boolean found = false;
System.out.println(targetCompleted ? "=== Completed Tasks ===" : "=== Incomplete Tasks ===");
for (int i = 0; i < tasks.size(); i++) {
Task task = tasks.get(i);
if (task.isCompleted() == targetCompleted) {
displayTask(task, i);
found = true;
}
}
if (!found) System.out.println("No matching tasks found.");
}
private void moveTaskToQueue() {
int index = chooseTaskIndex();
if (index == -1) return;
Task task = tasks.get(index);
if (processingQueue.contains(task)) {
System.out.println("That task is already in the processing queue.");
return;
}
processingQueue.offer(task);
recordAction(ActionType.MOVE_TO_QUEUE, task, "Moved task into FIFO processing queue.");
System.out.println("Task added to processing queue.");
}
private void viewProcessingQueue() {
System.out.println("=== Processing Queue ===");
if (processingQueue.isEmpty()) {
System.out.println("The processing queue is empty.");
return;
}
int position = 1;
for (Task task : processingQueue) {
System.out.print(position + ". ");
displayTask(task);
position++;
}
}
private void viewNextQueueTask() {
Task task = processingQueue.peek();
if (task == null) {
System.out.println("The processing queue is empty.");
return;
}
System.out.println("Next queue task:");
displayTask(task);
}
private void processNextQueueTask() {
Task task = processingQueue.poll();
if (task == null) {
System.out.println("The processing queue is empty.");
return;
}
recordAction(ActionType.PROCESS_QUEUE, task, "Processed the next task from FIFO queue.");
System.out.println("Processed task:");
displayTask(task);
}
private void viewMostRecentAction() {
if (recentActions.isEmpty()) {
System.out.println("No recent actions recorded.");
return;
}
System.out.println("Most recent action:");
System.out.println(recentActions.peek());
}
private void removeMostRecentActionFromHistory() {
if (recentActions.isEmpty()) {
System.out.println("No recent actions to remove.");
return;
}
ActionRecord removed = recentActions.pop();
System.out.println("Removed action from history:");
System.out.println(removed);
}
private void viewRecentActionHistory() {
System.out.println("=== Recent Action History ===");
if (recentActions.isEmpty()) {
System.out.println("No recent actions recorded.");
return;
}
for (int i = recentActions.size() - 1; i >= 0; i--) {
System.out.println(recentActions.get(i));
}
}
private void recordAction(ActionType actionType, Task task, String details) {
recentActions.push(new ActionRecord(actionType, task, details));
}
private void assignPriorityToTask() {
int index = chooseTaskIndex();
if (index == -1) return;
Task task = tasks.get(index);
PriorityLevel oldPriority = task.getPriority();
PriorityLevel newPriority = choosePriorityLevel();
task.setPriority(newPriority);
// Reinsert if needed
refreshPriorityQueueEntry(task);
recordAction(ActionType.ASSIGN_PRIORITY, task,
"Changed priority from " + oldPriority + " to " + newPriority + ".");
System.out.println("Priority updated.");
displayTask(task, index);
}
private void addTaskToPriorityQueue() {
int index = chooseTaskIndex();
if (index == -1) return;
Task task = tasks.get(index);
if (priorityQueue.contains(task)) {
System.out.println("That task is already in the priority queue.");
return;
}
priorityQueue.offer(task);
recordAction(ActionType.ADD_TO_PRIORITY_QUEUE, task, "Added task to priority queue.");
System.out.println("Task added to priority queue.");
}
private void viewPriorityQueue() {
System.out.println("=== Priority Queue ===");
if (priorityQueue.isEmpty()) {
System.out.println("The priority queue is empty.");
return;
}
// Copy + sort for display (PriorityQueue iterator isn't sorted)
List<Task> orderedView = new ArrayList<>(priorityQueue);
orderedView.sort(
Comparator.comparingInt(Task::getPriorityValue).reversed()
.thenComparing(Task::getTitle, String.CASE_INSENSITIVE_ORDER));
int position = 1;
for (Task task : orderedView) {
System.out.print(position + ". ");
displayTask(task);
position++;
}
}
private void viewHighestPriorityTask() {
Task task = priorityQueue.peek();
if (task == null) {
System.out.println("The priority queue is empty.");
return;
}
System.out.println("Highest-priority task:");
displayTask(task);
}
private void processHighestPriorityTask() {
Task task = priorityQueue.poll();
if (task == null) {
System.out.println("The priority queue is empty.");
return;
}
recordAction(ActionType.PROCESS_PRIORITY_QUEUE, task, "Processed the highest-priority task.");
System.out.println("Processed highest-priority task:");
displayTask(task);
}
// ------------------------------------------------------------
// Week 6 features (Set / Map)
// ------------------------------------------------------------
// Task 1 (Week6): add tag to task (uses Set, rejects duplicates)
private void addTagToTask() {
int index = chooseTaskIndex();
if (index == -1) return;
Task task = tasks.get(index);
String tag = readTag("Enter a tag: ");
boolean added = task.addTag(tag); // HashSet.add returns false if duplicate
if (!added) {
System.out.println("Tag already exists for this task (duplicate rejected).");
displayTask(task, index);
return;
}
refreshTagData();
recordAction(ActionType.ADD_TAG, task, "Added tag '" + tag + "' to task.");
System.out.println("Tag added.");
displayTask(task, index);
}
// Task 1 (Week6): membership test in task's tag Set
private void checkTaskTagMembership() {
int index = chooseTaskIndex();
if (index == -1) return;
Task task = tasks.get(index);
String tag = readTag("Enter a tag to check: ");
boolean has = task.hasTag(tag);
System.out.println(has ? "YES: task contains tag '" + tag + "'." : "NO: task does not contain tag '" + tag + "'.");
displayTask(task, index);
}
// Task 2 (Week6): show all unique tags (HashSet, not sorted)
private void viewAllTags() {
System.out.println("=== All Unique Tags ===");
refreshTagData();
if (allTags.isEmpty()) {
System.out.println("No tags available.");
return;
}
for (String tag : allTags) {
System.out.println(tag);
}
}
// Task 2 (Week6): filter tasks by a tag
private void filterTasksByTag() {
refreshTagData();
if (allTags.isEmpty()) {
System.out.println("No tags available.");
return;
}
String tag = readTag("Enter a tag to filter by: ");
boolean found = false;
System.out.println("=== Tasks with tag '" + tag + "' ===");
for (int i = 0; i < tasks.size(); i++) {
Task task = tasks.get(i);
if (task.hasTag(tag)) {
displayTask(task, i);
found = true;
}
}
if (!found) System.out.println("No tasks found with that tag.");
}
// Task 3 (Week6): Map lookup by ID
private void lookupTaskById() {
if (taskMap.isEmpty()) {
System.out.println("No tasks available.");
return;
}
int id = readInt("Enter task ID: ");
Task task = taskMap.get(id);
if (task == null) {
System.out.println("No task found with ID " + id + ".");
return;
}
System.out.println("Found task:");
displayTask(task);
}
// Task 4 (Week6): show tag frequency (HashMap, unordered)
private void viewTagFrequency() {
System.out.println("=== Tag Frequency ===");
refreshTagData();
if (tagFrequency.isEmpty()) {
System.out.println("No tags available.");
return;
}
for (Map.Entry<String, Integer> entry : tagFrequency.entrySet()) {
System.out.println(entry.getKey() + " = " + entry.getValue());
}
}
// Task 4 (Week6): find most frequent tag
private void viewMostFrequentTag() {
refreshTagData();
if (tagFrequency.isEmpty()) {
System.out.println("No tags available.");
return;
}
String bestTag = null;
int bestCount = -1;
// Stable tie-break: alphabetical
for (Map.Entry<String, Integer> e : tagFrequency.entrySet()) {
String tag = e.getKey();
int count = e.getValue();
if (count > bestCount) {
bestCount = count;
bestTag = tag;
} else if (count == bestCount && bestTag != null && tag.compareTo(bestTag) < 0) {
bestTag = tag;
}
}
System.out.println("Most frequent tag: " + bestTag + " (" + bestCount + ")");
}
// Task 5 (Week6): TreeSet sorted tags
private void viewSortedTags() {
refreshTagData();
System.out.println("=== Sorted Tags (TreeSet) ===");
if (allTags.isEmpty()) {
System.out.println("No tags available.");
return;
}
TreeSet<String> sorted = new TreeSet<>(allTags);
for (String tag : sorted) {
System.out.println(tag);
}
}
// Task 5 (Week6): TreeMap sorted frequencies by key (tag)
private void viewSortedTagFrequencies() {
refreshTagData();
System.out.println("=== Sorted Tag Frequency (TreeMap) ===");
if (tagFrequency.isEmpty()) {
System.out.println("No tags available.");
return;
}
TreeMap<String, Integer> sorted = new TreeMap<>(tagFrequency);
for (Map.Entry<String, Integer> e : sorted.entrySet()) {
System.out.println(e.getKey() + " = " + e.getValue());
}
}
// Task 5 (Week6): compare hash-based vs tree-based views
private void compareTagViews() {
refreshTagData();
System.out.println("=== Hash vs Tree Comparison ===");
if (allTags.isEmpty()) {
System.out.println("No tags available.");
return;
}
System.out.println("HashSet allTags (unordered): " + allTags);
System.out.println("TreeSet sortedTags (sorted): " + new TreeSet<>(allTags));
System.out.println("HashMap tagFrequency (unordered): " + tagFrequency);
System.out.println("TreeMap sortedTagFrequency (sorted by key): " + new TreeMap<>(tagFrequency));
System.out.println();
System.out.println("Explanation:");
System.out.println("- HashSet/HashMap do NOT guarantee iteration order (hash-based).");
System.out.println("- TreeSet/TreeMap keep keys/elements sorted (tree-based).");
}
private void viewSystemOverview() {
refreshTagData();
System.out.println("=== System Overview ===");
System.out.println("Task count: " + tasks.size());
System.out.println("Task map size: " + taskMap.size());
System.out.println("Queue size: " + processingQueue.size());
System.out.println("Recent action count: " + recentActions.size());
System.out.println("Priority queue size: " + priorityQueue.size());
System.out.println("Unique tag count: " + allTags.size());
int completedCount = 0;
for (Task task : tasks) {
if (task.isCompleted()) completedCount++;
}
System.out.println("Completed tasks: " + completedCount);
System.out.println("Incomplete tasks: " + (tasks.size() - completedCount));
// Week6 extra: show sorted tags + most frequent
if (!allTags.isEmpty()) {
System.out.println();
System.out.println("Sorted tags: " + new TreeSet<>(allTags));
}
if (!tagFrequency.isEmpty()) {
System.out.println();
System.out.println("Sorted tag frequencies (by tag):");
TreeMap<String, Integer> sorted = new TreeMap<>(tagFrequency);
for (Map.Entry<String, Integer> e : sorted.entrySet()) {
System.out.println(e.getKey() + " = " + e.getValue());
}
viewMostFrequentTag();
}
if (!tasks.isEmpty()) {
System.out.println();
System.out.println("Main task list:");
viewAllTasks();
}
if (!processingQueue.isEmpty()) {
System.out.println();
System.out.println("FIFO queue:");
viewProcessingQueue();
}
if (!priorityQueue.isEmpty()) {
System.out.println();
System.out.println("Priority queue:");
viewPriorityQueue();
}
if (!recentActions.isEmpty()) {
System.out.println();
System.out.println("Most recent activities (up to 10):");
int shown = 0;
for (int i = recentActions.size() - 1; i >= 0 && shown < 10; i--) {
System.out.println(recentActions.get(i));
shown++;
}
}
}
// ------------------------------------------------------------
// Helper methods
// ------------------------------------------------------------
private PriorityQueue<Task> createPriorityQueue() {
return new PriorityQueue<>(
Comparator.comparingInt(Task::getPriorityValue).reversed()
.thenComparing(Task::getTitle, String.CASE_INSENSITIVE_ORDER));
}
private void refreshPriorityQueueEntry(Task task) {
if (priorityQueue.remove(task)) {
priorityQueue.offer(task);
}
}
/**
* Rebuild allTags and tagFrequency from current tasks.
* This keeps Set/Map summaries consistent even after edits/removals.
*/
private void refreshTagData() {
allTags.clear();
tagFrequency.clear();
for (Task task : tasks) {
for (String tag : task.getTags()) {
allTags.add(tag);
tagFrequency.put(tag, tagFrequency.getOrDefault(tag, 0) + 1);
}
}
}
private int readInt(String prompt) {
while (true) {
System.out.print(prompt);
String line = input.nextLine().trim();
try {
return Integer.parseInt(line);
} catch (NumberFormatException ex) {
System.out.println("Please enter a valid integer.");
}
}
}
private String readNonEmptyLine(String prompt) {
while (true) {
System.out.print(prompt);
String line = input.nextLine().trim();
if (!line.isEmpty()) return line;
System.out.println("Input cannot be empty.");
}
}
private String readTag(String prompt) {
// Normalize tags to lower-case so "Urgent" and "urgent" become the same tag.
return readNonEmptyLine(prompt).toLowerCase();
}
private void displayTask(Task task, int index) {
System.out.printf(
"%d id=%d | %s | completed=%s | priority=%s | tags=%s%n",
index,
task.getId(),
task.getTitle(),
task.isCompleted(),
task.getPriority(),
formatTags(task));
}
private void displayTask(Task task) {
System.out.printf(
"id=%d | %s | completed=%s | priority=%s | tags=%s%n",
task.getId(),
task.getTitle(),
task.isCompleted(),
task.getPriority(),
formatTags(task));
}
private String formatTags(Task task) {
if (task.getTags().isEmpty()) return "\[\]";
// Display tags in sorted order for readability (TreeSet view)
return new TreeSet<>(task.getTags()).toString();
}
private int chooseTaskIndex() {
if (tasks.isEmpty()) {
System.out.println("No tasks available.");
return -1;
}
viewAllTasks();
int index = readInt("Enter task index: ");
if (index < 0 || index >= tasks.size()) {
System.out.println("Invalid task index.");
return -1;
}
return index;
}
private PriorityLevel choosePriorityLevel() {
System.out.println("1. HIGH");
System.out.println("2. MEDIUM");
System.out.println("3. LOW");
int choice = readInt("Choose a priority: ");
return switch (choice) {
case 1 -> PriorityLevel.HIGH;
case 2 -> PriorityLevel.MEDIUM;
case 3 -> PriorityLevel.LOW;
default -> {
System.out.println("Invalid choice. Defaulting to LOW.");
yield PriorityLevel.LOW;
}
};
}
// ------------------------------------------------------------
// Nested model types
// ------------------------------------------------------------
private enum ActionType {
ADD,
EDIT,
REMOVE,
COMPLETE,
MOVE_TO_QUEUE,
PROCESS_QUEUE,
ASSIGN_PRIORITY,
ADD_TO_PRIORITY_QUEUE,
PROCESS_PRIORITY_QUEUE,
ADD_TAG
}
private enum PriorityLevel {
HIGH(3),
MEDIUM(2),
LOW(1);
private final int value;
PriorityLevel(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
private static class Task {
private final int id;
private String title;
private boolean completed;
private PriorityLevel priority;
// Week 6: tags stored in a Set => no duplicates
private final Set<String> tags = new HashSet<>();
public Task(int id, String title) {
this.id = id;
this.title = title;
this.completed = false;
this.priority = PriorityLevel.LOW;
}
public int getId() { return id; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public boolean isCompleted() { return completed; }
public void setCompleted(boolean completed) { this.completed = completed; }
public PriorityLevel getPriority() { return priority; }
public void setPriority(PriorityLevel priority) { this.priority = priority; }
public int getPriorityValue() { return priority.getValue(); }
// Set-based operations
public boolean addTag(String tag) { return tags.add(tag); }
public boolean hasTag(String tag) { return tags.contains(tag); }
public Set<String> getTags() { return tags; }
@Override
public String toString() {
return "id="
-
id
-
" | "
-
title
-
" | completed="
-
completed
-
" | priority="
-
priority
-
" | tags="
-
new TreeSet<>(tags);
}
}
private static class ActionRecord {
private final ActionType actionType;
private final Task task;
private final String details;
public ActionRecord(ActionType actionType, Task task, String details) {
this.actionType = actionType;
this.task = task;
this.details = details;
}
@Override
public String toString() {
String taskSummary = task == null ? "n/a" : ("id=" + task.getId() + ", title=" + task.getTitle());
return actionType + " | task=" + taskSummary + " | details=" + details;
}
}
}
Week 8
0. 本周主题( Week 08 Theme )
- 前两周学习了经典数据结构(classic data structures):列表(lists)、栈(stacks)、队列(queues)、优先队列(priority queues)、集合(sets)、映射(maps),并用它们解决问题。
- 本周转向常见算法设计技巧(algorithmic techniques):动态规划(dynamic programming)、分治(divide-and-conquer)、回溯(backtracking),目标是开发高效算法(efficient algorithms)。
目标( objectives )
- 从大 O(Big O)与增长率(growth-rate analysis)开始,学会用复杂度(complexity)比较算法。
- 经典例子(classic examples):二分查找(binary search)、选择排序(selection sort)、汉诺塔(Tower of Hanoi)。
- 深入主题(study):动态规划(dynamic programming)、欧几里得算法(Euclidean algorithm)、埃氏筛(sieve of Eratosthenes)、分治(divide-and-conquer)。
- 最后(finish):八皇后(Eight Queens)、凸包构造(convex hull construction)、字符串匹配(string matching)。
1. 衡量算法效率( Measuring Algorithm Efficiency )
1.1 什么是算法效率
- 问题:两个算法做同一件事(same task),比如线性查找 vs 二分查找(linear search vs binary search),哪个更好?
- 直接跑程序比执行时间(execution time)不可靠,原因有两点:
- 多任务并发(concurrency):程序实际运行时间受系统负载(system load)影响。
- 输入相关(input-specific):例如目标刚好在开头,线性查找可能比二分查找更快。
- 因此需要理论分析(theoretical approach):让比较独立于具体机器与特定输入(independent of computers and specific input),关注输入规模变化的影响(effect of input size change)。
- 用增长率(growth rates)来比较:看输入变大时,运行时间增长有多快(how fast runtime increases as input size increases)。
1.2 增长率( Growth Rates )例:线性查找( linear search )
- 线性查找逐个比较(sequentially compares),直到找到或数组耗尽(found or exhausted)。
- 若 key 不在数组:需要 n 次比较(n comparisons, worst case)。
- 若 key 在数组:平均需要 n/2 次比较(n/2 comparisons on average)。
- 结论:执行时间与数组大小成正比(proportional to n);数组规模翻倍,比较次数也近似翻倍(double n → double comparisons)。
- 所以是线性增长(linear rate),数量级(order of magnitude)是 n。
2. Big O 记号( Big O Notation )
2.1 Big O 的三种分析( cases )
- Big O 的组织方式:
- 先描述增长率(describe growth rate)
- 再说明讨论的是最好/平均/最坏(best/average/worst case)
- 三种情况(cases):
- 最好情况(best case):最短时间(shortest time)
- 平均情况(average case):典型时间(typical time)
- 最坏情况(worst case):最长时间(longest time),并且是最常用(most used),因为给出安全上界(safe upper bound)。
- 同一输入规模(same input size)下,不同输入会触发不同情况(different inputs trigger different cases),所以执行时间仍可能变化。
2.2 Big O 要忽略什么( What to Ignore )
目的:只保留控制长期增长的部分(keep only the part that controls long-term growth)
- 忽略乘法常数(multiplicative constants):O(n)=O(n/2)=O(100n) 同一数量级(same order of magnitude)
- 因为增长比例(growth ratio)一样:n 翻倍时都翻倍(doubling n doubles them)
- 忽略非主导项(nondominating terms):n-1 → O(n) 因为 n 主导(dominant term)
- 常数时间(constant time)运行时间不随 n 变化, 是 O(1)
- 常见求和(summations):
- 1 + 2 + ... + (n−1) = n(n−1)/2 → O(n²)
- 1 + 2 + ... + n = n(n+1)/2 → O(n²)
2.3 时间与空间( Time vs Space Complexity )
- 时间复杂度(time complexity):算法运行多久(how long)。
- 空间复杂度(space complexity):算法用多少内存(how much memory)。
- 例子强调:输入数组随着 n 增长会占更多格子(allocated cells grows with n),所以空间是 O(n);少量辅助变量(helper variables)是 O(1)。
3. 如何判断 Big O :循环与分支( Determining Big O )
3.1 单层循环( single loop )
- 示例:for (i = 1; i <= n; i++) { k = k + 5; }
- 单次更新是常数成本 c(constant cost c)。
- 循环执行 n 次:T(n) = c * n → O(n)。
3.2 双层循环( double loop )
- 示例:for i=1..n 内层 for j=1..n,每次做常数操作。
- 总次数 n × n:T(n) = c * n * n → O(n²)。
- 截图也用"执行网格(execution grid)"展示工作量随 n² 增长(quadratic growth)。
3.3 依赖型双循环( dependent nested loop )
- 示例:内层跑到 j <= i(inner loop depends on i)。
- 总工作量是三角形求和(triangular sum):c + 2c + ... + nc。
- 化简仍是 O(n²)(still quadratic)。
3.4 内层是常数( constant inner loop )
- 示例:内层固定执行 20 次(inner loop fixed at 20),外层执行 n 次。
- T(n) = 20 * c * n → O(n)。
- 关键点:固定次数只算常数因子(constant factor),不改变 Big O。
3.5 顺序两段代码( two loop sequences )
- 先一个固定 10 次的循环(fixed loop 10c),再一个"外层 n 次、内层固定 20 次"的结构(20cn)。
- 总和:10c + 20cn → 由主导项决定(dominant term)→ O(n)。
- 关键点:顺序执行是加法(additive),最后看增长最快的那段(fastest-growing part)。
3.6 分支( selection statement )按最坏路径( worst-case path )
- if 条件用了 list.contains(e):对 n 个元素的 list 是 O(n)。
- else 分支里也遍历 list:也是 O(n)。
- 分支分析看最坏路径(worst-case path):O(n) + O(n) → O(n)。
- 核心结论:分支不取平均,而取最贵路径(take the most expensive path)。
3.7 对数级( logarithmic )例:重复平方( repeated squaring )
- 计算 a^n:朴素方法乘 n 次是 O(n)。
- 改进方法:重复平方(repeated squaring)把循环次数降到 log n(reduce loop count to log n)。
- 结论:改进后是 O(log n)(logarithmic)。
- 截图也强调:对数增长很慢(grows very slowly),对数底数不影响 Big O(log base doesn't matter in Big O)。
4. 经典复杂度例子( Classic Examples )
4.1 二分查找( Binary Search )
二分查找(Binary Search)用于已经排序的数组(sorted array)。它每一轮只做固定数量的工作,同时把剩余搜索范围减半。
- 每轮做常数工作并"折半"(halve):递推式 T(n)=T(n/2)+c → O(log n)。
4.2 选择排序( Selection Sort )
选择排序(Selection Sort)每一轮从未排序部分中找到最小值,然后把它交换到正确位置。
- 每一趟找最小值:比较次数 (n-1)+(n-2)+...+1 → O(n²)。
4.3 汉诺塔( Tower of Hanoi )
要求把 n 个盘子从塔 A 移动到塔 B,并借助塔 C。
- 先把上面的 n - 1个盘子从 A 移到 C。
- 把最大的第 n 个盘子从 A 移到 B。
- 再把 n - 1 个盘子从 C 移到 B。
- 递推:T(n)=2T(n-1)+1 → O(2ⁿ) 指数级(exponential)。
- 文档强调指数增长非常快:盘子数增加会导致移动次数爆炸。
4.4 常见递推关系速判( common recurrences )

- T(n)=T(n/2)+O(1) → O(log n)(binary search / Euclid)。
- T(n)=T(n-1)+O(1) → O(n)(linear search)。
- T(n)=2T(n/2)+O(n) → O(n log n)(merge sort)。
- T(n)=2T(n-1)+O(1) → O(2ⁿ)(Hanoi)。
4.5 常见增长函数对比( growth functions )
文档用 n=25→50 的变化展示:
从慢到快大致是:O(1) < O(log n) < O(n) < O(n log n) < O(n²) < O(n³) < O(2ⁿ)

- Best scaling(最佳扩展性):O(1), O(log n)
- Moderate scaling(中等扩展性):O(n), O(n log n)
- Poor scaling(较差扩展性):O(n²), O(n³), O(2ⁿ)
5. 动态规划( Dynamic Programming )
5.1 斐波那契:朴素递归( naive recursion )
直接递归计算斐波那契数(recursive Fibonacci)很容易写,但效率很低,因为它会重复计算大量相同的子问题。
原始递归方法
-
fib(0) = 0
-
fib(1) = 1
-
fib(n) = fib(n - 1) + fib(n - 2)
-
递归 fib:T(n)=T(n-1)+T(n-2)+c,文档指出会接近 O(2ⁿ),因为反复计算相同子问题(repeated subproblems)。
5.2 斐波那契:动态规划改进( DP Fibonacci )
- 通过保存连续的 f0,f1,f2 并迭代更新(iteratively update consecutive values),避免重复递归,时间降为 O(n)。
- DP 核心:子问题(subproblems)只求一次并复用(store and reuse)。
6. 最大公约数 GCD ( Greatest Common Divisor )
最大公约数(Greatest Common Divisor, GCD)是能够同时整除两个整数的最大整数。
6.1 暴力法( brute force )
- 逐个试除数 k:最多跑到 n,时间 O(n)。
6.2 欧几里得算法( Euclid's algorithm )
欧几里得算法(Euclid's Algorithm)用反复取余(remainder reduction)代替逐个检查除数。
- 规则:若 m%n==0,则 gcd(m,n) = n;否则 gcd(m,n)=gcd(n,m%n)。
- 复杂度:最坏也只有 O(log n),因为参数会快速变小(shrinks quickly)。



6.3 最坏情况( worst case )
- 最长递归链出现在相邻斐波那契数(successive Fibonacci numbers),但仍是 O(log n)。
比较 GCD Algorithms Compare

主要区别 Main Difference
前两种方法都属于除数扫描(divisor scanning):
- 第一种检查所有可能除数。
- 第二种减少了一部分检查范围。
- 但它们仍然是在一个个试除数,所以最坏情况下还是线性时间。
欧几里得算法不同,它改变了问题结构:
- 不再扫描候选除数
- 使用余数不断缩小问题
- 每一步都把数字变小很多
7. 质数( Prime Numbers )

大于 1 的整数如果只有两个正因数:
- 1
- 它自己
7.1 早期版本:试除到 n/2 ( divisors up to n/2 )
- 文档回顾:检查前 50 个质数时,对每个候选数试除到 number/2,代价很大。
程序不断增加 number:
- 假设当前数字是素数。
- 用 divisor 从 2 检查到 number / 2。
- 如果发现 number % divisor == 0,说明它不是素数。
- 如果没有找到任何除数,就打印该素数并增加计数。
- 当已经找到 50 个素数时停止。
7.2 改进:试除到 √n ( up to sqrt )
- 判断质数只需要试除到 √number:若 2..√n 无因子,则为质数;复杂度从 O(n) 降到 O(√n)。
- 如果 number 有一个大于 √number 的因子,那么它一定对应一个小于 √number 的因子

7.3 再改进:只用已发现的质数做除数( prime divisors only )
- 避免重复计算平方根边界(avoid recomputing square-root boundary)。
- 只用已经发现的素数作为除数(prime divisors only)。
改进一:缓存平方根 SquareRoot Cache

其实 √97 一直都是同一个值,没必要反复算。

第一版程序可能在循环中反复计算:Math.sqrt(number)
改进方法是维护一个变量:squareRoot
只有当 number 跨过新的完全平方数(perfect square)时才更新它。
例如 int(Math.sqrt(number)) 只会在 number 跨过:
4, 9, 16, 25, 36, 49, ...时改变。
不用每次都调用 Math.sqrt(number),只要检查:if (squareRoot * squareRoot < number) squareRoot++; 来减少重复计算。
改进二:只检查素数除数 Prime Divisors Only

如果一个数是合数(composite ),它一定有一个不超过 √n 的素因子(prime factor)。
因此没必要尝试所有整数除数,只需要尝试已经找到的素数列表(list of primes)中的素数。
复用结果 Reuse Results
程序把已经发现的素数保存到 list 中,后续判断新数字时复用这些结果。
这有点像动态规划思想(dynamic-programming-style improvement):之前的计算结果会变成之后问题的子结果。
复杂度思想
文档中给出的估计是:
不超过 n 的素数数量大约是:
n / log n
不超过 √n 的素数数量大约是:
2√n / log n
因此每个数的素数除数检查数量比检查所有整数除数少很多。
整体结果
文档给出的整体复杂度形式是:
O(n√n / log n)
7.4 埃拉托色尼筛法( Sieve of Eratosthenes )

埃拉托斯特尼筛法(Sieve of Eratosthenes)通过在布尔数组中标记合数倍数,找出 n 以内所有素数。
- 核心思想
与其单独测试每一个候选数,不如一次性"筛掉"所有合数(composite multiples)。
程序维护一个布尔数组:primes\[\]
初始时假设所有位置都是 prime。
然后从 2 开始,如果当前数仍被标记为 prime,就把它的倍数标记为 false。
- 筛法步骤
- 建立布尔数组(boolean array),先假设每个数都是素数。
- 从 base = 2 开始。
- 如果 base 是素数,就把它的倍数标记为非素数。
- 只需要处理到 k * k <= n。
- 最后数组中仍为 true 的位置就是素数。
- 为什么只到平方根
如果一个合数大于 √n 的因子才被发现,那么它必然还有一个小于 √n 的因子已经处理过。
所以循环条件可以是:
k * k <= n
- 宽松上界 Loose Upper Bound
文档中用一个宽松上界说明筛法效率:n/2 + n/3 + n/5 + n/7 + ...
这个和小于:O(n log n)
文档进一步给出更好的说明:筛法通常远好于逐个测试每个数,实际常用复杂度理解更接近:O(n log log n)
但文档比较表使用的是保守上界。
8. 分治:最近点对( Divide-and-Conquer: Closest Pair )
最近点对问题(Closest Pair of Points )要求在一组点中找到欧氏距离(Euclidean distance)最小的两个点。
8.1 暴力( brute force )
- 两两比较距离:约 n(n-1)/2 次 → O(n²)。
8.2 分治( divide-and-conquer )


算法先按 x 坐标排序(sort by x-coordinate),然后:
- 将点集分成左右两半。
- 分别递归求左半部分和右半部分的最近点对。
- 得到左右两边的最小距离 d1 和 d2。
- 令 d = min(d1, d2)。
- 只检查中线附近宽度为 d 的窄条区域(narrow strip)。
- 返回左侧、右侧、跨中线三种情况中的最小值。
- 为什么只检查窄条
如果最近点对跨过分割线,那么它们距离必须小于当前最小值 d。
因此只有离中线距离不超过 d 的点才可能组成更优解。
- 合并步骤为什么是线性
把 strip 中的点按 y 坐标处理(y-order)时,每个点只需要与附近有限数量的候选点比较。
因此合并工作是:O(n)
- 递推:T(n)=2T(n/2)+O(n) → O(n log n)。
9. 回溯:八皇后( Backtracking: Eight Queens )

八皇后问题(Eight Queens Problem)是一个回溯问题(backtracking problem)。目标是在 8×8 棋盘上放置 8 个皇后,使它们互不攻击。
状态表示 State
文档使用一维数组:
queens\[\]
其中:
queensi
表示第 i 行(row i)的皇后放在哪一列(column)。
搜索规则 Search Rule
算法按行放置皇后:
- 从第 0 行开始。
- 在当前行尝试每一列。
- 如果当前位置合法,就递归搜索下一行。
- 如果后续失败,就撤销当前位置并尝试下一列。
- 如果所有行都成功放置,说明找到完整解。
成功与失败
- If Successful:移动到下一行;如果当前行是最后一行,说明找到了完整解。
- If Not Successful:回到上一行,继续尝试下一列。
- If The First Row Fails:如果第 0 行没有任何可行列,则说明无解。
合法性检查 isValid
一个位置必须满足:
- 不能与之前皇后在同一列。
- 不能在同一对角线。
对角线判断使用:
Math.abs(queensi - queensrow) == row - i
10. 凸包( Convex Hull )
10.1 问题定义( definition )
- 凸包(convex hull)是包含一组点的最小凸多边形(smallest convex polygon)。
- 给定一组点,凸包是能包住所有点的最小外边界。
如果一个点位于边界上,它就是凸包的一部分;内部点不会影响凸包轮廓。
黑点代表普通点(ordinary point),蓝点代表凸包顶点(hull vertex),蓝色边界表示凸包边界(convex hull boundary)

- 观察重点
移动或添加一个外部点,可能立即改变整个多边形边界;
但如果添加的是内部点,凸包可能不变。
10.2 礼物包装( Gift-wrapping / Jarvis march )
- 从"最右且最低点"(rightmost lowest point)开始,重复扫描所有点找下一个最外点(outermost)。
- 步骤
- 选择最右下点(rightmost lowest point)作为起始 hull vertex。
- 扫描所有点,找到从当前顶点出发的下一个最外侧点。
- 重复,直到回到起点。
- 复杂度:若凸包顶点数为 h,总点数 n,则 O(hn)。
10.3 Graham 扫描( Graham's algorithm / Graham scan )
Graham 算法(Graham's Scan)先选一个锚点(anchor point),按极角排序,再用栈保留凸包边界。
核心思想
先选择最右下点作为锚点 p0,然后将其他点按相对于 p0 的角度排序(polar angle order)。
之后按顺序扫描点:
- 如果形成左转(left turn),保留。
- 如果形成右转或向内弯(bend inward),弹出栈顶点。
- 最终栈中剩下的点就是凸包顶点。
步骤
- 选择锚点 p0。
- 按极角对剩余点排序。
- 先把前三个点压入栈。
- 继续处理剩余点:
- 遇到右转就 pop。
- 遇到左转就 push。
- 栈中点构成最终 hull。
- 排序 O(n log n) + 扫描线性(each point pushed once, popped at most once)→ 总 O(n log n)。
优于 Gift-Wrapping 的 O(hn)。
11. 字符串匹配( String Matching )
11.1 暴力匹配( brute force )
- 文本长度是 n
- 模式串长度是 m
最坏情况下有:n - m + 1个对齐位置,每个位置最多比较 m 个字符。
- 对 text 的每个对齐位置 i(alignment)尝试比较 pattern 的每个字符,找到第一个匹配位置;最坏 O(nm)。
11.2 Boyer--Moore (坏字符规则 bad-character shift )
- 从右往左比(right-to-left compare),利用不匹配字符决定跳跃(shift farther)。
- 核心思想
比较方向从 pattern 的右端开始:
right to left
如果发生不匹配(mismatch),就根据文本中的失配字符决定移动距离。
- 坏字符规则 Bad-Character Shift
文档中的演示强调:
- 如果失配的文本字符没有出现在 pattern 中,可以直接把 pattern 跳过该字符。
- 如果该字符出现在 pattern 更早的位置,就把 pattern 中的最后一次出现对齐到该文本字符。
- 两种情况
Case 1:失配文本字符不在 pattern 剩余部分中
→ pattern 可以整体跳过该字符。
Case 2:失配字符在 pattern 中更早出现
→ 将 pattern 中该字符最后一次出现的位置与文本字符对齐。
- 文档说明:平均更快,但该简化版本最坏仍可能 O(nm)。
11.3 KMP ( Knuth--Morris--Pratt )

KMP(Knuth-Morris-Pratt)算法通过预处理 pattern 得到失败函数(failure function),从而在失配后复
用已经成功匹配的前缀信息。
- 核心思想
暴力法或 Boyer-Moore 在失配时可能重新开始一个新的对齐。
KMP 不让文本指针回退,而是根据 pattern 的已知结构移动 pattern 指针。
也就是说,KMP 会复用:
successful partial matches
- Failure Array
失败数组(failure array)记录 pattern 中每个位置之前,最长的相同前缀/后缀关系。
当发生失配时,算法不回到 pattern 开头,而是跳到:
failk - 1
对应的位置继续比较。
- 搜索过程
设:
- i 是 text 指针
- k 是 pattern 指针
如果字符匹配:
- i++
- k++
如果不匹配且 k > 0:
- k = failk - 1
如果不匹配且 k == 0:
- i++
- 成本 Cost
失败函数预处理和搜索过程加起来是线性的:O(n + m)
- 为什么 KMP 不同
KMP 在长的部分匹配后发生失配时,可以立即退回到较短但已知有效的前缀状态,而不是从头开始。
这就是它比暴力匹配更高效的原因。
12. 总结:算法设计的主线( Main Design Lessons )
- 选择算法时问:输入规模缩小得有多快(how fast input shrinks),能否复用以前的计算(reuse previous work),是否能剪枝(prune bad branches),主导项(dominant term)如何随 n 增长。
- 文档归纳:最大提升通常来自------更激进地减小问题规模(aggressive reduction)、存储可复用中间结果(store reusable intermediates)、预计算结构(precompute structure)、减少无谓比较(avoid unnecessary comparisons)。
lab sheet
0) 实验定位与主题( Lab purpose & theme )
- 本实验是在 Week 6 系统基础上继续扩展(extend Week 6 system),引入:
- 大 O 记号(Big O notation):时间复杂度(time complexity)与空间复杂度(space complexity)
- 面向性能的重构(performance-aware refactoring)
- 基于效率的显式约束(explicit design constraints based on efficiency)
- "更快查询 vs 更多内存"的权衡(trade-offs: faster queries vs extra memory)
- 要求:必须继续使用 Weeks 5&6 的同一个 todo 系统(same todo management system),不能新建程序(do not create a new program)。每个任务都是在原系统上优化(improve existing feature/data-access pattern)。
- Week 8 核心:从"正确性(correctness)"转向"效率(efficiency)",分析瓶颈(bottlenecks)并让常用操作可扩展(scale efficiently)。
1) 复杂度符号( Complexity symbols )
文档规定在分析中使用这些符号(symbols):
- n:主系统任务数量(number of tasks)
- u:唯一标签数(number of unique tags)
- T:全系统标签绑定总数(total tag assignments across all tasks)
- k:某个 tag 匹配到的任务数量(tasks matching a given tag)
- m:单个任务拥有的标签数(tags on one selected task)
- q:FIFO 队列中的任务数(tasks in FIFO queue)
- p:优先队列中的任务数(tasks in priority queue)
- 默认:用最坏情况(worst-case),忽略常数和非主导项(ignore constants, nondominating terms)。
2) Task 1 :现有系统复杂度审计( Complexity audit )
2.1 场景( Scenario )
- 改进前先找会随输入变大变慢的操作(identify operations expensive as input grows)。
2.2 必做内容( Required features )
你要做:
- 指出选定操作的当前时间复杂度(time complexity)。
- 说明主结构的辅助空间(auxiliary space)。
- 找出至少 3 个可能在大输入下低效的操作(inefficient at scale)。
- 把分析记录在复杂度表(complexity table):注释/markdown/report 均可。
2.3 指定要分析的方法( Methods to analyze )
至少包括(at least):
- lookupTaskById()
- viewTasksByStatus()
- filterTasksByTag()
- viewAllTags()
- viewTagFrequency()
- viewSortedTags()
- moveTaskToQueue() 的重复检查(duplicate check)
- addTaskToPriorityQueue() 的重复检查(duplicate check)
2.4 约束( Constraints )
- 先不要改变用户可见行为(do not change user-visible behaviour yet)。
- 以 Week 6 完整系统为基线(baseline)。
- 每个方法写:最坏时间(worst-case time)、必要时的平均情况说明(average-case notes for hash)、额外空间(extra space)。
- 忽略逐字符打印成本(ignore character-by-character output cost)。
2.5 预期结果( Expected outcome )
能区分:
- 已经高效的(already efficient),如 map-based ID lookup
- 重复扫描全系统的(repeated full scans)
- 用额外内存换速度的(trade memory for speed)
3) Task 2 :用索引加速标签过滤( Fast tag filtering with an index )
3.1 问题( Problem )
- Week 6 的 tag 过滤会变慢(inefficient when many tasks),因为每次过滤都可能要检查所有任务(scan every task each time)。
3.2 目标( Goal )
- 建立"标签 → 任务集合"的直接索引(direct tag index)。
3.3 必做( Required features )
- 添加结构:每个 tag 映射到包含该 tag 的任务(map each tag to tasks containing it)。
- 用该结构实现直接过滤(direct filtering)。
- 保持索引一致(keep consistent)在:新增 tag、删除任务、未来若支持删 tag。
- 显示匹配任务时不要扫描整个主列表(no full scan)。
- 安全处理未知 tag 与空结果(handle unknown tags/empty results)。
3.4 建议结构( Suggested structure )
- Map<String, Set<Integer>> tasksByTag = new HashMap<>();
- Set 里放任务 ID(task IDs),再用现有 taskMap 做 O(1) 查回 Task。
3.5 复杂度要求( Time/space constraints )
- filterTasksByTag(tag):平均 O(1 + k)(hash-based)+ 输出成本。
- 若用树结构保证有序(tree-based),可为 O(log u + k),但要解释。
- 新增 tag 后更新索引:平均 O(1)。
- 删除任务更新索引:O(m)。
- 额外空间:索引可能增长到 O(T)。
- 设计提示:HashSet 不保证顺序;若要排序可用树 Set,但更新更贵(higher update cost)。重点是快查(fast access),不是保持原 List 顺序。
4) Task 3 :增量维护标签统计( Incremental tag analytics )
4.1 问题( Problem )
- Week 6 的很多报表是每次都"从头重建"(full recomputation / rebuild),小数据可以,大数据会浪费。
4.2 必做( Required features )
- 让 allTags 随任务/标签变化自动保持最新(keep updated)。
- 让 tagFrequency 随变化自动更新(keep updated)。
- 不要每次请求报表都扫描所有任务重建(remove rebuild scans)。
- 仍需正确支持:查看所有 tags、频率、最频繁 tag、hash vs tree 对比、system overview。
- 解释你是偏向"更快更新"还是"更快查询"还是折中(update vs query trade-off)。
4.3 复杂度要求( Time/space constraints )
- 查询类方法不能通过扫描所有任务重建(must not rebuild by scanning every task)。
- 给一个任务新增一个 tag:平均 O(1) 更新统计。
- 删除任务:O(m) 更新统计。
- viewTagFrequency():在维护好后应为 O(u)。
- viewMostFrequentTag():
- 可以 O(u)(每次扫描 frequency map),或
- O(1)(维护额外状态),但要解释额外空间与更新成本。
4.4 有序视图策略( Sorted view strategy )
- 方案 1:按需排序(sort on demand)------展示时临时建 TreeSet/TreeMap(updates simpler, queries slower)。
- 方案 2:持续维护有序结构(maintain continuously)------查询快但更新更贵、内存更多(faster sorted queries, expensive updates, more memory)。
- 需要用 Big O 简要论证选择。
5) Task 4 :更快的状态访问( Faster status-based access )
5.1 问题( Problem )
- Week 6 按完成/未完成显示是扫描整个任务列表(scan full list),频繁调用浪费。
5.2 必做( Required features )
- 加一个"完成/未完成索引"(status index)。
- 在新增任务、标记完成、删除任务时更新索引(keep consistent)。
- 用索引展示,不扫描全表(no full scan)。
- 菜单行为和输出语义保持一致(same behaviour/meaningful output)。
- 正确处理空结果(handle empty results)。
5.3 建议结构( Implementation constraints )
二选一:
- Set<Integer> completedTaskIds + Set<Integer> incompleteTaskIds
- Map<Boolean, Set<Integer>> tasksByCompletion
并复用 taskMap 做快速取 Task(efficient retrieval)。
5.4 复杂度要求( Time/space constraints )
- 查看 completed:O(c)(c=完成任务数)。
- 查看 incomplete:O(i)(i=未完成任务数)。
- 标记完成:平均 O(1) 更新索引。
- 额外空间:O(n)。
6) 可选挑战:更快的队列重复检查( Optional: Faster queue membership checks )
6.1 问题( Problem )
- Week 6 里 FIFO queue / priority queue 的 contains() 检查是线性的(linear):
- LinkedList contains → O(q)
- PriorityQueue contains → O(p)
6.2 目标( Goal )
- 用额外结构让重复检查变成平均 O(1)(constant average time)。
6.3 建议结构( Suggested structures )
- Set<Integer> queuedTaskIds
- Set<Integer> priorityQueuedTaskIds
6.4 约束( Constraints )
- Membership checks:平均 O(1)。
- 插入/移除时必须保持 set 一致(keep consistent)。
- 额外空间:O(q + p)。
7) 提交要求与复杂度表( Submission requirements & complexity table )
7.1 你要提交( Submit )
- 更新后的 Java 程序(updated program)。
- 一份复杂度总结(complexity summary):改进特性的主要时间/空间复杂度。
- 至少一个设计权衡解释(design trade-off explanation):
- hash vs tree
- on-demand recompute vs maintaining index
- faster query vs extra memory
7.2 文档建议的复杂度表模板( Suggested table )
示例行包括:
- Look up by ID:O(1) avg → O(1) avg(existing map)
- Filter by tag:≈ O(T + n) → O(1 + k) avg(extra space O(T))
- View all tags:O(T) → O(u)(existing tag set)
- View tag frequency:O(T) → O(u)(existing frequency map)
- View tasks by status:O(n) → O(c) / O(i)(extra space O(n))
- Queue duplicate check:O(q) → optional O(1) avg(extra space O(q))
7.3 讨论题( Discussion questions )
- 为什么 lookupTaskById 在任务变大时仍高效(efficient at large n)?
- 为什么 tag index 能提升重复过滤(improves repeated filtering)?
- 哪个操作因为多用内存而变快(faster due to extra memory)?
- 如果经常需要排序输出,什么时候 tree 值得(tree-based worth it)?
- 为什么写 Big O 忽略常数(ignore constants)?
TodoSystemWeek8Improved
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Scanner;
import java.util.Set;
import java.util.Stack;
import java.util.TreeMap;
import java.util.TreeSet;
// Week8 改进版:用索引与增量维护提升查询效率,用空间换时间。
// Week8 improved: use indexes + incremental maintenance to speed up queries (space-time tradeoff).
public class TodoSystemWeek8Improved {
// 输入读取器:使用 nextLine+parseInt 避免 nextInt/nextLine 混用问题。
// Input reader: use nextLine+parseInt to avoid nextInt/nextLine newline issues.
private final Scanner input = new Scanner(System.in);
// 主任务列表:用于按索引展示与编辑(保持插入顺序)。
// Main task list: keeps insertion order for index-based UI operations.
private final List<Task> tasks = new ArrayList<>();
// FIFO 处理队列:按先进先出处理任务。
// FIFO processing queue: tasks are processed in first-in-first-out order.
private final Queue<Task> processingQueue = new LinkedList<>();
// 动作历史栈:用于记录最近操作(后进先出)。
// Action history stack: records recent actions in LIFO order.
private final Stack<ActionRecord> recentActions = new Stack<>();
// 优先队列:按 priorityValue(高→低)再按 title 排序。
// Priority queue: orders by priorityValue (high→low) then by title.
private final PriorityQueue<Task> priorityQueue = createPriorityQueue();
// Week6:ID→Task 的查找表,平均 O(1) 按 ID 取任务。
// Week6: ID→Task map for O(1) average lookup by ID.
private final Map<Integer, Task> taskMap = new HashMap<>();
// Week8:唯一标签集合(插入顺序),避免每次重建标签集合。
// Week8: unique tag set (insertion order), avoids rebuilding tags repeatedly.
private final Set<String> allTags = new LinkedHashSet<>();
// Week8:标签出现次数统计,增量维护用于快速输出频率。
// Week8: tag frequency counter, incrementally maintained for fast reporting.
private final Map<String, Integer> tagFrequency = new HashMap<>();
// Week8:tag→任务ID集合索引,过滤从 O(n) 降为平均 O(1+k)。
// Week8: tag→task IDs index, filtering drops from O(n) to avg O(1+k).
private final Map<String, Set<Integer>> tasksByTag = new HashMap<>();
// Week8:完成任务ID集合,用于按状态直接访问(不扫描全 tasks)。
// Week8: completed task IDs set for direct status view (no full scan).
private final Set<Integer> completedTaskIds = new LinkedHashSet<>();
// Week8:未完成任务ID集合,用于按状态直接访问(不扫描全 tasks)。
// Week8: incomplete task IDs set for direct status view (no full scan).
private final Set<Integer> incompleteTaskIds = new LinkedHashSet<>();
// 可选挑战:队列成员ID集合,避免 LinkedList.contains 的 O(q)。
// Optional: queued IDs set avoids LinkedList.contains O(q) duplicate checks.
private final Set<Integer> queuedTaskIds = new HashSet<>();
// 可选挑战:优先队列成员ID集合,避免 PriorityQueue.contains 的 O(p)。
// Optional: priority-queued IDs set avoids PriorityQueue.contains O(p) duplicate checks.
private final Set<Integer> priorityQueuedTaskIds = new HashSet<>();
// 自增任务ID:保证每个任务有唯一ID。
// Auto-increment task ID: ensures each task has a unique ID.
private int nextTaskId = 1;
// 程序入口:创建系统并启动主循环。
// Entry point: create system instance and start the main loop.
public static void main(String\[\] args) {
TodoSystemWeek8Improved system = new TodoSystemWeek8Improved();
system.run();
}
// 主循环:菜单驱动(menu-driven)直到用户选择退出。
// Main loop: menu-driven interaction until the user chooses to exit.
public void run() {
boolean running = true;
while (running) {
printMainMenu();
int choice = readInt("Choose an option: ");
switch (choice) {
case 1:
addTask();
break;
case 2:
viewAllTasks();
break;
case 3:
editTask();
break;
case 4:
removeTask();
break;
case 5:
markTaskCompleted();
break;
case 6:
viewTasksByStatus();
break;
case 7:
moveTaskToQueue();
break;
case 8:
viewProcessingQueue();
break;
case 9:
viewNextQueueTask();
break;
case 10:
processNextQueueTask();
break;
case 11:
viewMostRecentAction();
break;
case 12:
removeMostRecentActionFromHistory();
break;
case 13:
viewRecentActionHistory();
break;
case 14:
assignPriorityToTask();
break;
case 15:
addTaskToPriorityQueue();
break;
case 16:
viewPriorityQueue();
break;
case 17:
viewHighestPriorityTask();
break;
case 18:
processHighestPriorityTask();
break;
case 19:
addTagToTask();
break;
case 20:
checkTaskTagMembership();
break;
case 21:
viewAllTags();
break;
case 22:
filterTasksByTag();
break;
case 23:
lookupTaskById();
break;
case 24:
viewTagFrequency();
break;
case 25:
viewMostFrequentTag();
break;
case 26:
viewSortedTags();
break;
case 27:
viewSortedTagFrequencies();
break;
case 28:
compareTagViews();
break;
case 29:
viewSystemOverview();
break;
case 0:
running = false;
System.out.println("Exiting Todo System.");
break;
default:
System.out.println("Invalid option. Please try again.");
}
}
}
// 菜单打印:按 Week5 + Week8 功能分类展示。
// Menu printing: show grouped Week5 + Week8 features.
private void printMainMenu() {
System.out.println();
System.out.println("=== Todo System (Week 8 Improved) ===");
System.out.println("Week 5 - Core List Features");
System.out.println("1. Add task");
System.out.println("2. View all tasks");
System.out.println("3. Edit task");
System.out.println("4. Remove task");
System.out.println("5. Mark task as completed");
System.out.println("6. View tasks by status");
System.out.println();
System.out.println("Week 5 - Queue Features");
System.out.println("7. Move task to processing queue");
System.out.println("8. View processing queue");
System.out.println("9. View next queue task");
System.out.println("10. Process next queue task");
System.out.println();
System.out.println("Week 5 - Stack Features");
System.out.println("11. View most recent action");
System.out.println("12. Remove most recent action from history");
System.out.println("13. View recent action history");
System.out.println();
System.out.println("Week 5 - Priority Queue Features");
System.out.println("14. Assign priority to a task");
System.out.println("15. Add task to priority queue");
System.out.println("16. View priority queue");
System.out.println("17. View highest-priority task");
System.out.println("18. Process highest-priority task");
System.out.println();
System.out.println("Week 8 - Indexed Tag and Status Features");
System.out.println("19. Add tag to a task");
System.out.println("20. Check task tag membership");
System.out.println("21. View all unique tags");
System.out.println("22. Filter tasks by tag");
System.out.println("23. Look up task by ID");
System.out.println("24. View tag frequencies");
System.out.println("25. View most frequent tag");
System.out.println("26. View sorted tags");
System.out.println("27. View sorted tag frequencies");
System.out.println("28. Compare hash-based and tree-based views");
System.out.println("29. View system overview");
System.out.println("0. Exit");
}
// 添加任务:创建 Task 后同步更新 tasks、taskMap、状态索引。
// Add task: create Task and update tasks, taskMap, and status indexes consistently.
private void addTask() {
String title = readNonEmptyLine("Enter task title: ");
Task task = new Task(nextTaskId, title);
nextTaskId++;
tasks.add(task);
taskMap.put(task.getId(), task);
incompleteTaskIds.add(task.getId());
recordAction(ActionType.ADD, task, "Added a new task.");
System.out.println("Task added.");
displayTask(task, tasks.size() - 1);
}
// 查看全部任务:遍历 tasks 列表并按索引打印。
// View all tasks: iterate tasks list and print with list index.
private void viewAllTasks() {
System.out.println("=== All Tasks ===");
if (tasks.isEmpty()) {
System.out.println("No tasks available.");
return;
}
int index = 0;
for (Task task : tasks) {
displayTask(task, index);
index++;
}
}
// 编辑任务:修改 title 后若在优先队列中需刷新堆位置(remove+offer)。
// Edit task: after title change, refresh heap entry if task is in priority queue.
private void editTask() {
int index = chooseTaskIndex();
if (index == -1) {
return;
}
Task task = tasks.get(index);
String oldTitle = task.getTitle();
String newTitle = readNonEmptyLine("Enter the new title: ");
task.setTitle(newTitle);
refreshPriorityQueueEntry(task);
recordAction(ActionType.EDIT, task, "Changed title from '" + oldTitle + "' to '" + newTitle + "'.");
System.out.println("Task updated.");
displayTask(task, index);
}
// 删除任务:必须从主列表、队列、优先队列和所有索引结构中移除以保持一致性。
// Remove task: must remove from list, queues, priority queue, and all indexes for consistency.
private void removeTask() {
int index = chooseTaskIndex();
if (index == -1) {
return;
}
Task task = tasks.remove(index);
boolean removedFromQueue = processingQueue.remove(task);
if (removedFromQueue) {
queuedTaskIds.remove(task.getId());
}
boolean removedFromPriorityQueue = priorityQueue.remove(task);
if (removedFromPriorityQueue) {
priorityQueuedTaskIds.remove(task.getId());
}
removeTaskFromIndexes(task);
String details = "Removed task and updated indexes.";
if (removedFromQueue) {
details += " Also removed from processing queue.";
}
if (removedFromPriorityQueue) {
details += " Also removed from priority queue.";
}
recordAction(ActionType.REMOVE, task, details);
System.out.println("Task removed.");
}
// 标记完成:更新 Task.completed 并把 ID 从 incomplete 移到 completed。
// Mark completed: update Task.completed and move ID from incomplete set to completed set.
private void markTaskCompleted() {
int index = chooseTaskIndex();
if (index == -1) {
return;
}
Task task = tasks.get(index);
if (task.isCompleted()) {
System.out.println("That task is already marked as completed.");
displayTask(task, index);
return;
}
task.setCompleted(true);
incompleteTaskIds.remove(task.getId());
completedTaskIds.add(task.getId());
recordAction(ActionType.COMPLETE, task, "Marked task as completed.");
System.out.println("Task marked as completed.");
displayTask(task, index);
}
// 按状态查看:直接遍历 completedTaskIds/incompleteTaskIds,避免 O(n) 扫描。
// View by status: iterate completed/incomplete ID sets to avoid O(n) scanning.
private void viewTasksByStatus() {
System.out.println("1. View completed tasks");
System.out.println("2. View incomplete tasks");
int choice = readInt("Choose an option: ");
String heading;
Set<Integer> targetIds;
if (choice == 1) {
heading = "=== Completed Tasks ===";
targetIds = completedTaskIds;
} else if (choice == 2) {
heading = "=== Incomplete Tasks ===";
targetIds = incompleteTaskIds;
} else {
System.out.println("Invalid option.");
return;
}
System.out.println(heading);
if (targetIds.isEmpty()) {
System.out.println("No matching tasks found.");
return;
}
for (Integer taskId : targetIds) {
Task task = taskMap.get(taskId);
if (task != null) {
displayTask(task);
}
}
}
// 入队:用 queuedTaskIds 做 O(1) 平均重复检查,避免 LinkedList.contains 的 O(q)。
// Move to queue: use queuedTaskIds for O(1) avg duplicate check, avoiding O(q) contains.
private void moveTaskToQueue() {
int index = chooseTaskIndex();
if (index == -1) {
return;
}
Task task = tasks.get(index);
if (queuedTaskIds.contains(task.getId())) {
System.out.println("That task is already in the processing queue.");
return;
}
processingQueue.offer(task);
queuedTaskIds.add(task.getId());
recordAction(ActionType.MOVE_TO_QUEUE, task, "Moved task into FIFO processing queue.");
System.out.println("Task added to processing queue.");
}
// 查看队列:按队列迭代顺序打印(FIFO)。
// View queue: print in queue iteration order (FIFO).
private void viewProcessingQueue() {
System.out.println("=== Processing Queue ===");
if (processingQueue.isEmpty()) {
System.out.println("The processing queue is empty.");
return;
}
int position = 1;
for (Task task : processingQueue) {
System.out.print(position + ". ");
displayTask(task);
position++;
}
}
// 查看队首:peek 返回但不移除,若空则返回 null。
// View next: peek returns without removing; returns null if empty.
private void viewNextQueueTask() {
Task task = processingQueue.peek();
if (task == null) {
System.out.println("The processing queue is empty.");
return;
}
System.out.println("Next queue task:");
displayTask(task);
}
// 处理队首:poll 移除并返回队首,同时更新 queuedTaskIds。
// Process next: poll removes head and updates queuedTaskIds accordingly.
private void processNextQueueTask() {
Task task = processingQueue.poll();
if (task == null) {
System.out.println("The processing queue is empty.");
return;
}
queuedTaskIds.remove(task.getId());
recordAction(ActionType.PROCESS_QUEUE, task, "Processed the next task from FIFO queue.");
System.out.println("Processed task:");
displayTask(task);
}
// 查看最近动作:使用 Stack.peek() 读取栈顶但不弹出。
// View most recent action: Stack.peek() reads top without removing.
private void viewMostRecentAction() {
if (recentActions.isEmpty()) {
System.out.println("No recent actions recorded.");
return;
}
System.out.println("Most recent action:");
System.out.println(recentActions.peek());
}
// 删除最近动作:Stack.pop() 弹出栈顶动作。
// Remove most recent action: Stack.pop() removes the top action.
private void removeMostRecentActionFromHistory() {
if (recentActions.isEmpty()) {
System.out.println("No recent actions to remove.");
return;
}
ActionRecord removed = recentActions.pop();
System.out.println("Removed action from history:");
System.out.println(removed);
}
// 查看历史:从栈顶到栈底倒序打印(最近在前)。
// View history: iterate stack from top to bottom (most recent first).
private void viewRecentActionHistory() {
System.out.println("=== Recent Action History ===");
if (recentActions.isEmpty()) {
System.out.println("No recent actions recorded.");
return;
}
for (int i = recentActions.size() - 1; i >= 0; i--) {
System.out.println(recentActions.get(i));
}
}
// 记录动作:把 ActionRecord 压入栈中。
// Record action: push an ActionRecord onto the stack.
private void recordAction(ActionType actionType, Task task, String details) {
recentActions.push(new ActionRecord(actionType, task, details));
}
// 设置优先级:更新 Task.priority 后刷新优先队列中的位置。
// Assign priority: update Task.priority then refresh its priority-queue entry if present.
private void assignPriorityToTask() {
int index = chooseTaskIndex();
if (index == -1) {
return;
}
Task task = tasks.get(index);
PriorityLevel oldPriority = task.getPriority();
PriorityLevel newPriority = choosePriorityLevel();
task.setPriority(newPriority);
refreshPriorityQueueEntry(task);
recordAction(
ActionType.ASSIGN_PRIORITY,
task,
"Changed priority from " + oldPriority + " to " + newPriority + ".");
System.out.println("Priority updated.");
displayTask(task, index);
}
// 加入优先队列:用 priorityQueuedTaskIds 做 O(1) 平均重复检查。
// Add to priority queue: use priorityQueuedTaskIds for O(1) avg duplicate check.
private void addTaskToPriorityQueue() {
int index = chooseTaskIndex();
if (index == -1) {
return;
}
Task task = tasks.get(index);
if (priorityQueuedTaskIds.contains(task.getId())) {
System.out.println("That task is already in the priority queue.");
return;
}
priorityQueue.offer(task);
priorityQueuedTaskIds.add(task.getId());
recordAction(ActionType.ADD_TO_PRIORITY_QUEUE, task, "Added task to priority queue.");
System.out.println("Task added to priority queue.");
}
// 查看优先队列:PriorityQueue 迭代不保证有序,因此复制后排序再展示。
// View priority queue: PriorityQueue iteration is not sorted, so copy then sort for display.
private void viewPriorityQueue() {
System.out.println("=== Priority Queue ===");
if (priorityQueue.isEmpty()) {
System.out.println("The priority queue is empty.");
return;
}
List<Task> orderedView = new ArrayList<>(priorityQueue);
orderedView.sort(
Comparator.comparingInt(Task::getPriorityValue).reversed()
.thenComparing(Task::getTitle, String.CASE_INSENSITIVE_ORDER));
int position = 1;
for (Task task : orderedView) {
System.out.print(position + ". ");
displayTask(task);
position++;
}
}
// 查看最高优先级:peek 获取但不移除(若空返回 null)。
// View highest priority: peek returns without removal (null if empty).
private void viewHighestPriorityTask() {
Task task = priorityQueue.peek();
if (task == null) {
System.out.println("The priority queue is empty.");
return;
}
System.out.println("Highest-priority task:");
displayTask(task);
}
// 处理最高优先级:poll 移除堆顶,并同步更新 priorityQueuedTaskIds。
// Process highest priority: poll removes heap head and updates priorityQueuedTaskIds.
private void processHighestPriorityTask() {
Task task = priorityQueue.poll();
if (task == null) {
System.out.println("The priority queue is empty.");
return;
}
priorityQueuedTaskIds.remove(task.getId());
recordAction(ActionType.PROCESS_PRIORITY_QUEUE, task, "Processed the highest-priority task.");
System.out.println("Processed highest-priority task:");
displayTask(task);
}
// 添加标签:若 task.addTag(tag) 成功,增量更新 allTags/tagFrequency/tasksByTag。
// Add tag: if task.addTag(tag) succeeds, incrementally update all tag indexes.
private void addTagToTask() {
int index = chooseTaskIndex();
if (index == -1) {
return;
}
Task task = tasks.get(index);
String tag = readTag("Enter a tag: ");
if (task.addTag(tag)) {
addTagIndexes(task, tag);
recordAction(ActionType.ADD_TAG, task, "Added tag '" + tag + "'.");
System.out.println("Tag added.");
} else {
System.out.println("That task already has the tag '" + tag + "'.");
}
displayTask(task, index);
}
// 检查标签成员:task.hasTag(tag) 基于 HashSet,平均 O(1)。
// Check membership: task.hasTag(tag) uses HashSet, O(1) average.
private void checkTaskTagMembership() {
int index = chooseTaskIndex();
if (index == -1) {
return;
}
Task task = tasks.get(index);
String tag = readTag("Enter a tag to check: ");
if (task.hasTag(tag)) {
System.out.println("Task " + task.getId() + " contains the tag '" + tag + "'.");
} else {
System.out.println("Task " + task.getId() + " does not contain the tag '" + tag + "'.");
}
}
// 查看所有标签:直接遍历 allTags(增量维护),无需全量重建。
// View all tags: iterate allTags (incrementally maintained), no full rebuild needed.
private void viewAllTags() {
System.out.println("=== All Unique Tags ===");
if (allTags.isEmpty()) {
System.out.println("No tags available.");
return;
}
int position = 1;
for (String tag : allTags) {
System.out.println(position + ". " + tag);
position++;
}
}
// 按标签过滤:用 tasksByTag 直接得到匹配ID集合,平均 O(1+k)。
// Filter by tag: tasksByTag gives matching IDs directly, O(1+k) average.
private void filterTasksByTag() {
if (allTags.isEmpty()) {
System.out.println("No tags available.");
return;
}
String tag = readTag("Enter a tag to filter by: ");
Set<Integer> matchingTaskIds = tasksByTag.get(tag);
System.out.println("=== Tasks Tagged '" + tag + "' ===");
if (matchingTaskIds == null || matchingTaskIds.isEmpty()) {
System.out.println("No tasks found with that tag.");
return;
}
for (Integer taskId : matchingTaskIds) {
Task task = taskMap.get(taskId);
if (task != null) {
displayTask(task);
}
}
}
// 按ID查找:taskMap.get(id) 平均 O(1),不用扫描列表。
// Lookup by ID: taskMap.get(id) is O(1) average, avoids list scans.
private void lookupTaskById() {
if (taskMap.isEmpty()) {
System.out.println("No tasks available.");
return;
}
int id = readInt("Enter task ID: ");
Task task = taskMap.get(id);
if (task == null) {
System.out.println("No task found with ID " + id + ".");
return;
}
System.out.println("Task found:");
displayTask(task);
}
// 查看标签频率:遍历 tagFrequency 输出(O(u))。
// View tag frequency: iterate tagFrequency entries (O(u)).
private void viewTagFrequency() {
System.out.println("=== Tag Frequency ===");
if (tagFrequency.isEmpty()) {
System.out.println("No tags available.");
return;
}
for (Map.Entry<String, Integer> entry : tagFrequency.entrySet()) {
System.out.println(entry.getKey() + " -> " + entry.getValue());
}
}
// 最频繁标签:扫描 tagFrequency 找最大值(O(u)),平衡实现简单与性能。
// Most frequent tag: scan tagFrequency to find max (O(u)), simple vs performance tradeoff.
private void viewMostFrequentTag() {
if (tagFrequency.isEmpty()) {
System.out.println("No tags available.");
return;
}
System.out.println("Most frequent tag: " + getMostFrequentTagSummary());
}
// 排序标签:按需创建 TreeSet(O(u log u))展示排序视图。
// Sorted tags: build TreeSet on demand (O(u log u)) for sorted view.
private void viewSortedTags() {
System.out.println("=== Sorted Tags (TreeSet) ===");
if (allTags.isEmpty()) {
System.out.println("No tags available.");
return;
}
TreeSet<String> sortedTags = new TreeSet<>(allTags);
int position = 1;
for (String tag : sortedTags) {
System.out.println(position + ". " + tag);
position++;
}
}
// 排序频率:按需创建 TreeMap(按 key 排序)展示排序视图。
// Sorted frequencies: build TreeMap on demand (sorted by key) for display.
private void viewSortedTagFrequencies() {
System.out.println("=== Sorted Tag Frequency (TreeMap) ===");
if (tagFrequency.isEmpty()) {
System.out.println("No tags available.");
return;
}
TreeMap<String, Integer> sortedTagFrequency = new TreeMap<>(tagFrequency);
for (Map.Entry<String, Integer> entry : sortedTagFrequency.entrySet()) {
System.out.println(entry.getKey() + " -> " + entry.getValue());
}
}
// Hash vs Tree 对比:Hash 结构无序,Tree 结构排序;LinkedHashSet 保留插入序。
// Hash vs Tree: Hash is unordered, Tree is sorted; LinkedHashSet preserves insertion order.
private void compareTagViews() {
System.out.println("=== Hash vs Tree Comparison ===");
if (allTags.isEmpty()) {
System.out.println("No tags available.");
return;
}
System.out.println("allTags (insertion order): " + allTags);
System.out.println("TreeSet sortedTags (sorted): " + new TreeSet<>(allTags));
System.out.println("tagFrequency (hash map): " + tagFrequency);
System.out.println("TreeMap sortedTagFrequency (sorted): " + new TreeMap<>(tagFrequency));
}
// 系统概览:展示关键计数与常见视图,便于检查索引一致性。
// System overview: prints counts and views to help verify index consistency.
private void viewSystemOverview() {
System.out.println("=== System Overview ===");
System.out.println("Task count: " + tasks.size());
System.out.println("Task map size: " + taskMap.size());
System.out.println("Queue size: " + processingQueue.size());
System.out.println("Recent action count: " + recentActions.size());
System.out.println("Priority queue size: " + priorityQueue.size());
System.out.println("Unique tag count: " + allTags.size());
System.out.println("Completed tasks: " + completedTaskIds.size());
System.out.println("Incomplete tasks: " + incompleteTaskIds.size());
if (!allTags.isEmpty()) {
System.out.println("Sorted tags: " + new TreeSet<>(allTags));
System.out.println("Most frequent tag: " + getMostFrequentTagSummary());
}
if (!tasks.isEmpty()) {
System.out.println();
System.out.println("Main task list:");
viewAllTasks();
}
if (!processingQueue.isEmpty()) {
System.out.println();
System.out.println("FIFO queue:");
viewProcessingQueue();
}
if (!priorityQueue.isEmpty()) {
System.out.println();
System.out.println("Priority queue:");
viewPriorityQueue();
}
if (!tagFrequency.isEmpty()) {
System.out.println();
System.out.println("Sorted tag frequencies:");
viewSortedTagFrequencies();
}
if (!recentActions.isEmpty()) {
System.out.println();
System.out.println("Most recent activities (up to 10):");
int shown = 0;
for (int i = recentActions.size() - 1; i >= 0 && shown < 10; i--) {
System.out.println(recentActions.get(i));
shown++;
}
}
}
// 创建优先队列:比较器按 priorityValue 降序,再按标题不区分大小写。
// Create priority queue: comparator sorts by priorityValue desc then title case-insensitive.
private PriorityQueue<Task> createPriorityQueue() {
return new PriorityQueue<>(
Comparator.comparingInt(Task::getPriorityValue).reversed()
.thenComparing(Task::getTitle, String.CASE_INSENSITIVE_ORDER));
}
// 刷新优先队列条目:若任务已在队列中,则 remove+offer 以维护堆性质。
// Refresh PQ entry: if task is in PQ, remove+offer to restore heap order after changes.
private void refreshPriorityQueueEntry(Task task) {
if (priorityQueuedTaskIds.contains(task.getId()) && priorityQueue.remove(task)) {
priorityQueue.offer(task);
}
}
// 增量更新标签索引:更新 allTags、tagFrequency、tasksByTag 三者保持一致。
// Incremental tag index update: update allTags, tagFrequency, tasksByTag consistently.
private void addTagIndexes(Task task, String tag) {
allTags.add(tag);
tagFrequency.put(tag, tagFrequency.getOrDefault(tag, 0) + 1);
tasksByTag.computeIfAbsent(tag, ignored -> new LinkedHashSet<>()).add(task.getId());
}
// 删除任务索引:移除 taskMap、状态集合,并对该任务所有标签做反向更新。
// Remove task indexes: remove from taskMap/status sets and reverse-update all tag indexes.
private void removeTaskFromIndexes(Task task) {
taskMap.remove(task.getId());
completedTaskIds.remove(task.getId());
incompleteTaskIds.remove(task.getId());
for (String tag : task.getTags()) {
removeTagIndexes(task, tag);
}
}
// 反向更新标签索引:从 tasksByTag 删除ID,减少频率并在归零时清理 tag。
// Reverse tag index update: remove ID from tasksByTag, decrement frequency, cleanup if count hits 0.
private void removeTagIndexes(Task task, String tag) {
Set<Integer> taggedTaskIds = tasksByTag.get(tag);
if (taggedTaskIds != null) {
taggedTaskIds.remove(task.getId());
if (taggedTaskIds.isEmpty()) {
tasksByTag.remove(tag);
}
}
Integer currentCount = tagFrequency.get(tag);
if (currentCount == null) {
return;
}
if (currentCount <= 1) {
tagFrequency.remove(tag);
allTags.remove(tag);
} else {
tagFrequency.put(tag, currentCount - 1);
}
}
// 最频繁标签摘要:扫描频率表,若并列则按字母序选择更小的 tag。
// Most frequent tag summary: scan frequency map; tie-break by alphabetical order.
private String getMostFrequentTagSummary() {
String mostFrequentTag = null;
int highestCount = 0;
for (Map.Entry<String, Integer> entry : tagFrequency.entrySet()) {
String candidateTag = entry.getKey();
int candidateCount = entry.getValue();
if (mostFrequentTag == null
|| candidateCount > highestCount
|| (candidateCount == highestCount && candidateTag.compareTo(mostFrequentTag) < 0)) {
mostFrequentTag = candidateTag;
highestCount = candidateCount;
}
}
return mostFrequentTag + " (" + highestCount + ")";
}
// 读取整数:循环直到输入可解析为 int,避免程序崩溃。
// Read integer: loop until a valid int is parsed to prevent crashes.
private int readInt(String prompt) {
while (true) {
System.out.print(prompt);
String line = input.nextLine().trim();
try {
return Integer.parseInt(line);
} catch (NumberFormatException ex) {
System.out.println("Please enter a valid integer.");
}
}
}
// 读取非空行:用于标题等必须输入的字段。
// Read non-empty line: used for required fields like task title.
private String readNonEmptyLine(String prompt) {
while (true) {
System.out.print(prompt);
String line = input.nextLine().trim();
if (!line.isEmpty()) {
return line;
}
System.out.println("Input cannot be empty.");
}
}
// 读取标签:统一转小写实现规范化(normalization)。
// Read tag: normalize to lower-case for consistent matching.
private String readTag(String prompt) {
return readNonEmptyLine(prompt).toLowerCase();
}
// 显示任务(带索引):用于 tasks 列表显示。
// Display task (with index): used for list-based display.
private void displayTask(Task task, int index) {
System.out.printf(
"%d id=%d | %s | completed=%s | priority=%s | tags=%s%n",
index,
task.getId(),
task.getTitle(),
task.isCompleted(),
task.getPriority(),
formatTags(task));
}
// 显示任务(不带索引):用于队列/Map/索引查询结果显示。
// Display task (no index): used for queue/map/index-based outputs.
private void displayTask(Task task) {
System.out.printf(
"id=%d | %s | completed=%s | priority=%s | tags=%s%n",
task.getId(),
task.getTitle(),
task.isCompleted(),
task.getPriority(),
formatTags(task));
}
// 标签格式化:用 TreeSet 排序显示,方便阅读但不改变存储结构。
// Tag formatting: sort with TreeSet for readability without changing storage.
private String formatTags(Task task) {
if (task.getTags().isEmpty()) {
return "\[\]";
}
return new TreeSet<>(task.getTags()).toString();
}
// 选择任务索引:先显示所有任务再读入索引并校验范围。
// Choose task index: show tasks then read index and validate range.
private int chooseTaskIndex() {
if (tasks.isEmpty()) {
System.out.println("No tasks available.");
return -1;
}
viewAllTasks();
int index = readInt("Enter task index: ");
if (index < 0 || index >= tasks.size()) {
System.out.println("Invalid task index.");
return -1;
}
return index;
}
// 选择优先级:把用户输入映射到枚举 PriorityLevel。
// Choose priority: map user input to PriorityLevel enum.
private PriorityLevel choosePriorityLevel() {
System.out.println("1. HIGH");
System.out.println("2. MEDIUM");
System.out.println("3. LOW");
int choice = readInt("Choose a priority: ");
switch (choice) {
case 1:
return PriorityLevel.HIGH;
case 2:
return PriorityLevel.MEDIUM;
case 3:
return PriorityLevel.LOW;
default:
System.out.println("Invalid choice. Defaulting to LOW.");
return PriorityLevel.LOW;
}
}
// 动作类型枚举:用于 ActionRecord 标注操作类别。
// Action type enum: used by ActionRecord to label operations.
private enum ActionType {
ADD,
EDIT,
REMOVE,
COMPLETE,
MOVE_TO_QUEUE,
PROCESS_QUEUE,
ASSIGN_PRIORITY,
ADD_TO_PRIORITY_QUEUE,
PROCESS_PRIORITY_QUEUE,
ADD_TAG
}
// 优先级枚举:包含数值用于比较(HIGH=3, MEDIUM=2, LOW=1)。
// Priority enum: numeric values used for comparison (HIGH=3, MEDIUM=2, LOW=1).
private enum PriorityLevel {
HIGH(3),
MEDIUM(2),
LOW(1);
private final int value;
PriorityLevel(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
// 任务类:包含 id/title/completed/priority/tags(tags 用 Set 防重复)。
// Task model: includes id/title/completed/priority/tags (Set prevents duplicates).
private static class Task {
private final int id;
private String title;
private boolean completed;
private PriorityLevel priority;
private final Set<String> tags = new HashSet<>();
public Task(int id, String title) {
this.id = id;
this.title = title;
this.completed = false;
this.priority = PriorityLevel.LOW;
}
public int getId() {
return id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public boolean isCompleted() {
return completed;
}
public void setCompleted(boolean completed) {
this.completed = completed;
}
public PriorityLevel getPriority() {
return priority;
}
public void setPriority(PriorityLevel priority) {
this.priority = priority;
}
public int getPriorityValue() {
return priority.getValue();
}
public boolean addTag(String tag) {
return tags.add(tag);
}
public boolean hasTag(String tag) {
return tags.contains(tag);
}
public Set<String> getTags() {
return tags;
}
@Override
public String toString() {
return "id="
-
id
-
" | "
-
title
-
" | completed="
-
completed
-
" | priority="
-
priority
-
" | tags="
-
new TreeSet<>(tags);
}
}
// 动作记录类:记录动作类型、目标任务与细节文本。
// Action record: stores action type, target task, and detail message.
private static class ActionRecord {
private final ActionType actionType;
private final Task task;
private final String details;
public ActionRecord(ActionType actionType, Task task, String details) {
this.actionType = actionType;
this.task = task;
this.details = details;
}
@Override
public String toString() {
String taskSummary = task == null ? "n/a" : ("id=" + task.getId() + ", title=" + task.getTitle());
return actionType + " | task=" + taskSummary + " | details=" + details;
}
}
}