美文网首页
数据结构与算法基础七:线索二叉树与赫夫曼树

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

作者: Trigger_o | 来源:发表于2021-05-27 20:07 被阅读0次

一:线索二叉树

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


浪费的指针域

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


填充后继
填充前驱

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


增加两个tag
增加两个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

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


输出结果

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

二:赫夫曼树

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


成绩分布

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


条件筛选
树形结构

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


调整后的树

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)最短的二叉树树,就叫做赫夫曼树.

图1-1

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

N1和N2
重复上面的过程,当放置到D时,N4的权值是60,大于C的40,此时C是左孩子,然后只剩一个根节点,设置为T;
赫夫曼树完成

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

编码表
假设六个字母的权值是A 27,B 8,C 15, D 15, E 30, F 5;如下图左边的树.
接着把这个树的左孩子改成权值0,右孩子改成权值1;如下图右边的树.
树和改造后的树
现在每个字母根据路径就得到了唯一的编码;
新的编码表
结果对比
并且它还要一个好处,就是不会出现包含其他编码字串的情况出现;
解析的时候也通过赫夫曼树来解析,只要一个个数字顺着路径走就可以了.
解析

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

相关文章

  • 线索二叉树与哈夫曼编码

    线索二叉树与哈夫曼编码

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

    一:线索二叉树 二叉树遍历实际是将复杂的非线性结构转换为线性结构,一个n个节点的二叉链表,一共2n个指针域,n+1...

  • Java_二叉树用途

    赫夫曼树 赫夫曼树的构造 赫夫曼树构造过程 查找二叉树

  • 算法学习

    数据结构学习笔记:树与树的表示、二叉树及其遍历、二叉搜索树、平衡二叉树、堆、哈夫曼树、集合及其运算算法学习笔记浅谈...

  • 数据结构与算法-赫夫曼树

    赫夫曼树 过去我们小学、中学一般考试都是用百分制来表示学科成绩的。这带来了一个弊端,就是很容易让学生、家长,甚至老...

  • 数据结构与算法

    数据结构线性与非线性数组、链表、栈、队列、树、图 树二叉树:顺序,最优、线索、搜索,平衡多路查找树3、排序算法4、...

  • 赫夫曼树

    赫夫曼树 Q:什么是赫夫曼树(也叫最优二叉树,有点像得到最优解的贪心法)? A:带权路径长度最小的二叉树 Q:如何...

  • 数据结构与算法学习开篇

    数据结构与算法知识图谱 20个最常用的、最基础数据结构与算法 10个数据结构:数组、链表、栈、队列、散列表、二叉树...

  • 数据结构(五):哈夫曼树(Huffman Tree)

    哈夫曼树 哈夫曼树(或者赫夫曼树、霍夫曼树),指的是一种满二叉树,该类型二叉树具有一项特性,即树的带权路径长最小,...

  • 2018-11-20

    数据结构 复习了森林转换成二叉树,并写出先序中序序列和后续线索,学习哈夫曼树的构造

网友评论

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

      本文链接:https://www.haomeiwen.com/subject/wxsdsltx.html