第六章 树和二叉树.

Slides:



Advertisements
Similar presentations
第7章 樹與二元樹 (Trees and Binary Trees)
Advertisements

Chapter 06 Tree and binary tree 第六章 树和二叉树
動畫與遊戲設計 Data structure and artificial intelligent
第6章 二叉树和树 前面的章节主要讨论的是线性结构,二叉树和树属于非线性的结构。遍历非线性结构比线性结构要麻烦。
计算机软件技术基础 数据结构与算法(4).
数据结构——树和二叉树 1/96.
引入树 线性表、堆栈和队列都是线性结构,它们的共同特点:一对一; 计算机对弈中的一个格局可能有多个后继格局,用线性结构难以描述。
第六章 树和二叉树.
第六章 树和二叉树.
第6章 树和二叉树 (Tree & Binary Tree)
第5章 树( Tree )和二叉树 5.1 树的基本概念 5.2 二叉树 5.3 遍历二叉树和线索二叉树 5.4 树和森林
CH6 树和二叉树 6.1 树的定义和基本术语 6.2 二叉树 6.3 遍历二叉树和线索二叉树 6.4 树和森林
第8章 查找 在英汉字典中查找某个英文单词的中文解释;在新华字典中查找某个汉字的读音、含义;在对数表、平方根表中查找某个数的对数、平方根;邮递员送信件要按收件人的地址确定位置等等。可以说查找是为了得到某个信息而常常进行的工作。 计算机、计算机网络使信息查询更快捷、方便、准确。要从计算机、计算机网络中查找特定的信息,就需要在计算机中存储包含该特定信息的表。如要从计算机中查找英文单词的中文解释,就需要存储类似英汉字典这样的信息表,以及对该表进行的查找操作。本章将讨论的问题即是“信息的存储和查找”。
TA:于波 计算机科学工程系 上海交通大学 December 5, 2009
数据结构 第六章 树与二叉树 深圳大学计算机系 蔡茂国.
第五章 树 东南大学计算机学院 方效林 本课件借鉴了清华大学殷人昆老师 和哈尔滨工业大学张岩老师的课件.
数据结构 第6章 树和二叉树 什么是树和二叉树?? 二叉树的遍历 数据结构.
第六章 树和二叉树 6.1 树的定义和基本术语 6.2 二叉树 6.3 遍历二叉树和线索二叉树 6.4 树和森林 6.6 赫夫曼树及其应用.
Chap4 Tree.
Tree(樹) 什麼是「樹」? 「樹」的範例 「樹」的定義 「樹」的表示法.
Linked List Operations
Chapter 5 Tree & Binary Tree
單向鏈結串列 Singly Linked Lists.
樹狀結構 陳怡芬 2018/11/16 北一女中資訊專題研究.
佇列 (Queue).
Chapter8 Binary and Other Trees
講師:郭育倫 第3章 基本資料結構 講師:郭育倫
数 据 结 构 Ch.6 树 计 算 机 学 院 肖明军
哈夫曼编码.
Chapter 5 樹(Trees).
第12章 樹狀搜尋結構 (Search Trees)
第六章 二叉树和树 6.1树的基本概念 6.2二叉树 6.3二叉树遍历 6.4线索二叉树 6.5树和森林 6.6树的应用(霍夫曼树及其编码)
第六章 二叉树和树 6.1树的基本概念 6.2二叉树 6.3二叉树遍历 6.4线索二叉树 6.5树和森林 6.6树的应用(霍夫曼树及其编码)
线性表小结 元素之间的线性关系 顺序表 顺序表:元素相邻存储 单链表:后继指针链接 一维数组 给定下标随机存取
演算法與資料結構 製作單位: 高雄市立高雄中學.
第2章 线性表 线性表抽象数据类型 顺序表 主要知识点 单链表 循环单链表 循环双向链表 静态链表 设计举例.
第5章 堆疊(Stacks) 5-1 堆疊的基礎 5-2 堆疊的表示法 5-3 堆疊的應用 - 運算式的計算與轉換
第5章 堆疊(Stacks) 5-1 堆疊的基礎 5-2 堆疊的表示法 5-3 堆疊的應用 - 運算式的計算與轉換
本章重点难点 重点:顺序查找、二分查找、二叉排序树查找以及散列表查找的基本思想和算法实现。
第三章 栈与队列 £3.1 栈 £3.3 队列 £3.2 栈的应用举例 £3.1.1 栈的定义 £3.1.2 栈的顺序存储结构
第3章 栈和队列(一).
第三章 栈和队列.
第 七 章 樹狀結構 課程名稱:資料結構 授課老師:________ 2019/1/1.
教 师:曾晓东 电 话: E_mail: 计算机软件技术基础 教 师:曾晓东 电 话: E_mail:
樹狀結構 Tree Structure chapter 7 德明科技大學資訊科技系.
親愛的老師您好 感謝您選用本書作為授課教材,博碩文化準備本書精選簡報檔,特別摘錄重點提供給您授課專用。 說明: 博碩文化:
第7章 树和二叉树 7.1 树 7.2 二叉树 7.3 以结点类为基础的二叉树设计 7.4 二叉树类 7.5 二叉树的分步遍历
6.6 Huffman树及其应用 王 玲.
6.3 遍历二叉树和线索二叉树(知识点二) 遍历二叉树 一、问题的提出
二叉树和其他树 (Binary and other trees)
第六章 樹狀結構 T r e e 版權屬作者所有,非經作者 同意不得用於教學以外用途.
樹 2 Michael Tsai 2013/3/26.
感謝同學們在加分題建議. 我會好好研讀+反省~
Tree & Binary Tree.
書名 Java於資料結構與演算法之實習應用
第6章 树和二叉树 本章主题:树、二叉树 教学目的:掌握树和二叉树的类型定义、运算及存储结构 教学重点:树的各种表示、各种存储方式和运算,
第6章 树与二叉树 6.1 树的概念和运算 6.2 二叉树 6.3 树和森林 6.4 树的典型应用 6.5 本章小结.
二叉树的遍历.
7.1 广义表的概念 广义表是n(n≥0)个数据元素组成的序列,其中每个数据元素或是单个数据元素(简称原子),或仍然是一个广义表 。
第六章 树和二叉树 £6.1 树 £6.2 二叉树 £6. 3 二叉树的存储结构 £6.4 二叉树的遍历与线索化 £6.1.1 树的定义
第六章 树和二叉树 学习要点 理解树的定义和基本术语,重点了解二叉树的定义、性质、存储结构; 掌握二叉树遍历的递归算法及它的典型运算;
第7章 樹與二元樹(Trees and Binary Trees)
資料結構使用Java 第9章 樹(Tree).
Ch.9 查找 §9.1 基本概念 查找和排序是两个重要的运算
<编程达人入门课程> 本节内容 为什么要使用变量? 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ:
树和二叉树(一).
第 六 讲 栈和队列(一).
第7章 图.
第10章 二元搜尋樹 (Binary Search Tree)
本节内容 1.二叉排序树的定义 2.二叉排序树的查找操作 3.二叉排序树的插入操作 4.二叉排序树的删除操作 5.二叉排序树的总结
Presentation transcript:

第六章 树和二叉树

本章内容 树、二叉树的定义、性质和存储结构; 二叉树的遍历和线索化以及遍历算法的各种描述形式; 树遍历; 二叉树的多种应用。

本章要点 熟练掌握二叉树的结构特性; 熟悉二叉树的各种存储结构的特点及适用范围; 熟练掌握二叉树各种遍历策略的递归和非递归算法,了解遍历过程中“栈”的作用和状态,而且能灵活运用遍历算法实现二叉树的其它操作; 理解二叉树线索化的实质是建立结点与其在相应序列中的前驱或后继之间的直接联系; 熟悉树的各种存储结构及其特点; 学会编写实现树的各种操作的算法; 了解最优树的特性,掌握建立最优树和哈夫曼编码的方法。 

6.1 树的定义和基本术语

树的定义 树是一类重要的非线性数据结构,是以分支关系定义的层次结构; 树是n(n≥0)个结点的有限集,非空树满足: 有且仅有一个特定的称之为根(root)的结点; 除根以外的其余结点可分为m(0m<n)个互不相交的子集T1,T2,T3…Tm,其中每个子集本身又是一棵树,且称为根的子树。

树的表示方法 特点:除根结点外,每个结点都仅有一个前趋(父)结点。 1层 A 2层 其它表示方法: 3层 B C D 4层 嵌套集合表示法 凹入表表示法 参见教材120页 A B C D E F G H I J 1层 2层 3层 4层

树的一些基本术语 结点的度(degree) 叶子结点(leaf--又称终端结点 terminal node) 结点所拥有的子树的数目。 叶子结点(leaf--又称终端结点 terminal node) 度为零的结点。 分支结点(branch node) 度不为零的结点 孩子( child) 结点的子树的根称为结点的孩子。

树的一些基本术语(续) 双亲( parent) 祖先( forefather) 子孙(progeny) 兄弟(sibling) 结点是其孩子的双亲。 祖先( forefather) 从树根到双亲的所有结点称为该结点的祖先。 子孙(progeny) 以结点为根的子树的所有结点称为该结点的子孙。 兄弟(sibling) 同一父母的所有孩子互称兄弟。 层次(level) 树根为第一层,其孩子为第二层,孙子为第三层,以此类推。

树的一些基本术语(续) 森林(forest ) 堂兄弟(cousin) 深度(depth) 有序树(ordered tree) 双亲在同一层的结点互称堂兄弟。 深度(depth) 树中结点的最大层次称为树的深度。 有序树(ordered tree) 结点各子树从左至右是有秩序的树称为有序树。 无序树(unordered tree) 结点各子树没有秩序的树称为无序树。 森林(forest ) 森林是 m (m≥0) 棵互不相交的树的集合。

树的基本操作 初始化操作 InitTree(&T) 求根函数 Root(T) 求双亲函数 Parent(T,cur_e) 求左孩子结点函数 LeftChild(T,cur_e) 建树函数 CreateTree(&T,definiton) 清空树函数 ClearTree(&T) 插入子树函数 InsertChild(&T,&p,i,c) 删除子树函数 DeleteChild(&T,&p,i) 遍历函数 TraverseTree(T,visit()) 求右兄弟函数 RightSibling(T,cur_e)

6.2 二叉树

二叉树的定义 定义 二叉树是n(n0)个结点的有限集合,它或为空树(n=0),或由一个根结点和两棵互不相交的左子树和右子树的二叉树组成。 二叉树的特点: 定义是递归的; 0结点的度2; 是有序树。

二叉树(续)  二叉树的五种基本形态 两种特殊的二叉树 满二叉树:每一层上的结点数都是最大结点数。 完全二叉树:只有最下面两层结点的度可小于2,而最下一层的叶结点集中在左边若干位置上。  1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7

二叉树的性质 性质1 性质2 性质3 性质4 二叉树的第i层上至多有2i-1(i1)个结点。 深度为k的二叉树至多有2k-1个结点(k1)。 性质3 对任何一棵二叉树T,如果其终端结点数为n0,度为2的结点数为n2 ,则n0 = n2 + 1。 性质4 具有n个结点的完全二叉树的深度为 log2n+1。

二叉树的性质(续) 性质5 对一棵具有n个结点的完全二叉树(其深度为log2n+1),对其结点按层从上至下(每层从左至右)进行0至n-1的编号,则对任一结点i(0i<n)有: 若i=0,则i是根,无双亲;若i>0,则i的双亲是(i-1)/2。 若2i+1<n,则i有左孩子,左孩子是2i+1。 若2(i+1)<n,则有右孩子,右孩子是2(i+1)。 1 2 3 4 5 6 7 8 9 1 2 3 4 5 6

二叉树的存储结构 顺序存储结构 按完全二叉树存储 A B D E F C A B C D E F 0 1 2 3 4 5 6 #define MaxTreeSize 128 typedef TElemType SqBiTree[MaxTreeSize]; SqBiTree bt; A B D E F C A B C D E F 0 1 2 3 4 5 6

二叉树的存储结构(续) 顺序存储结构 用父母指针存储 A B D E F C A B C D E F - 1 2 0 1 2 3 4 5 存储结点数据和其父结点的序号 A B D E F C A B C D E F - 1 2 0 1 2 3 4 5 #define MaxTreeSize 100 typedef struct Node{ Telemtype data; int parent ; }Node; typedef Node bTree[MaxTreeSize];

二叉树的存储结构(续) 链式存储结构 链式存储结构 A ^ B ^ C ^ ^ D ^ E ^ bt A ^ ^ B ^ C ^ ^ D 用二叉链表存储 链式存储结构 用三叉链表存储 A ^ B ^ C ^ ^ D ^ E ^ bt A ^ ^ B ^ C ^ ^ D ^ E ^ bt

二叉树的存储结构(续) 二叉链表的类型定义 三叉链表的类型定义 typedef struct TriTNode typedef struct BiTNode { TElemType data; struct BiTNode*lchild; struct BiTNode*rchild; }BiTNode,*BiTree; 三叉链表的类型定义 typedef struct TriTNode { TElemType data; struct BiTNode*lchild; struct BiTNode*rchild; struct BiTNode*parent; }TriTNode,*TriTree;

二叉树的基本操作 建树函数 CreateBiTree(&T) 先序遍历函数 PreOrder(T,visit()) 中序遍历函数 InOrder(T,visit()) 后序遍历函数 PostOrder(T,visit()) 按层次遍历函数 LevelOrder(T,visit())

6.3.1 遍历二叉树

遍历二叉树 遍历二叉树 指按某条搜索路线走遍二叉树的每个结点,使得树中每个结点都被访问一次,且仅被访问一次。 根据搜索路径的不同,二叉树的遍历分为八种: √ 1、前序遍历(preorder traversal) DLR 2、中序遍历(inorder traversal) LDR 3、后序遍历(postorder traversal) LRD 4、逆前序遍历(inverse preorder traversal) DRL 5、逆中序遍历(inverse inorder traversal) RDL 6、逆后序遍历(inverse postorder traversal) RLD √ 7、按层次遍历(level-by-level traversal) 8、按层次逆遍历( inverse level-by-level traversal)

遍历二叉树示例 1-前序 2-中序 3-后序 4-逆前序 5-逆中序 6-逆后序 7-层次 8-层次逆 1. ABDCEF B C D E 遍历方法 先序遍历:ABDEC 中序遍历:DBEAC 后序遍历:DEBCA 1-前序 2-中序 3-后序 4-逆前序 5-逆中序 6-逆后序 7-层次 8-层次逆 1. ABDCEF 2. DBAECF A B D E F C 3. DBEFCA 4. ACFEBD 5. FCEABD 6. FECDBA 7. ABCDEF 8. ACBFED

遍历二叉树的递归算法 void Preorder(BiTree t) void Inorder(BiTree t) { if (t) { visit(t); Preorder(t->lchild); Preorder(t->rchild); } void Inorder(BiTree t) { if (t) { Inorder(t->lchild); visit(t); Inorder(t->rchild); } void Postorder(BiTree t) { if (t) { Postorder(t->lchild); Postorder(t ->rchild); visit( t ); }

利用遍历结果确定二叉树问题 A 先序序列: ABCDEFGH F B G C D E H 思考:层序+先序/中序/后序, 能否确定?如何做? 先序序列+中序序列 中序序列+后序序列 先序序列+后序序列 (x) 先序序列: ABCDEFGH 中序序列: BDCEAFHG F B G C D E H 思考:层序+先序/中序/后序, 能否确定?如何做? 例如:层序ABCDEFGHIJ,中序DBGEHJACIF

根据先序中序求后序的算法 void get_post(char *pre, char *in, char *post, int n) { if ( n == 0) return; for (k = 0; k < n; k++) if (in[k] == pre[0]) break; if (! k < n) { printf("错误的数据!\n"); exit(1); } post[n - 1] = pre[0]; get_post(pre + 1, in, post, k); get_post(pre + 1 + k, in + k + 1, post + k, n - k - 1); }

先序遍历的非递归算法

先序遍历的非递归算法(1) void Preorder(Struct NODE *t) { init_stack(); push(t); 非递归算法1(朴素的非递归算法) void Preorder(Struct NODE *t) { init_stack(); push(t); while (!stack_empty()) { if ((p = pop()) != NULL) { visit(p); push(p->rchild); push(p->lchild); }

先序遍历的非递归算法(2) 非递归算法,考虑取消多余的空指针入栈,空指针个数可观,为n+1个 init_stack(); p = t; while (!stack_empty() || p) { visit(p); if (p->rchild) push(p->rchild); if (p->lchild) p = p->lchild; else p = pop(); } init_stack(); p = t; push(NULL); while (p) { visit(p); if (p->rchild) push(p->rchild); if (p->lchild) p = p->lchild; else p = pop(); }

后序遍历的非递归算法

后序遍历的非递归算法(1) init_stack(); push(root,0); while(!stack_empty()) { p = pop(&state); if (p == NULL) continue; if (state == 0) { push(p, 1); push(p->lchild, 0); } else if (state == 1) { push(p, 2); push(p->rchild, 0); } else { visit(p); }

后序遍历的非递归算法(2) init_stack(); push(root, 0); while(!stack_empty()) { p = pop(&state); if (p == NULL) continue; if (state == 0) { push(p, 2); push(p->rchild, 0); push(p->lchild, 0); } else { visit(p); }

中序遍历的非递归算法

中序遍历的非递归算法(1) 朴素的算法 init_stack(); 效率问题 push(root, 0); while(!stack_empty()) { p = pop(&state); if (p == NULL) continue; if (state == 0) { push(p, 1); push(p->lchild, 0); } else if (state == 1) { visit(p); push(p->rchild, 0); } 效率问题 新压入堆栈的state0节点马上弹出然后再次压入作为state1节点

中序遍历的非递归算法(2) init_stack(); p = root; while (p) { push(p); p = p->lchild; } while(!stack_empty()) { p = pop(); visit(p); p = p->rchild; } init_stack(); p = root; while (p || !stack_empty()) { if (p) { push(p); p = p->lchild; } else { p = pop(); visit(p); p = p->rchild; }

二叉树遍历的应用

二叉树遍历的应用 在二叉树中查找结点值为x的结点 求二叉树中每个结点所处的层次 求二叉树的高度 复制一棵二叉树

6.4 树和森林

树的存储结构:双亲表示法 A B C E F G D A B C D E F G ^ 1 3 0 1 2 3 4 5 6 存储方法:使用顺序结构 #define MAX_TREE_SIZE 100 typedef struct PTNode { TElemType data; int parent; } PTNode; /* 节点的存储结构*/ typedef struct {/* 顺序存储结构 */ PTNode nodes[MAX_TREE_SIZE]; int r, n; /* 根位置和节点数*/ } PTree; A B C E F G D A B C D E F G ^ 1 3 0 1 2 3 4 5 6 优点:简单,紧凑,易于寻找双亲节点 缺点:查询某节点的孩子效率低

树的存储结构:孩子表示法 方法一 struct PTNode { TElemType data; struct PTNode *child[NUM_CHILD]; }; 缺点:NUM_CHILD值不容易确定; 空域数目太多 方法二: 改进方法1,记录节点的度,分配合适大小的内存 方法三: 使用链表勾链,链表的每个节点记录一个孩子指针 优缺点比较和其他存储方案 选用的实际存储结构该考虑到可能需要操作(增加,删除,检索)

树的存储结构:孩子表示法(方法3) A B C E F G D A B C D E F G 1 2 3 4 6 5 ∧

树的存储结构:孩子表示法(方法2) #define MAX_TREE_SIZE 100 struct TNode { TElemType data; int degree; int child[1]; }; struct PTree { struct TNode *node[MAX_TREE_SIZE]; int n; } PTree; struct TNode *create_tnode(void) { struct TNode *p; int k, degree; scanf(“%d”, &degree); p = malloc(sizeof(struct TNode)+(degree-1)* sizeof(int)); P->degree = degree; for (k = 0; k < degree; k++) scanf(“%d”, &p->child[k]); input_node_data(p); return(p); }

树的存储结构(孩子兄弟表示法) 孩子兄弟表示法 A B C E F G D A ∧ B C D E F G

森林与二叉树的转换 转换原则 按孩子兄弟表示法进行转换。 树与二叉树的转换 E D

森林与二叉树的转换

树和森林的遍历 树的遍历 A B C D E F G E F G 先序遍历 后序遍历 先访问树的根结点,然后依次先根遍历根的每棵子树; ABEFCDG (二叉树先序) 后序遍历 先依次后根遍历根的每棵子树,然后访问树的根结点 EFBCGDA (二叉树中序)

森林的遍历 先序遍历: 访问森林中第一棵树的根结点; 先序遍历第一棵树中根结点的子树森林; 先序遍历除第一棵树外剩余的树构成的森林; 举例:A B C D E F G H I J 中序遍历 中序遍历森林中第一棵树的根结点的子树森林; 访问第一棵树的根结点; 中序遍历除第一棵树外剩余的树构成的森林 举例:B C D A F E H J I G

6.5 树的等价问题

集合的查找与合并 集合的运算 集合的数据结构有多种实现方法 采用的结构取决于集合大小和所进行的操作 find(x) 判断元素x属于哪个集合 Merge(Si, Sj) 将集合Si和Sj合并 集合的数据结构有多种实现方法 位图法 O(1) 有序表方法 O(n) 树型结构表示集合 采用的结构取决于集合大小和所进行的操作

树的存储结构:双亲表示法 A B C E F G D A B C D E F G ^ 1 3 0 1 2 3 4 5 6 存储方法:使用顺序结构 #define MAX_TREE_SIZE 100 typedef struct PTNode { TElemType data; int parent; } PTNode; /* 节点的存储结构*/ typedef struct {/* 顺序存储结构 */ PTNode nodes[MAX_TREE_SIZE]; int r, n; /* 根位置和节点数*/ } PTree; A B C E F G D A B C D E F G ^ 1 3 0 1 2 3 4 5 6 优点:简单,紧凑,易于寻找双亲节点 缺点:查询某节点的孩子效率低

树型结构表示集合 采用双亲表示法,存储一个森林 type PTree MFSet; int find_mfset(MFSet S, int i) { if (i < 0 || i >= S.n) return -1; for (j = i; S.nodes[j].parent >= 0; j = S.nodes[j].parent); return j; } Status merge_mfset(MFSet S, int i, int j) { if (i < 0 || i >= S.n || j < 0 || j >= S.n) return ERROR; S.node[i].parent = j; return OK; }

算法分析和改进 分析算法的效率 改进思路 改进后的分析 合并O(1),检索O(d), d为深度,最糟糕情况下,d = n 合并时,将数量少的集合,合并到数量多的集合中 根的parent记录集合中节点个数,为区别于正常parent值,用负数 改进后的分析 合并O(1),树深度低于log2n

改进后的合并算法 void mix_mfset(MFSet S, int i, int j) { if (S.nodes[i].parent > S.nodes[j].parent) { /* i中节点少 */ swap(i, j); } /* i中节点多 */ S.nodes[i].parent += s.nodes[j].parent; S.[j].parent = i;

改进后的检索算法 改进思路: 检索时压缩路径 int fix_mfset(MFSet S, int i) { 改进思路: 检索时压缩路径 int fix_mfset(MFSet S, int i) { for(j=i; S.nodes[j].parent>=0; j=S.nodes[j].parent); for (k = i; k != j; k = t) { t = S.nodes[k].parent; S.nodes[k].parent = j; } return j;

6.6 赫夫曼树及其应用

赫夫曼树及其应用 赫夫曼树 最优树,是一类带权路径长度最短的树; 路径长度 树的路径长度 带权路径长度 指树中两个结点间路径上所含有的分支数目。 树的路径长度 指从树根到每一结点的路径长度之和。 带权路径长度 指结点的路径长度与该结点的权之积。

赫夫曼树 树的带权路径长度 树中所有叶子结点的带权路径长度; 最优二叉树或赫夫曼树 WPL 最小的二叉树

赫夫曼树的特点 赫夫曼树中除叶子结点外,所有分支结点的度均为2; 叶子结点(外部结点)可看成是由分支结点(内部结点)组成的树扩充出来的(扩充二叉树)

赫夫曼树的构造 根据给定的n个权值{w1,w2,...wn}构成n棵二叉树的集合F={T1,T2,...,Tn},其中每棵二叉树Ti中只有一个带权为wi的根结点,其左右子树均空; 在F中选取两棵根结点的权值最小的树作为左右子树构造一棵新的二叉树,且置新的二叉树的根结点的权值为其左、右子树上根结点的权值之和。 在F中删除这两棵树,同时将新得到的二叉树加入F中; 重复2和3,直到F中只含一棵树为止; 这棵树就是赫夫曼树;

赫夫曼树构造举例1 29 14 8 7 15 11 3 5 19 23 42 [例] w={5, 29, 7, 8, 14, 23, 3, 11} 5 14 29 7 8 23 3 11 14 29 7 8 23 11 3 5 11 3 5 8 19 23 42 29 14 7 15 58 8 7 15 14 29 23 3 5 11 11 3 5 8 19 14 29 23 7 15 11 3 5 8 19 23 42 29 14 7 15 58 100 11 3 5 8 19 29 23 14 7 15

赫夫曼编码的存储结构和构造算法 初始有n个叶子结点(0~n-1号),构造出的哈夫曼树有2n-1个结点 用大小m=2n-1的向量存储哈夫曼树(顺序存储) struct HTNode { int weight; int parent, lchild, rchild; } *ht; m = 2 * n - 1; ht = malloc(m*sizeof(struct HTNode)); for (p=&ht[0]; p < &ht[m]; p++) p->parent = -1; 输入ht前n个元素的weight; for (i = n; i < m; i++) { 在ht的前i个节点中寻找parent为-1且weight最小的两个节点s1,s2 ht[s1].parent = ht[s2].parent = i; ht[i].lchild = s1; ht[i].rchild = s2; ht[i].weight = ht[s1].weight + ht[s2].weight; }

赫夫曼树构造举例1 例: 7) 1.00 已知字符 A,B,C,D,E,F,G,使用频 度分别为:0.25,0.1,0.15,0.06 , 0.3 , 0.05,0.09,试以此构造霍夫曼树。 1) 0.25 0.1 0.15 0.06 0.3 0.05 0.09 2) 0.25 0.1 0.15 0.3 0.09 0.11 1 3) 0.25 0.15 0.3 0.11 0.19 A 0.44 E 0.56 4) 0.25 0.3 0.19 0.26 G B 0.19 C 0.26 5) 0.3 0.26 0.44 F D 0.11 6) 0.44 0.56 7) 1.00

霍夫曼树的应用 霍夫曼编码 G B A F D C E 1 A: 01 B: 001 C: 101 D: 1001 E: 11 1 A: 01 B: 001 C: 101 D: 1001 E: 11 F: 1000 G: 000 1、最佳判定树; 2、霍夫曼编码: WPL霍夫曼编码 = 2.56 WPL等长编码 = 3 等长编码 E: 100 A: 000 B: 001 C: 010 D: 011 F: 101 G: 110 1 G B A F D C E 利用霍夫曼 编码可提高效率: ( 3 - 2.56 ) / 3 ≈ 15%

赫夫曼编码 主数据结构:构造哈夫曼编码表,使用ht的parent域 辅助数据结构:char cd[N]; char *get_code(int i) // 求i号符号的编码串 { cd[N - 1] = '\0'; start = N - 1; for (c = i, f = ht[c].parent; f != -1; c = f, f = ht[f].parent) cd[--start] = ht[f].lchild == c ? '0' : '1'; return &cd[start]; } 解码程序比较简单,从树根出发按1或0分别走向右或左孩子,直到叶子节点,解出编码的符号

赫夫曼解码 解码程序:从树根出发按1或0分别走向右或左孩子,直到叶子节点,解出编码的符号 decode() // 求i号符号的编码串 { k = 2 * n - 2;/* 树根节点 */ for(j = 0; j < rcv_buf_len; j++) { k = rcvbuf[j] == ‘0’ ? ht[k].lchild : ht[k].rchild; if (k < n) { out_code(k); /* 解出第k号符号 */ k = 2 * n – 2; }

赫夫曼编码的特点 赫夫曼编码是不等长编码; 赫夫曼编码是前缀编码,即任一字符的编码都不是另一字符编码的前缀; 赫夫曼编码树中没有度为1的结点。若叶子结点的个数为n,则赫夫曼编码树的结点总数为 2n-1; 发送过程:根据由赫夫曼树得到的编码表送出字符数据; 接收过程:按左0、右1的规定,从根结点走到一个叶结点,完成一个字符的译码。反复此过程,直到接收数据结束;

作业和上机作业

作业 1.试分别画出具有3个结点的树和3个结点的二叉树的所有不同形态。 2.一棵含有n个结点的k叉树,何种形态达到最大深度?何种形态达到最小深度? 3.假设一棵二叉树的先序序列为EBADCFHGIKJ和中序序列为ABCDEFGHIJK。请画出该树。

作业(续) 4.假设用于通讯的电文仅由8个字母组成,字母在电文中出现的频率分别为0.07, 0.19, 0.02, 0.06, 0.32, 0.03, 0.21, 0.10。试为这8个字母设计哈夫曼编码。 5.画出下列森林对应的二叉树。 1 2 3 4 5 6 7 8 9 10 11 12 13 14

作业(续) 6.画出和下列二叉树相应的森林 A A A A A (1) (2) (3) (4) (5) 7.画出和下列已知序列对应的森林F: (1) (2) (3) (4) (5) 7.画出和下列已知序列对应的森林F: 森林的先序次序访问序列为:ABCDEFGHIJKL 森林的中序次序访问序列为:CBEFDGAJIKLH A A A A A B B B C B C C C D

作业(续) 8. 一棵完全 k 叉树按层次顺序从 1 开始对全部结点编号,若所求结点存在,则: ① 编号为 n 的结点的父结点的编号是多少? ② 结点 n 的第 i 个儿子的编号是多少? ③ 结点 n 有右兄弟的条件是什么? 9. 设计算法将二叉树中所有节点的左右孩子互换。 10. 设计算法拷贝二叉树。 11. 设计算法判断一个二叉树是否是完全二叉树。

二叉树上机作业 a b e c d (1) 建立二叉树:两种方法 ①用先序递归过程建立二叉树 (存储结构:二叉链表) 输入数据按前序遍历所得序列输入,当某结点左子树或右子树为空时,输入‘*’号,如输入abc**d**e**得到的二叉树为: a b e c d ②由二叉树的层次序列和中序序列建立一棵二叉树。 对上例:输入abecd和cbdae两个字符串 (2) 编写递归算法,先序,后序和后序输出二叉树 (3) 计算二叉树中叶子结点的数目,和每个节点的深度。 (4) 按凹入表方式输出该二叉树。 (5) 按层次遍历的方法输出该二叉树,求出每层的结点个数。

测试实例 应当设计多个测试实例,检验程序的正确性。下边是一个例子。 A B C D E F