5 > 数据结构与算法 树与二叉树

  1. 从根节点发展到分支节点,再发展到叶子节点。
  2. 空树:结点数为 0 。
  3. 非空树:有且仅有一个根节点。(只有分支节点有前驱和后继;每个分支节点和叶子结点有且仅有一个前驱)
  4. 子树:根节点下的子树一定互不相交。
  5. 节点关系:祖先节点、子孙节点、双亲(父)节点(族谱)、兄弟节点、孩子节点。
  6. 节点路径:只能从上往下。(单向)
  7. 树的路径长度:从根节点到每个叶子节点的路径长度之和。
  8. 属性:节点的层次(从上往下数)、节点的高度(深度)(从下往上数)、节点的度(有几个分支)、树的度(max(节点的度),树最宽的节点)
  9. 有序树:各子树从左到右有次序,不能互换。
  10. 无序树:各子树从左到右无次序,能互换。
  11. 森林:由 m(m≥0) 棵互不相交的树的集合。
  12. 性质:
    1. 节点数=总度数+1(总度数:总共有多少条分支(边);节点数:全树有多少节点)(总的节点的度+1)

    2. 度为 m 的树、m 叉树的区别:

      度为 m 的树m 叉树
      任意节点的度 ≤ m任意节点的度 ≤ m
      至少有一个节点度 = m允许所有节点的度都 < m
      一定是非空树,至少有 m+1 个节点可以是空树
      度为 m 的树中至少有一个节点有 m 个子节点m 叉树每个节点最多有 m 个子节点
    3. 度为 m 的树第 i 层最多只有   m i − 1   ~m^{i-1}~  mi1 个节点。

    4. 高度为 h 的 m 叉树最多有   m h − 1 m − 1   ~\frac{m^h-1}{m-1}~  m1mh1 个节点。

      m 0 + m 1 + m 2 + . . . + m h − 1 = a 1 ( 1 − q n ) 1 − q = 1 ∗ ( 1 − m h ) 1 − m = m h − 1 m − 1 m^0+m^1+m^2+...+m^{h-1}=\frac{a_1(1-q^n)}{1-q}=\frac{1*(1-m^h)}{1-m}=\frac{m^h-1}{m-1} m0+m1+m2+...+mh1=1qa1(1qn)=1m1(1mh)=m1mh1

    5. 高度为 h 的 m 叉树至少有 h h h 个节点。(节点 ≠ 子节点(分支数))

      高度为 h 、度为 m 的树至少有 h + m − 1 h+m-1 h+m1 个节点。

    6. 有 n 个节点的 m 叉树的最小高度为 ⌈ log ⁡ m ( n ( m − 1 ) + 1 ) ⌉ \lceil\log_m(n(m-1)+1)\rceil logm(n(m1)+1)⌉

      m h − 1 − 1 m − 1 ≤ n ≤ m h − 1 m − 1 = > m h − 1 ≤ n ( m − 1 ) + 1 ≤ m h = > h − 1 ≤ log ⁡ m ( n ( m − 1 ) + 1 ) ≤ h \frac{m^{h-1}-1}{m-1}\le n \le\frac{m^h-1}{m-1}=>m^{h-1}\le n(m-1)+1\le m^h\\=>h-1\le\log_m(n(m-1)+1)\le h m1mh11nm1mh1=>mh1n(m1)+1mh=>h1logm(n(m1)+1)h

二叉树

  1. 概念:要么为空树,要么为二叉树。左右子树不能颠倒,是有序树。(空二叉树:节点数为0)
  2. 特殊二叉树:
    1. 满二叉树:除开叶子节点,其他都满了。
      1. 只有最后一层有叶子节点,
      2. 如果根为 1 ,则第 i i i 层的左分支为 2 i 2i 2i,右分支为 2 i + 1 2i+1 2i+1;父节点为 ⌊ i / 2 ⌋ \lfloor i/2\rfloor i/2
    2. 完全二叉树:当且仅当其中每个节点都与高度为 h 的满二叉树中编号为 1~n 的节点一 一对应。
      1. 最后两层才会出现叶子节点,
      2. 至多只有一个度(分支)为 1 的节点。(多一个编号就不是一 一对应)
      3. 如果根为 1 ,则第 i i i 层的左分支为 2 i 2i 2i,右分支为 2 i + 1 2i+1 2i+1;父节点为 ⌊ i / 2 ⌋ \lfloor i/2\rfloor i/2
      4. 编号 ≤ ⌊ n / 2 ⌋ \le\lfloor n/2\rfloor n/2 为分支节点,编号 > ⌊ n / 2 ⌋ >\lfloor n/2\rfloor >n/2 为叶子节点。
      5. 如果某个节点只有一个分支,则其一定是左分支。(否则不会编号一 一对应)
      6. 若完全二叉树有偶数个节点,则必有一个单分支节点,其编号为 ⌊ n / 2 ⌋ \lfloor n/2\rfloor n/2
      7. 若完全二叉树有奇数个节点,则没有单分支节点。
    3. 二叉排序树:(二叉搜索树)
      1. 所有分支节点的左子树上的关键字均小于根节点的关键字,右子树的均大于。
      2. 适用于元素的排序、搜索等。
    4. 平衡二叉树:(特殊二叉排序树)
      1. 所有节点的左子树和右子树的深度差   ∈ { 0 , 1 } ~\in\{0,1\}  {0,1}
      2. 有更高的搜索效率。
  3. 性质:
    1. 非空二叉树中叶子节点比二分支节点多 1 个。 ( n = n 0 + n 1 + n 2 = 2 n 0 + n 1 − 1 n = n_0 + n_1 + n_2=2n_0+n_1-1 n=n0+n1+n2=2n0+n11

    2. 高度为 h 的二叉树最少有 2 h − 1 2^{h-1} 2h1个节点。

    3. 高度为 h 的二叉树最多有 2 h − 1 2^h-1 2h1 个节点。(满二叉树)

    4. 有 n 个节点的完全二叉树高度 h 为   ⌈ log ⁡ 2 ( n + 1 ) ⌉ 或 ⌊ log ⁡ 2 n ⌋ + 1 ~\lceil\log_2(n+1)\rceil或\lfloor\log_2n\rfloor+1  log2(n+1)⌉log2n+1

      2 h − 1 − 1 < n ≤ 2 h − 1 = > h − 1 < log ⁡ 2 ( n + 1 ) ≤ h = > h = ⌈ log ⁡ 2 ( n + 1 ) ⌉ 2 h − 1 ≤ n < 2 h = > h − 1 ≤ log ⁡ 2 n < h = > h = ⌊ log ⁡ 2 n ⌋ + 1 2^{h-1}-1<n\le2^h-1=>h-1<\log_2(n+1)\le h=>h=\lceil\log_2(n+1)\rceil\\2^{h-1}\le n<2^h=>h-1\le\log_2n<h=>h=\lfloor\log_2n\rfloor+1 2h11<n2h1=>h1<log2(n+1)h=>h=log2(n+1)⌉2h1n<2h=>h1log2n<h=>h=log2n+1

    5. 第 i 个节点所在层次为   ⌈ log ⁡ 2 ( i + 1 ) ⌉ 或 ⌊ log ⁡ 2 i ⌋ + 1 ~\lceil\log_2(i+1)\rceil或\lfloor\log_2i\rfloor+1  log2(i+1)⌉log2i+1

  4. 存储结构:
    1. 顺序存储:在顺序存储中一定要将二叉树编号和完全二叉树编号一一对应。
      1. 适用范围:只适用存储完全二叉树。(适用于完全二叉树性质)
    2. 链式存储:
      1. n 个节点的 m 叉链表总共有   m n − ( n − 1 )   ~mn-(n-1)~  mn(n1) 个空指针(空链域)。
  5. 二叉树遍历:
    1. 层次遍历:基于树的层次特性确定的次序规则。

    2. 先序遍历:根左右(NLR)(波兰式)

    3. 中序遍历:左根右(LNR)(常规式)

    4. 后序遍历:左右根(LRN)(逆波兰式)

    5. 递归实现:

      void perorder(myerca *temp){    //空间复杂度O(h+1)=O(h)
      	if (temp!=NULL){
      		visit(temp);
      		perorder(temp->left);
      		perorder(temp->right);
      	}
      }
      
    6. 层序遍历:采用队列。从根开始,节点依次入队,若节点为非空则出队一个元素,并把它的左右孩子依次入队,然后下一轮。

       void cengxu(BiTree s){
          Queue *q;
          InitQueue(&q);
          InsertQueue(q,&s);      //辅助队列
          BiTNode *a;       //临时变量
          while (q->next!=NULL){      //辅助队列不为空
              Output(q,&a);
              if (a==NULL){       //最后一次出队列 a=NULL
                  break;
              }
              printf("%d  ",a->data);
              if (s->lchild!=NULL){
                  InsertQueue(q,&a->lchild);         //左孩子不为空,将其加入队列
              }
              if (s->rchild!=NULL){
                  InsertQueue(q,&a->rchild);        //右孩子不为空,将其加入队列
              }
          }
      }
      
    7. 前序(后序)(层序) + 中序遍历序列唯一确定一棵二叉树。(先找到根节点再划分左右子树)(先序和后序不能确定)(根据前序、后序、层序依次套入中序中确定)

      1. 根据序列求二叉树的个数:卡特兰数: 1 n + 1 C 2 n n \frac{1}{n+1}C_{2n}^n n+11C2nn,唯一 一个中序确定一个二叉树
    8. 线索二叉树:(物理结构)

      1. 在普通二叉树中查找线性前驱只能再次进行前中后遍历。

      2. 线索:

        1. 由左孩子指针充当前驱,
        2. 由右孩子指针充当后继。
      3. 结构体:

        typedef struct xiansuo{
        		DATATYPE data;
        		struct xiansuo *left,*right;
        		int ltag,rtag;       //tag=1则表示指针是线索,tag=0表示指针指向孩子
        }myxiansuo;
        
      4. 线索化:(递归版)

        void threadtree(zizen t){       //线索化"主函数"
            pre=NULL;
            if (t!=NULL){
                traversetreem(t);
                if (pre->right==NULL)     //处理最后一个节点元素
                    pre->rtag=1;
            }
        }
        
        void printtree(zizen *t){       //中序遍历式线索化
            if ((*t)->left==NULL){          //左分支为空,则将左指针指向pre的地址
                (*t)->left=pre;
                (*t)->ltag=1;       //设置线索标记
            }
            if(pre->right==NULL && pre!=NULL){         //pre右分支为空且pre不为NULL,则将pre指向后继p
                pre->right = *t;
                pre->rtag=1;
            }
            pre=*t;     //pre前移到p处
        }
        
        void traversetreem(zizen t){     //中序递归遍历
            if (t!=NULL){
                traversetreem(t->left);
                printtree(&t);
                traversetreem(t->right);
        //        printtree(&t);          //后序递归遍历
            }
        }
        
        void traversetreel(zizen t){     //前序递归遍历
            if (t!=NULL){
                printtree(&t);
                if (pre->ltag!=1)       //防止前序转圈访问
                    traversetreel(t->left);
                traversetreel(t->right);
            }
        }
        
      5. 线索二叉树前趋、后继查找:

        **//前趋查找:**
        mytree * firstnodem(zizen *t){       //中序查找
        		if (t->ltag==0){
        				t=t->left;
        				while (t->rtag==0){
        						t=t->right;
        				}
        				return t;
        		}else {
        				return t->left;
        		}
        }
        
        mytree * firstnodef(zizen *t){          //先序查找
        		if (t->ltag==1){
        				return t->left;
        		}else{
        				//遍历 或 用三叉链表
        		}
        }
        
        mytree * firstnodel(zizen *t){       //后序查找
        		if (rtag==1){
        				return t->left;
        		}
        		if (rtag==0){
        				return t->right;
        		}
        }
        
        **//后继查找:**
        mytree * lastnodem(zizen *t){          //中序查找
        		if (t->rtag==0){
        				t=t->right;
        				while (t->ltag==0){
        						t=t->left;
        				}
        				return t;
        		}else {
        				return t->right;
        		}
        }
        
        mytree * lastnodef(zizen *t){          //先序查找
        		if (t->ltag==0){
        				return t->left;
        		}
        		else{
        				return t->right;
        		}
        }
        
        mytree * firstnodel(zizen *t){           //后序查找
        		if (t->rtag==1){
        				return t->right;
        		}
        		if (t->rtag==0){
        				//遍历 或 用三叉链表
        		}
        }
        
        线索二叉树前序中序后序
        找前趋×
        找后继×
      6. 只有后序线索化的树的遍历需要栈的支持。

树的存储结构

  1. 双亲表示法:在每个节点中存储数据和指向父节点的指针。(顺序存储)
    1. 找爹方便,找孩子不方便
    2. 除开根的所有节点的指针都指向父节点
    3. 增加数据方便
    4. 删除分支节点不方便
  2. 孩子表示法:在每个节点中存储数据和指向它儿子们的指针。(顺序+链式存储)
    1. 找孩子方面,找爹不方便
      在这里插入图片描述
  3. 孩子兄弟表示法:每个节点中存储数据、指向孩子的指针(左)、指向兄弟的指针(右)。(链式存储)(二叉树和树的转换)(表示方式唯一)
    在这里插入图片描述
  4. 森林和二叉树的转换:用兄弟孩子表示法,同级的都是兄弟。
  5. 遍历:
    1. 先根遍历(深度优先):树为非空,先访问根,在递归访问子树。(二叉树先序)

      void printtree(zizen *t){
      		if (!t){
      				visit(t);
      				while (t还有子树){
      						printtree(t);
      				}
      	
      }
      
    2. 后根遍历(深度优先):(二叉树中序)

      void printtree(zizen *t){
      		if (!t){
      				while(t还有子树){
      						printtree(t);
      				}
      				visite(t);
      		}
      }
      
    3. 层次遍历(广度优先):等于二叉树层次

  6. 森林遍历:
    1. 树的先序遍历 = 森林的先序 = 二叉树的先序
    2. 树的后序遍历 = 森林的中序 = 二叉树的中序
    3. 森林的后序 = 二叉树的中序遍历

哈夫曼树

  1. 哈夫曼树:
    1. 节点的权:某种现实意义的数值。

    2. 节点的带权路径长度:由根到节点的路径(经过的边个数)* 节点权值

    3. 树的带权路径长度:所有叶子节点的带权路径长度之和。

      W P L = ∑ i = 1 n w i   l i WPL=\sum^n_{i=1}w_i~l_i WPL=i=1nwi li

    4. 在 n 个带权叶子节点的二叉树中,WPL 值最小的树称为哈夫曼树,也就是最优二叉树。

    5. 哈夫曼树的构造:依次结合最小的权值节点,一路滚雪球。(带权路径最小)(权值越大路径越短)(哈夫曼树不唯一,但WPL相同且最优)(不存在度为 1 的节点(只有一个分支))

    6. 编码时所有分支按照左小右大或右小左大,不要混用。

    7. 应用:

      1. 哈夫曼编码:字符集中每一个字符作为叶子,出现频度作为叶子权值 进行构造。(只能对叶子节点进行编码)(属于变长编码)
      2. 哈夫曼解码:
        在这里插入图片描述
        • 字符的出现频次跟所在的层级没关系。(每次选的是最小的两个频次的)
        • 变长编码(只能将权值节点当做叶子节点处理(防止发生歧义))(若没有一个编码是另一个编码的前缀,则称为前缀编码(不会发生歧义))
          在这里插入图片描述
    • 用途:数据压缩、求最优解
    1. 定长编码:
      1. 所构成的二叉树是满二叉树。
      2. 所有字符(叶子)节点在相同层。(所有字符被赋予相同长度的编码)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值