堆排序的原理略,此处只是作为记录,提供整个代码的实现,其中每个细节会给出注释和函数的设计思路(代码末尾)。
注:堆排序算法的实现,以数组结构来实现要简洁高效!此处只是作为练手使用,由于堆排序的数组实现已经有很多,
此处略。
自定义模块:
这个模块我们只用到其节点对象的创建、根据数组生成完全二叉树的函数、根据节点在二叉树的层序(层序遍历时的顺序)
找到节点的引用等信息的函数。至于其他的遍历等函数,只是之前做别的用途用。
这个文件命名为: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__’方法屏蔽
'''