数据结构-树的复习笔记


一、引言:
1. 二分查找
/*二分查找 引出树*/
int BinarySearch(ElementType A[] , int N , ElementType K){
    int left , right , mid ,Notfound ; 
    Notfound = -1 ;
    left = 0 ;
    right = N - 1 ;
    while( left <= right ){
        mid = ( left + right ) / 2;
        if( K > A[mid] )    left = mid + 1 ;//不能直接取mid 否则会陷入死循环
        else if( K > A[mid] )   right = mid - 1 ;
        else return mid ;
    }
    return Notfound ;
}

$T(N) = log_2N $

2. 普通树的儿子兄弟表示法

普通树旋转45°即二叉树

typedef struct TNode *TList;
struct TNode{
    ElementType elem ;
    TList FisrtChild ; //从左边数第一个孩子节点
    TList RightBro ; // 邻接的兄弟节点 
};

二、二叉树的性质和储存方法
1.几种特殊的二叉树

斜二叉树 : 链表
完美/满 二叉树 :除最后一层 ,其他节点都有两个子节点
第i层节点数 : $ 2^{i-1}$
节点总数 = $ 2^N - 1$ (N为高度)
这也是二叉树节点数的上界
完全 二叉树 : 允许最后一层部分缺失

2.重要性质

$ N_i 表示度为i的节点数,i = {0 ,1 , 2}$
公式:$ N_0 = N_2 + 1$
证明: $ N_0 + N_1 + N_2 - 1= 0 * N_0 + 1 * N_1 + 2 * N_2 $
(左边是每个节点向上看的边数 , 右边是向下看的边数)
PS.若给出树的 N 总 N_总 N N 1 N_1 N1,树有可能不存在

3.两种存储方法

数组
完全二叉树 其实就是通常意义的堆
普通二叉树 补全
链表
类似于儿子兄弟表示法


三、二叉树的遍历
0.引言
  1. 其实二叉树的遍历,是为了得到一个包含所有节点的序列,因此其根本是将二维结构线性化,如何对二叉树划线便决定了不同的遍历方法。
  2. 在划线过程中,我们需要注意的是,我们访问一个节点的唯一途径是通过其父节点的的左右指针,因此就会存在一个问题:一旦父节点丢失 , 那么他的左右孩子就再也无法访问到了。
  3. 因此我们的应对是 , 如果某个节点的左右孩子没被访问到 , 我们先将它储存起来,到下一次该访问它的孩子时,再取出来。
  4. 用以储存的数据结构可以是栈或者队列 , 注意:保存节点的方式便决定了划出来的线是怎样的,根据栈和队列各自的特性,对应的线其实就是回溯与否。
  5. 前中后序对应栈 , 层序对应队列 ,我们可以很容易地从划线也就是遍历的路径得出
1.递归

以中序遍历的代码为例

void InorderTraversal( BinTree BT )
{
    if( BT ) {
        InorderTraversal( BT->Left );
        /* 此处假设对BT结点的访问就是打印数据 */
        printf("%d ", BT->Data); /* 假设数据为整型 */
        InorderTraversal( BT->Right );
    }
}

Tips :
三种遍历访问节点的路径完全一样,都是对于每棵树递归地使用左边-内部-右边
在访问过程中,每个节点会被遇到三次
先、中、后分别对应第1、2、3次遇到访问时print

2.非递归(栈)
中序遍历(特殊的DFS)
void InorderTraversal( Bintree BT ){
    Bintree T = BT ;
    Stack S = CreateStack(Maxsize) ;
    while( T || S){
        while( T ){
            Push( S , T ) ;//第一次遇到
            T = T->Left ; 
        }
        if( S ){
            T = Pop( S ) ;//第二次遇到
            Printf( T ) ;//对于中序 第二次遇到时print
            T = T->Right ;//这时候根和左孩子已经print完毕 WhichMeans 前两次访问结束
            //对右孩子进行中序遍历 Means 会对根和左孩子第三次访问
        }
    }
}

在这样的代码中,第一次访问和第二次访问是显式的,第三次访问藏在对右孩子的中序遍历中

先序遍历

因此我们只需要将print操作放在Push操作也就是第一次访问之后

后序遍历

一种思路 : 因为先序遍历是根左右 , 我们可以交换T->Left and T->Right的位置
得到根右左 , 再将其倒置即可得到后序遍历
具体做法是 每次Pop(S)操作时,将其压入新的栈 ,最后将新栈依次Pop出来

3.层序遍历(特殊的BFS)
void LevelTraversal( BinTree BT){
    if( !BT )   return ;
    BinTree T = BT;
    Queue Q = CreateQueue( Maxsize );
    InQueue( Q , T );
    while( Q ){
        T = DeQueue( Q );
        Print( T );
        if( T->Left )   InQueue( Q , T->Left );
        if( T-Right )   InQueue( Q , T->Right );        
    }
}

PS.我们如果保持算法风格不变,单纯将队列改为栈,即InQueue()DeQueue()改为Push()Pop(),那么同样可以做到根左右和根右左,区别仍然是Print()的位置

4.应用
求树的高度

公式 : H = m a x ( H L , H R ) + 1 H = max(H_L , H_R) + 1 H=max(HL,HR)+1
思路: 采用后序遍历的递归形式
代码 :

int GetTreeHeight( Bitree BT ){
    int HL , HR , MaxH ;
    if(BT){
        HL = GetTreeHeight( BT->Left ) ;
        HR = GetTreeHeight( BT->Right ) ;
        MaxH = ( HL > HR ) ? HL : HR ;
        return MaxH+1 ; 
    }
    else return 0;
}
由先、中序遍历推导二叉树

先序遍历第一个为根 , 对应在中序遍历中找出根 ,那么根的左边为左子树 ,对应的在先序中找出左子树
递归进行下去

同构判断

四、 二叉查找树
0.概念
for each root 
Root > Left && Root < Right

2.因此while 和 递归 是BST常用操作

1.Find()

思路 : 和二分查找一样 ,只不过用判断左右子树来代替Left和Right ;
递归和循环都可以 , 不过由于这个地方用到的是“尾递归”,效率不高 , 因此用循环
代码 :

Positon InterFind( ElementType X , BinTree BST){
    while( BST ){
        if( X > BST->Data )     BST = BST->Right ;
        else if( X < BST->Data )    BST = BST->Left ;
        else return BST;    
    }
    return NULL;
}
2.Insert()

思路 : 利用Find()操作 ,只不过循环的判定条件是 左/右节点是否空 ;
同时 Create()操作即是对每个元素执行插入操作
代码 :

/*
递归
*/
BinTree Insert( BinTree BST, ElementType X )
{
    if( !BST ){ /* 若原树为空,生成并返回一个结点的二叉搜索树 */
        BST = (BinTree)malloc(sizeof(struct TNode));
        BST->Data = X;
        BST->Left = BST->Right = NULL;
    }//真正的插入在这儿执行

    else { /* 开始找要插入元素的位置 */
        if( X < BST->Data )
            BST->Left = Insert( BST->Left, X );   /*递归插入左子树*/
        else  if( X > BST->Data )
            BST->Right = Insert( BST->Right, X ); /*递归插入右子树*/
        /* else X已经存在,什么都不做 */
    }
    return BST;
}
/*
while
*/
void Insert( ElementType X , Bintree BT ){
    Bintree T = BT ;
    while( 1 ){
        if( !T ){
            T->elem = X ;
            T-Left = NULL ;
            T->Right = NULL ;
            break ;
        }
        if( X > T->elem )   T = T->Right ;
        else if( X < T->elem )  T = T->Left ;
        else break ;
    }
}
3. Delete()

思路 :
代码 :


五、平衡二叉树
0. 引言
目的 :

构建二叉搜索树时 尽可能减少AFL/因此对于任何平衡操作 ,首先确保查找性不被改变

定义 :

对于任一节点 $|BF(T)| <= 1 其中BF(T) = H_L -H_R $

性质 :

对于N个节点的平衡二叉树 , 它的最大高度是多少 ?/对于H高度,最少要多少个节点?
$ N_H = N_{H-1} + N_{H-2} + 1 $
由斐波拉契数列可推导出 N = O ( l o g 2 N ) N = O(log_2 N) N=O(log2N)

1. 实现
思路

LL、RR、LR、RL 判断依据是寻找“破坏节点”和“发现节点”的关系

代码
typedef struct AVLNode *Position;
typedef Position AVLTree; /* AVL树类型 */
struct AVLNode{
    ElementType Data; /* 结点数据 */
    AVLTree Left;     /* 指向左子树 */
    AVLTree Right;    /* 指向右子树 */
    int Height;       /* 树高 */
};
 
int Max ( int a, int b )
{
    return a > b ? a : b;
}
 
AVLTree SingleLeftRotation ( AVLTree A )
{ /* 注意:A必须有一个左子结点B */
  /* 将A与B做左单旋,更新A与B的高度,返回新的根结点B */     
 
    AVLTree B = A->Left;
    A->Left = B->Right;
    B->Right = A;
    A->Height = Max( GetHeight(A->Left), GetHeight(A->Right) ) + 1;
    B->Height = Max( GetHeight(B->Left), A->Height ) + 1;
  
    return B;
}
 
AVLTree DoubleLeftRightRotation ( AVLTree A )
{ /* 注意:A必须有一个左子结点B,且B必须有一个右子结点C */
  /* 将A、B与C做两次单旋,返回新的根结点C */
     
    /* 将B与C做右单旋,C被返回 */
    A->Left = SingleRightRotation(A->Left);
    /* 将A与C做左单旋,C被返回 */
    return SingleLeftRotation(A);
}
 
/*************************************/
/* 对称的右单旋与右-左双旋请自己实现 */
/*************************************/
 
AVLTree Insert( AVLTree T, ElementType X )
{ /* 将X插入AVL树T中,并且返回调整后的AVL树 */
    if ( !T ) { /* 若插入空树,则新建包含一个结点的树 */
        T = (AVLTree)malloc(sizeof(struct AVLNode));
        T->Data = X;
        T->Height = 0;
        T->Left = T->Right = NULL;
    } /* if (插入空树) 结束 */
 
    else if ( X < T->Data ) {
        /* 插入T的左子树 */
        T->Left = Insert( T->Left, X);
        /* 如果需要左旋 */
        if ( GetHeight(T->Left)-GetHeight(T->Right) == 2 )
            if ( X < T->Left->Data ) 
               T = SingleLeftRotation(T);      /* 左单旋 */
            else 
               T = DoubleLeftRightRotation(T); /* 左-右双旋 */
    } /* else if (插入左子树) 结束 */
     
    else if ( X > T->Data ) {
        /* 插入T的右子树 */
        T->Right = Insert( T->Right, X );
        /* 如果需要右旋 */
        if ( GetHeight(T->Left)-GetHeight(T->Right) == -2 )
            if ( X > T->Right->Data ) 
               T = SingleRightRotation(T);     /* 右单旋 */
            else 
               T = DoubleRightLeftRotation(T); /* 右-左双旋 */
    } /* else if (插入右子树) 结束 */
 
    /* else X == T->Data,无须插入 */
 
    /* 别忘了更新树高 */
    T->Height = Max( GetHeight(T->Left), GetHeight(T->Right) ) + 1;
     
    return T;
}

六、堆(优先队列)

七、HuffmanTree and Huffman coding
0.引言
1. 目的 :

解决等长编码效率不高的问题

2. 方法 :

根据数据出现概率 , 使用不等长编码

1.HuffmanTree的定义

$ WPL(带权路径长度) = Σ {W_kL_k} $
k为叶子节点的个数 , W为权重(概率) ,L为该叶子节点到根节点路径长(经过的边)
HuffmanTree 即为 WPL 最小的 二叉树

2. 实现
思路

利用最小堆 每次将两个权重最小的树合并 更新权重 循环进行

代码

3. 利用HuffmanTree来编码
思路

将未编码数据按频率构建HuffmanTree,左子树为0,右子树为1,依次编码
这样有两点好处 : 1.Cost最小(也就是WPL)2.无二义性

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值