跳表的学习记录

跳表(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
}
相关推荐
ajsbxi1 分钟前
苍穹外卖学习记录
java·笔记·后端·学习·nginx·spring·servlet
Rattenking2 分钟前
React 源码学习01 ---- React.Children.map 的实现与应用
javascript·学习·react.js
dsywws24 分钟前
Linux学习笔记之时间日期和查找和解压缩指令
linux·笔记·学习
道法自然040225 分钟前
Ethernet 系列(8)-- 基础学习::ARP
网络·学习·智能路由器
爱吃生蚝的于勒31 分钟前
深入学习指针(5)!!!!!!!!!!!!!!!
c语言·开发语言·数据结构·学习·计算机网络·算法
cuisidong19972 小时前
5G学习笔记三之物理层、数据链路层、RRC层协议
笔记·学习·5g
南宫理的日知录2 小时前
99、Python并发编程:多线程的问题、临界资源以及同步机制
开发语言·python·学习·编程学习
数据与后端架构提升之路3 小时前
从神经元到神经网络:深度学习的进化之旅
人工智能·神经网络·学习
一行13 小时前
电脑蓝屏debug学习
学习·电脑
星LZX4 小时前
WireShark入门学习笔记
笔记·学习·wireshark