四. 树

note:
先理解思想, 再理解代码;
如下只是最基本和核心的;


树的定义

Tree是n个结点的有限集合, 非空树遵循:
(1) 有且仅有一个特定的称为根的结点;
(2) 当n>1时, 其余结点可分为m个互不相交的有限集合T1, T2, ..., Tm, 其中每个集合本身又是一棵树, 并且称为根的子树(SubTree).

  • 从定义不难看出, 树的结构先天给人以递归的方便;

树的各个名词

  • 度: 结点拥有的子树的数目叫作"度"(degree);
    • 度为0的节点就是叶子, 度非0的结点是内结点;
  • 父节点, 祖先节点, 孩子节点, 子孙节点;
  • 兄弟节点;
  • 层次是从根(第一层)往下算的层数, 也叫深度;
  • 高度是从最下层开始往上算;
  • 有序树: 如果将节点看成从左向右有次序的, 那么该树就是有序的, 否则就是无序的;
  • 森林: m棵互相不相交的树的集合;

二叉树

  • 只有左子树和右子树, 而且有序;
  • 完全二叉树: 除了最后一层, 其他都满;
  • 满二叉树: 完全二叉树+最后一层也满;

二叉树的特性

  • 性质1: 二叉树的第i层最多有2^(i-1)个节点;
  • 性质2: 深度为k的二叉树最多有(2^k)-1个节点;
    • 有n个节点的二叉树高度是floor(lgn)+1; //约定最下层的高度为1;
  • 性质3: n0 = n2+1 //度为0的节点数n0, 度为2的节点数为n2;

证明: 利用总数和, 出入相等的关系来证明.
和等式: 二叉树中, N = n0 + n1 + n2, 不存在任何其他度(因为二叉树最大的度就是2);
出入等式: N-1(入) = n1+2*n2;
和等式代入出入等式, 则n0 = n2+1;

  • 性质4: 某下标为i的非根节点的父节点是floor(i/2), 左子节点是2i, 右子节点是2i+1;
    • 比如4, 5的父节点是2; 4的左子节点是8, 右子节点是9;

二叉树的存储结构

顺序存储(顺序表)

/*顺序表存储树*/
//其中parent是也可以去掉的属性;
typedef struct TreeNode{
    ElemType data;
    int lchild, rchild, parent;
}

typedef struct BinaryTree{
    TreeNode[] tree;
    int root;
}

链式存储(链表) 比较推荐

/*三叉链表*/
//如果去掉parent, 那么就是二叉链表;
typedef struct TreeNode {
    ElemType data;
    struct TreeNode *lchild, *rchild, *parent;
}

typedef struct BinaryTree{
    TreeNode *root;
}

二叉树的遍历

深度优先

1. 先序

定义: 上左右

/*先序遍历*/
void Traverse( TreeNode T[], index ) {
if (index!=-1) 
    visit(Tree[index]); 
    Traverse( T, Tree[index].lchild]);
    Traverse( T, Tree[index].rchild);  
}

2. 中序

定义: 左上右

/*中序遍历*/
void Traverse( TreeNode T[], index ) {
if (index!=-1) 
    Traverse( T, Tree[index].lchild]);
    visit(Tree[index]);
    Traverse( T, Tree[index].rchild);  
}
/*中序不递归*/
void Traverse(BiTree T, visit) {
    Stack s = new Stack();
    while (T!=null || s.isEmpty==false) {
        s.push(T); 
        T=T.left;
    } else {
        T = s.pop();
        visit(T);
        T = T.right;
    }
}

中序迭代 Python

class Solution:
    def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        ans = []
        def traverse(root):
            S = []
            node = root
            while node or S:
                if node:
                    S.append(node)
                    node = node.left
                else:
                    node = S.pop()
                    ans.append(node.val)
                    node = node.right

        traverse(root)
        return ans

3. 后序

定义: 左右上

/*后序遍历*/
void Traverse( TreeNode T[], index ) {
if (index!=-1) 
    Traverse( T, Tree[index].lchild]);
    visit(Tree[index]);
    Traverse( T, Tree[index].rchild);  
}

后序迭代法 python

class Solution:
def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        ans = []
        def traverse(root):
            S = []
            node = root
            prev = None
            while node or len(S) != 0:
                if node:
                    S.append(node)
                    node = node.left
                else:
                    node = S.pop()
                    if not node.right or prev == node.right:  # visited or empty
                        ans.append(node.val)
                        prev = node
                        node = None
                    else: # visit right
                        S.append(node)
                        node = node.right

        traverse(root)
        return ans

广度优先

  • 广度优先就是要求从上到下, 从左到右, 在本层全部遍历后才往下一层;
void Traverse( TreeNode T[SIZE], index) {
      for i = index ~ SIZE
          visit T[i];
}

二叉树的线索化

对于链表表示的二叉树, 我们希望能获得每个节点在遍历顺序中的前驱和后继节点, 这就是二叉树的线索化问题.

  • 解决思路:
    (1) 直觉上, 直接加上fwd和bkwd指针分别指向遍历中的前驱和后继就行了;
    (2) 为了优化数据结构所占用的存储空间, 我们可以利用二叉链表表示二叉树的过程中有n+1个空指针的现象, 加以利用;
    (3) 我们把fwd和bkwd指针替换成一个只需要占1位bit的变量LTag和RTag, 用来指示指针lchild和rchild指针是否为空;
typdef struct TreeNode{
    ElemType data;
    struct TreeNode *lchild, *rchild;
    pointerTag LTag, RTag;
}

普通的树和森林

普通树的存储结构

1. 父亲表示法

typedef struct {
    ElemType data;
    int parent;
}TreeNode;

/*整棵树*/
typdef struct {
    TreeNode nodes[MAX_TREE_SIZE];
    int r, n;   //root的index和节点数目n; 
}Tree;

2. 孩子表示法

  • 孩子表示法比较费解一些, 预计使用频率不会很高
  • 只要让每个结点都存储其孩子的位置信息, 那么就是孩子表示法; 此处由于普通树的孩子数目不像二叉树那样确定, 最好使用链表来存储孩子节点位置信息;
  • 结构是: Tree -- TreeNode -- ChildInfo
/*孩子位置链表的结点*/
typedef struct {
    int childIndex;
    struct ChildInfo *next; 
}ChildInfo ,*ChildList;
/*树中的结点*/
typedef struct {
    ElemType data;
    ChildList childs;
} TreeNode;
/*整棵树*/
typdef struct {
    TreeNode nodes[MAX_TREE_SIZE];
    int r, n;   //root的index和节点数目n; 
}Tree;

3. 孩子兄弟表示法

  • 实际上已经把树转化为了二叉树;
/*Node*/
typedef struct {
    ElemType data;
    int firstChild, nextSibling;
} TreeNode;
/*整棵树Tree*/
typdef struct {
    TreeNode nodes[MAX_TREE_SIZE];
    int r, n;   //root的index和节点数目n; 
} Tree;

树和森林与二叉树之间的转化

1. 普通树和二叉树的相互转化(基本要求)

使用孩子兄弟法: 定义二叉树T的左子节点T->lchild是first-child, 这个左子节点的右子节点T->lchild->rchild是它的next-sibling, 而这个左子节点的左子节点则是它自己的first-child, 如此递归定义, 从而实现普通的树结构转化成二叉树;
note: 转化成的二叉树的根节点肯定没有右子节点!

2. 普通森林转化成二叉树(基本要求)

note: 这次, 跟上面不同, 二叉树的根节点有右子节点, 而且是代表森林中不同的树的根节点;

普通树和森林的遍历

1. 普通树的遍历

先根遍历普通树:
先访问根结点, 然后遍历子树
==> 如果转化为二叉树的话, 映射为先序; //画图容易看出;

后根遍历普通树:
先遍历子树, 然后才访问根结点
==> 如果转化为二叉树的话, 映射为中序; //画图容易看出;

2. 森林的遍历

  • 先序遍历森林(类似先根) ==> 二叉树的先序:
    (1) 先访问森林中第一棵树的根节点
    (2) 先序访问根节点的子树;
    (3) 先序访问其他树组成的森林;

  • 中序遍历森林(类似后根) ==> 二叉树的中序:
    (1) 中序访问森林中第一棵树的根节点的子树;
    (2) 访问第一棵树的根节点;
    (3) 中序访问其他树组成的森林;

哈夫曼编码(Huffman coding)

node BuildHuffmanTree(C[]) {
n = C.length;
Q is a Minimum Priority built from C;
for ( i = 1~n-1)  //到第n-1次的操作后, Q中应该只有一个元素
    node z is a new node;
    z.lchild = x = Q.extractMin();
    z.rchild = y = Q.extractMin();
    z.weight = x.weight+y.weight;
    Q.insert(z);
return Q.extractMin();  //return the root of the tree;
}

Encode(C, root) {
for (i = 1~C.length) {
    j = i;
    if (C[j].parent != null) {
         if (C is C.parent.lchild) {C[i].code = 0 + C[i].code}
         if (C is C.parent.rchild) {C[i].code = 1 + C[i].code}
    }
    j = C[j].parent;
}
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容

  • 四、树与二叉树 1. 二叉树的顺序存储结构 二叉树的顺序存储就是用数组存储二叉树。二叉树的每个结点在顺序存储中都有...
    MinoyJet阅读 1,499评论 0 7
  • 前面讲到的顺序表、栈和队列都是一对一的线性结构,这节讲一对多的线性结构——树。「一对多」就是指一个元素只能有一个前...
    Alent阅读 2,196评论 1 28
  • 前言 总括: 本文讲解了数据结构中的[树]的概念,尽可能通俗易懂的解释树这种数据结构的概念,使用javascrip...
    秦至阅读 797评论 0 6
  • 目录 1、什么是树 2、相关术语 3、二叉树 3.1、二叉树的类型 3.2、二叉树的性质 3.3、二叉树的结构 3...
    我哈啊哈啊哈阅读 2,530评论 0 10
  • 前面说的链表、栈、队列都是线性结构,而树是一个非线性节点。 树简介 树是一种非线性结构,由一个根节点和若干个子节点...
    一叶障目阅读 358评论 0 0