inc,dec,getMin,getMax均为O(1)——一个有趣的数据结构

做leetcode题时遇到这样一道题,挺有趣的数据结构,所以记下来:)

1. All O`one Data Structure –leetcode 432

Implement a data structure supporting the following operations:

  • Inc(Key) - Inserts a new key with value 1. Or increments an existing key by 1. Key is guaranteed to be a non-empty string.
  • Dec(Key) - If Key’s value is 1, remove it from the data structure. Otherwise decrements an existing key by 1. If the key does not exist, this function does nothing. Key is guaranteed to be a non-empty string.
  • GetMaxKey() - Returns one of the keys with maximal value. If no element exists, return an empty string “”.
  • GetMinKey() - Returns one of the keys with minimal value. If no element exists, return an empty string “”.

Challenge: Perform all these in O(1) time complexity.

2. 想法

我们知道hashmap 插入,删除,更改是O(1)的,为了得到最值也是O(1),关键是实现一个双向链表,同时利用到增减都为单位量。下面详细解释各部分

2.1. node

双向链表的结点,每个结点记录一个val,以及值为val的键的字典,val>0
* pre: 双向链表,指向前一结点
* next:双向链表,指向后一结点
* val: 关键字对应的值,可以看作频率,通过这个将不同值的关键值分组,类似分块查找,
* keys: 一个dict, 用来容纳val相同的键值,查找插入删除都为O(1)

2.2. 双向链表

有头指针,尾指针。有一个val=0的结点,初始化时头尾指针指向此头结点。
还有一个dict,键为val,值为对应val的结点,以便在O(1)内找到结点。

如果结点中不含任何关键字,则去掉此结点。如果还存在key,头指针指向val最小的除0的结点(必有关键字),尾指针则指向最大的结点,这样getMin,getMax都为O(1)。如果没有任何key,则只有一个不含关键字的头节点node(0),初始化时也是这样,则头尾指针都指向它

在增减时,需更新头尾指针(最值),可以发现,只有在增加结点或删除结点时才需更新

  • incTo(self,key,val):

    1. 判断val结点是否存在,若不,则创建此结点
    2. 使node(val)包含关键字key
    3. 如果前一结点(可以保证一定存在)含此关键字,则删除此关键字
    4. 由上一步,如果删除了前一结点的关键字后,前一结点不再含任何关键字,则删除此结点
  • dccTo(self,key,val):

    1. 判断val结点是否存在,若不,则创建此结点
    2. 使node(val)包含关键字key
    3. 后一结点(可以保证一定存在)一定含此关键字,删除此关键字
    4. 由上一步,如果删除了后一结点的关键字后,后一结点不再含任何关键字,则删除此结点
  • addNode(self,val):
    注意inc,dec时代码不一样,但思路是一样的

    1. 将结点注册到双向链表的access_mp,既给此dict,增加键值对val:node(val)
    2. 修改链连接
    3. 更新头尾指针指向的结点(由于inc,dec改变的是单位量,所以可以直接找到前一结点或后一节点,这是O(1)的
  • delNode(self,val):

    1. 修改链连接
    2. 更新头尾指针指向的结点(由于inc,dec改变的是单位量,所以可以直接找到此次dec后的最大值或最小值,即若上次最小值是val的话,则新的最小值为node(val).next,即下一节点(循环链表,如果为空时会自动指向头节点node(0),代码很简洁,同理可得上次最大值为val更新尾指针)
    3. 在access_mp中删除此结点

2.3. AllOne:

这个结构包含一个上述的双向链表和一个(key:val)键值对字典,通过此字典来增减关键字的val,同时在双向链表中找到对应结点,再找到对应的key

3. 总结

这有点像chain_map了,不过python中的chain_map只是简单的连接起来,方便操作多个map(dict),而这个数据结构通过双向链表将相同的val的key字典连接。由于增减的是单位量,所以更新时,更新最值可以通过链表在O(1)时间找到。
所以此结构可以用在更新关键字频繁且最好是单位量的地方,而且数据量很大时,查找最值也更有效。(不然创建删除结点费时间)

4. 测试

做了测试,与hashmap比较了一下(这里设定hashmap 当值<=0则删除该键,然后找最大值,最小值用O(n)时间)
测试是随机产生一些字符串,然后随机产生操作,inc,dec,getMin,getMax,以及对应的数据(参数),默认产生1000个操作,然后重复1000次,取总时间

-allOnehashMapratio
增减区间: [1,10]
增减与取最值操作的比例:1:1
2.83s1.67s1.69
增减区间: [1,5]
增减与取最值操作的比例:1:1
4.06s1.65s2.44
增减区间: [1,5]
增减与取最值操作的比例:1:5
1.59s2.21s0.72

4.1. 测试代码

from allOoneDS import AllOne
from time import time
from  random import choice,sample,randint

class hashMap:
    def __init__(self):
        self.op = {"inc":self.inc,"dec":self.dec,"getMaxKey":self.getMaxKey,"getMinKey":self.getMinKey}
        self.mp={'':0}
    def inc(self,key,n=1):
        if key in self.mp:self.mp[key]+=n
        else:self.mp[key]=n
    def dec(self,key,n=1):
        if key not in self.mp:return
        if self.mp[key]<=n:del self.mp[key]
        else: self.mp[key]-=n
    def getMinKey(self):
        return min(list(self.mp.keys()),key=lambda key:self.mp[key])
    def getMaxKey(self):
        return max(list(self.mp.keys()),key=lambda key:self.mp[key])


op_origin = ['inc','dec','getMinKey','getMaxKey','getMinKey','getMaxKey','getMinKey','getMaxKey','getMinKey','getMaxKey','getMinKey','getMaxKey']
ch=list('qwertyuiopasdfghjklzxcvbnm')
keys =[ ''.join(sample(ch,i)) for j in range(10) for i in range(1,20,5)]

def testCase(n=1000):
    ops=[]
    data=[]
    for i in range(n):
        p = randint(0,len(op_origin)-1)
        ops.append(op_origin[p])
        if p<2:
            data.append([randint(1,10)])
        else:data.append([])
    return ops,data

def test(repeat=1000):
    t1,t2=0,0
    for i in range(repeat):
        allOne = AllOne()
        hsmp = hashMap()
        ops,data = testCase()
        t1-=time()
        for op,datum in zip(ops,data):
            allOne.op[op](*datum)
        t1+=time()

        t2-=time()
        for op,datum in zip(ops,data):
            hsmp.op[op](*datum)
        t2+=time()
    return t1,t2


if __name__=='__main__':
    t1,t2= test()
    print(t1,t2)

5. allOne代码

class node:
    def __init__(self,val=None,data_mp=None,pre=None,next=None):
        self.val=val
        self.data_mp = {} if data_mp is None else data_mp
        self.pre=pre
        self.next=next
    def __lt__(self,nd):
        return  self.val<nd.val
    def getOne(self):
        if not self.data_mp:
            return ''
        else:return list(self.data_mp.items())[0][0]
    def __getitem__(self,key):
        return self.data_mp[key]
    def __iter__(self):
        return iter(self.data_mp)
    def __delitem__(self,key):
        del self.data_mp[key]
    def __setitem__(self,key,val):
        self.data_mp[key]= val
    def isEmpty(self):
        return self.data_mp=={}
    def __repr__(self):
        return 'node({},{})'.format(self.val,self.data_mp)
class doubleLinkedList:
    def __init__(self):
        self.head=  self.tail = node(0)
        self.head.next = self.head
        self.head.pre = self.head
        self.chain_mp={0:self.head}
    def __str__(self):
        li = list(self.chain_mp.values())
        li = [str(i) for i in li]
        return  'min:{}, max:{}\n'.format(self.head.val,self.tail.val)   \
               + '\n'.join(li)
    def getMax(self):
        return self.tail.getOne()
    def getMin(self):
        return self.head.getOne()
    def addIncNode(self,val):
        # when adding a node,inc 1, so it's guranted that node(val-1)  exists
        self.chain_mp[val].pre= self.chain_mp[val-1]   
        self.chain_mp[val].next= self.chain_mp[val-1].next
        self.chain_mp[val-1].next.pre = self.chain_mp[val-1].next = self.chain_mp[val]
    def addDecNode(self,val):
        # when adding a node,dec 1, so it's guranted that node(val+1)  exists
        self.chain_mp[val].next= self.chain_mp[val+1]   
        self.chain_mp[val].pre= self.chain_mp[val+1].pre
        self.chain_mp[val+1].pre.next = self.chain_mp[val+1].pre = self.chain_mp[val]
    def addNode(self,val,dec=False):
        self.chain_mp[val] = node(val)
        if dec:self.addDecNode(val)
        else:self.addIncNode(val)
        if self.tail.val<val:self.tail = self.chain_mp[val]
        if self.head.val>val or self.head.val==0:self.head= self.chain_mp[val]
    def delNode(self,val):
        self.chain_mp[val].next.pre = self.chain_mp[val].pre
        self.chain_mp[val].pre.next = self.chain_mp[val].next
        if self.tail.val==val:self.tail = self.chain_mp[val].pre
        if self.head.val==val:self.head = self.chain_mp[val].next
        del self.chain_mp[val]
    def incTo(self,key,val):  
        if val not in self.chain_mp: 
            self.addNode(val)
        self.chain_mp[val][key] = val
        if val!=1 :  # key in the pre node
            del self.chain_mp[val-1][key]
            #print(self.chain_mp[val-1])
            if self.chain_mp[val-1].isEmpty():
                #print('*'*20)
                self.delNode(val-1)
    def decTo(self,key,val):
        if val not in self.chain_mp:
            self.addNode(val,dec=True)
        # notice that the headnode(0) shouldn't add key
        if val!=0: self.chain_mp[val][key] = val  
        del self.chain_mp[val+1][key]
        if self.chain_mp[val+1].isEmpty():
            self.delNode(val+1)        

class AllOne:
    def __init__(self):
        """
        Initialize your data structure here.
        """
        self.op = {"inc":self.inc,"dec":self.dec,"getMaxKey":self.getMaxKey,"getMinKey":self.getMinKey}
        self.mp = {}
        self.dll = doubleLinkedList()
    def __str__(self):
        return str(self.dll)
    def __getitem__(self,key):
        return self.mp[key]
    def __delitem__(self,key):
        del self.mp[key]
    def __setitem__(self,key,val):
        self.mp[key]= val
    def __iter__(self):
        return iter(self.mp)
    def inc(self, key,n=1):
        """
        Inserts a new key <Key> with value 1. Or increments an existing key by 1.
        :type key: str
        :rtype: void
        """
        if key in self:
            self[key]+=n
        else:self[key]=n
        for i in range(n): self.dll.incTo(key, self[key])
    def dec(self, key,n=1):
        """
        Decrements an existing key by 1. If Key's value is 1, remove it from the data structure.
        :type key: str
        :rtype: void
        """
        if key in self.mp:
            mn = min( self[key],n)
            for i in range(mn): self.dll.decTo(key, self[key]-i-1)
            if self[key] == n:
                del self[key]
            else:
                self[key] = self[key]-n
    def getMaxKey(self):
        """
        Returns one of the keys with maximal value.
        :rtype: str
        """
        return self.dll.getMax()

    def getMinKey(self):
        """
        Returns one of the keys with Minimal value.
        :rtype: str
        """
        return self.dll.getMin()
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值