• [151225] Python3 实现最大堆、堆排序,解决TopK问题


    参考资料:

    1.算法导论,第6章,堆排序
    2. 堆排序学习笔记及堆排序算法的python实现 - 51CTO博客
    3. 堆排序 Heap Sort - cnblogs
    4. 小根堆实现优先队列:Python实现 -cnblogs

    大(小)根堆:是完全二叉树,也是大(小)根树。
    大小根堆的差异,主要表现在 比较函数的差异上。

    大根堆的操作:

    插入(nlog(n)):
        概述:把新元素val作为新节点,沿着新节点到根节点的路径,执行一趟冒泡排序。
        即:将新元素与父节点的元素进行比较交换,直到父节点不小于子节点为止。
        
    删除(nlog(n)):
        目的:删除最大值即根节点root。
        (1)首先换首尾节点,然后删除尾结点;
        (2)并从根节点出发,进行堆的维护(重构),使堆满足大小次序。
       
    完全二叉树转化为大根堆:
        (1)从最后一个具有孩子节的节点开始检查。
        (2)如果以该元素为根的子树不是大根堆,进行堆的维护,将该子树调整为大根堆。
        (3)依次检查i-1,i-2等节点为根的子树,直到到达树根为止。
    
    堆的维护:
        目的:将以当前节点为根节点的子树调整为大根堆。
        (1)首先找出当前节点和它的左右孩子节点中的最大值。
        maxnode = max(curr,curr_leftchild,curr_right_child)
        (2)如果最大节点不是当前节点,则进行交换,并对最大节点开始的子树进行堆维护。
    

    应用:

    堆排序:
    
    (1)可以使用优先队列构建堆,然后依次弹出即可。
    (2)可以构建优先队列。然后将堆的根(最值)与最右子节点互换,并将堆容量减一。并继续维护,直到容量为2。
    
    已维护的堆的根是最值。。。然后与尾部的进行交换,容量减一,继续维护
    
    TopK问题:
    
        (1)使用小根堆记录前K个最大值。
        (2)如果新元素大于堆顶,则移除堆顶,并插入新元素。然后进行堆排序/或构建堆!保证正确性
    

    遇到的问题:

    循环次数、循环终止条件、循环不变式。
    循环和迭代的方式,重写堆维护程序。
    大小排序时,注意比较函数别搞混。写出来!!!
    程序是让人看的,不要为了优化而优化。
    一口吃不了一个胖子,程序应该逐步迭代。
    
    什么时候用@property,什么时候不用?
    
    # !/usr/bin/env python3
    # encoding:utf8
    
    left = lambda i:i*2+1
    right = lambda i:i*2 +2
    parent = lambda i:(i-1)//2
    
    # 与右操作数进行比较
    def less(x,y):  return x < y
    def greater(x,y): return x > y
    
    class MyHeap(object):
        def __init__(self, l=None,IS_MIN_HEAP=True):
            '''初始化
            1.如果有数据,初始化数据,并用数据构建堆
            2.初始化大根堆或小根堆的比较函数
            
            '''
            self._heap=[] 
            self.cmp = less if IS_MIN_HEAP else greater
            if l is not None:
                self._heap=list(l)
                self.build_heap()
        
        #什么时候加@property,什么时候不加?        
        def top(self):
            '''返回堆顶'''
            return self.heap[0]               
    
        @property
        def heapsize(self):
            '''返回堆的大小'''
            return len(self.heap)
        
        @property
        def heap(self):
            '''返回堆的内容'''
            return self._heap
        
        def __swap(self,i,j):
            '''交换以i和j为下标的元素'''
            self.heap[i], self.heap[j] = self.heap[j], self.heap[i]
          
        def build_heap(self):
            '''构建堆
            从有叶子节点的最大序号的内部节点往前开始,
            对每一个节点进行维护
            '''
            curr_pos = parent(self.heapsize -1)
            max_pos = self.heapsize     
            
            #从最后一个具有孩子节点的节点(heapsize-1)//2 开始往根调整,构建大根堆       
            while curr_pos>=0:                   # 共循环 parent(self.heapsize -1) 次
                self.heapify(curr_pos,max_pos)
                curr_pos -= 1      
    
                         
        def heapify1(self,curr_pos,max_pos):
            '''递归的形式,将当前节点为根节点的子树的转为堆
            [curr_pos,max_pos)
            '''
            #最大/最小节点,左孩子,右孩子
            mm_pos,lc,rc = curr_pos,left(curr_pos),right(curr_pos)  
            
            #小根堆比较
            #if lc < max_pos and self.heap[lc] < self.heap[mm_pos]:
            if lc < max_pos and self.cmp(self.heap[lc], self.heap[mm_pos]):
                mm_pos = lc 
            #if rc < max_pos and self.heap[rc] < self.heap[mm_pos]:
            if rc < max_pos and self.cmp(self.heap[rc], self.heap[mm_pos]):
                mm_pos = rc 
                
            # 当最值节点不等于当前节点时,交换节点值,递归维护
            if mm_pos != curr_pos:
                self.__swap(curr_pos,mm_pos)
                self.heapify(mm_pos,max_pos)
    
        def heapify(self,curr_pos,max_pos):
            '''循环的形式,将当前节点为根节点的子树的转为堆
            [curr_pos,max_pos)
            '''
            
            mm_pos = curr_pos
            lc,rc = left(curr_pos),right(curr_pos)
            while lc <max_pos:
                if lc < max_pos and self.cmp(self.heap[lc], self.heap[mm_pos]):
                    mm_pos = lc 
                if rc < max_pos and self.cmp(self.heap[rc], self.heap[mm_pos]):
                    mm_pos = rc   
                if mm_pos != curr_pos:
                    self.__swap(curr_pos,mm_pos)
                    curr_pos = mm_pos 
                    lc,rc = left(curr_pos),right(curr_pos)
                else:
                    break
                 
                
        def push(self,v):
            '''插入元素
            插入新元素到尾部,并从下往上起泡排序
            '''
            self.heap.append(v)
            curr_pos = self.heapsize - 1
            par_pos = parent(curr_pos)
            #小根堆比较
            #while curr_pos >= 0 and self.heap[curr_pos] < self.heap[par_pos]:
            while curr_pos >= 0 and self.cmp(self.heap[curr_pos], self.heap[par_pos]):
                self.__swap(curr_pos,par_pos)
                curr_pos,par_pos = par_pos,parent(par_pos)
            self.heapify(0,self.heapsize)
    
        def pop(self):
            '''删除元素
            1.弹出最值( 首先交换首尾,然后弹出尾部)
            2.从根节点维护堆的结构
            '''
            if self.heapsize == 0:
                raise (IndexError,'pop from empty heap')
            self.__swap(0,-1) 
            mv = self.heap.pop()
            self.heapify(0,self.heapsize)
            return mv 
    
        def show(self):
            '''输出堆信息,注意是按照树有序,不是按行有序'''
            print(self.heap)
           
    class MinHeap(MyHeap):
        def __init__(self,l):
            MyHeap.__init__(self,l,IS_MIN_HEAP=True)
            
    class MaxHeap(MyHeap):
        def __init__(self,l):
            MyHeap.__init__(self,l,IS_MIN_HEAP=False)
            
    
    def getTopK(lst,topK):
        '''TopK的计算 
        (1)对前TopK个元素,使用小根堆保存
        (2)对后面的元素,依次取出新元素。如果比堆的最小值(top)大,则弹出堆顶,并插入该元素!
        '''
        if len(lst) < topK:
            return None 
        #前topK个构成小根堆
        minheap = MinHeap(lst[:topK])
        #后面的逐个进行筛选操作
        for v in lst[topK:] :
            if minheap.top() < v:
                print(minheap.top())
                minheap.pop()
                minheap.push(v)  
                minheap.build_heap()
        return minheap.heap
    
    def HeapSort(lst):
        
        def heapify(lst,curr_pos,max_pos):
            '''递归的形式,将当前节点为根节点的子树的转为堆
            [curr_pos,max_pos)
            '''
            #左孩子,右孩子,最大/最小节点
            mm_pos,lc,rc = curr_pos,left(curr_pos),right(curr_pos)  
            if lc < max_pos and lst[lc] < lst[mm_pos]:
                mm_pos = lc 
            if rc < max_pos and lst[rc] < lst[mm_pos]:
                mm_pos = rc 
            # 当最值节点不等于当前节点时,交换节点值,递归维护
            if mm_pos != curr_pos:
                lst[curr_pos],lst[mm_pos] = lst[mm_pos],lst[curr_pos] 
                heapify(lst,mm_pos,max_pos)
                
        curr_pos = (len(lst)-1)//2
        max_pos = len(lst)
        #从最后一个具有孩子节点的节点(heapsize-1)//2 开始往根调整,构建大根堆       
        while curr_pos>=0:                   # 共循环 parent(self.heapsize -1) 次
            heapify(lst,curr_pos,max_pos)
            curr_pos -= 1  
            
        # ## 当用于排序时,添加上一下的语句。注意,需要保证不再进行插入运算?!反正顺序刚反过来
        # #已维护的堆的根是最值。。。然后与尾部的进行交换,容量减一,继续维护
        while max_pos > 1:                                #共循环 self.heapsize-1 次
            lst[0],lst[max_pos-1] = lst[max_pos-1],lst[0] #堆首尾交换
            max_pos -= 1             #容量减去1
            heapify(lst,0, max_pos) #维护堆        
    
        return lst
        
    def test():
        lst=[1,23,-6,9,7]
        lst=[1,23,-6,9,7,-2,4,5]
        
        print(lst)
    
        for i in range(1,8):
            print("Top{}:{}".format(i,getTopK(lst,i)))
        
        print("小根堆:")
        mpq = MinHeap(lst)
        mpq.show()
        for i in range(len(lst)):
            print(mpq.pop(),)
        print("
    
    ")
    
        print("大根堆:")
        mpq = MaxHeap(lst)
        mpq.show()
        for i in range(len(lst)):
            print(mpq.pop(),)
        print("
    
    ")
    
        print(HeapSort(lst))
        print("Done!")
            
    if __name__=='__main__':
        test()
    
  • 相关阅读:
    剑指offer--12.不用加减乘除做加法
    剑指offer--11.数组中出现次数超过一半的数字
    剑指offer--10.最小的K个数
    剑指offer--9.字符串的排列
    剑指offer--8.调整数组顺序使奇数位于偶数前面
    剑指offer-7.旋转数组的最小数字
    剑指offer--6.数值的整数次方
    剑指offer--5.变态跳台阶
    剑指offer--4.斐波那契数列
    剑指offer--3.用两个栈实现队列
  • 原文地址:https://www.cnblogs.com/ausk/p/5121865.html
Copyright © 2020-2023  润新知