拒绝浪费时间!同学们!时间复杂度不再难懂,看这一篇就够了!!!

一、什么是时间复杂度

时间复杂度是衡量算法执行时间随输入规模增长而变化的一种度量方式。它描述了算法执行所需的时间与输入规模之间的关系。

通常,我们使用大O符号(O)来表示时间复杂度,并根据算法执行时间随输入规模增长的方式,将算法分为若干类别,如常数时间、对数时间、线性时间、平方时间等。

二、常见的时间复杂度

时间复杂度O(1)

时间复杂度为O(1)表示算法的执行时间是常数级别的,与输入规模无关。

举一个O(1)时间复杂度的例子是获取数组中第一个元素的值。无论数组的长度是多少,只需通过索引访问第一个位置即可,所需的时间是固定的。

例如,有一个函数用于获取数组的第一个元素:

def get_first_element(arr):
    return arr[0]

在这个例子中,不论数组的长度是10、100还是1000,获取第一个元素的时间都是恒定的,没有随数组长度增加而增加的趋势。因此,该函数的时间复杂度为O(1)。

时间复杂度O(n)

时间复杂度为O(n)的例子比较常见,其中算法的执行时间与输入规模成线性关系。

一个简单的O(n)时间复杂度的例子是计算数组中元素的总和。

以下是一个计算数组总和的示例代码:

def calculate_sum(arr):
    total = 0
    for num in arr:
        total += num
    return total

在这个示例中,算法遍历了输入数组中的每个元素,并将它们累加到变量total中。由于每个元素只会被访问一次,因此随着输入数组的长度n增加,执行时间也会线性增长,时间复杂度为O(n)。

需要注意的是,在实际编程中,有时可能存在多个步骤或循环,但只要它们都是以线性方式执行而不是嵌套执行,整体的时间复杂度仍然是O(n)。

时间复杂度O(log n)

时间复杂度为O(log n)通常出现在分治算法或者二分搜索等问题上,其中算法的执行时间随着输入规模的增加而以对数方式增长。

举一个O(log n)时间复杂度的例子是二分查找算法。二分查找算法用于在有序数组中查找特定元素的位置。

以下是二分查找的示例代码:

def binary_search(arr, target):
    left = 0
    right = len(arr) - 1

    while left <= right:
        mid = (left + right) // 2
        if arr[mid] == target:
            return mid
        elif arr[mid] < target:
            left = mid + 1
        else:
            right = mid - 1

    return -1  # 如果没有找到目标元素,返回 -1

在这个例子中,每次迭代都将输入数组的范围减半,直到找到目标元素或者确定目标元素不存在。因此,无论输入数组的长度是多少,所需的迭代次数都是以对数方式增长。

因此,二分查找算法的时间复杂度为O(log n),其中n是输入数组的长度。

时间复杂度O(n log n)

时间复杂度为O(n log n)通常出现在使用分治算法解决问题时,其中算法的执行时间随着输入规模的增加而以n*log(n)方式增长。

举一个O(n log n)时间复杂度的例子是归并排序。归并排序是一种常见的排序算法,通过将数组递归地划分为较小的子数组,然后按照一定规则合并子数组来实现排序。

以下是归并排序的示例代码:

def merge_sort(arr):
    if len(arr) <= 1:
        return arr

    mid = len(arr) // 2
    left = arr[:mid]
    right = arr[mid:]

    left = merge_sort(left)
    right = merge_sort(right)

    return merge(left, right)

def merge(left, right):
    merged = []
    i = 0
    j = 0

    while i < len(left) and j < len(right):
        if left[i] <= right[j]:
            merged.append(left[i])
            i += 1
        else:
            merged.append(right[j])
            j += 1

    while i < len(left):
        merged.append(left[i])
        i += 1

    while j < len(right):
        merged.append(right[j])
        j += 1

    return merged

在这个示例中,归并排序采用分治策略,将数组划分为较小的子数组,并逐步合并子数组以获得排序结果。在每一次递归调用中,数组的长度都会被划分为两半,因此每层的合并操作所需的时间复杂度是O(n)。总共有log n层递归,因此整个归并排序的时间复杂度为O(n log n)。

因此,归并排序是一个典型的O(n log n)时间复杂度的算法。

时间复杂度O(n^2)

时间复杂度为O(n^2)通常出现在嵌套循环的情况下,其中算法的执行时间随着输入规模的增加而呈二次方增长。

举一个O(n^2)时间复杂度的例子是选择排序(Selection Sort)。选择排序是一种简单但效率较低的排序算法,它的基本思想是每次从未排序的部分选择最小的元素放到已排序部分的末尾。

以下是选择排序的示例代码:

def selection_sort(arr):
    n = len(arr)

    for i in range(n):
        min_idx = i
        for j in range(i + 1, n):
            if arr[j] < arr[min_idx]:
                min_idx = j

        arr[i], arr[min_idx] = arr[min_idx], arr[i]

    return arr

在这个示例中,外层循环执行n次,内层循环执行n-i次,其中n是输入数组的长度。因此,总的比较次数是n + (n-1) + (n-2) + … + 1 = n*(n+1)/2,这是一个关于n的二次方的增长。因此,选择排序的时间复杂度为O(n^2)。

需要注意的是,选择排序的效率在大多数情况下较低,不适用于大规模数据的排序。但选择排序的优点是它是原地排序算法,不需要额外的存储空间,并且对于小规模数据或者部分有序的数据,选择排序可能会有一定的优势。

时间复杂度O(2^n)

时间复杂度为O(2^n)通常出现在指数级的算法中,其中算法的执行时间随着输入规模的增加呈指数级增长。

一个经典的O(2^n)时间复杂度的例子是求解斐波那契数列。斐波那契数列是一个以递归定义的数列,其中每个数字是前两个数字的和。

以下是一个使用递归方式求解斐波那契数列的示例代码:

def fibonacci(n):
    if n <= 1:
        return n

    return fibonacci(n-1) + fibonacci(n-2)

在这个示例中,求解第n个斐波那契数需要通过递归调用来求解前两个斐波那契数的和。每次递归调用都会分别调用两个子问题,而每个子问题的规模都是原问题规模的减少量。因此,总的递归调用次数将是指数级的,即O(2^n)。

需要注意的是,由于指数级的增长,使用递归方式求解斐波那契数列在较大的n值上效率非常低下。可以通过优化算法来降低时间复杂度,如使用动态规划或迭代方式来避免重复计算,从而提高效率。

猜你喜欢

转载自blog.csdn.net/weixin_46475607/article/details/131966593