堆排序-最大/最小及应用

建堆的时间复杂度是O(N), 维护堆 heapify的时间复杂度是O(logn)–N个节点–共nlogn,故总的时间复杂度是nlogn.

堆可以看成近似的完全二叉树。堆排序的时间复杂度是O(nlgn)。—输入的其实还是数组,只是一种结构–思想
最上面的根结点i从序号0开始算起的话,那么左节点是2i+1, 右节点是2i+2;从1开始算起的话,那么就是2i, 2i+1。

堆的高度是lgn,堆包括大顶堆和小顶堆。
大顶堆每一个根结点都比其子节点大–最上面的根结点最大,小顶堆每一个根节点都比子节点小----最上面的根节点最小。

Max-Heapify就是在维护最大堆的过程,让A[i]逐级下降,做一个临近根结点和子节点的交换只需要O(1)的时间复杂度。
每个孩子节点的子树的大小至多为2/3n。
通过递归式子:T(n) <= T(2n/3) + O(1)
可以得: 最大堆的时间复杂度是T(n) = O(lgn)
也就是对于一个树高为h的最大堆来说,时间复杂度是O(h)

构造一个最大堆的时间复杂度是O(n)

堆排序用的是最大堆,优先队列用的是最小堆。

堆排序的顺序就是 建堆—排除根----建堆(建堆就是从尾部开始heapify)

建堆

建最大堆的顺序是从左到右,从下到上,每一个向下移动都要跟最大的比,依次递归,直到比下面的大。
在这里插入图片描述
伪代码:输入数组A, 假定left(i)和right(i)都是最大堆,但是A(i)不一定构成最大堆。

—反正就是选择一个最大的作为largest,放在每棵树的根结点。

def Max_heapify(A, i):
	l = left(i)
	r = right(i)
	if l <= A.heap_size and A[l] > A[i]:
		largest = l
	else:
		largest = i
	if r <= A.heap_size and A[r] > A[largest]:
		largest = r
	if largest != i:
		exchange A[i] with A[largest]
		max_heapify(A, largest)	
	
def Build_max_heap(A):
	A.heap_size = A.length
	for i in range(A.length/2, 1):
		Max_heapify(A, i)
	

建堆是从左下角开始,依次往上,但是每次都会判断是否交换,交换后子树是否为最大堆,不是就递归下去。

堆排序

def Heap_sort(A):
	Build_max_heap(A)
	for i in range(A.length, 2):
		A[1], A[i] = A[i], A[1]
		A.heap_size = A.heap_size - 1
		Max_heapify(A, 1)

交换之后还要麻烦Max_heapify(A, i)再排列成最大堆的形式。
序号很关键,通过画图可以得知,满二叉树每层的最左的节点的序号是上层的两倍或是两倍加1。

最大堆

def max_heapify(A, i, heap_size):
    l = 2*i+1
    r = 2*i+2
    if l < heap_size and A[l] > A[i]:
        largest = l
    else:
        largest = i
    if r < heap_size and A[r] > A[largest]:
        largest = r
    if largest != i:
        A[i], A[largest] = A[largest], A[i]
        max_heapify(A, largest, heap_size)

def build_max_heap(A, heap_size):
    for i in range(heap_size//2, -1, -1): 
    # 前面一半逆序,因为后面一半一定是叶子结点,自底向上。
        max_heapify(A, i, heap_size)

def heapsort(A):
    heap_size = len(A)
    build_max_heap(A, heap_size)
    for i in range(len(A)-1, 0, -1):
        A[0], A[i] = A[i], A[0]
        heap_size -= 1
        max_heapify(A, 0, heap_size)

A = [27,17,3,16,13,10,1,5,7,12,4,8,9,0]
heapsort(A)
print(A)

最小堆
下面多用了个空间,其实不需要,如上即可。

def heapify(nums, index):
    left = 2 * index + 1
    right = 2 * index + 2
    minest = index
    if left < len(nums) and nums[minest] > nums[left]:
        minest, left = left, minest
    if right < len(nums) and nums[minest] > nums[right]:
        minest, right = right, minest
    if minest != index:
        nums[index], nums[minest] = nums[minest], nums[index]
        heapify(nums, minest)

def build_heap(nums):
    for i in range(len(nums) // 2, -1, -1):
        heapify(nums, i)
        
def Min_heap(nums, k):
    out = []
    while len(nums) > 0:
        build_heap(nums)  # 每删除一个就要选出一个小老弟。所以要重新排序。
        out.append(nums[0])
        nums.pop(0)
        
    return out

应用

对于 topk 问题:最大堆求topk小,最小堆求 topk 大。

topk小:构建一个 k 个数的最大堆,当读取的数小于根节点时,替换根节点,重新塑造最大堆
topk大:构建一个 k 个数的最小堆,当读取的数大于根节点时,替换根节点,重新塑造最小堆—当剩余的数目等于K的时候,那么就得到了想要的topk.

前 K 个高频元素

给定一个非空的整数数组,返回其中出现频率前 k 高的元素。

示例 1:

输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
示例 2:

输入: nums = [1], k = 1
输出: [1]

你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。
你的算法的时间复杂度必须优于 O(n log n) , n 是数组的大小。
题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的。
你可以按任意顺序返回

套用库

class Solution(object):
    def topKFrequent(self, nums, k):
        num_dict = {}
        for num in nums:
            if num not in num_dict:
                num_dict[num] = 1
            else:
                num_dict[num] += 1
        
        num_list = sorted(num_dict, key = lambda x : -num_dict[x])  
        # 加了负号就是从大到小, 注意此处变成了列表!!
        return num_list[0:k]

最小堆

# 最小堆
class Solution(object):
    def topKFrequent(self, nums, k):
        num_dict = {}
        for num in nums:
            if num not in num_dict:
                num_dict[num] = 1
            else:
                num_dict[num] += 1

        # 次数不一定是唯一的
        key_dict = {}
        num_list = []
        for j in num_dict:
            if num_dict[j] not in key_dict:
                key_dict[num_dict[j]] = [j]
                
            else:
                key_dict[num_dict[j]].append(j)
            num_list.append(num_dict[j])


        def Min_heap(nums, k):    
            def heapify(nums, index):
                left = 2*index+1
                right = 2*index+2
                minest = index
                if left < len(nums) and nums[minest] > nums[left]:
                    minest, left = left, minest
                if right < len(nums) and nums[minest] > nums[right]:
                    minest, right = right, minest
                if minest != index:
                    nums[index], nums[minest] = nums[minest], nums[index]
                    heapify(nums, minest)


            def build_heap(nums):
                for i in range(len(nums)//2, -1, -1):
                    heapify(nums, i)


            while len(nums) > k:
                build_heap(nums)   # 注意是一上来就建堆。
                nums.pop(0)
    
            return nums
        
        nums_out = Min_heap(num_list, k)
        out = []
        for i in nums_out:
            out.append(key_dict[i][0])
            if len(key_dict[i]) > 1:
                key_dict[i].pop(0)
        return out

快速排序

## 快速排序
class Solution(object):
    def topKFrequent(self, nums, k):
        
        def quick_partition(nums, index1, index2):
            index = index1 - 1
            num_cmp = nums[index2]
            for i in range(index1, index2+1):
                if nums[i] <= num_cmp:
                    index += 1
                    nums[index], nums[i] = nums[i], nums[index]
            return index

        
        num_dict = {}
        for num in nums:
            if num not in num_dict:
                num_dict[num] = 1
            else:
                num_dict[num] += 1

        # 次数不一定是唯一的
        key_dict = {}
        num_list = []
        for j in num_dict:
            if num_dict[j] not in key_dict:
                key_dict[num_dict[j]] = [j]
                
            else:
                key_dict[num_dict[j]].append(j)
            num_list.append(num_dict[j])
        
        index1 = 0
        index2 = len(num_list)-1
        index = -1
        while index != len(num_list)-k:
            index = quick_partition(num_list, index1, index2)
            if index < len(num_list)-k:
                index1 = index + 1
            else:
                index2 = index - 1

        choose_num = num_list[index::]
        out = []
        for i in choose_num:
            out.append(key_dict[i][0])
            if len(key_dict[i]) > 1:
                key_dict[i].pop(0)
        return out
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值