【问题标题】:Finding the index of an element in a list. Binary search or use the index function?查找列表中元素的索引。二分查找还是使用索引功能?
【发布时间】:2015-08-29 03:29:50
【问题描述】:

我是 python 新手,一直在编写一个练习程序来检查 400 万个单词的列表中的密码。我原来的解决方案是这样的(如果密码包含在列表中,将打印 true):

import sys
from bisect import bisect_left 

script, password, pwlist = sys.argv
password = password+"\r\n"

l=[line for line in open(pwlist)] 
l.sort() #Must be sorted for bisect_left to work                         

print (password <= l[-1]) and (l[bisect_left(l, password)] == password)

然后我意识到我可以使用 index 方法,如下所示:

import sys

script, password, pwlist = sys.argv
password = password+"\r\n"

l=[line for line in open(pwlist)] #Note we don't need to sort this time

#Catch the "not in list" exception
try:
    print (password <= l[-1]) and (l[l.index(password)] == password)
except ValueError:
    print "False"

我的第二个版本大大减少了执行时间,因为列表不需要排序。我是否以正确的方式处理了这个问题? index() 方法是如何工作的?如果它适用于未排序的列表,它肯定不会进行二进制搜索。对此的任何建议将不胜感激。

【问题讨论】:

  • AFAIK index 只是按顺序遍历列表。 O(N) 可能比 O(N*log(N)) 排序加上 O(log(N)) 二进制搜索要快。顺便说一句,你有没有考虑过print password in l

标签: python


【解决方案1】:

是的,在第一个示例中,您从自己设计算法开始,即二分搜索

在第二个示例中,您只需使用 python 内置的list.index() 函数。

第二种方法更快,因为排序列表的成本:O(N*log(N)) 比对数组进行线性搜索的成本:O(N)更大

考虑一下:如果您必须检查多个密码,最好将排序列表排序和存储一次,然后在排序列表上使用二进制搜索。

【讨论】:

    【解决方案2】:

    当数据结构已经排序时,使用二分搜索会更好,因为你可以在 O(log N) 中得到它。当您对列表进行排序时,您会在 O(N*log N) 中进行排序,它比线性搜索 O(N) 慢

    【讨论】:

    • 由此得出的结论是,如果您希望进行 足够 次搜索,那么值得花时间预先对列表进行排序。 (其中“足够”意味着大约 O(lg n) 次搜索。)
    【解决方案3】:

    list.index 方法的复杂度在最坏的情况下是 O(N),基于它的 Cpython function 它是一个优化的函数,它返回列表中第一个匹配项的索引。所以这将是更可取的方式,请注意,当您处理排序列表时,二进制搜索非常好。

    listindex(PyListObject *self, PyObject *args)
    {
        Py_ssize_t i, start=0, stop=Py_SIZE(self);
        PyObject *v;
    
        if (!PyArg_ParseTuple(args, "O|O&O&:index", &v,
                                    _PyEval_SliceIndex, &start,
                                    _PyEval_SliceIndex, &stop))
            return NULL;
        if (start < 0) {
            start += Py_SIZE(self);
            if (start < 0)
                start = 0;
        }
        if (stop < 0) {
            stop += Py_SIZE(self);
            if (stop < 0)
                stop = 0;
        }
        for (i = start; i < stop && i < Py_SIZE(self); i++) {
            int cmp = PyObject_RichCompareBool(self->ob_item[i], v, Py_EQ);
            if (cmp > 0)
                return PyLong_FromSsize_t(i);
            else if (cmp < 0)
                return NULL;
        }
        PyErr_Format(PyExc_ValueError, "%R is not in list", v);
        return NULL;
    }
    

    但是在您的第一个代码中,您做了很多额外的工作。

    首先,您不需要使用列表解析来获取文件的所有行,而您可以简单地使用 file.readlines() 方法。而且您也有一个排序方法,这使得您的第一个方法比第二个慢得多一个。

    P.S 如果您只是想以更 Python 的方式检查成员资格,您可以使用 set 对象来保存您的项目,并且只需使用其顺序为 O(1) 的 in 操作数。

    【讨论】:

    • 略显迂腐注意:平均情况为 O(1)。对于具有哈希冲突的集合,它可能更高,并且可能(最坏情况)为 O(n)(所有项目具有相同的哈希值)
    • @theB 对于set,我没有说搜索是 O(1)。我说检查会员资格!
    猜你喜欢
    • 2011-09-22
    • 1970-01-01
    • 2019-12-31
    • 2013-07-26
    • 2021-06-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多