A Skip List is a probabilistic data structure that allows O(log n) search, insertion, and deletion within an ordered sequence. It achieves this by maintaining multiple layers of linked lists, where each higher layer acts as an "express lane" skipping over elements.
Skip List là cấu trúc dữ liệu xác suất cho phép tìm kiếm, chèn và xóa O(log n) trong một dãy có thứ tự. Nó đạt được điều này bằng cách duy trì nhiều tầng danh sách liên kết, trong đó mỗi tầng cao hơn hoạt động như 'làn nhanh' bỏ qua các phần tử.
Mỗi nút được nâng lên tầng tiếp theo với xác suất p (thường là 0.5 hoặc 0.25). Với p=0.5, trung bình một nửa số nút xuất hiện ở tầng 2, một phần tư ở tầng 3, v.v. Điều này tạo ra O(log n) tầng kỳ vọng.
| Operation | Average | Worst | Notes |
|---|---|---|---|
| Search | O(log n) | O(n) | Worst case very unlikely |
| Insert | O(log n) | O(n) | Plus O(level) pointer updates |
| Delete | O(log n) | O(n) | Simpler than balanced trees |
| Range Query | O(log n + k) | O(n + k) | k = number of results |
| Space | O(n) expected (2n with p=0.5) | Extra pointers per level | |
n = number of elements, k = range query results, p = promotion probability
A skip list starts with a sorted linked list at the bottom level. Each element has a random "height" determining how many levels it appears in. Higher levels contain fewer elements, creating express lanes for faster traversal.
Skip list bắt đầu với danh sách liên kết đã sắp xếp ở tầng dưới cùng. Mỗi phần tử có 'chiều cao' ngẫu nhiên xác định nó xuất hiện ở bao nhiêu tầng. Các tầng cao hơn chứa ít phần tử hơn, tạo ra làn nhanh để duyệt nhanh hơn.
To search for a value, start at the highest level and move right until the next node is greater than the target. Then drop down one level and repeat. Continue until you find the value or reach the bottom level.
Để tìm một giá trị, bắt đầu từ tầng cao nhất và di chuyển sang phải cho đến khi nút tiếp theo lớn hơn mục tiêu. Sau đó xuống một tầng và lặp lại. Tiếp tục cho đến khi tìm thấy giá trị hoặc đến tầng dưới cùng.
1function search(target: number): Node | null {2 let current = head;34 // Start from top level, work down5 for (let level = maxLevel; level >= 0; level--) {6 // Move right while next is less than target7 while (current.next[level] &&8 current.next[level].value < target) {9 current = current.next[level];10 }11 }12Insertion involves finding the position (like search), then randomly determining the height of the new node. Update pointers at each level where the new node appears.
Chèn bao gồm tìm vị trí (như tìm kiếm), sau đó xác định ngẫu nhiên chiều cao của nút mới. Cập nhật con trỏ ở mỗi tầng nơi nút mới xuất hiện.
1function insert(value: number): void {2 const update: Node[] = new Array(maxLevel + 1);3 let current = head;45 // Find insert position at each level6 for (let level = maxLevel; level >= 0; level--) {7 while (current.next[level] &&8 current.next[level].value < value) {9 current = current.next[level];10 }11 update[level] = current; // Track predecessor12 }- Flip a coin (p = 0.5)
Tung đồng xu (p = 0.5)
- Heads: increment level, flip again
Mặt ngửa: tăng tầng, tung tiếp
- Tails: stop, return current level
Mặt sấp: dừng, trả về tầng hiện tại
- Cap at maximum level (e.g., 16 for 65536 elements)
Giới hạn tầng tối đa (ví dụ: 16 cho 65536 phần tử)
Deletion is similar to insertion: find the node and its predecessors, then update pointers to skip over the deleted node at each level.
Xóa tương tự như chèn: tìm nút và các tiền nhiệm của nó, sau đó cập nhật con trỏ để bỏ qua nút bị xóa ở mỗi tầng.
1function delete(value: number): boolean {2 const update: Node[] = new Array(maxLevel + 1);3 let current = head;45 // Find node and track predecessors6 for (let level = maxLevel; level >= 0; level--) {7 while (current.next[level] &&8 current.next[level].value < value) {9 current = current.next[level];10 }11 update[level] = current;12 }- Skip List: Probabilistic balance | AVL/Red-Black: Guaranteed balance
Skip List: Cân bằng xác suất | AVL/Red-Black: Cân bằng đảm bảo
- Skip List: Simpler implementation | Trees: Complex rotations required
Skip List: Cài đặt đơn giản hơn | Cây: Cần phép xoay phức tạp
- Skip List: Cache-friendly traversal | Trees: Pointer-heavy navigation
Skip List: Duyệt thân thiện cache | Cây: Điều hướng nhiều con trỏ
- Skip List: Easy concurrent access | Trees: Lock contention on rotations
Skip List: Dễ truy cập đồng thời | Cây: Xung đột khóa khi xoay
- Skip List: O(log n) average case | Trees: O(log n) worst case
Skip List: O(log n) trung bình | Cây: O(log n) worst case
Chọn skip list khi bạn cần code đơn giản hơn, truy cập đồng thời, hoặc truy vấn theo khoảng. Chọn cây cân bằng khi bạn cần đảm bảo hiệu suất worst-case hoặc tiết kiệm bộ nhớ.
- Forgetting to update maxLevel: When highest level becomes empty, reduce maxLevel
Quên cập nhật maxLevel: Khi tầng cao nhất trở nên trống, cần giảm maxLevel
- Probability p too high: p=0.9 creates very tall lists, wasting memory
Xác suất p quá cao: p=0.9 tạo cây quá cao, lãng phí bộ nhớ
- Probability p too low: p=0.1 creates very flat lists, losing speed benefits
Xác suất p quá thấp: p=0.1 tạo cây quá nông, mất lợi ích tốc độ
- Not capping maxLevel: With n elements, maxLevel should be approximately log(n)
Không giới hạn maxLevel: Với n phần tử, maxLevel nên là log(n)
- Ignoring sentinel nodes: Head and tail sentinels simplify edge cases
Bỏ qua sentinel nodes: Head và tail sentinels đơn giản hóa edge cases
- Relying on worst case: Worst case O(n) is extremely rare with good PRNG
Dựa vào worst case: Worst case O(n) cực kỳ hiếm với PRNG tốt
- Much simpler implementation than AVL/Red-Black trees
Cài đặt đơn giản hơn nhiều so với AVL/Red-Black trees
- No complex rotations needed on insert/delete
Không cần phép xoay phức tạp khi chèn/xóa
- Easy to implement lock-free version for concurrent access
Dễ dàng cài đặt phiên bản lock-free cho đa luồng
- Efficient range queries due to linked structure
Truy vấn khoảng hiệu quả nhờ cấu trúc liên kết
- Good practical performance with small constants
Hiệu suất tốt trên thực tế với hằng số nhỏ
- Theoretical O(n) worst case (though very rare)
Worst case O(n) về mặt lý thuyết (dù rất hiếm)
- Higher memory usage due to multi-level pointers
Sử dụng nhiều bộ nhớ hơn do con trỏ đa tầng
- Depends on quality of random number generator
Phụ thuộc vào chất lượng PRNG
- Worse cache locality than array-based structures
Cache locality kém hơn array-based structures
- Use p=0.25 instead of 0.5: Fewer levels, saves memory, still O(log n)
Dùng p=0.25 thay vì 0.5: Ít tầng hơn, tiết kiệm bộ nhớ, vẫn O(log n)
- $Pre-calculate maxLevel: maxLevel = log_{1/p}(n) for expected n elements
Tính trước maxLevel: maxLevel = log_{1/p}(n) cho n phần tử dự kiến
- Use memory pool: Reduces memory allocation overhead
Sử dụng memory pool: Giảm overhead cấp phát bộ nhớ
- Store pointer array contiguously: Improves cache locality
Lưu pointer array liền kề: Cải thiện cache locality
- Redis: Sorted sets (ZSET) for O(log n) range queries with ZADD, ZRANGE
Redis: Sorted sets (ZSET) cho truy vấn khoảng O(log n) với ZADD, ZRANGE
- LevelDB/RocksDB: MemTable index in memory before flushing to SSTable
LevelDB/RocksDB: MemTable index trong bộ nhớ trước khi flush to SSTable
- Apache Lucene: Concurrent skip list for posting lists
Apache Lucene: Concurrent skip list cho posting lists
- MemSQL/SingleStore: Lock-free concurrent skip lists for OLTP
MemSQL/SingleStore: Lock-free concurrent skip lists cho OLTP
- Java ConcurrentSkipListMap: Sorted concurrent map implementation
Java ConcurrentSkipListMap: Sorted concurrent map implementation
- HBase: MemStore uses ConcurrentSkipListMap
HBase: MemStore sử dụng ConcurrentSkipListMap
Redis chọn skip list thay vì balanced tree cho sorted sets vì: (1) Dễ cài đặt và debug hơn, (2) Truy vấn khoảng nhanh với traversal đơn giản, (3) Có thể tune bằng cách thay đổi p, (4) Antirez (tác giả Redis) thấy thực tế skip list nhanh như balanced tree.
Skip lists are particularly well-suited for concurrent environments because they can be implemented lock-free. Unlike balanced trees that need to lock multiple nodes during rotations, skip lists only need to update local pointers.
Skip list đặc biệt phù hợp cho môi trường đa luồng vì có thể cài đặt lock-free. Không giống như cây cân bằng cần khóa nhiều nút khi xoay, skip list chỉ cần cập nhật con trỏ cục bộ.
1function concurrentInsert(value: number): boolean {2 while (true) {3 // Find predecessors (no locks needed for reading)4 const [preds, succs] = findPosition(value);56 // Check if already exists7 if (succs[0]?.value === value) {8 return false;9 }1011 const newLevel = randomLevel();12 const newNode = new Node(value, newLevel);Java's ConcurrentSkipListMap và ConcurrentSkipListSet sử dụng kỹ thuật này để cung cấp sorted collections an toàn cho đa luồng mà không cần khóa toàn cục.