Class TreeSet<E>
TreeSet trong Java là một triển khai của SortedSet interface, có nghĩa là các phần tử trong TreeSet được sắp xếp theo một thứ tự nhất định. Để đạt được điều này, TreeSet sử dụng một cấu trúc dữ liệu cây đặc biệt, thường là cây đỏ đen (Red-Black Tree).
Cây đỏ-đen là một loại cây nhị phân tự cân bằng, nghĩa là cây sẽ tự động duy trì trạng thái cân bằng để đảm bảo các thao tác như tìm kiếm, thêm, hoặc xóa một phần tử đều diễn ra trong thời gian O(log n).
Các quy tắc về màu sắc của cây Đỏ-Đen
Để TreeSet (và các cấu trúc dữ liệu dựa trên cây đỏ-đen khác) có thể duy trì các phần tử được sắp xếp một cách hiệu quả, chúng cần tuân thủ một bộ quy tắc nghiêm ngặt về màu sắc. Những quy tắc này đảm bảo cây luôn cân bằng, từ đó tối ưu hóa hiệu suất cho các thao tác tìm kiếm, thêm và xóa. Dưới đây là sáu quy tắc cơ bản chi phối màu sắc của các nút trong cây Đỏ-Đen:
↳ 1. Mỗi nút có màu đen hoặc đỏ: Mỗi nút trong cây Đỏ-Đen phải được tô màu hoặc là đen hoặc là đỏ. Đây là điều kiện cơ bản và đơn giản của cây Đỏ-Đen.
↳ 2. Nút gốc (Root) luôn là đen: Nút gốc của cây luôn được tô màu đen. Điều này đảm bảo rằng cây luôn duy trì được tính chất Đỏ-Đen ngay từ nút đầu tiên.
↳ 3. Mọi nút lá (Nút null) đều là đen: Tất cả các nút lá (các nút không có con, thường được đại diện bởi các con trỏ null) đều được coi là đen. Điều này bao gồm cả các nút null nằm ngoài cây thực tế.
↳ 4. Hai nút đỏ liên tiếp không được xuất hiện: Không có hai nút đỏ liền kề trên một đường từ gốc đến lá, nghĩa là một nút đỏ không thể có con trực tiếp là nút đỏ. Nếu một nút đỏ có con, thì con của nó phải là nút đen.
↳ 5. Đường đi từ một nút đến các nút lá của nó phải có cùng số lượng nút đen: Tất cả các đường từ một nút bất kỳ đến các nút lá của nó phải có cùng số lượng nút đen, được gọi là "chiều cao đen" (black height). Điều này giúp cây luôn duy trì sự cân bằng về chiều cao, và ngăn cây trở nên quá mất cân bằng.
↳ 6. Chèn và xóa có thể thay đổi cấu trúc và màu sắc: Khi chèn hoặc xóa một nút, cây có thể trở nên mất cân bằng. Trong trường hợp này, các thao tác cân bằng (như xoay cây và đổi màu nút) sẽ được thực hiện để đảm bảo các quy tắc Đỏ-Đen vẫn được giữ nguyên.
Cấu trúc của một phần tử trong TreeSet
Để hiểu cách TreeSet lưu trữ và quản lý các phần tử một cách có thứ tự, chúng ta cần nắm rõ cấu trúc cơ bản của nó. TreeSet sử dụng một cây đỏ-đen (Red-Black Tree) để đảm bảo các phần tử luôn được sắp xếp và các thao tác tìm kiếm, thêm, xóa diễn ra hiệu quả. Mỗi phần tử trong TreeSet được lưu trữ dưới dạng một nút trong cấu trúc cây này. Dưới đây là mô tả chi tiết về các thành phần cấu tạo nên một phần tử trong TreeSet:
↳ Nodes (Nút): Mỗi phần tử trong TreeSet được lưu trữ trong một nút của cây đỏ-đen.
↳ Root (Gốc): Là nút đầu tiên được chèn vào cây, nơi bắt đầu của mọi thao tác tìm kiếm. Gốc là nút duy nhất trong cây không có nút cha (parent == null).
↳ Left Child (Con Trái): Nút con nằm bên trái của một nút cha, chứa các giá trị nhỏ hơn nút cha.
↳ Right Child (Con Phải): Nút con nằm bên phải của một nút cha, chứa các giá trị lớn hơn nút cha.
↳ Parent (Cha): Nút mà một nút con được kết nối đến. Mọi nút trong cây (trừ gốc) đều có một nút cha.
↳ Nút lá (Leaf Node) là một nút trong cây không có bất kỳ con trỏ nào trỏ đến các nút con (tức là, nó không có con trái hoặc con phải). Trong một cây nhị phân (bao gồm cả cây đỏ-đen), nút lá là những nút nằm ở cuối cây, nơi mà cả con trái và con phải đều là null.

Quy trình thêm phần tử vào cây nhị phân
↳ Bắt đầu từ nút gốc (Root):
↳ Nếu cây trống (chưa có phần tử nào), phần tử mới sẽ trở thành nút gốc.
↳ Nếu cây không trống, bắt đầu so sánh giá trị của phần tử mới với giá trị của nút gốc.
↳ So sánh với giá trị của nút hiện tại (Current Node):
↳ Nếu phần tử mới nhỏ hơn nút hiện tại: Di chuyển đến con trái của nút hiện tại. Nếu con trái là null, phần tử mới sẽ được chèn vào vị trí này.
↳ Nếu phần tử mới lớn hơn nút hiện tại: Di chuyển đến con phải của nút hiện tại. Nếu con phải là null, phần tử mới sẽ được chèn vào vị trí này.
↳ Cập nhật các con trỏ (Pointers):
↳ Sau khi phần tử mới được chèn vào đúng vị trí, các con trỏ left và right của nút mới sẽ được đặt thành null.
↳ Nếu phần tử mới trở thành con trái hoặc con phải của một nút cha hiện có, con trỏ left hoặc right của nút cha sẽ được cập nhật để trỏ đến phần tử mới này.
Một số đặc điểm nổi bật của Class TreeSet<E>
↳ Không cho phép giá trị null: TreeSet không cho phép lưu trữ giá trị null.
↳ Chứa các phần tử duy nhất: TreeSet chỉ chứa các phần tử duy nhất, giống như HashSet.
↳ Duy trì thứ tự tăng dần: TreeSet duy trì thứ tự tăng dần của các phần tử.
↳ Thời gian thực hiện: TreeSet cung cấp thời gian thực hiện logarit (log(n)) cho các thao tác cơ bản như thêm (add), xóa (remove), và kiểm tra sự tồn tại (contains).
↳ Không đồng bộ: Giống như nhiều lớp khác trong Java Collections Framework, TreeSet không đồng bộ (not synchronized). Nếu nhiều luồng truy cập vào cùng một TreeSet đồng thời và có ít nhất một luồng thay đổi tập hợp, thì cần phải đồng bộ hóa bên ngoài để đảm bảo an toàn luồng.
↳ Fail-Fast Iterator: Các bộ lặp (iterator) của TreeSet là "fail-fast". Nếu TreeSet bị thay đổi sau khi bộ lặp được tạo ra (ngoại trừ thông qua phương thức remove của bộ lặp), bộ lặp sẽ ném ra ngoại lệ ConcurrentModificationException.
Khai báo Class TreeSet<E> trong Java
Để sử dụng Class TreeSet<E>, bạn cần import gói java.util vào đầu file Java của mình. Gói này cung cấp các lớp và giao diện để làm việc với các collection trong Java.
Cú pháp câu lệnh import:
Cú pháp
import java.util.TreeSet;
Cú pháp khai báo Class TreeSet<E>:
Cú pháp
public class TreeSet<E>
extends AbstractSet<E>
implements NavigableSet<E>, Cloneable, Serializable
Dưới đây là giải thích chi tiết về cú pháp khai báo này:
↳ public: Lớp TreeSet có thể được truy cập từ bất kỳ đâu trong chương trình.
↳ class: Từ khóa để khai báo một lớp trong Java.
↳ TreeSet<E>: Tên lớp là TreeSet có sử dụng generic type E, với E là kiểu dữ liệu tổng quát (generic type). Điều này có nghĩa là TreeSet có thể chứa các phần tử thuộc bất kỳ kiểu dữ liệu nào, miễn là chúng đều cùng một kiểu.
↳ extends AbstractSet<E>: Lớp TreeSet kế thừa từ lớp AbstractSet. Điều này có nghĩa là TreeSet thừa hưởng các phương thức và thuộc tính từ AbstractSet, đồng thời có thể triển khai thêm hoặc ghi đè các phương thức đó.
↳ implements NavigableSet<E>: TreeSet triển khai giao diện NavigableSet, nghĩa là nó cung cấp các phương thức để điều hướng và xử lý tập hợp các phần tử theo thứ tự sắp xếp.
↳ implements Cloneable: TreeSet triển khai giao diện Cloneable, cho phép tạo một bản sao của đối tượng TreeSet.
↳ implements Serializable: TreeSet triển khai giao diện Serializable, nghĩa là các đối tượng của TreeSet có thể được tuần tự hóa, tức là có thể được chuyển đổi thành một chuỗi byte để lưu trữ hoặc truyền qua mạng.
Các constructor của lớp TreeSet
Lớp TreeSet cung cấp các constructor để tạo một tree set rỗng hoặc một tree set chứa các phần tử từ một bộ sưu tập khác. Bạn có thể chỉ định thứ tự sắp xếp của các phần tử trong tree set bằng cách cung cấp một bộ so sánh.
↳ TreeSet(): Tạo một tree set rỗng mới, được sắp xếp theo thứ tự tự nhiên của các phần tử của nó.
↳ TreeSet(Collection<? extends E> c): Tạo một tree set mới chứa các phần tử từ một bộ sưu tập khác, được sắp xếp theo thứ tự tự nhiên của các phần tử của nó.
↳ TreeSet(Comparator<? super E> comparator): Tạo một tree set rỗng mới, được sắp xếp theo bộ so sánh đã cho.
↳ TreeSet(SortedSet<E> s): Tạo một tree set mới chứa các phần tử giống nhau và sử dụng thứ tự giống nhau như sorted set đã cho.
Ví dụ
// Tạo một TreeSet rỗng, sắp xếp theo thứ tự tự nhiên
TreeSet<String> set1 = new TreeSet<>();
// Tạo một TreeSet từ một List, sắp xếp theo thứ tự tự nhiên
List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5);
TreeSet<Integer> set2 = new TreeSet<>(numbers);
// Tạo một TreeSet sắp xếp theo thứ tự giảm dần
Comparator<Integer> descendingOrder = (a, b) -> b - a;
TreeSet<Integer> set3 = new TreeSet<>(descendingOrder);
TreeSet<Integer> set3 = new TreeSet<>(descendingOrder);
// Tạo một TreeSet mới dựa trên SortedSet đã có
TreeSet<String> treeSet = new TreeSet<>(sortedSet);
Các phương thức chính trong lớp TreeSet
Lớp TreeSet cung cấp các phương thức để thao tác với một set được sắp xếp. Bạn có thể thêm, xóa, tìm kiếm các phần tử theo thứ tự, và lấy các phần tử gần nhất theo thứ tự sắp xếp. Dưới đây là danh sách tất cả các phương thức của Class TreeSet<E> trong Java:
↳ boolean add(E e): Thêm phần tử e vào set này nếu nó chưa có sẵn.
↳ boolean addAll(Collection<? extends E> c): Thêm tất cả các phần tử trong bộ sưu tập đã cho vào set này.
↳ E ceiling(E e): Trả về phần tử nhỏ nhất trong set này lớn hơn hoặc bằng phần tử đã cho, hoặc null nếu không có phần tử như vậy.
↳ void clear(): Xóa tất cả các phần tử khỏi set này.
↳ Object clone(): Trả về một bản sao nông của instance TreeSet này.
↳ Comparator<? super E> comparator(): Trả về bộ so sánh được sử dụng để sắp xếp các phần tử trong set này, hoặc null nếu set này sử dụng thứ tự tự nhiên của các phần tử của nó.
↳ boolean contains(Object o): Trả về true nếu set này chứa phần tử đã cho.
↳ Iterator<E> descendingIterator(): Trả về một iterator duyệt qua các phần tử trong set này theo thứ tự giảm dần.
↳ NavigableSet<E> descendingSet(): Trả về một view của các phần tử trong set này theo thứ tự giảm dần.
↳ E first(): Trả về phần tử đầu tiên (nhỏ nhất) hiện có trong set này.
↳ E floor(E e): Trả về phần tử lớn nhất trong set này nhỏ hơn hoặc bằng phần tử đã cho, hoặc null nếu không có phần tử như vậy.
↳ SortedSet<E> headSet(E toElement): Trả về một view của phần của set này mà các phần tử của nó nhỏ hơn toElement.
↳ NavigableSet<E> headSet(E toElement, boolean inclusive): Trả về một view của phần của set này mà các phần tử của nó nhỏ hơn (hoặc bằng, nếu inclusive là true) toElement.
↳ E higher(E e): Trả về phần tử nhỏ nhất trong set này lớn hơn phần tử đã cho, hoặc null nếu không có phần tử như vậy.
↳ boolean isEmpty(): Trả về true nếu set này không chứa phần tử nào.
↳ Iterator<E> iterator(): Trả về một iterator duyệt qua các phần tử trong set này theo thứ tự tăng dần.
↳ E last(): Trả về phần tử cuối cùng (cao nhất) hiện có trong set này.
↳ E lower(E e): Trả về phần tử lớn nhất trong set này nhỏ hơn phần tử đã cho, hoặc null nếu không có phần tử như vậy.
↳ E pollFirst(): Lấy và xóa phần tử đầu tiên (nhỏ nhất) của set này, hoặc trả về null nếu set này rỗng.
↳ E pollLast(): Lấy và xóa phần tử cuối cùng (cao nhất) của set này, hoặc trả về null nếu set này rỗng.
↳ boolean remove(Object o): Xóa phần tử đã cho khỏi set này nếu nó có mặt.
↳ int size(): Trả về số lượng phần tử trong set này (số phần tử của nó).
↳ Spliterator<E> spliterator(): Tạo một Spliterator duyệt qua các phần tử trong set này.
↳ NavigableSet<E> subSet(E fromElement, boolean fromInclusive, E toElement, boolean toInclusive): Trả về một view của phần của set này mà các phần tử của nó nằm trong khoảng từ fromElement đến toElement (bao gồm hoặc không bao gồm).
↳ SortedSet<E> subSet(E fromElement, E toElement): Trả về một view của phần của set này mà các phần tử của nó nằm trong khoảng từ fromElement (bao gồm) đến toElement (không bao gồm).
↳ SortedSet<E> tailSet(E fromElement): Trả về một view của phần của set này mà các phần tử của nó lớn hơn hoặc bằng fromElement.
↳ NavigableSet<E> tailSet(E fromElement, boolean inclusive): Trả về một view của phần của set này mà các phần tử của nó lớn hơn (hoặc bằng, nếu inclusive là true) fromElement.
Ví dụ về các phương thức thêm, xóa và kiểm tra phần tử
Dưới đây là ví dụ về việc sử dụng các phương thức add(E e), remove(Object o), contains(Object o), clear(), isEmpty(), size() của lớp TreeSet trong Java:
Ví dụ: Example.java
import java.util.TreeSet;
public class Example {
public static void main(String[] args) {
// Tạo một TreeSet với kiểu Integer
TreeSet<Integer> numbers = new TreeSet<>();
// Thêm phần tử vào TreeSet
numbers.add(10);
numbers.add(20);
numbers.add(15);
// Thêm tất cả các phần tử từ một Collection
TreeSet<Integer> moreNumbers = new TreeSet<>();
moreNumbers.add(25);
moreNumbers.add(5);
numbers.addAll(moreNumbers);
// Kiểm tra xem TreeSet có chứa phần tử 15 hay không
boolean contains15 = numbers.contains(15);
System.out.println("TreeSet chứa 15: " + contains15);
// Xóa phần tử 20 khỏi TreeSet
numbers.remove(20);
// Trả về số lượng phần tử trong TreeSet
int size = numbers.size();
System.out.println("Kích thước của TreeSet: " + size);
// Kiểm tra xem TreeSet có rỗng hay không
boolean isEmpty = numbers.isEmpty();
System.out.println("TreeSet có trống không? " + isEmpty);
// Xóa tất cả các phần tử khỏi TreeSet
numbers.clear();
System.out.println("TreeSet đã được xóa. Có trống không? " + numbers.isEmpty());
}
}
Kết quả của chương trình là:
Kích thước của TreeSet: 4
TreeSet có trống không? false
TreeSet đã được xóa. Có trống không? true
Ví dụ về các phương thức truy xuất phần tử
Dưới đây là ví dụ về việc sử dụng các phương thức first(), last(),ceiling(E e), floor(E e), higher(E e), lower(E e), pollFirst(), pollLast() của lớp TreeSet trong Java:
Ví dụ: Example.java
import java.util.TreeSet;
public class Example {
public static void main(String[] args) {
TreeSet<Integer> numbers = new TreeSet<>();
// Thêm các phần tử vào TreeSet
numbers.add(10);
numbers.add(20);
numbers.add(30);
numbers.add(40);
numbers.add(50);
// Lấy phần tử đầu tiên (nhỏ nhất)
Integer first = numbers.first();
System.out.println("Phần tử đầu tiên (nhỏ nhất) là: " + first);
// Lấy phần tử cuối cùng (cao nhất)
Integer last = numbers.last();
System.out.println("Phần tử cuối cùng (cao nhất) là: " + last);
// Tìm phần tử gần nhất lớn hơn hoặc bằng 25
Integer ceiling = numbers.ceiling(25);
System.out.println("Phần tử lớn hơn hoặc bằng 25 là: " + ceiling);
// Tìm phần tử gần nhất nhỏ hơn hoặc bằng 25
Integer floor = numbers.floor(25);
System.out.println("Phần tử nhỏ hơn hoặc bằng 25 là: " + floor);
// Tìm phần tử lớn hơn 20
Integer higher = numbers.higher(20);
System.out.println("Phần tử lớn hơn 20 là: " + higher);
// Tìm phần tử nhỏ hơn 30
Integer lower = numbers.lower(30);
System.out.println("Phần tử nhỏ hơn 30 là: " + lower);
// Lấy và xóa phần tử đầu tiên
Integer pollFirst = numbers.pollFirst();
System.out.println("Phần tử đầu tiên sau khi bị xóa là: " + pollFirst);
// Lấy và xóa phần tử cuối cùng
Integer pollLast = numbers.pollLast();
System.out.println("Phần tử cuối cùng sau khi bị xóa là: " + pollLast);
}
}
Kết quả của chương trình là:
Phần tử cuối cùng (cao nhất) là: 50
Phần tử lớn hơn hoặc bằng 25 là: 30
Phần tử nhỏ hơn hoặc bằng 25 là: 20
Phần tử lớn hơn 20 là: 30
Phần tử nhỏ hơn 30 là: 20
Phần tử đầu tiên sau khi bị xóa là: 10
Phần tử cuối cùng sau khi bị xóa là: 50
Ví dụ về các phương thức tạo view phần tử
Dưới đây là ví dụ về việc tạo view của một phần của TreeSet bằng các phương thức như headSet(), tailSet(), subSet(), và phiên bản mở rộng của subSet() với các giới hạn bao gồm hay không bao gồm phần tử giới hạn.
Ví dụ: Example.java
import java.util.SortedSet;
import java.util.TreeSet;
public class Example {
public static void main(String[] args) {
// Tạo một TreeSet với kiểu String
TreeSet<String> fruits = new TreeSet<>();
// Thêm các phần tử vào TreeSet
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Cherry");
fruits.add("Date");
fruits.add("Fig");
fruits.add("Grape");
// Tạo view của phần tử từ đầu đến "Date" (không bao gồm "Date")
SortedSet<String> headSet = fruits.headSet("Date");
System.out.println("HeadSet (không bao gồm 'Date'): " + headSet);
// Tạo view của phần tử từ "Cherry" đến cuối
SortedSet<String> tailSet = fruits.tailSet("Cherry");
System.out.println("TailSet (bắt đầu từ 'Cherry'): " + tailSet);
// Tạo view của phần tử từ "Banana" đến "Fig" (bao gồm cả "Banana" và "Fig")
SortedSet<String> subSet = fruits.subSet("Banana", "Fig");
System.out.println("SubSet (bao gồm 'Banana' và 'Fig'): " + subSet);
// Tạo view của phần tử từ "Banana" đến "Fig" (bao gồm 'Banana' nhưng không bao gồm 'Fig')
SortedSet<String> subSetWithBounds = fruits.subSet("Banana", true, "Fig", false);
System.out.println("SubSet (bao gồm 'Banana' nhưng không bao gồm 'Fig'): " + subSetWithBounds);
}
}
Kết quả của chương trình là:
TailSet (bắt đầu từ 'Cherry'): [Cherry, Date, Fig, Grape]
SubSet (bao gồm 'Banana' và 'Fig'): [Banana, Cherry, Date]
SubSet (bao gồm 'Banana' nhưng không bao gồm 'Fig'): [Banana, Cherry, Date]
Ví dụ về các phương thức descendingIterator()
Dưới đây là ví dụ về cách sử dụng phương thức descendingIterator() của lớp TreeSet trong java:
Ví dụ: Example.java
import java.util.Iterator;
import java.util.TreeSet;
public class Example {
public static void main(String[] args) {
// Tạo một TreeSet với kiểu Double
TreeSet<Double> numbers = new TreeSet<>();
// Thêm các phần tử vào TreeSet
numbers.add(10.5);
numbers.add(20.3);
numbers.add(40.1);
numbers.add(50.2);
numbers.add(30.7);
// Lấy iterator duyệt qua các phần tử theo thứ tự giảm dần
Iterator<Double> descendingIterator = numbers.descendingIterator();
// Duyệt và in các phần tử theo thứ tự giảm dần
System.out.println("Các phần tử theo thứ tự giảm dần:");
while (descendingIterator.hasNext()) {
Double number = descendingIterator.next();
System.out.println(number);
}
}
}
Kết quả của chương trình là:
50.2
40.1
30.7
20.3
10.5
Ví dụ về các phương thức spliterator()
Dưới đây là ví dụ về cách sử dụng phương thức spliterator() của lớp TreeSet trong java:
Ví dụ: Example.java
import java.util.Spliterator;
import java.util.TreeSet;
import java.util.function.Consumer;
public class Example {
public static void main(String[] args) {
// Tạo một TreeSet với kiểu Double
TreeSet<Double> numbers = new TreeSet<>();
// Thêm các phần tử vào TreeSet
numbers.add(10.5);
numbers.add(20.3);
numbers.add(40.1);
numbers.add(50.2);
numbers.add(30.7);
// Lấy Spliterator duyệt qua các phần tử trong TreeSet
Spliterator<Double> spliterator = numbers.spliterator();
// Sử dụng Spliterator để duyệt và in các phần tử
System.out.println("Các phần tử trong TreeSet:");
spliterator.forEachRemaining(new Consumer<Double>() {
@Override
public void accept(Double number) {
System.out.println(number);
}
});
}
}
Kết quả của chương trình là:
10.5
20.3
30.7
40.1
50.2
Ví dụ về các phương thức comparator()
Dưới đây là ví dụ về cách sử dụng phương thức comparator() của lớp TreeSet với kiểu dữ liệu String. Phương thức này trả về bộ so sánh (comparator) được sử dụng để sắp xếp các phần tử trong TreeSet. Nếu TreeSet sử dụng sắp xếp tự nhiên (natural ordering), phương thức này sẽ trả về null.
Ví dụ: Example.java
import java.util.Comparator;
import java.util.TreeSet;
public class Example {
public static void main(String[] args) {
// Tạo một TreeSet với kiểu String và một bộ so sánh đảo ngược
TreeSet<String> fruits = new TreeSet<>(Comparator.reverseOrder());
// Thêm các phần tử vào TreeSet
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Cherry");
fruits.add("Date");
fruits.add("Fig");
// Lấy bộ so sánh được sử dụng trong TreeSet
Comparator<?> comparator = fruits.comparator(); // Sử dụng Comparator<?> để tránh lỗi kiểu
// Kiểm tra và in ra bộ so sánh
if (comparator != null) {
System.out.println("Bộ so sánh được sử dụng trong TreeSet là: " + comparator);
} else {
System.out.println("TreeSet sử dụng sắp xếp tự nhiên (natural ordering).");
}
// In các phần tử trong TreeSet
System.out.println("Các phần tử trong TreeSet:");
for (String fruit : fruits) {
System.out.println(fruit);
}
}
}
Kết quả của chương trình là:
Fig
Date
Cherry
Banana
Apple
Ví dụ TreeSet với đối tượng do người dùng định nghĩa
Dưới đây là ví dụ về cách sử dụng lớp TreeSet với một đối tượng do người dùng định nghĩa. Trong ví dụ này, chúng ta sẽ tạo một lớp Person với thuộc tính name và age, và sau đó sử dụng TreeSet để lưu trữ và sắp xếp các đối tượng Person dựa trên thuộc tính age.
Ví dụ: Example.java
package style;
import java.util.Comparator;
import java.util.TreeSet;
class Person {
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return name + " (" + age + " tuổi)";
}
}
public class Example {
public static void main(String[] args) {
// Tạo một TreeSet với bộ so sánh theo tuổi (age)
TreeSet<Person> people = new TreeSet<>(Comparator.comparingInt(p -> p.age));
// Thêm các đối tượng Person vào TreeSet
people.add(new Person("Alice", 30));
people.add(new Person("Bob", 25));
people.add(new Person("Charlie", 35));
people.add(new Person("Diana", 28));
// In các đối tượng Person trong TreeSet
System.out.println("Danh sách người theo tuổi:");
for (Person person : people) {
System.out.println(person);
}
// Lấy bộ so sánh đang được sử dụng
Comparator<Person> comparator = (Comparator<Person>) people.comparator();
System.out.println("\nBộ so sánh được sử dụng: " + comparator);
}
}
Kết quả của chương trình là:
Bob (25 tuổi)
Diana (28 tuổi)
Alice (30 tuổi)
Charlie (35 tuổi)
Bộ so sánh được sử dụng: java.util.Comparator$$Lambda/0x000002763d041340@65ab7765
So sánh giữa HashSet, LinkedHashSet và TreeSet
Dưới đây là bảng so sánh giữa HashSet, LinkedHashSet, và TreeSet trong Java:
Đặc Điểm | HashSet | LinkedHashSet | TreeSet |
---|---|---|---|
Kế thừa | AbstractSet<E> | HashSet<E> | AbstractSet<E> |
Triển khai giao diện | Set<E> | Set<E> | NavigableSet<E>, SortedSet<E> |
Cấu trúc dữ liệu | Hash table | Hash table với danh sách liên kết | Cây đỏ-đen (Red-Black Tree) |
Thứ tự duy trì | Không duy trì thứ tự | Thứ tự theo cách chèn | Thứ tự theo khóa (Ascending Order) |
Cho phép null | Có (1 giá trị null) | Có (1 giá trị null) | Không (không cho phép khóa null) |
Tốc độ thực thi | Tốt cho các thao tác cơ bản (O(1)) | Hơi chậm hơn HashSet do danh sách liên kết | O(log n) cho các thao tác cơ bản |
Synchronized | Không đồng bộ | Không đồng bộ | Không đồng bộ |
Fail-fast iterators | Có | Có | Có |
Cloneable | Có | Có | Có |
Serializable | Có | Có | Có |
Hy vọng bảng so sánh này giúp bạn hiểu rõ hơn về các đặc điểm của HashSet, LinkedHashSet, và TreeSet.
Như vậy, chúng ta đã khám phá các phương thức chính của lớp TreeSet. Với khả năng duy trì các phần tử luôn được sắp xếp và cung cấp các thao tác tìm kiếm, thêm, xóa hiệu quả dựa trên cấu trúc cây đỏ-đen, TreeSet là lựa chọn tuyệt vời khi bạn cần một tập hợp có thứ tự và khả năng truy xuất các phần tử theo phạm vi hoặc giá trị lân cận. Nắm vững TreeSet sẽ giúp bạn quản lý dữ liệu hiệu quả trong các tình huống đòi hỏi tính thứ tự và tìm kiếm dựa trên so sánh trong Java.