无序数组求中位数——小根堆法原理(附python代码)

小根堆法:

定义中位数为一个有序数组(len(array)+1)//2处的元素,“//”代表下取整。我知道中位数的定义分按照数组长度的奇偶性分两种,但是面试官会告诉你这里求的中位数就是有序数组(len(array)+1)//2处的元素。
如:
1 2 3 4 5 6 7 8 9 的中位数是(9+1)//2 = 5
1 2 3 4 5 6 7 8 9 10 的中位数是(10+1)//2=5
现在来求无序数组的中位数:
步骤 1 :如果数组长度为奇数则取数组的前 (len(array)+1)//2 个元素建立 一个最小堆,如果为偶数则取(len(array)+1)//2 +1个元素建立 一个最小堆。
步骤 2 :遍历剩余元素,如果该元素小于堆顶元素,则丢弃或不作处理;如果该元素大于堆顶元素,则将其取代堆顶元素,并且重新调整当前堆为最小堆。
步骤 3 :遍历结束后,返回堆顶元素,它就是所要寻找的中位数。
原理:
(建立大根堆也是行得通,这里选小根堆来解释)
这里假设数组长度为奇数,步骤1中说是从前往后取(len(array)+1)//2 个元素建立 一个最小堆,其实任意取(len(array)+1)//2 个元素都可以,所取的这(len(array)+1)//2 个元素有两种可能的情况。
情况1:这(len(array)+1)//2 个元素中包含了中位数。
情况2:这(len(array)+1)//2 个元素没有包含中位数。
对于情况1:
中位数可能位于堆顶或者堆内部。
(1)当中位数位于堆顶的时候,因为是小根堆,所以数组内所有比中位数大的元素都在小根堆内,此时遍历数组剩余元素,根本不可能找到比堆顶大的元素,所以遍历完数组剩余元素堆顶保持不变。
如1 2 3 4 5 6 7 8 9 任取(9+1)//2=5个元素建立小根堆,如果5是堆顶,那堆内的其余元素只能是6 7 8 9了。遍历剩余元素1 2 3 4 ,堆顶保持不变。
(2)当中位数位于堆内的时候,堆顶元素比中位数小,则数组剩余元素中必然含有比中位数大的元素。如果用数组剩余元素中比堆顶小的来替换堆顶,那小根堆根本不用调整,这谁顶得住啊,小根堆不调整那中位数什么时候能到堆顶。所以要用数组剩余元素中比堆顶大的来替换堆顶,上面说了数组剩余元素中必然含有比中位数大的元素,现记它为T,那T必然比堆顶要大,所以用T替换堆顶,当T成了堆顶,而且它比中位数要大,所以T必然下沉,中位数必然上升,只要经过若干次这样的替换与调整,中位数肯定会到达堆顶。
对于情况2:
这(len(array)+1)//2 个元素没有包含中位数,那这些元素中肯定包含比中位数大的和比中位数小的,并且比中位数小的必然在堆顶,此时中位数位于数组的剩余元素中。与情况1描述的操作一样,多次替换与调整后中位数肯定会替换掉比它小的堆顶元素,此时中位数又到了小根堆内,这就和情况1一模一样了。

代码如下:

# 调整自顶向下调整小根堆
def changed_top_bot(array):
    flag = len(array)//2  # 最后一个分支节点的索引
    for i in range(1, flag+1):  # 从堆顶开始到最后一个分支节点
        if i == flag:
            if (2 * i + 1) > len(array):  # 判断最后一个分支节点是否有右孩子
                if array[i-1] > array[2 * i-1]:
                    temp = array[i-1]
                    array[i-1] = array[2 * i-1]
                    array[2 * i-1] = temp
                continue

        if not (array[i-1] <= array[2*i-1] and array[i-1] <= array[2*i]):
            temp = array[i-1]
            if array[2*i-1] >= array[2*i]:
                array[i-1] = array[2*i]
                array[2*i] = temp
            else:
                array[i-1] = array[2*i-1]
                array[2*i-1] = temp
    return array


# 调整自底向上调整小根堆
def changed_bot_top(array):
    flag_branch = len(array) // 2  # 数组长度除以2下取整表示最后一个分支节点
    # print 'flag_branch: ', flag_branch
    while flag_branch != 0:  # 从最后一个分支结点开始自底向上调整该二叉树
        # print '执行了'
        if flag_branch == len(array)//2:
            if (2 * flag_branch + 1) > len(array):  # 因为是完全二叉树,最后一个分支结点可能没有右孩子
                # print '执行了'
                if array[flag_branch - 1] >= array[2 * flag_branch - 1]:  # 数组中索引要减1
                    temp = array[flag_branch - 1]
                    array[flag_branch - 1] = array[2 * flag_branch - 1]
                    array[2 * flag_branch - 1] = temp
                flag_branch -= 1
                continue
        # print array[flag_branch-1] <= array[2 * flag_branch-1] and array[flag_branch-1] <= array[2 * flag_branch]
        if not (array[flag_branch-1] <= array[2 * flag_branch-1] and array[flag_branch-1] <= array[2 * flag_branch]):
            # 该分支结点的孩子节点中有比它小的,所以选择小的替换该分支节点
            temp = array[flag_branch-1]
            if array[2 * flag_branch-1] >= array[2 * flag_branch]:
                array[flag_branch-1] = array[2 * flag_branch]
                array[2 * flag_branch] = temp
            else:
                array[flag_branch-1] = array[2 * flag_branch-1]
                array[2 * flag_branch-1] = temp
        flag_branch -= 1
        # print 'flag_branch: ', flag_branch
        # print array
    return array


# 实现小根堆找无序数组中位数
def find_mid_num(array):
    # 根据数组长度奇偶性取部分元素
    extract_array = []
    remain_array = []
    if not len(array) % 2:
        extract_array += array[:(len(array)+1)//2+1]
        remain_array += array[(len(array)+1)//2+1:]
    else:
        extract_array += array[:(len(array)+1)//2]
        remain_array += array[(len(array)+1)//2:]
    # print 'extract_array: \n', extract_array
    # print remain_array
    # 利用所取的部分元素建立完全二叉树
    # 根据完全二叉树在数组的中的存储,现在调整该完全二叉树使其成为小根堆
    # 将完全二叉树调整为小根堆需要先自低向上调整再自顶向下调整
    extract_array = changed_bot_top(extract_array)
    # 自底向上调整完,必须在自顶向下调整一次才可以完成初始小根堆
    extract_array = changed_top_bot(extract_array)
    # print extract_array
    small_root_array = extract_array  # 得到了初始小根堆

    for remain_item in remain_array:  # 遍历剩余元素数组
        if remain_item < small_root_array[0]:  # 剩余元素小于堆顶元素
            continue
        else:
            small_root_array[0] = remain_item  # 替换
            small_root_array = changed_top_bot(small_root_array)  # 只要建立了初始小根堆,后面的调整只需自定向下)
    return small_root_array[0]  # 堆顶元素就是中位数

if __name__ == '__main__':
    array = [5, 2, 4, 1, 3, 6, 7, 8, 9]
    # array = [5, 2, 4, 1, 3, 6, 7, 8, 9, 10]
    mid_num = find_mid_num(array)
    print mid_num

猜你喜欢

转载自blog.csdn.net/watermelon12138/article/details/89196962