精华内容
下载资源
问答
  • set时间复杂度
    千次阅读
    2020-12-04 00:53:33

    列表list,一个有序的队列

    列表内的个体为元素,由若干个元素按照顺序进行排列,列表是可变化的,也就是说可以增删

    list定义

    常用的列表定义方式: 使用[] 或者 a = list()

    取数列表可以用range()

    列表查询方法

    index

    index =索引,以0开始查找

    方法:value,[start,[stop]]

    通过对应位置的索引进行查找,找到列表内的元素是否有匹配,如有则返回索引值

    匹配到第一个元素则立即返回索引位

    有时候从右边查找更加便利,比如反斜杠/ 从右边找更加便捷

    例:

    In [22]: a

    Out[22]: [1, 2, 3, 5, 10, 20, 33, 55]

    In [23]: a.index(3)

    Out[23]: 2

    In [24]: a.index(33)

    Out[24]: 6

    从右向左查找 index(value, [start, [stop]])

    从某个值开始查找

    In [46]: a.index(33,3)

    Out[46]: 6

    In [50]:a.index(55,-1)

    Out[50]: 7

    计算元素出现的次数

    In [53]: a = [1,1,1,3,2,11,5,43,1,1]

    In [54]: a.count(1)

    Out[54]: 5

    时间复杂度

    计算机科学中,算法的时间复杂度是一个函数,它定性描述了该算法的运行时间。这是一个关于代表算法输入值的字符串的长度的函数。时间复杂度常用大O符号表述,不包括这个函数的低阶项和首项系数。使用这种方式时,时间复杂度可被称为是渐近的,它考察当输入值大小趋近无穷时的情况

    index和count 方法对应的时间复杂度

    数学符号对应 O

    O(n)

    O(2)

    O(1)

    n表示多少个元素,意思为有多少个元素就从前到后多少个元素

    将所有的元素都遍历一遍

    需要考虑index方法和count方法适用性,是否该用,选型哪个需要考虑

    随着列表数据规模增加而效率下降,如果能做到O1/2/3 这样则可以很快返回结果

    list列表元素修改

    对某一项索引位置赋值(修改)

    In [59]: a

    Out[59]: [1, 1, 1, 3, 2, 11, 5, 43, 1, 1]

    In [60]: a[1] = 2

    In [61]: a

    Out[61]: [1,2, 1, 3, 2, 11, 5, 43, 1, 1]

    列表就地修改

    对列表本身进行追加元素

    lst.append(100)

    [1, 2, 3, 2, 2, 5, 6, 100]

    append对list进行增加元素,返回一个None

    In [71]: lst = [1,2,3,2,2,5,6]

    In [72]: a = lst.append(100)

    In [73]: type(a)

    Out[73]: NoneType

    In [74]: a

    In [75]: print(a)

    None

    这里返回值为空

    list运算

    In [77]: a = [1,2,3]

    In [78]: a * 3

    Out[78]: [1, 2, 3, 1, 2, 3, 1, 2, 3]

    这里有返回打印

    有输出则是没有被就地修改,都是构造新的列表

    我们看到增加之后原列表发生了变化,这样被称为就地修改,就地修改为只对当前列表进行修改,修改的是对象的本身

    append对时间复杂度为O(1),因为通过索引进行修改,而且是从尾部进行修改

    这样通过索引线性修改所耗时间很快,并非O(n) ,O(n)为逐步遍历,找到某个项再进行修改

    insert插入元素

    In [81]: a.insert(1,'a')

    In [82]: a

    Out[82]: [1, 'a', 2, 3]

    insert为在指定处插入对象,这样会引起整个内存结构变化,所有数据统一向后错位,如果量级大则不要去做,尽可能new一个

    所以尽可能避免挪动

    insert时间复杂度为 O(n),如果放在开头则不建议,一般list规模很大,所以要考虑效率问题

    所以,insert更适合链表方式

    extend将迭代对象追加

    迭代对象不用解释了,可以是列表,可以是字典等等

    b = {'c':123}

    In [85]: a.extend(b)

    In [86]: a

    Out[86]: [1, 'a', 2, 3, 'c']

    追加迭代自己

    In [88]: a.extend(a)

    In [89]: a

    Out[89]: [1, 'a', 2, 3, 'c', 1, 'a', 2, 3,'c']

    remove删除某个元素

    remove为删除某个内容,而并非索引

    remove为就地修改,在做位置的挪动,所以这里需要注重效率

    In [89]: a

    Out[89]: [1, 'a', 2, 3, 'c', 1, 'a', 2, 3,'c']

    In [90]: a.remove(1)

    In [91]: a

    Out[91]: ['a', 2, 3, 'c', 1, 'a', 2, 3,'c']

    In [92]: a.remove(1)

    In [93]: a

    Out[93]: ['a', 2, 3, 'c', 'a', 2, 3, 'c']

    在顺序列表中,在中间包括开头,需要考虑效率问题

    pop弹出

    从尾部进行弹出并且删除尾部的元素

    In [103]: a = [1,2,3,11,13,12,20]

    In [104]: a.pop()

    Out[104]: 20

    pop效率为O(1)由于是在尾部进行就地修改,所以效率非常高

    使用index进行pop,而索引则是从1开始并非是0

    In [108]: a.pop(0)

    Out[108]: 1

    In [109]: a

    Out[109]: [2, 3, 11, 13, 12]

    pop的特性直接将前端显示,移除+修改并行操作

    在清除对象过多的情况下,会引起大规模GC垃圾回收,同样要考虑到效率问题

    list的排序

    sort()排序

    In [113]: a = [63,1,44,2,19,94,64,21]

    In [114]: a.sort()

    In [115]: a

    Out[115]: [1, 2, 19, 21, 44, 63, 64, 94]

    reverse进行到排序

    默认为: sort(Key=None,reverse=False)

    默认情况下是升序排列,降序由大到小,那么进行到排序:

    In [116]: a.sort(reverse=True)

    In [117]: a

    Out[117]: [94, 64, 63, 44, 21, 19, 2, 1]

    但是当前如果遇到字符串则无法进行

    In [117]: a

    Out[117]: [94, 64, 63, 44, 21, 19, 2, 1]

    In [118]: a.append('haha')

    In [119]: a.sort(reverse=False)

    ---------------------------------------------------------------------------

    TypeError                               Traceback (most recent call last)

    in()

    ----> 1 a.sort(reverse=False)

    TypeError: unorderable types: str()

    那么我们可以使用key=None 的方法进行对字符串排序

    In [121]: a.sort(key=str)

    In [122]: a

    Out[122]: [1, 19, 2, 21, 44, 63, 64, 94,'haha']

    In [123]: a.sort(key=str,reverse=True)

    In [124]: a

    Out[124]: ['haha', 94, 64, 63, 44, 21, 2,19, 1]

    同样可以按照字母进行正排倒排

    In [125]: a.append('ab')

    In [126]: a.append('ba')

    In [127]: a.sort(key=str,reverse=True)

    In [128]: a

    Out[128]: ['haha', 'ba', 'ab', 94, 64, 63,44, 21, 2, 19, 1]

    In [129]: a.sort(key=str)

    In [130]: a

    Out[130]: [1, 19, 2, 21, 44, 63, 64, 94,'ab', 'ba', 'haha']

    排序规则:将每个元素转为字符串,其都是直接转为ASCII码进行排序,这里的str为当前定义的函数,如果是自己写的函数可以自定义排序规则

    取随机数

    涉及random

    choice从非空序列的元素中随机选择

    In [167]: a

    Out[167]: [1, 19, 2, 21, 44, 63, 64, 94,'ab', 'ba', 'haha']

    In [168]: import random

    In [169]: random.choice(a)

    Out[169]: 1

    In [170]: random.choice(a)

    Out[170]: 64

    randrange取之间的随机数的,以及步长

    In [172]: random.randrange(1,10)

    Out[172]: 5

    shuffle打乱元素

    In [174]: random.shuffle(a)

    In [175]: a

    Out[175]: [94, 64, 'ba', 21, 44, 19, 63, 2,1, 'ab', 'haha']

    列表复制

    ==和is 的区别

    In [131]: lst0 = list(range(4))

    In [132]: lst0

    Out[132]: [0, 1, 2, 3]

    In [133]: id(lst0)

    Out[133]: 140196597896584

    首先进行哈希匹配

    In [134]: hash(id(lst0))

    Out[134]: 140196597896584

    给lst1 进行赋值 让其等于lst0

    In [135]: lst1 = list(range(4))

    In [136]: id(lst1)

    Out[136]: 140196608816840

    查看两个列表的值

    In [138]: lst1

    Out[138]: [0, 1, 2, 3]

    In [139]: lst0

    Out[139]: [0, 1, 2, 3]

    In [140]: lst0 == lst1

    Out[140]: True

    In [141]: lst0 is lst1

    Out[141]: False

    通过以上,可以明白:

    ==比较返回值 判断是否依次相等

    is比较内存地址是否一致

    地址空间的引用

    In [142]: id(lst0[1])

    Out[142]: 9177888

    In [143]: id(lst1[1])

    Out[143]: 9177888

    以上看到,是没有复制的过程,而是被引用了同样的内存地址空间

    使用copy进行复制并返回一个新的列表

    In [150]: lst0

    Out[150]: [0, 1, 2, 3]

    In [151]: lst5=lst0.copy()

    In [152]: lst5

    Out[152]: [0, 1, 2, 3]

    使用= 进行拷贝

    In [163]: lst5 = lst0

    In [164]: lst0[1] = 555

    In [165]: lst0

    Out[165]: [0, 555, 2, 3]

    In [166]: lst5

    Out[166]: [0, 555, 2, 3]

    因为赋值的是引用类型,所以直接将嵌套的list拷贝的内存地址

    通过这个内存地址修改,则对两个list同时修改

    需要注意的是:需要观察拷贝的类型是什么,不然会引起副作用,但是也可以通过特性批量进行操作

    深拷贝和潜拷贝的基本概念

    浅拷贝

    在一般都是实现了浅拷贝,只拷贝了第一层结构,

    被称为 shadow copy,但是引用的都是同一个内存地址

    深拷贝

    如果出现层次嵌套,会对引用类型进行深入拷贝,在结构上拷贝的一模一样,引用的内存地址则独立开辟

    使用deepcopy可以进行深拷贝

    使用list求100内的质数:

    lst1 = []

    for x in range(2,101):

    for i in lst1:

    if x % i == 0:

    break

    else:

    lst1.append(x)

    print(lst1)

    更多相关内容
  • Set和List的时间复杂度

    千次阅读 2021-01-26 17:50:58
    ArrayList本质就是通过数组实现的,查找一个元素是否包含要用到遍历,时间复杂度是O(n) 而HashSetHashSet的查找是通过HashMap的KeySet来实现的,判断是否包含某个元素的实现,时间复杂度是O(1) ArrayList判断...

    变量申明、函数返回一般用通用类型
    例如:

    Set<String> list=new HashSet<>();
    List<String> list=new ArrayList<>();
    

    ArrayList本质就是通过数组实现的,查找一个元素是否包含要用到遍历,时间复杂度是O(n) 而HashSetHashSet的查找是通过HashMap的KeySet来实现的,判断是否包含某个元素的实现,时间复杂度是O(1)

    ArrayList判断是否包含某个元素的源码实现:

    public boolean contains(Object o) {
            return indexOf(o) >= 0;
        }
    
    public int indexOf(Object o) {
            if (o == null) {
                for (int i = 0; i < size; i++)
                    if (elementData[i]==null)
                        return i;
            } else {
                for (int i = 0; i < size; i++) //从头遍历
                    if (o.equals(elementData[i]))
                        return i;
            }
            return -1;
        }
    

    HashSet判断是否包含某个元素的源码实现:

    public boolean contains(Object o) {
            return map.containsKey(o);
        }
    
     public boolean containsKey(Object key) {
            return getNode(hash(key), key) != null;
        }
    
       final Node<K,V> getNode(int hash, Object key) {
            Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
            if ((tab = table) != null && (n = tab.length) > 0 &&
                (first = tab[(n - 1) & hash]) != null) { //直接通过hash确定元素位置,不用从头遍历
                if (first.hash == hash && // always check first node
                    ((k = first.key) == key || (key != null && key.equals(k))))
                    return first;
                if ((e = first.next) != null) {
                    if (first instanceof TreeNode)
                        return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                    do {
                        if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))//部分情况下可能会继续遍历链表定位
                            return e;
                    } while ((e = e.next) != null);
                }
            }
            return null;
        }
    
    展开全文
  • 引言我们在使用python开发过程中,list属于使用非常广泛的数据结构。不管是自己程序存放数据,还是处理接口...所以我们要剖析下list的各个方法的时间复杂度,以帮助我们在业务开发中对应该怎样用list或者是否该用lis...

    引言

    我们在使用python开发过程中,list属于使用非常广泛的数据结构。不管是自己程序存放数据,还是处理接口返回的数据,我们都更倾向于使用list。因为list用起来不仅方便,而且提供的功能较丰富。在开发中我们都知道不同的业务场景,如果背后使用的数据结构不同,处理的时间也会有本质上的差异。所以我们要剖析下list的各个方法的时间复杂度,以帮助我们在业务开发中对应该怎样用list或者是否该用list做出较优的判断。

    背景知识

    数组是一种线性表结构,其用一块连续的内存空间,来存储一组具有相同类型的数据

    时间复杂度,也叫做渐进时间复杂度,通常用大O公式书写,表示代码的执行时间随数据规模增长的变化趋势,而非真正的执行时间。因此大O关注的是变化趋势。

    上面提到的两个知识点,不熟悉的或者记得不太清楚了,可以通过google认真学习一下。

    列表(list)特点

    底层基于数组实现

    我们可以看下github上python list的部分代码实现,如下:

    typedef struct {

    PyObject_VAR_HEAD

    /* Vector of pointers to list elements. list[0] is ob_item[0], etc. */

    PyObject **ob_item;

    /* ob_item contains space for 'allocated' elements. The number

    * currently in use is ob_size.

    * Invariants:

    * 0 <= ob_size <= allocated

    * len(list) == ob_size

    * ob_item == NULL implies ob_size == allocated == 0

    * list.sort() temporarily sets allocated to -1 to detect mutations.

    *

    * Items must normally not be NULL, except during construction when

    * the list is not yet visible outside the function that builds it.

    */

    Py_ssize_t allocated;

    } PyListObject;

    list_resize(PyListObject *self, Py_ssize_t newsize)

    {

    PyObject **items;

    size_t new_allocated;

    Py_ssize_t allocated = self->allocated;

    /* Bypass realloc() when a previous overallocation is large enough

    to accommodate the newsize. If the newsize falls lower than half

    the allocated size, then proceed with the realloc() to shrink the list.

    */

    if (allocated >= newsize && newsize >= (allocated >> 1)) {

    assert(self->ob_item != NULL || newsize == 0);

    Py_SIZE(self) = newsize;

    return 0;

    }

    /* This over-allocates proportional to the list size, making room

    * for additional growth. The over-allocation is mild, but is

    * enough to give linear-time amortized behavior over a long

    * sequence of appends() in the presence of a poorly-performing

    * system realloc().

    * The growth pattern is: 0, 4, 8, 16, 25, 35, 46, 58, 72, 88, ...

    */

    new_allocated = (newsize >> 3) + (newsize < 9 ? 3 : 6);

    ...

    }

    # 截取部分代码

    可以看到,python list本质上是一个over-allocate的数组,啥叫over-allocate呢?就是当底层数组容量满了而需要扩充的时候,python依据规则会扩充多个位置出来。比如初始化列表array=[1, 2, 3, 4],向其中添加元素23,此时array对应的底层数组,扩充后的容量不是5,而是8。这就是over-allocate的意义,即扩充容量的时候会多分配一些存储空间。这样做的优点当然是提高了执行效率,否则每次添加元素,都要对底层数组进行扩充,效率是很低下的。另外,当列表存储的元素在变少时,python也会及时收缩底层的数组,避免造成内存浪费。这里可以通过对列表的实践,验证扩充与收缩的过程(通过 __sizeof__()或sys.getsizeof()查看内存变化,并推算容量值)。

    属于引用数组

    python列表存储的是对象引用,而非对象本身。不管是python文档的说明还是我们开发过程中的实践,均能感知到列表存储的是对象的引用。我们可以验证一下:

    >>> a = [1, 2, 3, 4]

    >>> a.__sizeof__()

    72

    >>> b = ['hello', 'world', 'mac']

    >>> b.__sizeof__()

    64

    >>> c = [int('10'), str(8)]

    >>> c.__sizeof__()

    56

    >>> d = 23

    >>>

    >>> l = []

    >>> l.__sizeof__()

    40

    >>> l.append(a)

    >>> l

    [[1, 2, 3, 4]]

    >>> l.__sizeof__()

    72

    >>> l.append(b)

    >>> l.__sizeof__()

    72

    >>> l.append(c)

    >>> l.__sizeof__()

    72

    >>> l.append(d)

    >>> l.__sizeof__()

    72

    我们知道,初始列表的底层数组容量是0,第一次会扩充为4个元素的容量,占用32个字节,每个元素占用8个,注意这里就是每个元素占用8个字节,而不是平均的结果为8个字节。如果列表中存放实际的元素,那上面实践中列表l添加完元素列表a之后,其占用的字节就不会是72了。因此列表本质上存储的是对象的引用。

    列表各操作时间复杂度分析

    分析python list常用操作对应的时间复杂度,这里设定列表data,内部元素个数为n。有些操作可以直接推算出结果,有些则需要通过平均时间复杂度或者是均摊时间复杂度的分析方法计算出结果,因此这些知识点,我们也是要熟悉的。开始不熟悉不用怕,用多了、分析多了自然就熟悉了。

    index or index assignment

    列表的索引操作,比如data[i]或data[i]=new_item,对应的时间复杂度为O(1),即常量阶。对于一块连续的存储空间,获取某个位置的地址通过一步计算即可算出。通常的公式是i_addr = start_addr + i * unit_byte,比如一个int类型的数组,想要计算数组下标为2的元素的内存地址,此时就等于数组的起始内存地址(数组下标为0的地址)+ 2 * unit_byte,unit_byte为不同类型对应的字节数,比如int类型占用2个节点,float类型占用4个字节。因为数组存储的是一组具有相同类型的数据,unit_byte固定,因此i位置内存地址较容易算出。python列表可以存储任意类型,为啥也能通过这个公式算计算呢,本质上还是因为列表实际存储的是对象的引用,每个对象的引用占用的字节数固定,因此i_addr = start_addr + i * unit_byte公式可用。

    append

    append(object):向列表尾部添加元素,最好情况即列表容量足够,添加元素一步完成,对应的时间复杂度为O(1);最坏情况即列表需要扩容,这时需要对现有元素一一遍历移动位置,此时时间复杂度为O(n)。因此最终的时间复杂度为O(1)。这是均摊计算的结果,大家可以自己推算下。看下append的代码实现:

    static int

    app1(PyListObject *self, PyObject *v)

    {

    Py_ssize_t n = PyList_GET_SIZE(self);

    assert (v != NULL);

    if (n == PY_SSIZE_T_MAX) {

    PyErr_SetString(PyExc_OverflowError,

    "cannot add more objects to list");

    return -1;

    }

    if (list_resize(self, n+1) == -1)

    return -1;

    Py_INCREF(v);

    PyList_SET_ITEM(self, n, v);

    return 0;

    }

    int

    PyList_Append(PyObject *op, PyObject *newitem)

    {

    if (PyList_Check(op) && (newitem != NULL))

    return app1((PyListObject *)op, newitem);

    PyErr_BadInternalCall();

    return -1;

    }

    pop

    pop([index]):删除并返回特定索引的元素,默认删除最后一个元素。默认的pop操作最好情况是底层不需要收缩数组,一步操作即可,对应的时间复杂度为O(1);最坏的情况是删除元素后,紧接着python要执行收缩数组的操作,此时时间复杂度为O(n);因此最终的时间复杂度为O(1)。对于删除指定索引的元素,不管这步操作是否需要收缩数组,该索引以后的数据都要向前移动位置,以确保存储空间的连续性。因此对应的时间复杂度为O(n)。

    insert

    insert(index, object):在指定索引前插入元素object。在第一个位置插入元素时,列表中原有的所有数据均要向后移动,在最后一个位置插入元素时,这时就不需要移动元素的操作。同时insert伴随着容量扩容的潜在操作,因此最终的时间复杂度为O(n)。

    del

    del通常的操作是del data[i] 或者 del data[i:j],删除元素后,涉及到i或j之后元素的向前移动的操作,同时还会伴随着数组收缩的潜在操作,因此对应的时间复杂度为O(n)。

    contains or in

    判断一个元素是否在列表内,需要从头遍历列表,最好情况是第一个元素就是,最坏情况是最后一个元素才是,或者元素根本就不在列表中,这两种情况均会完整遍历列表元素。看下contains的实现代码:

    static int

    list_contains(PyListObject *a, PyObject *el)

    {

    Py_ssize_t i;

    int cmp;

    for (i = 0, cmp = 0 ; cmp == 0 && i < Py_SIZE(a); ++i)

    cmp = PyObject_RichCompareBool(el, PyList_GET_ITEM(a, i),

    Py_EQ);

    return cmp;

    }

    因此,时间复杂度为O(n)。

    index

    index(value, [start, [stop]]):获取列表中第一次出现value的索引值;最好情况是第一个元素就是value,最坏情况是最后一个元素才是或者列表不包含这个元素,因此时间复杂度为O(n)。

    iteration

    iteration需要遍历列表内的所有元素,因此时间复杂度为O(n)。

    remove

    remove(value):删除列表中第一次出现的value。首先要从头开始遍历列表,判断是否找到value值,若找到value,则紧接着将其删除,然后后续的元素向前移动。remove实现的代码如下:

    static PyObject *

    listremove(PyListObject *self, PyObject *v)

    {

    Py_ssize_t i;

    for (i = 0; i < Py_SIZE(self); i++) {

    int cmp = PyObject_RichCompareBool(self->ob_item[i], v, Py_EQ);

    if (cmp > 0) {

    if (list_ass_slice(self, i, i+1,

    (PyObject *)NULL) == 0)

    Py_RETURN_NONE;

    return NULL;

    }

    else if (cmp < 0)

    return NULL;

    }

    PyErr_SetString(PyExc_ValueError, "list.remove(x): x not in list");

    return NULL;

    }

    如果要删除的元素在列表的第一个位置,删除之后,后续所有的元素均向前移动;若要删除的元素在最后一个位置,删除之后,不存在要移动位置的元素,但需要遍历到最后一个元素。这些操作同时伴随着底层数组收缩的潜在操作,因此最终的时间复杂度为O(n)。

    count

    count(value):获取列表中value出现的次数,这个仅需要完整遍历列表元素即可,不涉及元素移动位置的操作,因此时间复杂度为O(n)。count代码实现为:

    static PyObject *

    listcount(PyListObject *self, PyObject *v)

    {

    Py_ssize_t count = 0;

    Py_ssize_t i;

    for (i = 0; i < Py_SIZE(self); i++) {

    int cmp = PyObject_RichCompareBool(self->ob_item[i], v, Py_EQ);

    if (cmp > 0)

    count++;

    else if (cmp < 0)

    return NULL;

    }

    return PyInt_FromSsize_t(count);

    }

    len

    len(list):获取列表内元素的个数,因为在列表实现中,其内部维护了一个Py_ssize_t类型的变量表示列表内元素的个数,因此时间复杂度为O(1)。

    reverse

    reverse():反转列表内的所有元素,至少需要遍历一半的元素,因此时间复杂度为O(n)。

    sort

    sort(cmp=None, key=None, reverse=False):对列表内的元素,依据某种策略进行排序。其时间复杂度为O(nlogn),这里咱们先记住这个复杂度,等到后续的排序文章中再给大家做具体的分析。

    通过分析可以发现,列表不太适合做元素的查找、删除、插入等操作,对应的时间复杂度为O(n);访问某个索引的元素、尾部添加元素或删除元素这些操作比较适合做,对应的时间复杂度为O(1)。比如我们要在业务开发中,判断一个value是否在一个数据集中,如果数据集用list存储,那此时的判断操作就很耗时,如果我们用hash table(set or dict,后续也会专门写文章分析)来存储,此时的判断就能在O(1)的复杂度下完成,这样我们的程序就会有一定的提高。当然,在开发中如何有效的评估数据量也是非常重要的。

    结论

    上述对python list常用的操作做了具体的时间复杂度分析,知道某个方法、某个操作具体的时间复杂度之后,有助于我们在开发中合理的选择使用,防止不合理或无知的使用,造成程序运行的低效。这里有几点需要说明一下:

    文章中python的代码基于2.7版本

    时间复杂度包含最好、最坏、平均以及均摊情况的复杂度分析,上述分析中分别都会使用到这些方法得出结果

    列表的每个方法,github上都有具体的实现,上述仅是对一些方法列出了具体的实现

    对一些方法的分析,结合理论推导与具体的实践结果,分析起来会更加直观一些

    好了,就写到这里了。后续在研究过程中,有什么新鲜的想法也会及时更新到这篇文章里,如果您对文章有什么不一样的看法或者是好的想法,欢迎一起交流学习。

    引用

    展开全文
  • Redis中Sorted-Set时间复杂度和实战

    千次阅读 2019-04-03 23:43:51
    Sorted-Sets和Sets类型极为相似,它们都是字符串的集合,都不允许重复的成员出现在一个Set中。它们之间的主要差别是Sorted-Sets中的每一个成员都会有一个分数(score)与之关联,Redis正是通过分数来为集合中的成员...

    一、概述:

        Sorted-Sets和Sets类型极为相似,它们都是字符串的集合,都不允许重复的成员出现在一个Set中。它们之间的主要差别是Sorted-Sets中的每一个成员都会有一个分数(score)与之关联,Redis正是通过分数来为集合中的成员进行从小到大的排序。然而需要额外指出的是,尽管Sorted-Sets中的成员必须是唯一的,但是分数(score)却是可以重复的。
        在Sorted-Set中添加、删除或更新一个成员都是非常快速的操作,其时间复杂度为集合中成员数量的对数。由于Sorted-Sets中的成员在集合中的位置是有序的,因此,即便是访问位于集合中部的成员也仍然是非常高效的。事实上,Redis所具有的这一特征在很多其它类型的数据库中是很难实现的,换句话说,在该点上要想达到和Redis同样的高效,在其它数据库中进行建模是非常困难的。

    二、相关命令列表:

    命令原型时间复杂度命令描述返回值
    ZADD key score member [score] [member] O(log(N))时间复杂度中的N表示Sorted-Sets中成员的数量。添加参数中指定的所有成员及其分数到指定key的Sorted-Set中,在该命令中我们可以指定多组score/member作为参数。如果在添加时参数中的某一成员已经存在,该命令将更新此成员的分数为新值,同时再将该成员基于新值重新排序。如果键不存在,该命令将为该键创建一个新的Sorted-Sets Value,并将score/member对插入其中。如果该键已经存在,但是与其关联的Value不是Sorted-Sets类型,相关的错误信息将被返回。本次操作实际插入的成员数量。
    ZCARD key O(1)获取与该Key相关联的Sorted-Sets中包含的成员数量。返回Sorted-Sets中的成员数量,如果该Key不存在,返回0。
    ZCOUNTkey min maxO(log(N)+M) 时间复杂度中的N表示Sorted-Sets中成员的数量,M则表示min和max之间元素的数量。该命令用于获取分数(score)在min和max之间的成员数量。针对min和max参数需要额外说明的是,-inf+inf分别表示Sorted-Sets中分数的最高值和最低值。缺省情况下,min和max表示的范围是闭区间范围,即min <= score <= max内的成员将被返回。然而我们可以通过在min和max的前面添加"("字符来表示开区间,如(min max表示min < score <= max,而(min (max表示min < score < max分数指定范围内成员的数量。
    ZINCRBYkey increment member O(log(N))时间复杂度中的N表示Sorted-Sets中成员的数量。该命令将为指定Key中的指定成员增加指定的分数。如果成员不存在,该命令将添加该成员并假设其初始分数为0,此后再将其分数加上increment。如果Key不存,该命令将创建该Key及其关联的Sorted-Sets,并包含参数指定的成员,其分数为increment参数。如果与该Key关联的不是Sorted-Sets类型,相关的错误信息将被返回。以字符串形式表示的新分数。
    ZRANGEkey start stop [WITHSCORES] O(log(N)+M)时间复杂度中的N表示Sorted-Set中成员的数量,M则表示返回的成员数量。该命令返回顺序在参数start和stop指定范围内的成员,这里start和stop参数都是0-based,即0表示第一个成员,-1表示最后一个成员。如果start大于该Sorted-Set中的最大索引值,或start > stop,此时一个空集合将被返回。如果stop大于最大索引值,该命令将返回从start到集合的最后一个成员。如果命令中带有可选参数WITHSCORES选项,该命令在返回的结果中将包含每个成员的分数值,如value1,score1,value2,score2...。  返回索引在start和stop之间的成员列表。
    ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count] O(log(N)+M)时间复杂度中的N表示Sorted-Set中成员的数量,M则表示返回的成员数量。该命令将返回分数在min和max之间的所有成员,即满足表达式min <= score <= max的成员,其中返回的成员是按照其分数从低到高的顺序返回,如果成员具有相同的分数,则按成员的字典顺序返回。可选参数LIMIT用于限制返回成员的数量范围。可选参数offset表示从符合条件的第offset个成员开始返回,同时返回count个成员。可选参数WITHSCORES的含义参照ZRANGE中该选项的说明。最后需要说明的是参数中min和max的规则可参照命令ZCOUNT。返回分数在指定范围内的成员列表。
    ZRANK key member O(log(N))  时间复杂度中的N表示Sorted-Set中成员的数量。Sorted-Set中的成员都是按照分数从低到高的顺序存储,该命令将返回参数中指定成员的位置值,其中0表示第一个成员,它是Sorted-Set中分数最低的成员。如果该成员存在,则返回它的位置索引值。否则返回nil。
    ZREM key member [member ...]O(M log(N))时间复杂度中N表示Sorted-Set中成员的数量,M则表示被删除的成员数量。该命令将移除参数中指定的成员,其中不存在的成员将被忽略。如果与该Key关联的Value不是Sorted-Set,相应的错误信息将被返回。实际被删除的成员数量。
    ZREVRANGE key startstop[WITHSCORES]  O(log(N)+M) 时间复杂度中的N表示Sorted-Set中成员的数量,M则表示返回的成员数量。该命令的功能和ZRANGE基本相同,唯一的差别在于该命令是通过反向排序获取指定位置的成员,即从高到低的顺序。如果成员具有相同的分数,则按降序字典顺序排序。返回指定的成员列表。
    ZREVRANKkey member O(log(N))时间复杂度中的N表示Sorted-Set中成员的数量。该命令的功能和ZRANK基本相同,唯一的差别在于该命令获取的索引是从高到低排序后的位置,同样0表示第一个元素,即分数最高的成员。如果该成员存在,则返回它的位置索引值。否则返回nil。 
    ZSCOREkey memberO(1)获取指定Key的指定成员的分数。如果该成员存在,以字符串的形式返回其分数,否则返回nil。
    ZREVRANGEBYSCOREkey max min [WITHSCORES] [LIMIT offset count] O(log(N)+M) 时间复杂度中的N表示Sorted-Set中成员的数量,M则表示返回的成员数量。该命令除了排序方式是基于从高到低的分数排序之外,其它功能和参数含义均与ZRANGEBYSCORE相同。返回分数在指定范围内的成员列表。 
    ZREMRANGEBYRANKkey start stop O(log(N)+M)时间复杂度中的N表示Sorted-Set中成员的数量,M则表示被删除的成员数量。删除索引位置位于start和stop之间的成员,start和stop都是0-based,即0表示分数最低的成员,-1表示最后一个成员,即分数最高的成员。  被删除的成员数量。
    ZREMRANGEBYSCOREkey min max O(log(N)+M)时间复杂度中的N表示Sorted-Set中成员的数量,M则表示被删除的成员数量。删除分数在min和max之间的所有成员,即满足表达式min <= score <= max的所有成员。对于min和max参数,可以采用开区间的方式表示,具体规则参照ZCOUNT。 被删除的成员数量。

    三、命令示例:

       1. ZADD/ZCARD/ZCOUNT/ZREM/ZINCRBY/ZSCORE/ZRANGE/ZRANK:
        #在Shell的命令行下启动Redis客户端工具。
        /> redis-cli
        #添加一个分数为1的成员。

        redis 127.0.0.1:6379> zadd myzset 1 "one"
        (integer) 1
        #添加两个分数分别是2和3的两个成员。
        redis 127.0.0.1:6379> zadd myzset 2 "two" 3 "three"
        (integer) 2
        #0表示第一个成员,-1表示最后一个成员。WITHSCORES选项表示返回的结果中包含每个成员及其分数,否则只返回成员。
        redis 127.0.0.1:6379> zrange myzset 0 -1 WITHSCORES
        1) "one"
        2) "1"
        3) "two"
        4) "2"
        5) "three"
        6) "3"
        #获取成员one在Sorted-Set中的位置索引值。0表示第一个位置。
        redis 127.0.0.1:6379> zrank myzset one
        (integer) 0
        #成员four并不存在,因此返回nil。
        redis 127.0.0.1:6379> zrank myzset four
        (nil)
        #获取myzset键中成员的数量。    
        redis 127.0.0.1:6379> zcard myzset
        (integer) 3
        #返回与myzset关联的Sorted-Set中,分数满足表达式1 <= score <= 2的成员的数量。
        redis 127.0.0.1:6379> zcount myzset 1 2
        (integer) 2
        #删除成员one和two,返回实际删除成员的数量。
        redis 127.0.0.1:6379> zrem myzset one two
        (integer) 2
        #查看是否删除成功。
        redis 127.0.0.1:6379> zcard myzset
        (integer) 1
        #获取成员three的分数。返回值是字符串形式。
        redis 127.0.0.1:6379> zscore myzset three
        "3"
        #由于成员two已经被删除,所以该命令返回nil。
        redis 127.0.0.1:6379> zscore myzset two
        (nil)
        #将成员one的分数增加2,并返回该成员更新后的分数。
        redis 127.0.0.1:6379> zincrby myzset 2 one
        "3"
        #将成员one的分数增加-1,并返回该成员更新后的分数。
        redis 127.0.0.1:6379> zincrby myzset -1 one
        "2"
        #查看在更新了成员的分数后是否正确。
        redis 127.0.0.1:6379> zrange myzset 0 -1 WITHSCORES
        1) "one"
        2) "2"
        3) "two"
        4) "2"
        5) "three"
        6) "3"

       2. ZRANGEBYSCORE/ZREMRANGEBYRANK/ZREMRANGEBYSCORE
        redis 127.0.0.1:6379> del myzset
        (integer) 1
        redis 127.0.0.1:6379> zadd myzset 1 one 2 two 3 three 4 four
        (integer) 4
        #获取分数满足表达式1 <= score <= 2的成员。
        redis 127.0.0.1:6379> zrangebyscore myzset 1 2
        1) "one"
        2) "two"
        #获取分数满足表达式1 < score <= 2的成员。
        redis 127.0.0.1:6379> zrangebyscore myzset (1 2
        1) "two"
        #-inf表示第一个成员,+inf表示最后一个成员,limit后面的参数用于限制返回成员的自己,
        #2表示从位置索引(0-based)等于2的成员开始,去后面3个成员。

        redis 127.0.0.1:6379> zrangebyscore myzset -inf +inf limit 2 3
        1) "three"
        2) "four"
        #删除分数满足表达式1 <= score <= 2的成员,并返回实际删除的数量。
        redis 127.0.0.1:6379> zremrangebyscore myzset 1 2
        (integer) 2
        #看出一下上面的删除是否成功。
        redis 127.0.0.1:6379> zrange myzset 0 -1
        1) "three"
        2) "four"
        #删除位置索引满足表达式0 <= rank <= 1的成员。
        redis 127.0.0.1:6379> zremrangebyrank myzset 0 1
        (integer) 2
        #查看上一条命令是否删除成功。
        redis 127.0.0.1:6379> zcard myzset
        (integer) 0
          
       3. ZREVRANGE/ZREVRANGEBYSCORE/ZREVRANK:
        #为后面的示例准备测试数据。
        redis 127.0.0.1:6379> del myzset
        (integer) 0
        redis 127.0.0.1:6379> zadd myzset 1 one 2 two 3 three 4 four
        (integer) 4
        #以位置索引从高到低的方式获取并返回此区间内的成员。
        redis 127.0.0.1:6379> zrevrange myzset 0 -1 WITHSCORES
        1) "four"
        2) "4"
        3) "three"
        4) "3"
        5) "two"
        6) "2"
        7) "one"
        8) "1"
        #由于是从高到低的排序,所以位置等于0的是four,1是three,并以此类推。
        redis 127.0.0.1:6379> zrevrange myzset 1 3
        1) "three"
        2) "two"
        3) "one"
        #由于是从高到低的排序,所以one的位置是3。
        redis 127.0.0.1:6379> zrevrank myzset one
        (integer) 3
        #由于是从高到低的排序,所以four的位置是0。
        redis 127.0.0.1:6379> zrevrank myzset four
        (integer) 0
        #获取分数满足表达式3 >= score >= 0的成员,并以相反的顺序输出,即从高到底的顺序。
        redis 127.0.0.1:6379> zrevrangebyscore myzset 3 0
        1) "three"
        2) "two"
        3) "one"
        #该命令支持limit选项,其含义等同于zrangebyscore中的该选项,只是在计算位置时按照相反的顺序计算和获取。
        redis 127.0.0.1:6379> zrevrangebyscore myzset 4 0 limit 1 2
        1) "three"
        2) "two"
        
    四、应用范围:

        1). 可以用于一个大型在线游戏的积分排行榜。每当玩家的分数发生变化时,可以执行ZADD命令更新玩家的分数,此后再通过ZRANGE命令获取积分TOP TEN的用户信息。当然我们也可以利用ZRANK命令通过username来获取玩家的排行信息。最后我们将组合使用ZRANGE和ZRANK命令快速的获取和某个玩家积分相近的其他用户的信息。
        2). Sorted-Sets类型还可用于构建索引数据。

    展开全文
  • 学习参考: Java集合时间复杂度 - Alex-XYL - 博客园 类型 底层结构 重复 null值 使用场景 备注 查询 删除 增加 List ArrayList 动态数组 可重复 允许 快速随机访问元素 0(1) 0(n) 尾部增加0(1),中部增加0(n...
  • set集合是无序不重复的。 支持集合间的操作,交集(这个特性可以用来做共同关注)、并集、差集。 结构 key value test1 a  b  c  ...
  • 不管是刷题还是平时项目中,程序的时间复杂度都是评价程序好坏的一个很重要的因素,但是一些内置函数的时间复杂度由于不是自己编写,因此需要自行记忆,本篇文章是Python中set,list和dict基本操作的时间复杂度总结...
  • Java 集合时间复杂度

    千次阅读 2021-03-03 11:16:56
    ListArrayListget() 直接读取下标,复杂度 O(1)add(E) 直接在队尾添加,复杂度 O(1)add(index, E) 在第n个元素后插入,n后面的元素需要向后移动,复杂度 O(n)remove() 删除元素后面的元素需要逐个前移,复杂度 O(n)...
  • vector、map、queue 等 C++ 常用 STL的特性、函数时间复杂度和用法。
  • set key value O(1) 设置key对应string类型的值,返回1表示成功,0失败。 setnx key value O(1) 如果key不存在,设置key对应string类型的值。如果key已经存在,返回0。 get key O(1) 获取key对应的string值...
  • 总结:对于可随机访问的有序容器使用 algorithm 库中的 lower_bound 和 upper_bound 函数时间复杂度为O(logn), 但对于set,multiset这种不能随机访问的有序容器,要用其自带的 lower_bound 和 upper_...
  • python数据结构之 set

    2021-02-03 00:26:48
    在数学概念中,被意为整合元素的定义区域在python中,set最大的作用是用来去重set常见操作:In [158]: s ={1,1,1,1,2,22,33,3,3,3}In [159]: sOut[159]: {1,2, 3, 22, 33}在定义一个集合的时候,只能使用大括号定义...
  • python-集合(set)知识整理

    千次阅读 2020-12-07 17:48:29
    #### 集合 ####set 集合 和 线性结构线性结构 的查询时间复杂度是 O(n),即随着数据规模的增大而增加耗时。set集合 ,dict 字典 等结构,内部使用hash值作为key,时间复杂度可以做到O(1),查询时间和数据规模无关可hash...
  • Redis各种命令时间复杂度一览表

    千次阅读 2020-12-09 10:55:17
    时间复杂度 set 0(1) get 0(1) del 0(k),k是键的个数 mset 0(k),k是键的个数 mget 0(k),k是键的个数 incr 0(1) decr 0(1) incryby 0(1) decryby 0(1) incrybyfloat 0(1) append 0(1) ...
  • c++ STL中常见容器的时间复杂度

    千次阅读 2019-02-28 15:12:18
    不同操作的时间复杂度近似为: 插入: O(logN) 查看:O(logN) 删除:O(logN) unordered_map,unordered_set,unordered_multimap, and unordered_multiset 上述四种容器采用哈希表实现,不同操作的时...
  • set练习(示例代码)

    2020-12-07 17:48:24
    //定义setset name;(如果typename是一个STL容器, 定义时要记得在>>符号前加上空格setname;setname;setname;set name; //node是结构体的类型//set数组的定义:set Arrayname[arraySize];set a[100];//set...
  • C++ STL中各种数据结构操作的时间复杂度比较 访问 push_back() push_front() insert() pop_back() pop_front() erace() find() list O(n) O(1) O(1) O(1) O(1) O(1) ...
  • python 里面内置的 in 时间复杂度

    万次阅读 2020-09-03 14:41:12
    target, array): for line in array: #遍历每一行 if target in line: #目标在这行里面吗 return True return False # write code here 然后我就想这个的时间复杂度怎么样呢,我感觉就像双循环暴力查找,应该 o(n2)...
  • 我们先看Redis的几个常见命令时间复杂度: keys * 返回所有的key,keys命令最好不要在生产环境用,会全局遍历,会很慢,要堵塞其他命令。 dbsize 显示一共有几个key,这个可以在生产用,不会全局遍历 exists ...
  • Java中HashMap的get和put算法时间复杂度空间复杂度是多少?在JDK8之前在JDK8之后解释 在JDK8之前 用单链表HashMap作为一个桶来储存存在哈希碰撞的元素。无论是get还是put方法,步骤都可以分为第一步找桶(找桶时间都...
  • set 是 Python 非常重要的一种数据结构,不能包含相同的元素,几乎所有用过 Python 的人都知道, set 有进行列表去重的功能。但是鲜有人意识到了 set 在数学概念上的意义,那就是集合,能进行交集、并集这些操作。很...
  • HashMap 时间复杂度

    千次阅读 2020-05-24 22:58:27
    HashMap 时间复杂度? hashmap时间复杂度,理想情况下HashMap的时间复杂度为O(1) 因为对于内存来说,访问任何地址的时间是一样的,即时间极短,相当于可以同时访问到所有地址。而在时间复杂度为O(1)时,需要很...
  • 较全且详细的python list,set,dict时间复杂度解释 参考网址 https://pyocean.com/post/2019_09_02_python%E4%B8%8A%E7%9A%84%E6%97%B6%E9%97%B4%E5%A4%8D%E6%9D%82%E5%BA%A6/
  • 时间维度:是指执行当前算法所消耗的时间,我们通常用「时间复杂度」来描述。 空间维度:是指执行当前算法需要占用多少内存空间,我们通常用「空间复杂度」来描述。 因此,评价一个算法的效率主要是看它的时间复杂度...
  • C++ unordered_setset

    千次阅读 2022-04-10 21:32:12
    set对插入,删除,查找操作的时间复杂度都为O(1),它运用Hash表将unordered_set中的元素存储在buckets中,buckets是hash函数内部通过插入的元素值分配的一个内存空间,便于快速查找,但是对于数据量较大的情况下ha
  • 今日碰见有两方唇枪舌战,争辩 Set时间复杂度。我腹诽:居然有人认为它是 O(1) 吗?但我人怂胆小,宛如街头争霸时两大帮派摩拳擦掌时弱小可怜但充满正义感的小豆芽。所谓 “Talk is cheap. Show me the code.”,...
  • What is the the time complexity of each of python's set operations in Big O notation?I am using Python's set type for an operation on a large number of items. I want to know how each operation's perfo...
  • STL 之 unordered_set

    2019-09-07 17:54:18
    unordered_set与与unordered_map相似,unordered_set基于哈希表,数据插入和查找的时间复杂度很低,几乎是常数时间,而代价是消耗比较多的内存,无自动排序功能。底层实现上,使用一个下标范围比较大的数组来存储...
  • 今天看启发式合并的时候发现一个题解里面直接对 set 进行 swap,就好奇其时间复杂度。测了一下,容器的 swap 应该是 O(1) 的。 vector 的 swap 时间近似 O(1) set, map 的 swap 时间常数稍大,是 vector 的 2 倍左右...
  • C++容器之unordered_set

    2021-07-25 16:20:54
    unordered_setset的区别

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 169,414
精华内容 67,765
关键字:

set时间复杂度