Redis研究-3.3数据结构之树与查找、排序等(后续)

3.树、二叉树、森林之间的转换 算法

   前面咱们又说到,二叉树中的节点咱们能够表示成一个具备左孩子域、右孩子域、双亲域、自身数据域的一个数据结构,那么对于通常的树或者森林中的节点来讲,能不能也这样子表示呢?答案是能够的,表示成二叉树节点的形式,咱们就能很好的使用二叉树的一些特性和算法。 网络

在二叉树中,left表示节点的左孩子、right表示节点的右孩子,那么,对于通常的树节点来看,若是存在孩子,第一个孩子就是对应的left区域,若是有第二个、第三个孩子等,就用right造成一个链表,那么,这种树就转换为二叉树啦,只是这里两个指针域的说法不太同样而已。实际上,咱们对于节点来讲,咱们能够改进一下获得以下的表示: 数据结构

//修改后的通常的树节点表示
typedef struct gTBiTreeNode{
    struct gTBiTreeNode *left;
    struct gTBiTreeNode *right;
    void *data;
    struct gTBiTreeNode *next;//兄弟节点
}gTBiTreeNode,*gTBiTreeNode;



3.1 树转换为二叉树 学习

  上面已经改造了树节点的表示,那么通常的树怎么转换为咱们常见的二叉树呢?只须要三个步骤便可: 优化

  1).加线。在全部的兄弟节点之间加一条链接线; 编码

  2).去线。对数中的每一个节点,只保留他与第一个孩子节点的链接,删除他与其余孩子节点之间的链接线。 spa

  3).层次调整。以树的根为轴心,将整棵树顺时针旋转必定的角度,使之井井有条。这里要注意的是,第一个孩子是二叉树节点的左孩子,兄弟转换过来的孩子是节点的右孩子。 翻译

咱们用图来表示一下 指针

 

 

 

咱们重点来看看,怎么调整成最后的这个层次了,首先咱们应该清楚第三个步骤调整的原则: code

  第一个孩子是节点的左孩子,那么B固然是A的左孩子啦。根据第二条原则,兄弟转换过来的孩子是节点的右孩子,由于C是B的兄弟,因此,转换过来后,就变成了B的右孩子。一样的,由于E是B的作孩子,因此转换后固然是B的左孩子。一样的,根据第二个原则,F是E的兄弟,因此,转换后,F变成了E的右孩子,G先前是F的兄弟,如今变成了F的右孩子。一样的,咱们的先前的树中C的第一个孩子就是H,因此,如今H固然是C的左孩子,一样的,由于D是C的兄弟,因此如今变成了C的右孩子。I在之前的树上就是D的第一个孩子,因此,如今是D的左孩子,又由于J先前是I的兄弟,因此,如今变成了I的右孩子。

经过上面的文字描述,咱们要特别注意,第二个原则,就是"兄弟孩子变成了节点的右孩子这个说法".

3.2 森林转换为二叉树

什么是森林?森林固然是由不少的树组成的啦。那么,咱们固然能够把其中的每一颗树看作是兄弟,所以,咱们就能够获得下面的转换步骤了。

1).把每棵树转换成一颗二叉树;

2).第一颗二叉树保持不动,从第二棵二叉树开始,依次把后一棵树的根节点做为前一棵二叉树的根节点的右孩子,而后用线链接起来。

用图来演示一下:

 

OK,应该说清楚了,那么,二叉树又怎么转换成树呢?

3.3 二叉树转换成树

前面咱们已经从树转换成二叉树了,他要经历过三个步骤,分别是加线,去线,调整层次,那么,二叉树转换为树,也就是这个过程的一个逆过程,怎么作呢?

任然是

1).加线。若是节点的左孩子存在,则将这个左孩子的右孩子节点、右孩子的右孩子节点、。。。都做为这个节点的孩子节点,将该节点与这些右孩子节点连线。

2).去线。删除原二叉树中全部节点与其右孩子节点的连线。

3).层次调整。

有图有真相。

 

 

so easy,不是么?

3.4 二叉树转换成森林?

   一棵树可否转换成森林,判断的标准很简单,就是看这个二叉树的根节点有没有右孩子节点,若是有,那就能够转换。转换步骤是:

1).从根节点开始,若右孩子存在,则把与右孩子及诶单的链接线删除,分离之后,继续迭代。

2).将每棵分离后的二叉树转换为树便可。

上图

估计是傻瓜也能看得懂了吧???O(∩_∩)O哈哈~

3.5 哈夫曼编码

  我不知道你们在大学时候有没有学过运筹学,运筹学里面很重要的一个分支就是讲动态规划的(哈哈,在下本科就是数学系的哈,当时的运筹学考了67分,低分飘过,不过最高分也就是72啦)。在某些求解最优化问题的算法中,每一个步骤都面临着多种选择,动态规划是这种问题的杀手级算法,可是有时候又会显得有点笨重,因此,在这个时候,咱们须要一种更简单、更高效的算法,贪心算法就是这样一种算法,贪心算法的核心就是在每一步都作出当时看起来最佳的选择,或者叫作局部最优的选择,经过这种选择来获得最后的一个全局最优解。固然,这只是一种但愿,因此,贪心算法并不保证能获得一个最优解。咱们这里就先学习一种贪心算法-哈夫曼编码。

在说这玩意儿以前,先看个咱们现实生活中的例子(这个例子来自《大话数据结构》,请各位参考)。里面就是说,老师在给学生评“不及格”、“及格”、“中等”、“良好”、“优秀”的时候,是根据学生的分数段来进行的,一般状况下,咱们使用下面的一个结构来判断:

int degree(int score){
  if(score<60){
    printf("%s","不及格");
  }else if(score<70){
    printf("%s","及格");
  }else if(score<80){
    printf("%s","中等");
  }else if(score<90){
    printf("%s","良好");
  }else{
    printf("%s","优秀");
  }
}



获得的图化结构是:

当咱们看到在实际的学习生活中,学生的成绩阶段比例是以下所示的时候,咱们就会感到这个算法是大有问题的了


分数 0-59 60-69 70-79 80-89 90-100
比例 5% 15% 40% 30% 10%
,要查看70分以上的学生数据,至少要经过3此比较才能作出判断,那么,怎样来改进呢?
int degree(int score){
  if(score<80){
    if(score<70){
        if(score<60){
           printf("%s","不及格");
        }else{
          printf("%s","及格");
        }
    }else {
        printf("%s","中等");
    }
  }else if(score<90){
      printf("%s","良好");
  }else {
    printf("%s","优秀");
  }
}



经过此次改进之后,70-79之间的分数最多须要两次就能判断了,是否是更优化了呢?二叉树的表示方法以下:

 

 

假如,如今有1000学生,那么没改进以前,须要的判断次数是3150次,而改进后,须要用到的次数是2200次,效果很明显,特别是数据量大的时候。

 为了说清楚接下来的内容,有几个概念须要明确一下:

 

1).从树中一个节点到另一个节点之间的分支构成两个节点之间的路径,路径上的分支数据叫作路径长度(走得通的路径)。

2).树的路径长度是从根到每个节点的路径长度之和。树A的路径是:1+1+2+2+3+3+4+4=20.

3).节点的带权的路径长度是从该节点到树根之间的路径长度与节点上权的乘积。树A中的及格的带权路径是15*2=30;

4).树的带权路径路径是树中全部叶子节点的带权路径长度之和。树A的带权路径是:5*1+15*2+40*3+30*4+10*4=315;

5).带权路径长度WPL最小的二叉树就是哈夫曼树。

 

那么,怎么来构建哈夫曼树呢?遵循如下步骤

1).先把带有全职的叶子节点按照从小到大的顺序来排列成一个有序序列。

2).从这个有序序列中选择较小的两个来构造一个新的二叉树,较小的权值的节点做为新二叉树的左孩子,较大的做为右孩子,新的二叉树的根节点的权值是两个孩子的权值之和。

3).从序列中删除已经选择的两个较小权值的节点,并把步骤2中构造的新二叉树的根节点带到这个序列中排序。

4).重复步骤二、3就能够获得最终的哈夫曼树。

 

咱们从树B来看怎么构造一个哈夫曼树:

 

 

新排序:N=15,B,D,C

 

 

从新排序:N=30,D,C

从新排序:C,N=60

构造后的这颗树的带权路径=40*1+30*2+15*3+10*4+5*4=205;

而原来的这颗树的带权路径是:5*3+15*3+40*2+30*2+10*2=220;

咱们算一下须要多少判断步骤3050次,反而比不是哈夫曼树的算法还低效?那这说明哈夫曼树没用么?不是的。

 

咱们再看一下:

假设我要给你远程发送一段“BADCADFEED”的内容,网络传输中,通常都是用二进制来表示的,在这段文字中出现了A,B,C,D,E,F这6个字符,假设咱们分别以三位二进制来代替一个字母,则能够获得下面的对应表:


A B C D E F
000 001 010 011 100 101

那么,这段内容的编码就是:001000011010000011101100100011,一共是30位。可是咱们发现,在这段内容中,各个字母出现的频率是不同的,他们出现的频率分别是:


A B C D E F
0.2 0.1 0.1 0.3 0.2 0.1

也就是说,咱们能够用哈夫曼树来进行一个构建

 

咱们将权值作分支改成0,右分支改成1:

因此,获得的对应的字母编码映射表是:


A B C D E F
110 11110 11111 0 10 1110

那么,使用这种编码之后,发送内容应该是:111101100111111100111010100,一共是27位。

也就是说,咱们改进后,节约了10的存储或者传输成本。

实际上,咱们从上面来看到,这其实就是一种变长的编码,核心思想就是将高频的字符用最短的码字来表示,低频的用长的码字来表示。

其实咱们还能够获得,咱们构造的这棵二叉树,是一颗满二叉树。

哈夫曼编码的正确性的证实请参考由殷建平、徐云等人翻译的《算法导论-原书第三版》第248页。

二叉搜索树:就是在二叉树的基础上知足一个特性的二叉树就叫作二叉搜索树,这个特性就是对于树上任何节点X,其左子树的关键字不能超过X的关键字,其右子树的的关键字不能小于X的关键字。

红黑树:就是在二叉搜索树的每一个节点上增长了一个颜色属性,这个属性有两个黑色和红色取值。同时要知足下面的特性:

1).每一个节点要么是红色的,要么是黑色的。

2).根节点是黑色的。

3).每一个叶子节点是黑色的;

4).若是一个节点是红色的,则他的两个子节点都是黑色的;

5).对每一个节点,从该节点到其全部的后代叶节点的简单路劲上,均包含相同数目的黑色节点。

 遇到其余的树,再来写,基本上都是在二叉树的基础上加了一些限制

下一节,将继续说排序、查找

欢迎拍砖,同时也能够加QQ:359311095讨论

相关文章
相关标签/搜索