Python中的堆问题

Heap in python

Heap,即堆,也就是优先队列。我们可以在这里找到[]维基百科](https://en.wikipedia.org/wiki/Heap_(data_structure))

堆(英语:Heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象。在队列中,调度程序反复提取队列中第一个作业并运行,因为实际情况中某些时间较短的任务将等待很长时间才能结束,或者某些不短小,但具有重要性的作业,同样应当具有优先权。堆即为解决此类问题设计的一种数据结构。

逻辑定义:

n个元素序列{k1,k2...ki...kn},当且仅当满足下列关系时称之为堆:
(ki <= k2i,ki <= k2i+1)或者(ki >= k2i,ki >= k2i+1), (i = 1,2,3,4...n/2)

性质:

堆的实现通过构造二叉堆(binary heap),实为二叉树的一种;由于其应用的普遍性,当不加限定时,均指该数据结构的这种实现。这种数据结构具有以下性质。

  • 任意节点小于(或大于)它的所有后裔,最小元(或最大元)在堆的根上(堆序性)。
  • 堆总是一棵完全树。即除了最底层,其他层的节点都被元素填满,且最底层尽可能地从左到右填入。

将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。常见的堆有二叉堆、斐波那契堆等。

支持的基本操作[编辑]

操作 描述 时间复杂度
build 创建一个空堆 O(n)
insert 向堆中插入一个新元素 O(log n)
update 将新元素提升使其匹配堆的性质
get 获取当前堆顶元素的值 O(1)
delete 删除堆顶元素 O(log n)
heapify 使删除堆顶元素的堆再次成为堆

某些堆实现还支持其他的一些操作,如斐波那契堆支持检查一个堆中是否存在某个元素。

示例程序:

为将元素X插入堆中,找到空闲位置,创建一个空穴,若满足堆序性(英文:heap order),则插入完成;否则将父节点元素装入空穴,删除该父节点元素,完成空穴上移。直至满足堆序性。这种策略叫做上滤(percolate up)。[1]

void Insert( ElementType X, PriorityQueue H )
{
    int i;

    if( IsFull(H) )
    {
        printf( "Queue is full.\n" );
        return;
    }

    for( i = ++H->Size; H->Element[i/2] > X; i /= 2 )
        H->Elements[i] = H->Elements[i/2];
    H->Elements[i] = X;
}

以上是插入到一个二叉堆的过程。
DeleteMin,删除最小元,即二叉树的根或父节点。删除该节点元素后,队列最后一个元素必须移动到堆得某个位置,使得堆仍然满足堆序性质。这种向下替换元素的过程叫作下滤。

ElementType
DeleteMin( PriorityQueue H )
{
    int i, Child;
    ElementType MinElement, LastElement;

    if( IsEmpty( H ) )
    {
        printf( "Queue is empty.\n" );
        return H->Elements[0];
    }
    MinElement = H->Elements[1];
    LastElement = H->Elements[H->Size--];

    for( i = 1; i*2 <= H->Size; i = Child )
    {
        // Find smaller child.
        Child = i*2;
        if( Child != H->Size && H->Elements[Child+1]
                             <  H->Elements[Child] )
            Child++;

        // Percolate one level.
        if( LastElement > H->Elements[Child] )
            H->Elements[i] = H->Elements[Child];
        else
            break;
    }
    H->Elements[i] = LastElement;
    return MinElement;
}

应用

堆排序,或者运用堆的排序以选择优先

Python中的heapq模块 -- 堆排序算法

Purpose:

目的:

The heapq implements a min-heap sort algorithm suitable for use with Python’s lists.
heapq模块执行了一个适用于Python列表的最小堆排序算法。

A heap is a tree-like data structure where the child nodes have a sort-order relationship with the parents. Binary heaps can be represented using a list or array organized so that the children of element N are at positions 2N+1 and 2N+2 (for zero-based indexes). This layout makes it possible to rearrange heaps in place, so it is not necessary to reallocate as much memory when adding or removing items.
堆,是一个类似于树的数据结构,其中子节点们与其父节点有一个排序的关系。二叉堆能够表示为使用一个列表或者数组来组织,因此元素N的子节点位于2N+1和2N+2(对于基于0的索引)。这样的布局允许在原来位置重置堆,因此没有必要在添加和删除元素的时候重置过多的内存空间。

A max-heap ensures that the parent is larger than or equal to both of its children. A min-heap requires that the parent be less than or equal to its children. Python’s heapq module implements a min-heap.
一个最大堆可以确保父节点大于或者等于其两个子节点。最小堆需要父节点小于或者等于其子节点。Python的heapq模块使用最小堆。

Example Data

示例数据

The examples in this section use the data in heapq_heapdata.py.
本节的这个示例使用heapq_heapdata.py中的data。

heapq_heapdata.py

# This data was generated with the random module.

data = [19, 9, 4, 10, 11]

The heap output is printed using heapq_showtree.py:
heapq_showtree.py使用heap的输出:

heapq_showtree.py

import math
from io import StringIO


def show_tree(tree, total_width=36, fill=' '):
    """Pretty-print a tree."""
    output = StringIO()
    last_row = -1
    for i, n in enumerate(tree):
        if i:
            row = int(math.floor(math.log(i + 1, 2)))
        else:
            row = 0
        if row != last_row:
            output.write('\n')
        columns = 2 ** row
        col_width = int(math.floor(total_width / columns))
        output.write(str(n).center(col_width, fill))
        last_row = row
    print(output.getvalue())
    print('-' * total_width)
    print()

Creating a Heap

创建一个堆

There are two basic ways to create a heap, heappush() and heapify().
想要创建一个堆,有两种基本方法:heappush() 和 heapify()。

heapq_heappush.py

import heapq
from heapq_showtree import show_tree
from heapq_heapdata import data

heap = []
print('random :', data)
print()

for n in data:
    print('add {:>3}:'.format(n))
    heapq.heappush(heap, n)
    show_tree(heap)

Using heappush(), the heap sort order of the elements is maintained as new items are added from a data source.
使用heappush(),当来自于数据源的新元素添加到堆中时,堆排序算法将维护元素的顺序。
$ python3 heapq_heappush.py

random : [19, 9, 4, 10, 11]

add 19:

             19

add 9:

             9
    19

add 4:

             4
    19                9

add 10:

             4
    10                9
19

add 11:

             4
    10                9
19       11

If the data is already in memory, it is more efficient to use heapify() to rearrange the items of the list in place.
如果data已经在内存之中,使用heapify()来在列表内部重置元素将会更高效。

heapq_heapify.py

import heapq
from heapq_showtree import show_tree
from heapq_heapdata import data

print('random    :', data)
heapq.heapify(data)
print('heapified :')
show_tree(data)

The result of building a list in heap order one item at a time is the same as building it unordered and then calling heapify().
按照堆的顺序,一次一个元素构建一个列表的结果,与构建一个构建一个未排序的列表一致,直接使用heapify():

$ python3 heapq_heapify.py

random : [19, 9, 4, 10, 11]
heapified :

             4
    9                 19
10       11

Accessing Contents of a Heap

访问堆中的内容

Once the heap is organized correctly, use heappop() to remove the element with the lowest value.
一旦堆正确的组织,就可以使用heappop()来移除最小的元素值。

heapq_heappop.py

import heapq
from heapq_showtree import show_tree
from heapq_heapdata import data

print('random    :', data)
heapq.heapify(data)
print('heapified :')
show_tree(data)
print

for i in range(2):
    smallest = heapq.heappop(data)
    print('pop    {:>3}:'.format(smallest))
    show_tree(data)

In this example, adapted from the stdlib documentation, heapify() and heappop() are used to sort a list of numbers.
在本示例中,参考标准库的文档,heapify() 和 heappop()用于对于一个数字列表进行排序。
$ python3 heapq_heappop.py

random : [19, 9, 4, 10, 11]
heapified :

             4
    9                 19
10       11

pop 4:

             9
    10                19
11

pop 9:

             10
    11                19

To remove existing elements and replace them with new values in a single operation, use heapreplace().
想要用一次操作移除一个已经存在的元素,然后使用一个新的元素替代它,可以使用 heapreplace():

heapq_heapreplace.py
import heapq
from heapq_showtree import show_tree
from heapq_heapdata import data

heapq.heapify(data)
print('start:')
show_tree(data)

for n in [0, 13]:
    smallest = heapq.heapreplace(data, n)
    print('replace {:>2} with {:>2}:'.format(smallest, n))
    show_tree(data)

Replacing elements in place makes it possible to maintain a fixed size heap, such as a queue of jobs ordered by priority.
在原来的位置替换元素,这样就可以维护一个固定大小的堆,例如按照优先级排列的任务队列。

$ python3 heapq_heapreplace.py

start:

             4
    9                 19
10       11

replace 4 with 0:

             0
    9                 19
10       11

replace 0 with 13:

             9
    10                19
13       11

Data Extremes From a Heap

堆中两端的数据

heapq also includes two functions to examine an iterable to find a range of the largest or smallest values it contains.
heapq包含两个函数用与迭代查找堆中一定范围内最大或者最小的值。

heapq_extremes.py

import heapq
from heapq_heapdata import data

print('all       :', data)
print('3 largest :', heapq.nlargest(3, data))
print('from sort :', list(reversed(sorted(data)[-3:])))
print('3 smallest:', heapq.nsmallest(3, data))
print('from sort :', sorted(data)[:3])

Using nlargest() and nsmallest() are only efficient for relatively small values of n > 1, but can still come in handy in a few cases.
使用 nlargest() 和 nsmallest() 这两个函数对于查找 n > 1的较小数值会显得更高效,但是在一些场景下更灵活。

$ python3 heapq_extremes.py

all : [19, 9, 4, 10, 11]
3 largest : [19, 11, 10]
from sort : [19, 11, 10]
3 smallest: [4, 9, 10]
from sort : [4, 9, 10]

Efficiently Merging Sorted Sequences

高效的合并已排序的序列

Combining several sorted sequences into one new sequence is easy for small data sets.
使用如下所示的方法,对于较小的数据集,合并一些已经排序的序列到一个新的序列会变的很容易。

list(sorted(itertools.chain(*data)))

For larger data sets, this technique can use a considerable amount of memory. Instead of sorting the entire combined sequence, merge() uses a heap to generate a new sequence one item at a time, and determine the next item using a fixed amount of memory.
对于较大的数据集,使用如上所示的代码将会占用大量的内存。与对整个已经组合的序列进行排序相比,merge()使用了堆来一次性生成新的序列,并且判断新生成的序列使用固定数量的内存。

heapq_merge.py

import heapq
import random

random.seed(2016)

data = []
for i in range(4):
    new_data = list(random.sample(range(1, 101), 5))
    new_data.sort()
    data.append(new_data)

for i, d in enumerate(data):
    print('{}: {}'.format(i, d))

print('\nMerged:')
for i in heapq.merge(*data):
    print(i, end=' ')
print()

Because the implementation of merge() uses a heap, it consumes memory based on the number of sequences being merged, rather than the number of items in those sequences.
由于在堆中执行merge()函数,所消耗的内存取决于所合并的序列数量,而不是这些序列中的元素数量。

$ python3 heapq_merge.py

0: [33, 58, 71, 88, 95]
1: [10, 11, 17, 38, 91]
2: [13, 18, 39, 61, 63]
3: [20, 27, 31, 42, 45]

Merged:
10 11 13 17 18 20 27 31 33 38 39 42 45 58 61 63 71 88 91 95

最让我眼前一亮的应用,就是leetcode中的第二十三题目:merge k sorted list,我们可以将问题转换为合并queue:

class ListNode(object):
    def __init__(self, x):
        self.val = x
        self.next = None

def mergeKLists(lists):
    current = dummy = ListNode(0)

    heap = []
    for sorted_list in lists:
        if sorted_list:
            heapq.heappush(heap, (sorted_list.val, sorted_list))

    while heap:
        smallest = heapq.heappop(heap)[1]
        current.next = smallest
        current = current.next
        if smallest.next:
            heapq.heappush(heap, (smallest.next.val, smallest.next))

    return dummy.next

将链表的问题转换成堆排序的问题。

1

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容