Python3实现最小堆建堆算法

今天看Python CookBook中关于“求list中最大(最小)的N个元素”的内容,介绍了直接使用python的heapq模块的nlargest和nsmallest函数的解决方式,记得学习数据结构的时候有个堆排序算法,所以顺便研究了一下“堆”结构(这里特指二叉堆)。

概念

所谓二叉堆(binary heap)实际上就是一颗特殊的完全二叉树,其特殊性在于:

  1. 二叉树中所有的父节点的值都不大于/不小于其子节点;
  2. 根节点的值必定是所有节点中最小/最大的。

父节点值不大于子节点且根节点值最小称为最小堆,反之称为最大堆。最大堆和最小堆没有本质上的区别。如下图是一个典型的最小堆:

算法

现在实现一个对给定list完成初始建堆的算法。(以最小堆为例)

假设 list = [1, 8, 2, 23, 7, -4, 18, 23, 42, 37, 2]

先记录一个自己当时看堆结构时琢磨出来的算法,后来查了查资料发现不是最优的。

渣渣算法

直接根据list中元素的index构建二叉树,这里我们不使用链表,完全以列表实现并以0为基(根节点index为0):

根据完全二叉树的特点(节点如果存在右子节点,则必存在左子节点且如果右子节点存在子节点,则左子节点必存在左右子节点),元素个数为N的完全二叉树的最后一个拥有子节点的节点的index为N//2 -1 。

为了实现二叉树中所有父节点的值不大于其子节点(特性1),只需要从根节点(index = 0)遍历到最后一个拥有子节点的节点(index = N//2 -1),将父节点与其子节点值作比较,必要时进行交换即可。完成一次上述过程后就能完成最底层节点的归位了。元素个数为N的二叉树层数为ceil(log2n),因此一共执行floor(log2n)次上述过程就能实现最小堆的建堆了。算法如下:

#!/usr/bin/env python

import os
import sys
import math

def heap(list):
    n = len(list)
    for i in range(0,int(math.log(n,2))):                #每循环依次就完成了一层的建堆
        for j in range(0,n//2):
            k = 2*j+2 if 2*j+2 < n and list[2*j+2] < list[2*j+1] else 2*j+1    #让k成为较小的子节点的index
            if list[j] > list[k]:
                (list[j],list[k]) = (list[k],list[j])         #交换值

def main(argv):
    list = [int(arg) for arg in argv]
    heap(list)
    print(list)
if __name__ == "__main__":
    if len(sys.argv) > 1:
        main(sys.argv[1:])

这是自顶向下的遍历方式,还可以自底向上遍历,则首先归位的是根节点。

很明显,这个算法的复杂度为O(nlogn), 但实际上,最优的建堆算法的复杂度是O(n),而且这个算法还使用了数学函数。。。

最优算法

下面贴一个使用递归的最优算法:

思路还是一样,直接根据list构建二叉树,然后从最后一个拥有子节点的节点向上遍历,使用下沉算法将遍历到的每一个子树变成二叉堆。最终整个二叉树就成为一个二叉堆。

#!/usr/bin/env python

import os
import sys

def sink(list,root):
    if 2*root+1 < len(list):
        k = 2*root+2 if 2*root+2 < len(list) and list[2*root+2] < list[2*root+1] else 2*root+1     #让k成为较小的子节点的index
        if list[root] > list[k]:
            (list[root],list[k]) = (list[k],list[root])     #交换值
            sink(list,k)              #对子节点为根节点的子树建堆

def main(argv):
    list = [int(arg) for arg in argv]
    for i in range(len(list)//2-1,-1,-1):
        sink(list,i)
    print(list)
if __name__ == "__main__":
    if len(sys.argv) > 1:
        main(sys.argv[1:])

两种算法运行截图:

堆排序

最后说一下堆排序,建堆完成后,排序就简单了:

将根节点(即list[0])弹出:list.pop(0),然后将最后一个节点放到根节点位置,对剩下的list再次进行建堆(针对算法1,算法2则是直接调用sink方法即可)。反复此过程就能输出排序结果。

想要直接在list内排序的话,则不弹出根节点,而是直接将根节点和最后一个节点交换位置,反复调用sink方法(但是不能再用len(list),而是给定一个从len(list)依次递减的参数,避免让已排序好的节点继续参与建堆)

时间: 2024-12-18 04:24:00

Python3实现最小堆建堆算法的相关文章

堆+建堆、插入、删除、排序+java实现

package testpackage; import java.util.Arrays; public class Heap { //建立大顶堆 public static void buildMaxHeap(int[] a) { for(int i=(a.length/2)-1;i>=0;i--) { adjustDown(a,i,a.length); } } //向下调整 public static void adjustDown(int[] a,int i,int len) { int

建堆[HihoCoder-1405]

Building Heap HihoCoder-1405 hihoCoder太阁最新面经算法竞赛11 问题大意:给定一个$N$个元素的数组$A$(元素互不相同),要求你建立满足下列要求的二叉树$T$,并输出其前序遍历: 1)$T$满足最小堆性质: 2)输入的数组$A$满足$T$的中序遍历. 这是一道数据结构基础题,主要是解决如何建堆.题目要求最小堆,其树根必然是整个树的最小值.因此在建堆过程中,只需寻找最小值,根据找到的最小值的位置将中序遍历的序列一分为二,继续对这两个子序列建堆,直到子序列没有

建堆,以及对堆排序

建堆,以及堆排序 代码1: #include<stdio.h> int h[101];//用来存放堆的数组 int n;//用来存储堆中元素的个数,就是堆的大小 //交换函数,用来交换堆中的俩个元素的值 void swap(int x,int y) { int t; t=h[x]; h[x]=h[y]; h[y]=t; } //向下调整函数 void siftdown(int i) {//传入一个须要向下调整的的节点编号i,这里传入1.即从堆的顶点開始向下调整 int t,flag=0;//f

自下而上建堆(heap)的时间复杂度证明

作者:Tobin 日期:2019/04/13 缘由:看python cookbook时,用到了heapq的库,书中提到,如果仅仅是返回一个数组的最大值,用max就可以了,但是如果返回多个较大或者较小元素用堆,如果返回的个数接近于数组本身的元素个数时,直接用排序即可.那么我在想,为啥返回几个元素的时候用堆效果比较好呢?于是我翻开了尘封许久的<算法导论>. 什么是堆 堆是一种数据结构.二叉堆是一个数组,近似于一个完全二叉树.树上的每个结点对应于数组的一个元素,除了最底层外树是充满的.下面的图是一个

建堆复杂度O(n)证明

堆排序中首先需要做的就是建堆,广为人知的是建堆复杂度才O(n),不过很少有人去了解过这个复杂度的证明过程,因为不是那么直观地可以一眼就看出来.本文不讲堆排序,只单纯讲建堆过程. 建堆代码 欲了解复杂度的计算过程,必先看懂建堆代码.先看这个建堆过程 // 将arr[n]向上调整至合适位置 void AdjustHeap(vector<int> &arr, int n) { if(n<=0) return ; if(arr[(n-1)/2] > arr[n]) { //与父结点

配对堆优化Dijkstra算法小记

关于配对堆的一些小姿势: 1.配对堆是一颗多叉树. 2.包含优先队列的所有功能,可用于优化Dijkstra算法. 3.属于可并堆,因此对于集合合并维护最值的问题很实用. 4.速度快于一般的堆结构(左偏树,斜堆,随机堆--),具体时间复杂度: 合并(Merge):$O(1)$: 插入(Insert/Push):$O(1)$: 修改值(Change):$O(1) \sim O(\log n)$: 取出维护的最值(Top):$O(1)$: 弹出堆顶元素(Pop):$O(\log n)$: 我们依然拿洛

从海量数据中查找出前k个最小或最大值的算法(java)

现在有这么一道题目:要求从多个的数据中查找出前K个最小或最大值 分析:有多种方案可以实现.一.最容易想到的是先对数据快速排序,然后输出前k个数字.   二.先定义容量为k的数组,从源数据中取出前k个填充此数组,调整此数组的最大值maxValue到首位,然后对剩下的n-k个数据迭代,对于每个遍历到的数字x,如果x < maxValue,用x把maxValue替换掉,然后调整数组最大值的位置. 三.基于二的思路,维护容量为k的堆,从源数据中取出前k个填充实例化堆,调整此堆中的最大值maxValue到

数据结构之二叉堆(构建堆,堆排序)-(七)

/* * @author Lip * @date 2015-04-23 */ public class Heap { public static void main(String[] args) { // TODO Auto-generated method stub //System.out.println((int)-1.5); int []array={4,2,7,9,3,6,1,12,10,5}; System.out.println("原始:"); printHeapByLe

C++ 堆 和 堆 分析

[摘要] 堆和栈,即是数据结构,又是分配存储空间的不同方式.在数据结构上.堆是树型层次结构,结点按keyword次序排列,经常使用的堆为二叉堆:栈是一种先进后出的数据结构.在内存分配上的堆和栈,首要差别在于申请方式不同.其次在存取速度.存储空间的大小.存储内容(一定要记住,栈中是第一条可运行语句地址.然后是各个參数.堆中头部是堆的大小描写叙述.之后有程序猿自己安排).内存中的相对位置和系统相应的响应上都各有自己差别.在C语言 的学习过程中,堆和栈即是基础也是重点. [正文] 堆栈是一个非常模糊的