堆排序的时间和空间复杂度

1,342 阅读8分钟

在这篇文章中,我们解释了堆排序的时间和空间复杂性,并详细分析了不同情况,如最差情况、最佳情况和平均情况。

目录。

  1. 堆排序的概述
  2. 堆数据结构的时间复杂度
  3. 堆排序的最坏情况下的时间复杂度
  4. 堆排序的最佳情况下的时间复杂度
  5. 堆排序的平均情况下的时间复杂度
  6. 堆排序的空间复杂度
  7. 总结

前提是。堆排序堆数据结构

让我们开始学习堆排序的时间和空间复杂度。

堆排序的概述

堆排序算法主要由两部分组成--将列表转换为堆,并将堆中的最大元素添加到列表的末尾,同时保持堆的结构。为了便于实现,我们使用一个最大堆结构,其中最大值总是存在于根部。在将列表转换为堆后,我们从其中取出最大元素并将其添加到列表的末尾。我们重复这个过程,直到堆中的元素数量变为零。这表明我们已经按照正确的顺序排列了列表中的所有项目。因此,总结起来,在实现堆排序时,我们主要关注两个方面 --

  • 建立最大堆
  • 从堆中获取最大值(根节点的值),将其添加到列表的末尾,并更新max-heap。重复进行,直到max-heap包含零项。

实现这一算法的伪代码如下

lst = [a1, a2, ..., aN]

# heap sort the list
heapsort(lst):

    set heap_size equal to list length
    create_heap(lst)
    
    for i from 0 to heap_size-1, decrement by 1:
        lst[0], lst[i] = lst[i], lst[0]
        heap_size -= 1
        max_heapify(lst, heap_size, 0)
        
# function to style a heap as per max-heap properties
max_heapify(lst, heap_size, i):

    get index of left child node of i

    get index of right child node of i

    create a variable to track index of largest list item

    if left < heap_size and lst[left] > lst[largest]:
        largest = left
    if right < heap_size and lst[right] > lst[largest]:
        largest = right
        
    if largest != i:
        swap(i, largest)
        max_heapify(lst, heap_size, largest)


#  function that creates a heap
#  uses the max_heapify function to create a max-heap
create_heap(lst):

    get length of list to get heap_size

    for i from heap_size//2 to -1, decrement by 1:
        max_heapify (lst, heap_size, i)


# print the sorted list at the end
heapsort(lst)
print(lst) 

这给出了Heapsort算法背后的基本想法。

堆数据结构的时间复杂性

在该算法中,我们使用了max_heapifycreate_heap ,这是该算法的第一部分。当使用create_heap ,我们需要了解如下所示的最大堆结构是如何工作的。
max-heap-bin-tree-1

因为我们使用的是二叉树,所以堆的底部包含最大数量的节点。当我们往上走的时候,节点的数量减少了一半。考虑到有'n'个节点,那么从最底层开始的节点数为

  • n/2
  • n/4 (在下一级)
  • n/8
  • 以此类推

插入一个新节点的复杂性

因此,当我们在制作堆的时候插入一个新的值,我们需要采取的最大步骤数得出是O(log(n))。由于我们使用二叉树,我们知道这样一个结构的最大高度总是O(log(n))。当我们在堆中插入一个新的值时,我们将用一个比它大的值来交换它,以保持最大堆的特性。这种交换的数量将是O(log(n))。因此,在建立最大堆时插入一个新值将是O(log(n))。

从堆中删除最大值节点的复杂性

同样地,当我们从堆中移除最大值节点,添加到列表的末尾时,所需的最大步骤数也是O(log(n))。由于我们交换最大值节点直到它降到最底层,我们需要的最大步骤数与插入一个新节点时相同,即O(log(n))。

因此,max_heapify 函数的总时间复杂性变成了O(log(n))。

创建一个堆的复杂性

使用create_heap 函数将一个列表转换为一个堆的时间复杂度不是O(log(n))。这是因为当我们创建一个堆时,不是所有的节点都会向下移动O(log(n))次。只有根节点才会这样做。最底层的节点(由n/2给出)根本不会向下移动。倒数第二层的节点(n/4)会向下移动1次,因为下面只剩下一层可以向下移动。最后第三层的节点将向下移动2次,以此类推。因此,如果我们将所有节点的移动次数相乘,在数学上,它将变成一个几何数列,解释如下

(n/2 * 0) + (n/4 * 1) + (n/8 * 2) + (n/16 * 3) + ...h

这里h代表最大堆结构的高度。

这个系列的总和,经过计算,最后得到的数值是n/2。因此,create_heap 的时间复杂度变成了O(n)。

总的时间复杂度

heapsort 的最后一个函数中,我们利用create_heap ,它运行一次来创建一个堆,运行时间为O(n)。然后使用for-loop,我们为每个节点调用max_heapify ,每当我们在堆中删除或插入一个节点时,都要保持最大堆的特性。由于有'n'个节点,因此,算法的总运行时间变成了O(n(log(n)),我们对每个节点使用max-heapify 函数。
从数学上看,我们看到

  • 一个节点的第一次删除需要log(n)时间
  • 第二次删除需要log(n-1)时间
  • 第三次删除需要log(n-2)时间
  • 以此类推,直到最后一个节点,这将需要log(1)时间。

因此,将所有的条款相加,我们得到---

log(n) + log(n-1) + log(n-2) + ....log(1)
由于log(x) + log(y) = log(x * y) ,我们得到
=log(n∗(n-1)∗(n-2)∗...∗2∗1)
=log(n!
进一步简化后(使用斯特林的近似值),log(n!)变成

=n∗log(n)-n+O(log(n)

)

考虑到最高排序项,总运行时间变成了O(n(log(n))。

堆排序的最坏情况下的时间复杂度

堆排序的最坏情况可能发生在列表中的所有元素都是不同的。因此,我们需要在每次删除一个元素时调用max-heapify 。在这种情况下,考虑到有'n'个节点------。

  • 删除每个元素的交换次数将是log(n),因为这是堆的最大高度。
  • 考虑到我们对每个节点都这么做,总的移动次数将是n * (log(n))。

因此,最坏情况下的运行时间将是O(n(log(n))。

堆排序的最佳情况下的时间复杂度

堆排序的最佳情况是当列表中所有要排序的元素都是相同的。在这种情况下,对于'n'数量的节点--

  • 从堆中移除每个节点只需要一个恒定的运行时间,即O(1)。由于所有的项目都是相同的,所以不需要将任何节点降低或将最大值节点提高。
  • 由于我们对每个节点都这样做,所以移动的总数将是n * O(1)。

因此,在最好的情况下,运行时间将是O(n)。

堆排序的平均案例时间复杂度

就总的复杂性而言,我们已经知道我们可以在O(n)时间内创建一个堆,并在O(log(n))时间内进行节点的插入/移除。 就平均时间而言,我们需要考虑到所有可能的输入,无论是否有不同的元素。如果节点的总数是'n',在这种情况下,max-heapify 函数需要执行。

  • 在第一次迭代中进行log(n)/2的比较(因为我们一次只比较两个值来建立最大堆)
  • 在第二次迭代中进行log(n-1)/2的比较
  • 第三次迭代中的log(n-2)/2
  • 以此类推

因此,从数学上讲,总和将变成--

(log(n))/2 + (log(n-1))/2 + (log(n-2))/2 + (log(n-3))/2 + ...
经近似计算,最终结果为
=1/2(log(n!))
=1/2(n∗log(n)-n+O(log(n)

)

考虑到最高排序项,那么max-heapify 的平均运行时间将是O(n(log(n))。

由于我们在最后的heapsort 函数中为所有节点调用这个函数,运行时间将是(n * O(n(log(n)))。计算平均数,除以n后,我们会得到最终的平均运行时间为O(n(log(n))

堆排序的空间复杂度

由于heapsort是一种就地设计的排序算法,空间需求是恒定的,因此是O(1)。这是因为,在任何输入--------------------------------的情况下,我们都要把所有的列表项就地排列。

  • 我们使用堆结构就地排列所有的列表项
  • 在从最大堆中移除最大节点后,我们将移除的项目放在同一个列表的末尾。

因此,在实现这个算法时,我们不使用任何额外的空间。这使得该算法的空间复杂度为O(1)。

结论

综上所述,heapsort具有。

  • 最坏情况下的时间复杂度为O(n(log(n))[列表中的所有元素都是不同的]
  • 最佳情况下的时间复杂度为O(n) [所有元素都是相同的] 。
  • 平均情况下的时间复杂度为O(n(log(n))
  • 空间复杂度为O(1)

通过OpenGenus的这篇文章,你一定对堆排序的时间和空间复杂度有了完整的了解。