跳表的学习记录

跳表(Skip List)是一种数据结构,它通过在多个层次上添加额外的前向指针来提高有序数据的搜索效率。跳表与其他常见的有序数据结构(如二叉搜索树、平衡树如AVL树和红黑树、B树等)相比,具有其独特的优缺点:

跳表的优点

复制代码
简单性: 跳表的算法和数据结构相对简单,容易理解和实现。与平衡树的复杂旋转和重新平衡相比,跳表的维护成本较低。
高效的搜索操作: 跳表可以提供接近二叉搜索树的搜索性能,平均时间复杂度为 O(logn)。
灵活的插入和删除: 跳表在插入和删除操作时,只需要调整相邻的指针,而不需要像在平衡树中那样复杂的重新平衡。
有序性: 与哈希表不同,跳表维护了元素的有序性,使得范围查询等操作更加高效。

跳表的缺点

复制代码
空间消耗: 由于跳表在每个节点中存储多个指针,它通常会消耗比二叉树更多的内存空间。
随机性: 跳表的性能部分依赖于随机化过程,这可能导致性能的不稳定性。虽然在平均情况下表现良好,但最坏情况的表现可能不如某些确定性结构。
缓存不友好: 由于跳表的节点可能分布在内存的不同位置,它可能不如紧凑存储的数据结构(如数组)那样对缓存友好。

举个例子

跳表的 put 操作

想象一下你在一个有多层的购物中心里。每一层都有很多商店,而且每层都有楼梯和电梯连接到其他层。现在,你要在这个购物中心里开一家新店。

put 操作就像是在购物中心里找一个合适的位置来开设你的店铺。

选择楼层: 你首先决定你的店铺要开在哪些楼层。这就像跳表中随机决定节点应该在哪些层上有链接。

找到位置: 接着,你从最高层开始,寻找开设店铺的位置。你会经过每一家店,直到找到合适的空位。这就像在跳表中,从最高层开始向右搜索,直到找到合适的插入点。

开设店铺: 一旦找到位置,你就在那里开设你的店铺,并确保楼梯或电梯可以通到你的店铺。这在跳表中对应于将新节点插入到链表中,并更新相邻节点的指针。
跳表的 del 操作

现在,想象你决定关闭你在购物中心的某个店铺。

del 操作就像是在购物中心中关闭并移除你的店铺。

寻找店铺: 你首先需要找到你的店铺。这就像在跳表中寻找一个特定的节点。

关闭并移除: 一旦找到,你就关闭店铺,并且移除所有通向这家店铺的楼梯和电梯的指示标志。这在跳表中对应于移除一个节点,并更新所有指向该节点的指针,确保它们指向下一个正确的节点。

调整楼层: 如果你关闭的是最高层的唯一店铺,那么整个楼层可能会被关闭。这就像在跳表中,如果删除节点后某一层没有任何节点了,我们就降低跳表的高度。

代码实现

go 复制代码
package main

import (
	"fmt"
	"math/rand"
	"time"
)

type node struct {
	next     []*node
	key, val int
}

type SkipList struct {
	head *node
}

func (s *SkipList) search(key int) *node {
	move := s.head
	for level := len(s.head.next) - 1; level >= 0; level-- {
		for move.next[level] != nil && move.next[level].key < key {
			move = move.next[level]
		}
		if move.next[level] != nil && move.next[level].key == key {
			return move.next[level]
		}
	}
	return nil
}

func (s *SkipList) Get(key int) (int, bool) {
	if searchNode := s.search(key); searchNode != nil {
		return searchNode.val, true
	}
	return -1, false
}

// Adjust the roll method to control the height distribution
func (s *SkipList) roll() int {
	level := 0
	for rand.Float32() < 0.5 { // Adjust the probability as needed
		level++
	}
	return level
}

func (s *SkipList) Put(key, val int) {
	if search := s.search(key); search != nil {
		search.val = val
		return
	}
	level := s.roll()

	for len(s.head.next)-1 < level {
		s.head.next = append(s.head.next, nil)
	}
	newNode := node{
		next: make([]*node, level+1),
		key:  key,
		val:  val,
	}

	move := s.head
	for l := level; l >= 0; l-- {
		for move.next[l] != nil && move.next[l].key < key {
			move = move.next[l]
		}
		newNode.next[l] = move.next[l]
		move.next[l] = &newNode
	}
}

func (s *SkipList) Del(key int) {
	if _node := s.search(key); _node == nil {
		return
	}

	move := s.head
	for level := len(s.head.next) - 1; level >= 0; level-- {
		for move.next[level] != nil && move.next[level].key < key {
			move = move.next[level]
		}
		if move.next[level] != nil && move.next[level].key == key {
			move.next[level] = move.next[level].next[level]
		}
	}

	for len(s.head.next) > 1 && s.head.next[len(s.head.next)-1] == nil {
		s.head.next = s.head.next[:len(s.head.next)-1]
	}
}

func (s *SkipList) ceiling(target int) *node {
	move := s.head
	for level := len(s.head.next) - 1; level >= 0; level-- {
		for move.next[level] != nil && move.next[level].key < target {
			move = move.next[level]
		}
		if move.next[level] != nil && move.next[level].key == target {
			return move.next[level]
		}
	}
	return move.next[0]
}

func (s *SkipList) Range(start, end int) [][2]int {
	ceilNode := s.ceiling(start)
	if ceilNode == nil {
		return [][2]int{}
	}

	var res [][2]int
	for move := ceilNode; move != nil && move.key <= end; move = move.next[0] {
		res = append(res, [2]int{move.key, move.val})
	}
	return res
}

func main() {
	sl := createTestSkipList()

	// Test Insertion
	fmt.Println("Testing Insertion...")
	sl.Put(3, 30)
	sl.Put(1, 10)
	sl.Put(2, 20)
	fmt.Println("Insertion Passed")

	// Test Search
	fmt.Println("Testing Search...")
	if val, found := sl.Get(2); !found || val != 20 {
		fmt.Printf("Search Failed: expected 20, got %d\n", val)
	} else {
		fmt.Println("Search Passed")
	}

	// Test Deletion
	fmt.Println("Testing Deletion...")
	sl.Del(1)
	if _, found := sl.Get(1); found {
		fmt.Println("Deletion Failed: 1 should not exist")
	} else {
		fmt.Println("Deletion Passed")
	}

	// Test Ceiling Function
	fmt.Println("Testing Ceiling Function...")
	ceilNode := sl.ceiling(2)
	if ceilNode == nil || ceilNode.key != 2 {
		fmt.Printf("Ceiling Failed: expected key 2, got %d\n", ceilNode.key)
	} else {
		fmt.Println("Ceiling Passed")
	}

	// Test Range Query
	fmt.Println("Testing Range Query...")
	expected := [][2]int{{2, 20}, {3, 30}}
	result := sl.Range(2, 3)
	if len(result) != len(expected) {
		fmt.Printf("Range Failed: expected length %d, got %d\n", len(expected), len(result))
	} else {
		allMatch := true
		for i, pair := range expected {
			if result[i] != pair {
				fmt.Printf("Range Failed at index %d: expected %v, got %v\n", i, pair, result[i])
				allMatch = false
				break
			}
		}
		if allMatch {
			fmt.Println("Range Passed")
		}
	}
}

func createTestSkipList() *SkipList {
	rand.Seed(time.Now().UnixNano())
	sl := SkipList{head: &node{next: make([]*node, 1)}}
	return &sl
}
相关推荐
西岸行者10 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意10 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码10 天前
嵌入式学习路线
学习
毛小茛10 天前
计算机系统概论——校验码
学习
babe小鑫10 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms10 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下10 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。10 天前
2026.2.25监控学习
学习
im_AMBER10 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode
CodeJourney_J10 天前
从“Hello World“ 开始 C++
c语言·c++·学习