堆排序

树的概念

# 树是一种数据结构         		 比如:目录结构 
# 树是一种可以递归定义的数据结构 
# 树是由n个节点组成的集合: 
	# 如果n=0,那这是一棵空树; 
	# 如果n>0,那存在1个节点作为树的根节点,其他节点可以分为m个集合,每个集合本身又是一棵树。

# 一些概念 
# 根节点、叶子节点 
# 树的深度(高度) 
# 树的度  (最大节点的度)
# 孩子节点/父节点 
# 子树

二叉树

# 二叉树:度不超过2的树 
# 每个节点最多有两个孩子节点 
# 两个孩子节点被区分为左孩子节点和右孩子节点

# 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。 
# 完全二叉树:叶节点只能出现在最下层和次下层,并且最下面一层的结点都集中在该层最左边的若干位置的二叉树。

# 二叉数的存储方式(表示方法)
#1 链式存储
#2 顺序存储(如列表)

二叉树的顺序存储方式

# 父节点和左孩子节点的编号下标有什么关系?
0-1 1-3 2-5 3-7 4-9 
i → 2i+1 
# 父节点和右孩子节点的编号下标有什么关系?
0-2 1-4 2-6 3-8 4-10 
i → 2i+2

# 子节点找父节点: 
i → (i-1)//2

# 堆:一种特殊的完全二叉树结构 
# 大根堆:一棵完全二叉树,满足任一节点都比其孩子节点大 (大跟堆排序是升序)
# 小根堆:一棵完全二叉树,满足任一节点都比其孩子节点小 (小跟堆排序是降序)

堆的向下调整性质

# 假设根节点的左右子树都是堆,但根节点不满足堆的性质 
# 可以通过一次向下的调整来将其变成一个堆。

堆排序

原理

#1.建立堆(大跟堆);
#2.得到堆顶元素,为最大元素;
#3.去掉堆顶,将堆最后一个元素放到堆顶,此时可通过一次调整重新使堆有序; 
#4.堆顶元素为第二⼤大元素; 
#5.重复步骤3,直到堆变空。

代码实现

基于大跟堆实现列表升序排序

堆排序的时间复杂度是 O(nlogn)

# -*- coding: utf-8 -*-
# created by X. Liu on 2020/3/12

def sift(li, low, high):
    """
    实现堆的向下调整性质
    :param li:
    :param low: 堆顶位置
    :param high: 堆最后一个节点位置
    :return: None
    """
    tmp = li[low]
    i = low
    j = 2*i+1
    while j <= high:
        if j+1 <= high and li[j+1] > li[j]:      # 比较左孩子节点和右孩子节点的大小
            j = j+1                             # 如果有右孩子且比左孩子点大,j指向右孩子节点

        if li[j] > tmp:         # 如果孩子节点元素大于i其父节点
            li[i] = li[j]       # 将子节点元素移动到父节点位置
            i = j               # i 指向该子节点,做下一轮的比较
            j = 2*i+1
        else:                   # 这种情况指的是tmp适合这个位置,大于它的子节点上的数
            li[i] = tmp
            break
    else:                       # 这种情况值得是,比较到最下层了,将tmp放到此时i的位置(最下层位置)
        li[i] = tmp


def heap_sort(li):
    """
    先建堆,再挨个出数
    :param li:
    :return:
    """
    n = len(li)
    # 建堆
    for i in range((n-2)//2, -1, -1):     # i 是父节点,用堆最后一个位置行使high参数的功能
        sift(li, i, n-1)                # 子节点找父节点: k --> (k-1)//2
                                        # high参数的功能是为了标记向下调整时子节点位置(j or j+1)不越界

    # 挨个出数
    for i in range(n-1, -1, -1):            # n个数遍历n次
        li[0], li[i] = li[i], li[0]     # 将堆顶元素与堆最后一个位置的数据交换
        sift(li, 0, i-1)                    # 将堆顶元素换下后,向下调整
                                            # 值得注意的是,此时high位置是i-1。因为最后一个位置放最大元素了,已经不再堆内啦

            
 # sift的时间复杂度是O(logn)
 # heap_sort的时间复杂度是O(nlogn)

总结思考

# 堆排序实现需要注意三点:
	- 向下调整的实现
    - 建堆
    - 挨个出数
    
# 向下调整:
	- high参数指的是列表最后一个元素所在的位置
    - 参数j需要时刻注意越界问题:j <= high
    - 参数j+1 也需要注意越界问题 j+1 <= high
    
# 建堆:
	- 建堆采用'农村包围城市的策略',在调用sift函数时,可以使用列表最后一个参数的位置当high参数

# 挨个出数:
	- 挨个出数时,因为要将堆顶元素放在列表最后一个位置,需要时时缩小堆的范围。
    
posted @ 2020-03-12 23:26  the3times  阅读(201)  评论(0编辑  收藏  举报