python 算法 day9 二叉堆

为了使堆操作高效运行,我们将利用二叉树的操作复杂度为对数级这一性质来实现堆操作。同时使堆操作的复杂度始终保持在对数水平上,就必须保持二叉树的平衡,平衡二叉树树根左右子树有着相同的数量节点。完全二叉树,指每个内部节点都有两个子节点,最多可有一个节点列外。

完全树的另一个特性,我们可以用单个列表来实现完全树而不需要使用节点,如果节点在列表中的位置为p,那么其左子节点的位置为2p 右子节点位置为2p+1

堆次序:

指堆中任意一个节点x,其父节点p中的key均小于或等于x中的key  兄弟节点没有要求

实现一个二叉堆:可以采用一个列表来保存堆数据,构造函数初始化仅仅需要初始化一个列表和一个currentSize来跟踪记录当前堆的大小

class BinHeap:
    def __init__(self):
        self.heapList = [0]
        self.currentSize = 0


    def percUp(self,i):  #使新节点上浮到正确位置
        while i//2 > 0:
            if self.heapList[i] < self.heapList[i//2]:
                tmp = self.heapList[i//2]
                self.heapList[i//2] = self.heapList[i]
                self.heapList[i] = tmp
            i = i//2

    def insert(self, k):
        self.heapList.append(k)
        self.currentSize = self.currentSize + 1
        self.percUp(self.currentSize)

    def percDowm(self,i): #替换节点下沉
        while (i * 2)<= self.currentSize:
            mc = self.minChild(i)
            if self.heapList[i]>self.heapList[mc]:
                tmp = self.heapList[i]
                self.heapList[i] = self.heapList[mc]
                self.heapList[mc] = tmp
            i = mc

    def minChild(self,i):
        if i*2+1 >self.currentSize:
            return i*2
        else:
            if self.heapList[i*2] < self.heapList[i*2+1]:
                return i*2
            else:
                return i*2+1

    def delMin(self):   #比较容易移走最小项  但移走根节点数据后如何恢复堆结构和堆次序比较困难
    retval = self.heapList[1]
    self.heapList[1] = self.heapList[self.currentSize]
    self.heapList.pop()
    self.currentSize = self.currentSize -1
    self.percDowm(1)
    return retval
  def buildHeap(self,alist): 
    i
= len(alist)//2
    self.currentSize =len(alist)
    self.heapList
= [0] +alist[:]
    while(i>0):
       self.percDowm(i)
       i
= i -1

 .如何恢复堆结构和堆次序:首先用最后一个节点来代替根节点,移走最后一个节点维持了堆结构的性质,简单替换 但很可能破坏堆次序

第二步 用新替换的节点来下沉恢复堆次序二叉堆的最后一个方法buildHeap是从无序表中生成一个堆,如果采用insert方法,复杂度是onlogn,多次调用insert是把数据一个个放进来,而buildheap是把整个列表中的所有元素全部放进来,再挑几个下沉在on复杂度下生成二叉堆,因为二叉堆只保证父子间的顺序,并不保证兄弟间的大小。

二叉搜索树

一个二叉搜索树,如果左子树的键值Key都小于父节点,而右子树中键值Key都大于父节点,我们将这种树称为BST搜索树

所有左子树的键值都小于右子树的键值

实现二叉搜索树,使用节点和引用方法

用put方法创建二叉搜索树BST,先检查树是否已经有根节点,如果没有,put将根据传入的参数创建一个新的TreeNode并把它作为树的根节点,作为子树的根,如果一个根节点已经到位,我们就调用它自己进行递归 用_put方法来搜索树:

首先,从树的根开始搜索,比较新的键值,如果新的键值小于当前根的值,搜索左子树,如果大于,就搜索右子树

当无左子树或右子树时,我们发现的位置就是安装新节点的位置

向树添加一个新的节点,在上一步发现插入对象的位置创建一个新的TreeNode。

    def put(self,key,val):
        if self.root:
            self._put(key,val,self.root)
        else:
            self.root = TreeNode(key,val)
            self.size = self.size+1
    def _put(self,key,val,currentNode):
        if key<currentNode.key:
            if currentNode.hasleftChild():
                self._put(key,val,currentNode.leftChild)   遍历递归到最下方没有子节点的位置
            else:
                currentNode.leftChild = TreeNode(key,val,parent=currentNode)
        else:
            if currentNode.hasrightChild():
                self._put(key,val,currentNode.rightChild)
            else:
                currentNode.rightChild = TreeNode(key,val,parent=currentNode)

 树被构建后,接下来就是给定键实现对值的检索,get采用递归的搜索子树,直到发现匹配的值就返回

    def get(self,key):
        if self.root:
            res = self._get(key,self.root)
            if res:
                return res.payload
            else:
                return None
        else:
            return None
    def _get(self,key,currentNode):
        if not currentNode:
            return None
        elif currentNode.key ==key:
            return currentNode
        elif key <currentNode.key:
            return self._get(key,currentNode.leftChild)
        else:
            return self._get(key,currentNode.rightChild)

 一些小TIPs

__contains__()方法 从而能够实用操作符 in

__setitem__()方法 重载[]为操作符 

__getitem__()方法 可以使我们像访问字典一样调用

    def __getitem__(self, item):
        return self.get(item)
    def __contains__(self, key):
        if self._get(key,self.root):
            return True
        else:
            return False
     def __setitem__(self, key, value):
         self.put(key,value)

删除方法:我们可以使用get方法去找到需要删除的树节点,一旦找到我们需要删除的键的节点,有三种情况:

1、要删除的节点没有子树  只需要父节点对此节点的引用指向None

2、要删除的节点只有一个子节点

如果当前节点有左子节点,我们只需要更新当前节点的左子节点指向当前节点的父节点引用,然后将父节点对当前节点的引用更新到当前节点的左子节点

如果当前节点有右子节点,我们只需要更新当前节点的右子节点指向当前节点的父节点引用,然后将父节点对当前节点的引用更新到当前节点的右子节点

如果当前节点为根节点(没有父节点)则只需更换键

3、要删除的节点有两个子节点

如果有两个子节点,我们不能简单的选其中一个提升至父节点的位置,这就需要寻找一个节点,用来替代一个计划删除的节点,还需要保持现有的 左右子树以及二叉搜索树的关系

 符合该要求的是比要删节点大的第一个值 即在右子树上寻找最左子树 比如上图比5大的第一个值是7,我们通过寻找该节点,保证其没有一个以上的子节点,然后将其移动到删除节点处 并保证保证原有子树的位置

    def spliceOut(self):
        if self.isLeaf():
            if self.isLeftChild():
                self.parent.leftChild = None
            else:
                self.parent.rightChild = None
        elif self.hasAnyChildren():
            if self.hasLeftChild():
                if self.isLeftChild():
                    self.parent.leftChild = self.leftChild
                else:
                    self.parent.rightChild = self.leftChild
                    self.leftChild.parent = self.parent
            else:
                self.parent.rightChild = self.leftChild
                self.leftChild.parent = self.parent
        else:
            if self.isLeftChild():
                self.parent.leftChild = self.rightChild
            else:
                self.parent.rightChild = self.rightChild
                self.rightChild.parent = self.parent
    def findSuccessor(self):
        succ = None
        if self.hasRightChild():
            succ = self.rightChild.findMin()
        else:
            if self.parent:
                if self.isLeftChild():
                    succ = self.parent
                else:
                    self.parent.rightChild = None
                    succ = self.parent.findSuccessor()
                    self.parent.rightChild = self
        return succ
    def findMin(self):
        current = self
        while current.hasleftChild():
            current = current.leftChild
        return current

找到继任者的函数findSuccessor() 需要考虑三种情况:

1、如果节点有右子节点,那么继任者是右子树中最小的键

2、如果节点没有右子节点,是其父节点的左子节点 ,则父节点是继任者

3、如果节点是其父节点的右子节点,而本身无右子节点,那么该节点的继任者是其父结点的继任者 不包括这个节点(因为父节点还有左子节点)

另外第三种情况移动继任者时 我们还需要再分割的节点处保持正确的顺序

findmin函数就不断对左子树进行循环取最小值

使用迭代器 重写__iter__()方法 采用yield记录函数目前运行的状态

完整代码

class BinarySearchTree:
    def __init__(self):
        self.root = None
        self.size = 0
    def length(self):
        return self.size
    def __len__(self):
        return self.size
    def put(self,key,val):
        if self.root:
            self._put(key,val,self.root)
        else:
            self.root = TreeNode(key,val)
            self.size = self.size+1
    def _put(self,key,val,currentNode):
        if key<currentNode.key:
            if currentNode.hasleftChild():
                self._put(key,val,currentNode.leftChild)
            else:
                currentNode.leftChild = TreeNode(key,val,parent=currentNode)
        else:
            if currentNode.hasrightChild():
                self._put(key,val,currentNode.rightChild)
            else:
                currentNode.rightChild = TreeNode(key,val,parent=currentNode)
    def __setitem__(self, key, value):
        self.put(key,value)
    def get(self,key):
        if self.root:
            res = self._get(key,self.root)
            if res:
                return res.payload
            else:
                return None
        else:
            return None
    def _get(self,key,currentNode):
        if not currentNode:
            return None
        elif currentNode.key ==key:
            return currentNode
        elif key <currentNode.key:
            return self._get(key,currentNode.leftChild)
        else:
            return self._get(key,currentNode.rightChild)
    def __getitem__(self, item):
        return self.get(item)
    def __contains__(self, key):
        if self._get(key,self.root):
            return True
        else:
            return False
    def delete(self,key):
        if self.size >1:
            nodeToRemove = self._get(key,self.root)
            if nodeToRemove:
                self.remove(nodeToRemove)
                self.size =self.size -1
            else:
                raise KeyError("key not in tree")
        elif self.size==1 and self.root.key ==key:
            self.root = None
            self.size = self.size -1
        else:
            raise KeyError("key not in tree")
    def __delitem__(self, key):
        self.delete(key)
    def spliceOut(self):
        if self.isLeaf():
            if self.isLeftChild():
                self.parent.leftChild = None
            else:
                self.parent.rightChild = None
        elif self.hasAnyChildren():
            if self.hasLeftChild():
                if self.isLeftChild():
                    self.parent.leftChild = self.leftChild
                else:
                    self.parent.rightChild = self.leftChild
                    self.leftChild.parent = self.parent
            else:
                self.parent.rightChild = self.leftChild
                self.leftChild.parent = self.parent
        else:
            if self.isLeftChild():
                self.parent.leftChild = self.rightChild
            else:
                self.parent.rightChild = self.rightChild
                self.rightChild.parent = self.parent
    def __iter__(self):
        if self:
            if self.hasleftChild():
                for elem in self.leftChild:
                    yield elem
            yield self.key
            if self.hasrightChild():
                for elem in self.rightChild:
                    yield elem

    def findSuccessor(self):
        succ = None
        if self.hasrightChild():
            succ = self.rightChild.findMin()
        else:
            if self.parent:
                if self.isLeftChild():
                    succ = self.parent
                else:
                    self.parent.rightChild = None
                    succ = self.parent.findSuccessor()
                    self.parent.rightChild = self
        return succ
    def findMin(self):
        current = self
        while current.hasleftChild():
            current = current.leftChild
        return current



    def remove(self,currentNode):
        if currentNode.isLeaf():
            if currentNode ==currentNode.parent.leftChild:
                currentNode.parent.leftChild =None
            else:
                currentNode.parent.rightChild =None
        elif currentNode.hasBothChildren():
            succ = currentNode.findSuccessor()
            succ.spliceOut()
            currentNode.key = succ.payload
        else:
            if currentNode.hasLeftChild():
                if currentNode.isLeftChild():
                    currentNode.leftChild.parent = currentNode.parent
                    currentNode.parent.leftChild = currentNode.leftChild
                elif currentNode.isRightChild():
                    currentNode.leftChild.parent = currentNode.parent
                    currentNode.parent.rightChild = currentNode.leftChild
                else:
                    currentNode.replaceNodeData(currentNode.leftChild.key,
                                                currentNode.leftChild.payload,
                                                currentNode.leftChild.leftChild,
                                                currentNode.leftChild.rightChild
                                                )
            else:
                if currentNode.isLeftChild():
                    currentNode.rightChild.parent = currentNode.parent
                    currentNode.parent.leftChild = currentNode.rightChild
                elif currentNode.isRightChild():
                    currentNode.rightChild.parent = currentNode.parent
                    currentNode.parent.rightChild = currentNode.rightChild
                else:
                    currentNode.replaceNodeData(currentNode.rightChild.key,
                                                currentNode.rightChild.payload,
                                                currentNode.rightChild.leftChild,
                                                currentNode.rightChild.rightChild
                                                )




class TreeNode:
    def __init__(self,key,val,left=None,right =None,parent =None):
        self.key = key
        self.payload = val
        self.leftChild=left
        self.rightChild = right
        self.parent = parent

    def replaceNodeData(self,key,value,lc,rc):
        self.key=key
        self.payload = value
        self.leftChild = lc
        self.rightChild = rc
        if self.hasleftChild():
            self.leftChild.parent = self
        if self.hasrightChild():
            self.rightChild.parent = self
    def hasleftChild(self):
        return self.leftChild
    def hasrightChild(self):
        return self.rightChild
    def isLeftChild(self):
        return self.parent and self.parent.leftChild ==self
    def isRightChild(self):
        return self.parent and self.parent.rightChild ==self
    def isRoot(self):
        return not self.parent
    def isLeaf(self):
        return not (self.rightChild or self.leftChild)
    def hasAnyChildren(self):
        return self.rightChild or self.leftChild
    def hasBothChildren(self):
        return self.rightChild and self.leftChild
mytree = BinarySearchTree()
mytree[3]="red"
mytree[4]="blue"
mytree[6]="yellow"
mytree[2]="at"
print(mytree.root.key)
print(mytree.root.hasleftChild().key)
print(mytree[2])

 搜索树分析

如果键是以随机顺序加到树中的,那么当节点的数目为n时,树的高度大概会在log2n






原文地址:https://www.cnblogs.com/suizhixxie/p/10426220.html