堆排序-python实现

选择排序 每次在n个记录中选择一个最小的需要比较n-1次,但是这样的操作并没有把每一趟的比较结果保存下来,在后一趟的比较中,有许多的比较在前一趟就已经做过了,但是由于前一趟排序时并未保存这些比较结果,所以后一趟排序时又重复执行了这些比较操作,因而记录的比较次数比较多
如果可以做到每次在选择最小记录时,并根据比较结果对其他记录做出相应的调整,那样排序的总体效率就会非常高了,而堆排序就是对简单选择排序进行的一种改进,这种改进的效率是非常明显的

一.堆结构
1.堆是具有下列性质的完全二叉树:
(1)每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;
(2)或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆;
从堆的定义可知:根节点一定是堆中所有结点中共的最大值或者最小值

2.按照层序遍历的方式给结点进行编号,那么非叶结点的编号满足如下关系:
1<=i<=[n/2] [n/2]表示不超过n/2的最大整数
因为完全二叉树的性质:(这里的i指的是编号)
(1)如果2i>n,那么这个i对应的节点是叶节点,且没有左孩子,反之,我们知道不是叶节点的节点就满足2i<=n,即得到了上面的表达式
(2)编号为i的节点的左右子节点编号分别是2i和2i+1
那么按照层序遍历的方式,将最大堆和最小堆存入数组,那么一定满足上面的关系

二.堆排序算法
1.基本思想
将待排序的序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点,将它移走,然后将剩余的n-1个序列重新构造一个堆,这样就会得到n个元素中的次大值,如此反复执行,便能得到一个有序序列了
那么实现这个思想要解决两个问题
(1)如何由一个无序序列构建成一个堆
(2)在输出堆顶元素后,如何调整剩余元素称为一个新的堆

2.代码实现思路:
(1)无序序列调整为大顶堆
调整为大顶堆主要是遍历非叶子节点,对每个非叶子节点都找到其和左右子树中的最大值,然后调换顺序,调整最大值到双亲节点,按照这个过程,可以将一个无序序列调整为完全二叉树。
复杂度分析
非叶子节点的个数大约为所有节点个数的一半,而每个非叶子节点的处理时间都是常量时间,因此时间复杂度为O(n)

(2)排序:
外循环:i=1,2,,,k:
第i次循环,将堆顶元素和倒数第i个元素交换,然后对前面的n-i个元素调整为最大堆,这里的调整略和上面的不同(原理,可参照算法导论),每次循环是从根节点到最大编号的叶节点进行遍历,调整每个节点和其子节点的最大值到双亲节点,每一次循环过后,序列的最后i个元素都是有序的
复杂度分析
每次外循环从根节点到最后一层的叶节点只需要经过log(n-i)次内循环,如果我们需要得到序列的全排序,复杂度计算就是logn+log(n-1)+log(n-2)+...+log1=log(n!),根据时间复杂度计算原则:只保留最高次方的,去掉常数系数,调整n个无序元素为有序的时间复杂度为O(log(n^n))=O(nlogn);
如果是topk,那么我们只需要外循环k次,那么复杂度就是logn+log(n-1)+log(n-2)+...log(n-k)=O(log(n^k))=klogn

"""
heap-sort
"""
def heap_sort(lst,k):
    """
    contrust a max-heap based on given array ranging from the last non-leaf node to root node
    in python:non-leaf node number reduce from half of the last index of the array minus 1 to 0
    """
    for i in range((len(lst)-1-1)/2,-1,-1):
        heap_adjust(lst,i,len(lst)-1)
    print "\n"
    """
    put the the top of the heap in the end of array in turn,then re-construct a max-heap
    finally we can get a array whose element is sorted from small to large,if we only get
    the top k ,then we iterate k times ,in other words,we need put the top of the heap in
    the end k times
    """
    for j in range(len(lst)-1, len(lst)-1-k, -1):
        """
        swap the top of the heap and the last of unordered part ,if we iterate
        k times ,then we get a array whose the last k elements is the top k
        """
        # print lst[0]
        lst[j], lst[0]=lst[0], lst[j]
        heap_adjust(lst, 0, j-1)

def heap_adjust(lst,s,m):
    """
    re-contruct a max-heap from s to m based on array
    """
    i = 2*s+1 #the left node of the s
    temp=lst[s]
    while i <= m:
        if (i < m) and (lst[i] < lst[i+1]):
            i = i+1
        if temp >= lst[i]:
            break
        else:
            lst[s] = lst[i]
        s = i
        i=i*2+1
        # print lst
    lst[s]=temp

if __name__=="__main__":
    sequence1=[50,10,90,30,70,40,80,60,20]
    k=5
    heap_sort(sequence1,k)
    topk=sequence1[-k:len(sequence1)]
    topk.reverse()
    print "topk:"+str(topk)
运行结果

2. 堆排序的应用
海量数据的topk,即得到海量数据的topk元素
(1)如果没有内存限制,可以采用内排序,将数据全部加载进来进行排序,可以采用类冒泡排序的思想,循环k次,每次将第k大的元素调整到上面,内存循环用来比较大小和交换元素顺序,复杂度是n-1+n-2+...+n-k)=O(kn)

#coding=UTF-8
"""
inner sort
"""
sequence = [-23,18,2,3,9,-4,5,7]
k = 5
for i in range(k):
    for j in range(i + 1, len(sequence)):
        if sequence[i] < sequence[j]:
            sequence[i], sequence[j] = sequence[j], sequence[i]
    print sequence
print "topk:"+str(sequence[:k - 1])
运行结果

复杂度还是比较大,我们可以采用堆排序的方法:

(2)如果在有内存限制的情况下,即我们无法将数据全部加载进来,只能采用外排序方法,这里我们仍然采用堆排序,但是和上面的堆排序思路和过程都不一样,区别在于上面我们是将数据全部加载到内存,实现的全排序(k=len(sequence1),但是这里因为在不消耗内存的情况下:
先初始化一个k维的数组存放海量数据的前k个元素,然后将这个k个元素构建成一个最小堆;
循环以下过程:再从海量数据的第k+1个元素进行遍历,每次比较前面的最小堆的根节点与后面的每个元素的大小,如果根节点元素小于后面的元素,那么将前面的根节点元素替换为这个元素;再重新调整这个数组为一个最小堆,这样每次都会扔掉更小的元素,加进来更大的元素,直至遍历完所有元素,得到的数组就是我们的topk
时间复杂度分析:一开始构建最小堆的复杂度是O(K),然后后面遍历了n-K个元素,每次的复杂度是O(K),因此总复杂度是O(k+(n-k)logk)=O(nlogK)
空间复杂度分析:这里比上面的堆排序增加了一个K维的数组作为缓存topk元素
总的算法效率分析:减少了内存的消耗,空间复杂度和时间复杂度都比上面增加了
具体实现如下:

#coding=UTF-8
"""
heap-sort
"""
def heap_adjust(lst,s,m):
    i = 2*s+1 #the left node of the s
    temp=lst[s]
    while i <= m:
        if (i < m) and (lst[i] > lst[i+1]):
            i = i+1
        if temp <= lst[i]:
            break
        else:
            lst[s] = lst[i]
        s = i
        i=i*2+1
        # print lst
    lst[s]=temp

def heap_sort(lst,k):
    topk = []
    m=0
    while len(topk) < k:
        topk.append(lst[m])
        m+=1
    print "初始tok:"+str(topk)
    for i in range((k-1-1)/2,-1,-1):
        heap_adjust(topk,i,k-1)
    min_k=topk[0]
    print "初始最小值:"+str(min_k)
    print "初始topk构成的最小堆:"+str(topk)
    print "\n"
    for j in range(k,len(lst)):
        # print lst[0]
        if min_k<lst[j]:
            topk[0]=lst[j]
            for i in range((k-1-1)/2, -1, -1):
                heap_adjust(topk,i,k-1)
            min_k=topk[0]
        # print topk
    return topk

if __name__=="__main__":
    sequence1=[50,10,90,30,70,40,80,60,20]
    k=5
    print "最后得到的topk:"+str(heap_sort(sequence1,k))
运行结果

可以看出来:这里得到的topk不是内部排序的,因为我们上面每次只是构建了最小堆,如果我们想要得到有序的topk,进一步实现如下:


if __name__=="__main__":
    sequence1=[50,10,90,30,70,40,80,60,20]
    k=5
    topk=heap_sort(sequence1,k)
    print "小顶堆tok:"+str(topk)
    for j in range(len(topk)-1,-1,-1):
        topk[0],topk[j]=topk[j],topk[0]
        heap_adjust(topk,0,j-1)
    print "有序的topk:"+str(topk)
运行结果

即在内存限制的情况下运用堆排序实现了海量数据的topk

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

推荐阅读更多精彩内容

  • 概述 排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    蚁前阅读 5,101评论 0 52
  • 概述:排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    每天刷两次牙阅读 3,706评论 0 15
  • 1.插入排序—直接插入排序(Straight Insertion Sort) 基本思想: 将一个记录插入到已排序好...
    依依玖玥阅读 1,177评论 0 2
  • 概述排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的...
    Luc_阅读 2,212评论 0 35
  • 亲爱的自己: 您好! 我心疼你。你不要那么轻易就否定了自己,也不要忘了自己的初衷。你只是想还自己内心一片宁静,喜欢...
    此丧非丧阅读 173评论 0 0