数据结构与算法基础七:线索二叉树与赫夫曼树

一:线索二叉树

二叉树遍历实际是将复杂的非线性结构转换为线性结构,一个n个节点的二叉链表,一共2n个指针域,n+1个分支,也就是说只有n+1个指针域是有值的,另外n-1个是空的;
线索二叉树指的就是,利用这些空的指针域,存储上这个节点的前驱或者后继节点,前驱和后继依据遍历得到.
相当于把遍历的信息附加在链表上.这样的二叉链表叫做线索链表.


数据结构与算法基础七:线索二叉树与赫夫曼树_第1张图片
浪费的指针域

如下图,中序遍历这个二叉树,把空着的指针指向前驱或者后继节点


数据结构与算法基础七:线索二叉树与赫夫曼树_第2张图片
填充后继

数据结构与算法基础七:线索二叉树与赫夫曼树_第3张图片
填充前驱

但是现在又有一个问题,我们不知道一个节点,它的指针域存的到底是孩子还是前驱/后继,因此需要作出区分.
增加ltag和rtag做标记,ltag为0表示左孩子,为1表示前驱,rtag表示右孩子或者后继.


增加两个tag

数据结构与算法基础七:线索二叉树与赫夫曼树_第4张图片
增加两个ta

swift实现:

class Node{
    
    var name : String
    weak var leftChild : Node?
    weak var rightChild : Node?
    var ltag = 0
    var rtag = 0
    
    init(_ dataName:String) {
        name = dataName
    }
    
    deinit {
        print("node:\(name) release")
    }
}

线索链表已经是一个双向链表,在swift中指针不能再直接引用了,否则无法释放,因此需要weak引用;
ltag和rtag初始化为0比较方便;

 var nodes = [Node]()
  var pre : Node?

func createBinaryTree(_ list:[String]) -> Node?{
        if(list.count == 0){
            return nil
        }
        var index = 0
        
        func createTree() -> Node?{
            let str = list[index]
            var tree : Node?
            index += 1;
            if index < list.count && str != "#"{
                tree = Node.init(str)
                nodes.append(tree!)
                tree!.leftChild = createTree()
                tree!.rightChild = createTree()
            }
            return tree
        }
                
        return createTree()
    }

设置一个全局的指针pre,用于标记当前处理的那个节点;
初始化一个数组nodes用于真正的节点存储;
根据前序遍历生成二叉树,每个节点存储在nodes中.

func binaryTreeThread(_ root : Node?){
        guard let node = root else {
            return
        }
        if node.leftChild == nil{
            node.ltag = 1
            node.leftChild = pre
        }
        if pre?.rightChild == nil{
            pre?.rtag = 1
            pre?.rightChild = node
        }
        pre = node
        if node.ltag == 0{
            binaryTreeThread(node.leftChild)
        }
        if node.rtag == 0{
            binaryTreeThread(node.rightChild)
        }
        
    }

实现线索化,如果leftChild是空的,则把leftChild设置为前一个节点pre,也就是前驱;如果前一个节点的rightChild是空的,就把rightChild设置为当前的节点,也就是后继,然后更新pre;最后,为了防止循环无法停止,需要在tag为0的情况下进行递归.

func binaryTreeForEach(_ root : Node?){
        guard let node = root else {
            return
        }
        print(node.name + "前驱是\(node.ltag == 1 ? node.leftChild!.name : "")"  + "后继是\(node.rtag == 1 ? node.rightChild!.name : "")")
        if node.ltag == 0{
            binaryTreeForEach(node.leftChild)
        }
        if node.rtag == 0{
            binaryTreeForEach(node.rightChild)
        }
    }

现在遍历方法也要修改,不然会无限循环;
在遍历的时候输出前驱和后继

        let node = createBinaryTree(["A","B","#","D","#","#","C","#","#"])
        binaryTreeThread(node)
        binaryTreeForEach(node)
        nodes.removeAll()
        pre = nil

最后调用方法,然后置空,全部释放.


数据结构与算法基础七:线索二叉树与赫夫曼树_第5张图片
输出结果

现在我们添加一个头结点,让头结点的lchild指向中序遍历的第一个节点N1,rchild指向中序遍历的最后一个节点N2;
并且N1的lchild指向头结点,也就是头结点是N1的前驱,N2的rchild指向头结点,也就是头结点是N2的后继.
这样一来,从N1开始,顺序访问后继一直到头结点,就可以遍历整个二叉树,反过来从N2开始顺序访问前驱也可以.

二:赫夫曼树

首先看一个例子,学生的考试成绩需要按照分段筛选到不同的评优等级;
假如学生的成绩际分布是这样的:


成绩分布

那么下面这段代码虽然合理但是显然其效率是有优化空间的.


数据结构与算法基础七:线索二叉树与赫夫曼树_第6张图片
条件筛选

数据结构与算法基础七:线索二叉树与赫夫曼树_第7张图片
树形结构

我们调整一下,把分布概率更大的节点放在前面,显然效率会提高.


数据结构与算法基础七:线索二叉树与赫夫曼树_第8张图片
调整后的树

1.定义
有这样两个二叉树(图1-1),我们给叶节点加上权重,权重根据业务有具体的意义;
从一个节点到另一个节点叫做路径,每间隔一个节点,路径长度增加1;
从根节点到每一个节点的路径长度之和, 叫做树的路径长度;下图中左边的树,根节点到其他节点一共8条路径,路径长度是1+1+2+2+3+3+4+4 = 20; 右边是1+2+3+3+2+1+2+2 = 16.
节点的路径长度乘上该节点的权,就叫带权路径长度;
而树的带权路径长度是所有的叶节点的带权路径长度之和;左边是1x5+2x15+3x40+4x30+4x10 = 315;右边是3x5+3x15+2x40+2x30+2x10 = 220.
带权路径长度(WPL)最短的二叉树树,就叫做赫夫曼树.

数据结构与算法基础七:线索二叉树与赫夫曼树_第9张图片
图1-1

2.构造赫夫曼树
首先将权值最小的两个节点组合成一个子树N1,注意权值小的设置为左孩子,现在N1的权值是15;
然后将N1和权值倒数第三小的节点组成子树N2,同样权值小的设置为左,也就是15(N1)和15(B)比较,这里左右都行,现在N2的权值是30;

数据结构与算法基础七:线索二叉树与赫夫曼树_第10张图片
N1和N2

重复上面的过程,当放置到D时,N4的权值是60,大于C的40,此时C是左孩子,然后只剩一个根节点,设置为T;
数据结构与算法基础七:线索二叉树与赫夫曼树_第11张图片
赫夫曼树完成

3.赫夫曼编码
现在要发送一段文字"BADCADFEED",一共出现六个字母ABCDEF,进行编码

编码表

假设六个字母的权值是A 27,B 8,C 15, D 15, E 30, F 5;如下图左边的树.
接着把这个树的左孩子改成权值0,右孩子改成权值1;如下图右边的树.
数据结构与算法基础七:线索二叉树与赫夫曼树_第12张图片
树和改造后的树

现在每个字母根据路径就得到了唯一的编码;
新的编码表

结果对比

并且它还要一个好处,就是不会出现包含其他编码字串的情况出现;
解析的时候也通过赫夫曼树来解析,只要一个个数字顺着路径走就可以了.
数据结构与算法基础七:线索二叉树与赫夫曼树_第13张图片
解析

定义:设需要编码的字符集为{d1,d2...dn},各个字符出现的频率是{w1,w2....wn},以d1,d2...dn为叶节点,以w1,w2...wn作为节点的权值来构造哈赫夫曼树,规定该树的左分支为0,右分支为1,则字符的路径对应的编码就是赫夫曼编码.

你可能感兴趣的:(数据结构与算法基础七:线索二叉树与赫夫曼树)