美文网首页
数据结构、算法的组合

数据结构、算法的组合

作者: Wu杰语 | 来源:发表于2019-12-05 21:14 被阅读0次

    算法是个抽象,底层可以由不同的数据结构实现。在学习算法的时候,我们往往要针对某个算法进行训练,因而一般都是学习的是比较纯粹的某种数据结构实现的算法。例如说实现抽象数据结构(ADT)堆栈或者队列,底层可以由数组或者链表实现。

    但是,你可知真实的算法世界是复杂的,学习了纯化的数据结构和算法后,我们需要去真实的商业级语言库、代码库中去看看算法实现,此时很自然会发现面目全非,除了底层是纯化的数据结构和算法的影子这个本质不会变,真实世界的算法都是量体裁衣,经过组合或者优化了的。

    组合是面向对象的一个原则,这个原则在算法世界中也有广泛应用,我们就以一些实例看一下数据结构组合完成新数据结构和新算法、算法组合完成新算法。

    数据结构组合成新数据结构

    第一个例子就是LinkedHashMap,如下图:


    image.png

    先不看红线,这是一个散列表,当hash冲突时,采用链表挂在散列节点下。在散列表基础上,增加了一个基础数据结构,链表,标红的就是链表。这个组合数据结构有什么用处呢?

    基本的散列表操作不会变,但是遍历数据时,链表就派上用场了,因为链表是按照插入顺序构造的,所以可以按照插入顺序遍历散列表数据。

    数据结构组合成新算法

    最小栈问题

    设计一个支持 push,pop,top 操作,并能在常数时间内检索到最小元素的栈。
    push(x) -- 将元素 x 推入栈中。
    pop() -- 删除栈顶的元素。
    top() -- 获取栈顶元素。
    getMin() -- 检索栈中的最小元素。
    

    这个题目,我们就使用了两个栈进行组合,一个是常规的栈,另外一个栈只存储最小值。处理过程如下:

    stack  minStack
    
    入栈 5 , 5 大于 minStack 栈顶,不处理
    |   |    |   |
    | 5 |    |   |
    |_3_|    |_3_|
    stack  minStack
    
    入栈 2 ,此时右边的 minStack 栈顶就保存了当前最小值 2 
    | 2 |    |   |
    | 5 |    | 2 |
    |_3_|    |_3_|
    stack  minStack
    
    出栈 2,此时右边的 minStack 栈顶就保存了当前最小值 3
    |   |    |   |
    | 5 |    |   |
    |_3_|    |_3_|
    stack  minStack
    
    出栈 5,右边 minStack 不处理
    |   |    |   |
    |   |    |   |
    |_3_|    |_3_|
    stack  minStack
    
    出栈 3
    |   |    |   |
    |   |    |   |
    |_ _|    |_ _|
    stack  minStack
    

    如下是go代码实现

    type Node struct {
        Val int
        Prev *Node
        Next *Node
    }
    
    func NewNode(x int) *Node {
        return &Node{x, nil, nil}
    }
    
    type Stack struct {
        Bottom *Node
        TopNode *Node      
    }
    
    func NewStack() Stack {
        node := NewNode(0)
        return Stack{node, node}
    }
    
    func (s *Stack)IsEmpty() bool {
        return s.Bottom == s.TopNode
    }
    
    func (s *Stack)Top() int {
        if s.IsEmpty() {
            panic("stack is empty")
        } else {
            return s.TopNode.Val
        }
    }
    
    func (s *Stack)Push(x int) {
        node := NewNode(x)
        node.Prev = s.TopNode
        s.TopNode.Next = node
        s.TopNode = node
    }
    
    func (s *Stack)Pop() {
        if s.IsEmpty() {
            panic("stack is empty")
        } else {
            prev := s.TopNode.Prev
            prev.Next = nil
            s.TopNode = prev
        }
    }
    
    type MinStack struct {
        Data Stack
        Min  Stack           
    }
    
    
    /** initialize your data structure here. */
    func Constructor() MinStack {
        return MinStack{NewStack(), NewStack()}
    }
    
    
    func (this *MinStack) Push(x int)  {
        this.Data.Push(x)
        if this.Min.IsEmpty() || this.Min.Top() >= x {
            this.Min.Push(x)
        }
    }
    
    
    func (this *MinStack) Pop()  {
        x := this.Data.Top()
        this.Data.Pop()
        if x == this.Min.Top() {
            this.Min.Pop()
        }
    }
    
    
    func (this *MinStack) Top() int {
        return this.Data.Top()
    }
    
    
    func (this *MinStack) GetMin() int {
        return this.Min.Top()
    }
    

    关键点在于最小栈的入栈和出栈的条件。

    两个栈组合形成新的算法的例子还有较多。

    算法组合成新算法

    排序算法我们很熟悉,基本算法按时间复杂度分:

    • O(n^2) 插入排序、冒泡排序、选择排序
    • O(nlgn) 快速排序、归并排序
    • O(n) 桶排序、基数排序、计数排序

    真实算法世界中Arrays.Sort()是怎样排序的呢?资料百度可查,使用了TimSort,具体原理可以查阅资料。这个算法就典型的是插入排序和归并排序的组合,组合的依据也就是复杂度。插入排序虽然复杂度高,但在小数据量的时候,是快于归并排序的。

    小结

    学习了纯化的数据结构和算法后,我们必须将目光投入到真实的算法世界。组合只是纯化的数据结构和算法为原料,构造了新的原料;我们还可以观察到纯化数据结构和算法和锁结合,形成支持并发的数据结构和算法;继续观察,有redis上为了高效率,量体裁衣,构造了自己的字符串、压缩列表等各种数据结构。

    从观察到的东西来看,设计上的用到的理念,算法中通用。和编码设计对比,总结一下就是,纯化的数据结构和算法,要滚瓜乱熟,然后要通过实战经验积累真实世界的算法。

    相关文章

      网友评论

          本文标题:数据结构、算法的组合

          本文链接:https://www.haomeiwen.com/subject/dbyjgctx.html