二分查找和跳表

我们常见的排序和搜索算法,基本都是基于数组的,因为数组有良好的随机访问特性,基于这个特性,能够设计许多性能良好的算法。

实际上,这些算法也不是不可以使用链表实现,我也尝试过,是一样可以实现的。但是由于需要采用特殊技巧以及损失了随机访问特性,所以这种实现基本无法与基于数组的实现匹敌,没有工程使用的价值。

但是跳表这种数据结构确打破了这个常识,跳表就是基于链表实现,只是采用了时间换空间的做法,做到了和基于数组的二分查找一样的效果。

二分查找应用

例如对于这个题,有个有序数组,截断后将前后两部分进行倒序,例如{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的效率。

跳表

image.png

跳表构造了多级索引,真真的是基于链表的一个实现。其空间复杂度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)的复杂度,媲美与二分查找,功能强大,可以和红黑树相较高下,确实是一种精巧的设计。

你可能感兴趣的:(二分查找和跳表)