跳表的学习记录

跳表(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
}
相关推荐
Chef_Chen4 小时前
从0开始学习R语言--Day18--分类变量关联性检验
学习
键盘敲没电4 小时前
【IOS】GCD学习
学习·ios·objective-c·xcode
海的诗篇_5 小时前
前端开发面试题总结-JavaScript篇(一)
开发语言·前端·javascript·学习·面试
AgilityBaby5 小时前
UE5 2D角色PaperZD插件动画状态机学习笔记
笔记·学习·ue5
AgilityBaby5 小时前
UE5 创建2D角色帧动画学习笔记
笔记·学习·ue5
武昌库里写JAVA6 小时前
iview Switch Tabs TabPane 使用提示Maximum call stack size exceeded堆栈溢出
java·开发语言·spring boot·学习·课程设计
一弓虽7 小时前
git 学习
git·学习
Moonnnn.9 小时前
【单片机期末】串行口循环缓冲区发送
笔记·单片机·嵌入式硬件·学习
viperrrrrrrrrr710 小时前
大数据学习(131)-Hive数据分析函数总结
大数据·hive·学习
fen_fen10 小时前
学习笔记(26):线性代数-张量的降维求和,简单示例
笔记·学习·算法