堆排序及python中heapq堆详解

一、知识点

堆的结构可以分为大根堆和小根堆,是一个完全二叉树,而堆排序是根据堆的这种数据结构设计的一种排序。

1. 大根堆和小根堆

性质:每个结点的值都大于其左孩子和右孩子结点的值,称之为大根堆;每个结点的值都小于其左孩子和右孩子结点的值,称之为小根堆。如下图在这里插入图片描述
我们对上面的图中每个数都进行了标记,上面的结构映射成数组就变成了下面这个样子在这里插入图片描述
还有一个基本概念:查找数组中某个数的父结点和左右孩子结点,比如已知索引为i的数,那么

1.父结点索引:(i-1)/2(这里计算机中的除以2,省略掉小数)

2.左孩子索引:2*i+1

3.右孩子索引:2*i+2

所以上面两个数组可以脑补成堆结构,因为他们满足堆的定义性质:

大根堆:arr(i)>arr(2i+1) && arr(i)>arr(2i+2)

小根堆:arr(i)<arr(2i+1) && arr(i)<arr(2i+2)

二、堆排序基本步骤

基本思想:

1.首先将待排序的数组构造成一个大根堆,此时,整个数组的最大值就是堆结构的顶端

2.将顶端的数与末尾的数交换,此时,末尾的数为最大值,剩余待排序数组个数为n-1

3.将剩余的n-1个数再构造成大根堆,再将顶端数与n-1位置的数交换,如此反复执行,便能得到有序数组

2.1 构造堆

将无序数组构造成一个大根堆(升序用大根堆,降序就用小根堆)

假设存在以下数组
在这里插入图片描述

主要思路:第一次保证0~0位置大根堆结构(废话),
第二次保证0~1位置大根堆结构,
第三次保证0~2位置大根堆结构...
直到保证0~n-1位置大根堆结构

(每次新插入的数据都与其父结点进行比较,如果插入的数比父结点大,则与父结点交换,否则一直向上交换,直到小于等于父结点,或者来到了顶端)

插入6的时候,6大于他的父结点3,即arr(1)>arr(0),则交换;此时,保证了0~1位置是大根堆结构,如下图:
在这里插入图片描述
(待交换的数为蓝色,交换后的数为绿色)

插入8的时候,8大于其父结点6,即arr(2)>arr(0),则交换;此时,保证了0~2位置是大根堆结构,如下图
在这里插入图片描述
插入5的时候,5大于其父结点3,则交换,交换之后,5又发现比8小,所以不交换;此时,保证了0~3位置大根堆结构,如下图
在这里插入图片描述
插入7的时候,7大于其父结点5,则交换,交换之后,7又发现比8小,所以不交换;此时整个数组已经是大根堆结构
在这里插入图片描述

2.2 固定最大值再构造堆

此时,我们已经得到一个大根堆,下面将顶端的数与最后一位数交换,然后将剩余的数再构造成一个大根堆
在这里插入图片描述
(黑色的为固定好的数字,不再参与排序)

此时最大数8已经来到末尾,则固定不动,后面只需要对顶端的数据进行操作即可,拿顶端的数与其左右孩子较大的数进行比较,如果顶端的数大于其左右孩子较大的数,则停止,如果顶端的数小于其左右孩子较大的数,则交换,然后继续与下面的孩子进行比较

下图中,5的左右孩子中,左孩子7比右孩子6大,则5与7进行比较,发现5<7,则交换;交换后,发现5已经大于他的左孩子,说明剩余的数已经构成大根堆,后面就是重复固定最大值,然后构造大根堆
在这里插入图片描述
如下图:顶端数7与末尾数3进行交换,固定好7,
在这里插入图片描述
剩余的数开始构造大根堆 ,然后顶端数与末尾数交换,固定最大值再构造大根堆,重复执行上面的操作,最终会得到有序数组在这里插入图片描述

三、大根堆和堆排序python实现

堆的特点就是FIFO(first in first out)先进先出
堆在接收数据的时候先接收的数据会被先弹出。
栈的特性正好与堆相反,是属于FILO(first in/last out)先进后出的类型。

大根堆调整(max_heapify):
将堆的末端子结点作调整,使得子结点永远小于父结点。这是核心步骤,在建堆和堆排序都会用到。
比较根结点和与其所对应的孩子结点的值,当i根结点的值比左孩子结点的值要小的时候,就把根结点和左孩子结点所对应的值交换,同理,就把根结点和右孩子结点所对应的值交换。
然后再调用堆调整这个过程,可见这是一个递归的过程。

# 大根堆
def max_heapify(arr, heapSize, root):
    # 先记最大的数索引为root,给定某个结点下标root
    largest = root
    # 左孩子下标
    left = 2 * root + 1  # left = 2*root + 1
    # 右孩子下标
    right = 2 * root + 2  # right = 2*root + 2
    # 当根结点root的值比左孩子结点的值小时
    if left < heapSize and arr[root] < arr[left]:
        # largest记录左孩子的下标值
        largest = left
    # # 当根结点root的值比右孩子结点的值小时
    if right < heapSize and arr[largest] < arr[right]:
        # largest记录右孩子的下标值
        largest = right
    # 如果此时largest记录的下标不为root,则把根结点root和孩子结点所对应的值交换
    if largest != root:
        arr[root], arr[largest] = arr[largest], arr[root]  # 交换
        # 进行递归调用
        max_heapify(arr, heapSize, largest)

# 堆排序
def heapSort(arr):
    n = len(arr)
    # 建立大根堆,自底向上建堆,-1代表递减,从n到-1进行遍历
    for i in range(n, -1, -1):
        max_heapify(arr, n, i)

    # 进行堆排序,一个个交换元素
    # -1代表递减,从n-1到0进行遍历
    for i in range(n - 1, 0, -1):
        arr[i], arr[0] = arr[0], arr[i]  # 交换
        max_heapify(arr, i, 0)

arr = [12, 11, 13, 5, 6, 7]
heapSort(arr)
n = len(arr)
print("排序后:")
print(arr) # [5, 6, 7, 11, 12, 13]

四、python中heapq堆详解

python中heapq自带的为小根堆

import math
import random
import heapq
from io import StringIO

# 显示树结构的函数
def show_tree(tree, total_width=36, fill=' '):
   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 * 1.0) / columns))
     output.write(str(n).center(col_width, fill))
     last_row = row
   print(output.getvalue())
   print('-' * total_width)
   print
   return

# 显示树结构
data = random.sample(range(1,8), 7)
print('data: ', data)
show_tree(data)
"""
输出:
data:  [4, 6, 3, 1, 2, 7, 5]

                 4                  
        6                 3         
    1        2        7        5    
------------------------------------
"""


# 1.heappush插入元素到堆中
data = random.sample(range(1,8), 7)
print('data: ', data)

heap = []
for i in data:
  print('add %3d:' % i)
  heapq.heappush(heap, i)
  show_tree(heap)
"""
输出:
data:  [2, 1, 7, 3, 4, 5, 6]
add   2:

                 2                  
------------------------------------
add   1:

                 1                  
        2         
------------------------------------
add   7:

                 1                  
        2                 7         
------------------------------------
add   3:

                 1                  
        2                 7         
    3    
------------------------------------
add   4:

                 1                  
        2                 7         
    3        4    
------------------------------------
add   5:

                 1                  
        2                 5         
    3        4        7    
------------------------------------
add   6:

                 1                  
        2                 5         
    3        4        7        6    
------------------------------------
"""
# 2.heapq.heapify(list),将list类型转化为heap, 在线性时间内, 重新排列列表。
print('data: ', data)
heapq.heapify(data)
print('data: ', data)
show_tree(data)
"""
输出:
data:  [2, 1, 7, 3, 4, 5, 6]
data:  [1, 2, 5, 3, 4, 7, 6]

                 1                  
        2                 5         
    3        4        7        6    
------------------------------------
"""

# 3.heappop弹出堆顶的数(删除堆中最小的元素)
data = random.sample(range(1,8), 7)
print('data: ', data)
show_tree(data)

heap = []
while data:
  i = heapq.heappop(data)
  print('pop %3d:' % i)
  show_tree(data)
  heap.append(i)
"""
输出:
data:  [1, 5, 4, 2, 7, 6, 3]

                 1                  
        5                 4         
    2        7        6        3    
------------------------------------
pop   1:

                 3                  
        5                 4         
    2        7        6    
------------------------------------
pop   3:

                 4                  
        5                 6         
    2        7    
------------------------------------
pop   4:

                 5                  
        2                 6         
    7    
------------------------------------
pop   5:

                 2                  
        7                 6         
------------------------------------
pop   2:

                 6                  
        7         
------------------------------------
pop   6:

                 7                  
------------------------------------
pop   7:

------------------------------------
"""

# 4.heapq.heapreplace(iterable, n),现有元素并将其替换为一个新值。
data = random.sample(range(1, 8), 7)
print('data: ', data)
heapq.heapify(data)
show_tree(data)

for n in [8, 9, 10]:
    smallest = heapq.heapreplace(data, n)
    print('replace %2d with %2d:' % (smallest, n))
    show_tree(data)
"""
输出:
data:  [7, 3, 5, 2, 4, 1, 6]

                 1                  
        2                 5         
    3        4        7        6    
------------------------------------
replace  1 with  8:

                 2                  
        3                 5         
    8        4        7        6    
------------------------------------
replace  2 with  9:

                 3                  
        4                 5         
    8        9        7        6    
------------------------------------
replace  3 with 10:

                 4                  
        8                 5         
    10       9        7        6    
------------------------------------
"""

# 5.heapq.nlargest(n, iterable) 和 heapq.nsmallest(n, iterable),返回列表中的n个最大值和最小值
data = range(1, 6)
l = heapq.nlargest(3, data)
print(l)  # [5, 4, 3]

s = heapq.nsmallest(3, data)
print(s)  # [1, 2, 3]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值