堆排序算法基于二叉树数据结构的python实现

堆排序的原理略,此处只是作为记录,提供整个代码的实现,其中每个细节会给出注释和函数的设计思路(代码末尾)。
注:堆排序算法的实现,以数组结构来实现要简洁高效!此处只是作为练手使用,由于堆排序的数组实现已经有很多,
此处略。

自定义模块:

这个模块我们只用到其节点对象的创建、根据数组生成完全二叉树的函数、根据节点在二叉树的层序(层序遍历时的顺序)
找到节点的引用等信息的函数。至于其他的遍历等函数,只是之前做别的用途用。
这个文件命名为:treeCreate.py

# -*- coding: utf-8 -*-
"""
Created on Thu Sep 13 16:46:46 2018
Description:二叉树
Version:
@author: HJY
"""

class Node:
    def __init__(self,item=None,left=None,right=None):
        self.item = item
        self.lchild = left
        self.rchild = right
    
    def __str__(self):
        return str(self.item)
       
    #中序遍历
    def print_mtree(self):       
        if self.lchild:
            self.lchild.print_mtree()
        print(self)
        if self.rchild:
            self.rchild.print_mtree()

    #前序遍历
    def print_ftree(self):   
        print(self)
        if self.lchild:
            self.lchild.print_ftree()  

        if self.rchild:
            self.rchild.print_ftree()  
            
    #后序遍历
    def print_etree(self):        
        if self.lchild:
            self.lchild.print_etree()

        if self.rchild:
            self.rchild.print_etree()  
        print(self)

###########################################################
#Function:create_tree
#Description:根据传入的数组创建完全二叉树
#Author:HJY
#params:op_list:列表,二叉树的元素
#params:i:当前元素的索引值,初始创建传值i=0,意即从第一位开始创建
#Return:返回该完全二叉树的根节点
#History:
# <author>  <time>  
#  HJY       --
###########################################################        
def create_tree(op_list,i):   
    if i > len(op_list)-1:
        return
       
    return Node(op_list[i],
            left = create_tree(op_list,(i+1)*2-1),
            right = create_tree(op_list,(i+1)*2)
            )
    
#########################################################
#Function:find_node
#Descrition:根据节点序号(层序),找到节点
#Author:HJY
#params:order --要查找的节点的序号
#params:root --树的根节点
#Return:node,node_parent,nodefor
#注释:返回该节点的引用;以及其父节点的引用;该节点属于左孩子还是右孩子
#History:
#########################################################
def find_node(node,order):

    order_list = []   
    #需要倒序后使用[::-1]
    def get_orient(order):       
        if order == 1:
            return      
        if order % 2 == 0:#偶数,左子树
            order_list.append('l')
            get_orient(order/2)           
        else:
            order_list.append('r')
            get_orient((order-1)/2)

    #对于整棵的根节点-----------------------------
    if order == 1:
        node = node
        node_parent = node
        nodefor = 'root'    
    #-------------------------------------------
    #对于其他普通节点-----------------------------
    else:
        get_orient(order)
#        print(order_list)   
        for i in order_list[::-1]:
            #返回父节点
            if order_list.index(i) == 0:
                node_parent = node
                
            #返回该节点
            if i == 'r':
                node = node.rchild
            else:
                node = node.lchild
                
        nodefor = order_list[0]     
    #-------------------------------------------
    return node,node_parent,nodefor
    

if __name__ == '__main__':   
    #二叉树构建测试
    string = 'abcdefg'
    string_list = list(string)
    root = create_tree(string_list,0)
    print('后序遍历')
    root.print_etree()
    print('先序遍历')
    root.print_ftree()
    print('中序遍历')
    root.print_mtree()
         
    #二叉树遍历测试           
    A = Node(item='A',left = Node(item = 'B',left=Node(item='D'),right=Node(item='E')),
             right = Node(item = 'C',left=Node(item='F'))) 
    
    A.print_mtree()
    print('-'*10)
    A.print_ftree()
    print('-'*10)
    A.print_etree()       

主实现:

# -*- coding: utf-8 -*-
"""
Created on Tue Nov 13 14:34:07 2018

Description:堆排序---大顶堆/小顶堆
Version:
    
@author: HJY
"""

import random
#自己实现的模块
import treeCreate


#********************************************************
#Readme:大顶堆与小顶堆的处理方式一样,除了比较时取大还是取小的问题,
#       因此代码直接提供了两种方式,用户只需在compare_nodeitem函
#       数对标明的语句注释或解注释即可
#*********************************************************

#########################################################
#Function:turn
#Description:调换父节点node与子节点turnNode在二叉树中的位置,
#            该函数被compare_nodeitem调用
#Author:HJY
#Params:node:当前节点的引用
#Parama:node_parent:当前节点的父节点的引用
#Params:node_loc:指明node是(node的父节点)的左孩子还是右孩子
#Params:turnNode:需要与node节点交换位置的孩子节点
#params:orient:指明turnNode是node的左孩子还是右孩子
#Return:backNode
###########################################################
def turn(node,node_parent,node_loc,turnNode,orient):
    #backNode在最终比较根节点发生改变时使用,其余时候无用
    backNode = treeCreate.Node()
    #保存需要上调的子节点turnNode的子节点的引用
    lchild = turnNode.lchild
    rchild = turnNode.rchild   
    #使子节点turnNode的左右孩子节点分别指向node和node的另一个孩子
    if orient == 'l':
        turnNode.lchild = node
        turnNode.rchild = node.rchild       
    else:
        turnNode.rchild = node
        turnNode.lchild = node.lchild            
   
    #找到node的父节点修改孩子节点指向turnNode
    if node_loc == 'root':
        backNode = turnNode                 
    elif node_loc == 'l':
        node_parent.lchild = turnNode
    else:
        node_parent.rchild = turnNode
    #将turnNode节点的左右孩子修改为替代node节点的左右孩子
    node.lchild = lchild
    node.rchild = rchild  
    
    return backNode
    

##############################################################
#Function:compare_nodeitem
#Description:比较节点与其左右孩子的大小,并以大顶堆的方式重置该节点所在的位置
#Author:HJY
#Params:nodeinfo:由find_node函数返回的关于该节点的信息组成的元组
#Return:a,flag
################################################################
def compare_nodeitem(nodeinfo):  
    node = nodeinfo[0]
    node_parent = nodeinfo[1]
    node_loc = nodeinfo[2]  
    
    #标志位与根调整指标
    a = treeCreate.Node()
    flag = False
           
    if node.rchild:
#        if not node.item == max(node.lchild.item,node.rchild.item,node.item):#大顶堆使用
        if not node.item == min(node.lchild.item,node.rchild.item,node.item):#小顶堆使用
            flag = True
#            if node.lchild.item > node.rchild.item: #大顶堆使用  
            if node.lchild.item < node.rchild.item: #小顶堆使用     
                a = turn(node,node_parent,node_loc,node.lchild,'l')          
            else:
                a = turn(node,node_parent,node_loc,node.rchild,'r')
    else:
#        if node.lchild.item > node.item: #大顶堆使用
        if node.lchild.item < node.item: #小顶堆使用  
            flag = True
            a = turn(node,node_parent,node_loc,node.lchild,'l')          
    return a,flag


################################################################
#Function:Put_out1
#Description:进行一次堆调整,使堆顶元素最大
#---------------------------------------------------------------
#+idea:以int(len(base)/2)获得先进行调整的节点,并以逆序的方式逐步调整
#+      比如先调整5节点,再是4节点...
#+      对于调整后破坏了子树的大顶堆结构的,重复调整,直到每一轮都没有变化
#-----------------------------------------------------------------
#Author:HJY
#Params:start_order,根据当前剩余节点数/2取整求出    
#Params:sort_t,当前树根,treeCreate.Node类类型
#Return:返回当前树的根引用
################################################################
def Put_out1(start_order,sort_t):  
    #对于每一轮调整,只要产生一次调整,就继续下一轮调整:
    haschange = True
    #一个空节点
    a = treeCreate.Node()
    
    while haschange:     
        #初始haschange指示为没有调整,与i构成锁
        haschange = False
        i = 1
        #除非发生改变,否则在调用compare_nodeitem时返回的a为空节点,即a.item=None
        
        #对每一个节点进行调整
        while start_order:       
            nodeinfo = treeCreate.find_node(sort_t,start_order)   
            a,flag = compare_nodeitem(nodeinfo)
            start_order -=1
            
            if i and flag:
                haschange = True
                i = 0
        #当返回的a节点的item项不为空,则说明根节点发生调整
        if a.item:
            sort_t = a
    return sort_t


##################################################################
#Function:sort_bigTopTree
#Description:对序列进行堆排序并返回排序后的序列
#Author:HJY
#Params:base:待排序序列,list类型
#Return:sort_list,已排序序列,list类型
##################################################################
def sort_bigTopTree(base):
    #使用树结构实现的堆排序
    #构造完全二叉树
    sort_t = treeCreate.create_tree(base,0) 
    #方案1:使堆顶元素出局,堆底元素替代
    #sort_list用来装载出局的数值
    sort_list = []  
    sortlength = len(base)
    
    while len(sort_list) <= sortlength-1: 
        #进行一轮堆调整
        start_order = int((sortlength-len(sort_list))/2)
        #print('当前起始节点:',start_order)
        sort_t = Put_out1(start_order,sort_t)   
        #找到当前堆尾
        nowhas = sortlength-len(sort_list)
        node,node_parent,nodefor = treeCreate.find_node(sort_t,nowhas)
        #将堆尾元素的左右孩子链接为堆顶的左右孩子
        node.lchild = sort_t.lchild
        node.rchild = sort_t.rchild
        #堆尾元素的父节点与其链接断开
        if nodefor == 'r':
            node_parent.rchild = None
        else:
            node_parent.lchild = None
        #保存堆顶的值,并释放堆顶对象的内存引用,将尾节点修改为堆顶
        sort_list.append(sort_t.item)
        del sort_t
        sort_t = node
        #重进行堆调整  
    
    return sort_list


if __name__ == '__main__':

    base = [random.randint(1,100) for _ in range(10)]
#    base = [41, 81, 80, 89, 81, 70, 28, 62, 98, 67]
    print(base)

    cv = sort_bigTopTree(base)
    print(cv)
    

'''
实现思路:
step1:使用之前实现好的二叉树模块生成完全二叉树
step2:由于需要根据公式:int(len(base)/2)获得一开始进行堆调整的节点的序号,
    而首要的问题是获取到序号后,如何找到该节点?
    因此在二叉树模块treeCreate,又实现了一个find_node方法,用以返回该节点
    的相关信息(节点引用,父节点引用,节点属于父节点的左/右孩子)
step3:接着是对每一拥有孩子节点的节点进行比较,在这个过程中我们实现turn函数,
    一旦需要进行节点与其孩子节点的调整就进行调用
    
step4:对所有节点进行一次堆调整,我们实现了put_out1函数,其中由于对每一个节点的
    堆调整可能会破坏了之前堆调整的规则,比如2号节点的堆调整结果,可能导致4号节点
    的堆结构不正确,因此需要反复验证一个节点的堆调整是否会影响。解决的办法便是忽略
    每一个节点的影响,直到完成一整轮的堆调整,根据一个flag标志判断该轮是否产生过
    一次位置变换,也就是是否调用了turn函数,一旦产生一次,那就继续进行一整轮的堆调整
    直到没有位置变换产生,则说明此时堆顶节点为符合规则的节点。
    此时完成了一次一整轮堆调整。
    紧接着我们变换堆尾节点的位置为堆顶节点,并输出原堆顶节点
    
step5:值得注意的是对根节点的处理。
    我们每一次输出后的完全二叉树的节点数已经发生变化,其值应该是我们的原序列
    长度减去输出序列长度,我们可以利用这个关系作为我们进行堆调整(一次为一整轮)
    停止的标志

Note: 在程序编写过程中,引用传递是个问题,可以观察节点的内存位置进行调试
    其中使用打印语句之前,可以先将treeCreate的Node类的‘__str__’方法屏蔽


'''

猜你喜欢

转载自blog.csdn.net/yeshankuangrenaaaaa/article/details/84335493