我们常见的排序和搜索算法,基本都是基于数组的,因为数组有良好的随机访问特性,基于这个特性,能够设计许多性能良好的算法。
实际上,这些算法也不是不可以使用链表实现,我也尝试过,是一样可以实现的。但是由于需要采用特殊技巧以及损失了随机访问特性,所以这种实现基本无法与基于数组的实现匹敌,没有工程使用的价值。
但是跳表这种数据结构确打破了这个常识,跳表就是基于链表实现,只是采用了时间换空间的做法,做到了和基于数组的二分查找一样的效果。
二分查找应用
例如对于这个题,有个有序数组,截断后将前后两部分进行倒序,例如{4, 5, 6, 7, 0, 1, 2},求这个数组的最小值。
排序后取最小值,这种方法是效率最低的。
// T(n) = O(nlgn)
func findMin_v1(nums []int) int {
sort.Ints(nums)
return nums[0]
}
暴力法:
// T(n) = O(n)
func findMin_v2(nums []int) int {
result := nums[0]
for _, num := range nums {
if result > num {
result = num
}
}
return result
}
二分查找,效率最优
// T(n) = O(lgn)
func findMin_v3(nums []int) int {
low, high := 0, len(nums) - 1
result := nums[0]
for low <= high {
mid := int(low + (high - low)/2)
if nums[low] <= nums[mid] {
if result > nums[low] {
result = nums[low]
}
low = mid + 1
} else {
high = mid - 1
if result > nums[mid] {
result = nums[mid]
}
}
}
return result
}
灵活使用二分查找,达到lgn的效率。
跳表

跳表构造了多级索引,真真的是基于链表的一个实现。其空间复杂度O(n),搜索、插入、删除的复杂度O(lgn)。具体原理可以查阅相关资料。
实现如下:
// go 用起来还是不熟练
const (
MAX_LEVEL = 16
)
// 跳表节点
type skipListNode struct {
// value
v interface{}
// 层高
level int
// 层指针
forwards []*skipListNode
}
func newSkipListNode(v interface{}, level:int) *skipListNode{
return &skipListNode(v, forwards:make([]*skipListNode, level, level), level: level)
}
// 跳表
type SkipList struct {
// 跳表头节点
head* skipListNode
// 跳表当前层数
level int
// 跳表长度
length int
}
func NewSkipList() *SkipList {
head := newSkipListNode(0, MAX_LEVEL)
return &SkipList{head, 1, 0}
}
func (sl *SkipList) Length() int {
return s1.length
}
// 插入节点
func (s1 *SkipList) Insert(v interface{}) {
if nil == v {
return
}
cur := s1.head
// 暂存update
update := [MAX_LEVEL]*skipListNode{}
for i := MAX_LEVEL - 1; i >= 0; i-- {
for nil != cur.forwards[i] && cur.forwards[i].v < v {
cur = cur.forwards[i]
}
update[i] = cur
}
level := 1
for i := 1; i < MAX_LEVEL; i++ {
if rand.Int31()%7 == 1 {
level ++
}
}
newNode = newSkipListNode(v, level)
for i:= 0; i < level; i++ {
newNode.forwards[i] = update[i].forwards[i]
update[i].forwards[i] = newNode
}
// 更新level和长度
if level > sl.level {
s1.level = level
}
s1.length++
}
def (s1 *SkipList)Delete(v interface{}) bool {
if nil == v {
return
}
cur := s1.head
// 暂存update
update := [MAX_LEVEL]*skipListNode{}
for i := MAX_LEVEL - 1; i >= 0; i-- {
for nil != cur.forwards[i] && cur.forwards[i].v < v {
cur = cur.forwards[i]
}
update[i] = cur
}
if cur.forwards[0] == nil || cur.forwards[0].v != v {
return false
}
for i := MAX_LEVEL - 1; i >= 0; i-- {
if update[i].forwards[i] != nil && update[i].forwards[i].v == v {
update[i].forwards[i] = update[i].forwards[i].forwards[i]
}
}
sl.length--
return true
}
def (s1 *SkipList)Find(v interface{}) bool {
if nil == v {
return
}
cur := s1.head
for i := s1.level - 1; i >= 0; i-- {
for nil != cur.forwards[i] && cur.forwards[i].v < v{
cur = cur.forwards[i]
}
}
if p.forwards[0] != nil && p.forwards[0].v = v {
return true
}
return false
}
小结
从二分查找和跳表对比看,二分查找基于数组,达到了查找效率O(lgn)。而跳表基于链表实现,打破了常识,牺牲了O(n)的空间复杂度,但是达到了查找、插入、删除操作O(lgn)的复杂度,媲美与二分查找,功能强大,可以和红黑树相较高下,确实是一种精巧的设计。
网友评论