Algorithm 如何在堆数据结构中删除?

Algorithm 如何在堆数据结构中删除?,algorithm,data-structures,heap,Algorithm,Data Structures,Heap,我知道如何从最大堆中删除根节点,但从中间删除节点的过程是否重复删除和替换根节点,直到删除所需的节点 O(logn)是这个过程的最佳复杂度吗 由于必须删除其他节点才能删除特定节点,这是否会影响大O复杂性 如果您有一个最大堆,您可以通过为要删除的项分配一个比任何其他值(例如int.MaxValue或inf等语言)都大的值来实现这一点,然后重新堆化,它将成为新的根。然后定期删除根节点 这将导致另一次重新健康,但我看不到一个明显的方法来避免做两次。这表明,如果您需要经常从堆的中间提取节点,那么堆可能不适

我知道如何从最大堆中删除根节点,但从中间删除节点的过程是否重复删除和替换根节点,直到删除所需的节点

  • O(logn)是这个过程的最佳复杂度吗

  • 由于必须删除其他节点才能删除特定节点,这是否会影响大O复杂性


  • 如果您有一个最大堆,您可以通过为要删除的项分配一个比任何其他值(例如
    int.MaxValue
    inf
    等语言)都大的值来实现这一点,然后重新堆化,它将成为新的根。然后定期删除根节点

    这将导致另一次重新健康,但我看不到一个明显的方法来避免做两次。这表明,如果您需要经常从堆的中间提取节点,那么堆可能不适合您的用例


    (对于最小堆,您显然可以使用
    int.MinValue
    -inf
    或任何东西)

    您想要实现的不是典型的堆操作,在我看来,一旦引入“删除中间元素”作为方法,其他一些二叉树(例如红黑树或AVL树)就是更好的选择。在某些语言中实现了红黑树(例如c++中的map和set)

    否则,执行中间元素删除的方法如rejj的回答中所建议的那样:为元素分配一个大值(对于最大堆)或一个小值(对于最小堆),对其进行筛选,直到它是根元素,然后删除它

    这种方法仍然保持中间元素删除的O(log(n))复杂性,但您提出的方法没有。它的复杂性为O(n*log(n)),因此不是很好。
    希望这会有所帮助。

    从堆中删除任意元素的问题是您找不到它

    在堆中,查找任意元素是
    O(n)
    ,因此删除元素[如果由值给定]也是
    O(n)

    如果要从数据结构中删除任意元素是很重要的,堆可能不是最好的选择,应该考虑完全排序的数据结构,例如,或../p>。


    但是,如果您的元素是通过引用给出的,则可以在
    O(logn)
    中删除它,方法是简单地用最后一个叶“替换”它[记住,堆是作为一个完整的二叉树实现的,因此有最后一个叶,您确切地知道它在哪里],删除这些元素,然后重新堆化相关的子堆。

    实际上,您可以轻松地从堆的中间删除项

    这样做的目的是获取堆中的最后一项,并从当前位置(即保存已删除项的位置)开始筛选新项是否大于旧项的父项。如果不大于父项,则筛选它

    这是最大堆的过程。当然,对于最小堆,您将反转大小写

    在堆中查找项是一个O(n)操作,但是如果您已经知道它在堆中的位置,那么删除它就是O(logn)

    几年前,我为DevSource发布了一个基于堆的优先级队列。完整资料来源于

    更新 一些人询问,在移动堆中的最后一个节点以替换已删除的节点后,是否可以向上移动。考虑这个堆:

            1
        6       2
      7   8   3
    
    如果删除值为7的节点,则值3将替换该节点:

            1
        6       2
      3   8
    
    现在必须将其向上移动以生成有效堆:

            1
        3       2
      6   8
    

    这里的关键是,如果要替换的项与堆中的最后一项位于不同的子树中,则替换节点可能比被替换节点的父节点小。

    从已知堆数组位置移除元素具有
    O(log n)
    复杂性(这对于堆来说是最佳的)。因此,此操作与提取(即移除)根元素具有相同的复杂性

    删除第i个元素的基本步骤(其中
    0a[i])
    maxi=r;
    if(maxi==i)
    打破
    交换(A、i、maxi);
    i=最大值;
    }
    }
    
    由于堆是一个(几乎)完整的二叉树,所以它的高度为
    O(logn)
    。在最坏的情况下,这两个heapify函数都必须访问所有树级别,因此按索引删除是在
    O(logn)
    中进行的

    请注意,在堆中查找具有特定键的元素是在
    O(n)
    中进行的。因此,由于查找的复杂性,按键值删除是在
    O(n)
    中进行的

    那么,我们如何跟踪插入的元素的数组位置呢?毕竟,进一步的插入/删除可能会移动它


    我们还可以通过在堆上为每个元素的键旁边存储一个指向元素记录的指针来跟踪。元素记录包含一个具有当前位置的字段,然后由修改后的堆插入和堆交换函数维护该字段。因此,如果我们在插入之后保留指向元素记录的指针,那么我们可以在常量时间内得到堆中元素的当前位置。

    为什么你要在Max堆中删除中间的一个节点?@ BrkgnLass:这样一个非常实际的使用就是一个预定任务的优先级队列的堆表示,有人取消了其中一个作业。@BrokenGlass:我最近实现了LPA*寻路算法,一种基于a*的重新规划算法。它需要能够从优先级队列的中间删除。通常,您希望发布新问题,而不是在已发布四年的帖子中添加新问题。但要回答您的问题:1)在标准二进制堆中,O(logn)是最佳复杂性。2) 从堆的中间删除永远不会比删除根节点更昂贵,并且该操作已经被证明是O(logn)。O(logn)是在堆中任何位置删除节点的最坏情况复杂性。然而,请注意,我在我的origi中指出的
    A[parent(i)] >= A[i], for 0 < i < n
    
    A[parent(i)] <= A[i], for 0 < i < n
    
    void heap_remove(A, i, &n)
    {
        assert(i < n);
        assert(is_heap(A, i));
        
        --n;
        if (i == n)
          return;
        
        bool is_gt = A[n] > A[i];
    
        A[i] = A[n]; 
        
        if (is_gt)
            heapify_up(A, i);
        else
            heapify(A, i, n);
    }
    
    void heapify_up(A, i)
    {
        while (i > 0) {
            j = parent(i);
            if (A[i] > A[j]) {
                swap(A, i, j);
                i = j;
            } else {
                break;
            }
        }
    }
    
    void heapify(A, i, n)
    {
        for (;;) {
            l = left(i);
            r = right(i);
    
            maxi = i;
            if (l < n && A[l] > A[i])
                maxi = l;
            if (r < n && A[r] > A[i])
                maxi = r;
            if (maxi == i)
                break;
            swap(A, i, maxi);
            i = maxi;
        }
    }