反句算法的复杂性

时间:2017-05-31 08:19:45

标签: python algorithm data-structures

我正在研究Python中的数据结构问题,我必须以最有效的方式颠倒数组中单词的顺序。我提出了以下问题的解决方案

def reverse(arr, st, end):
    while st < end:
        arr[st], arr[end] = arr[end], arr[st]
        end -= 1
        st += 1

def reverse_arr(arr):
    arr = arr[::-1]
    st_index = 0
    length = len(arr)

    for i, val in enumerate(arr):
        if val == ' ':
            end_index = i-1
            reverse(arr, st_index, end_index)
            st_index = end_index + 2

        if i == length - 1:
            reverse(arr, st_index, length-1)

    return arr

如果arr是:

arr = [ 'p', 'e', 'r', 'f', 'e', 'c', 't', ' ',
        'm', 'a', 'k', 'e', 's', ' ',
        'p', 'r', 'a', 'c', 't', 'i', 'c', 'e' ]

它返回:

['p', 'r', 'a', 'c', 't', 'i', 'c', 'e', ' ', 
 'm', 'a', 'k', 'e', 's', ' ', 
 'p', 'e', 'r', 'f', 'e', 'c', 't']

解决方案工作正常,但我不明白这个算法的复杂性是如何O(n)。编写的是,每个项目以恒定数量的动作遍历数组两次是线性的,即O(n),其中n是数组的长度。

我认为它应该超过O(n),因为根据我的说法,每个单词的长度不固定,反转每个单词的时间复杂度取决于单词的长度。有人可以用更好的方式解释这个吗?

3 个答案:

答案 0 :(得分:3)

reverse将为每个单词调用一次。在那次通话中,它会为每个角色做一定量的工作。

您可以根据单词数量和单词的平均长度(即O(wordCount*averageWordLength))或数组中的字符总数来表示。如果你选择后者,很容易看出你仍然在为每个角色做一定量的工作(因为reversereverse_arr每个角色都有不变的工作量字符,并且没有两个reverse调用将包含相同的字符),导致O(characterCount)复杂性。

我不会假设&#34;阵列的长度&#34;在解释中指的是单词的数量,而是字符的数量,或者他们假设单词长度具有固定的上限(其中复杂性确实是O(wordCount))。

TL; DR: n O(n)characterCountwordCount,而不是<GenerateManifests>true</GenerateManifests>

答案 1 :(得分:2)

def reverse(arr, st, end):
    while st < end:
        arr[st], arr[end] = arr[end], arr[st]
        end -= 1
        st += 1

def reverse_Cha(arr):
    arr = arr[::-1]
    st_index = 0
    length = len(arr)

    for i, val in enumerate(arr):
        if val == ' ':
            end_index = i-1
            reverse(arr, st_index, end_index)
            st_index = end_index + 2

        if i == length - 1:
            reverse(arr, st_index, length-1)

    return arr

def reverse_Jon(arr):
    r = [ch for word in ' '.join(''.join(arr).split()[::-1]) for ch in word]
    return r

def reverse_Nua(arr):
    rev_arr = list(' '.join(''.join(arr).split()[::-1]))
    return rev_arr

如果我们考虑了3个提议的解决方案:您的reverse_Cha,Jon Clements的reverse_Jon,我的reverse_Nua。 我们注意到,当我们使用O(n)时,当我们检查列表的每个元素(长度[::-1])等时,我们会n

reverse_Cha使用[::-1],然后检查每个元素两次(然后阅读以进行交换),因此复杂性取决于我们写为{{O(3n+c)的元素总数(O(n) 1}}(+c来自O(1)次操作))

reverse_Jon使用[::-1],然后检查每个元素两次(检查每个单词的每个字符),因此复杂性取决于元素总数和单词数(O(3n+m)我们写为O(n+m)m字数))

reverse_Nua使用[::-1],然后坚持使用python list函数,因此复杂性仍然取决于元素的总数(这次只是O(n)}

作为绩效期(1e6循环),我们得到reverse_Cha:2.785867s; reverse_Jon:4.11845s(由于for); reverse_Nua:1.185973s。

答案 2 :(得分:1)

我认为这是一个纯粹的理论问题,因为在现实世界的应用程序中,您可能宁愿将列表拆分为单字子列表,然后以相反的顺序重新加入子列表 - 这需要更多内存,但要快得多。

话虽如此,我想指出你所展示的算法确实是O(n) - 它取决于你的单词的长度,而不是长度个别的话。换句话说:20个3个字母的单词,6个10个字母的单词,10个6个字母的单词需要相同的时间......你总是只翻阅每个字母两次:一次是在单个单词的反转期间(这是第一次调用reverse中的reverse_arr和整个数组的反转期间(第二次调用reverse)。