树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩

感谢每一个能看到最后的过路人,衷心感谢。~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

 

在介绍树的概念之前,先来看一下树的四种遍历方式以此更直观了解树

void InorderTraversal( BinTree BT )
{
    if( BT ) {
        InorderTraversal( BT->Left );
        /* 此处假设对BT结点的访问就是打印数据 */
        printf("%d ", BT->Data); /* 假设数据为整型 */
        InorderTraversal( BT->Right );
    }
}//中序遍历
 
void PreorderTraversal( BinTree BT )
{
    if( BT ) {
        printf("%d ", BT->Data );
        PreorderTraversal( BT->Left );
        PreorderTraversal( BT->Right );
    }
}//先序遍历
 
void PostorderTraversal( BinTree BT )
{
    if( BT ) {
        PostorderTraversal( BT->Left );
        PostorderTraversal( BT->Right );
        printf("%d ", BT->Data);
    }
}//后序遍历
 
void LevelorderTraversal ( BinTree BT )
{ 
    Queue Q; 
    BinTree T;
 
    if ( !BT ) return; /* 若是空树则直接返回 */
     
    Q = CreatQueue(); /* 创建空队列Q */
    AddQ( Q, BT );
    while ( !IsEmpty(Q) ) {
        T = DeleteQ( Q );
        printf("%d ", T->Data); /* 访问取出队列的结点 */
        if ( T->Left )   AddQ( Q, T->Left );
        if ( T->Right )  AddQ( Q, T->Right );
    }
}//层次遍历

 那到底什么是树呢到底

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第1张图片

 

 

在对树有一个直观的了解后我们再来了解下的几个定义以及基本术语

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第2张图片树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第3张图片

 

 

 

 

 

 

 

 树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第4张图片

 为了方便在计算机中表示出树以及应用,我们引入二叉树的概念,

在介绍二叉树的之前呢,我们也一样先直观地看一下二叉树的代码构成

typedef struct TNode *Position;
typedef Position BinTree; /* 二叉树类型 */
struct TNode{ /* 树结点定义 */
    ElementType Data; /* 结点数据 */
    BinTree Left;     /* 指向左子树 */
    BinTree Right;    /* 指向右子树 */
};

为何要引入二叉树,优势在哪,结构又是怎样,我们下面慢慢来揭晓

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第5张图片树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第6张图片

 

具体存储结构如下

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第7张图片

 

 

 

 

从这里我们可以看到二叉树可以以链表的形式把树给串到一块。

 

同时节点的子父关系我们也可以用指针把它们给联系起来。

再来看一下二叉树的详细定义

 树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第8张图片

 

 

 

 

 

在介绍完二叉树之后,我们可以接着来学二叉搜索树

 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第9张图片

 

 

那我们要用二叉搜索树来实现哪些操作呢

 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第10张图片

 

 

 

 

看完这些概括,下面我们来具体看一下这些操作具体是怎么来实现的吧

 

先来看一下查找

 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第11张图片

 

具体的代码实现如下,尾递归和迭代

 

 

 

 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第12张图片树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第13张图片

 

 

我们再来看看实际操作中如何来查找二叉树的最大值和最小值吧

先来看看最大值和最小值在树中充当着一个怎样的角色

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第14张图片

 

 

具体操作的迭代与递归算法

 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第15张图片

 

 

接下来我们再来认识一个新的概念-完全二叉树

 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第16张图片树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第17张图片

 

 

 

 

 在了解二叉树的几个特点之后,我们再来认识一下二叉树的一些性质

 树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第18张图片

 

 

在前面的随笔中,我们有谈到抽象数据类型,接下来

 

我们看看二叉树的抽象数据类型是怎样的吧

 

 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第19张图片

 

 

这其实就是我们一开头看到的几种遍历,我们再来看一下具体的实现过程吧

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第20张图片

 

 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第21张图片树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第22张图片

 

 

 树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第23张图片

 

 树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第24张图片树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第25张图片

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第26张图片

 

 

学完各种遍历,我们再来看几个二叉树遍历的实例

 树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第27张图片

 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第28张图片

 

 

 

 

 树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第29张图片

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第30张图片树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第31张图片

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第32张图片

 

 

接下来我们再来学习一下二叉树的存储结构

 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第33张图片

 

 

一般的二叉树也可以转换成完全二叉树的,如下图所示

但是直接转会造成很多空间上的浪费以至于降低效率

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第34张图片

 

 

我们再来看一个完整过程的利用二叉树解决问题案例-树的同构问题

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第35张图片

 

 

我们该怎么来解读这道题呢

 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第36张图片

 

看完输入样例,我们很自然而然地会思考这个二叉树到底该怎么搭建呢

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第37张图片树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第38张图片

 

 

然后我们下一步就要开始分析整个程序框架的搭建了

 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第39张图片

具体的实现如下

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第40张图片

 

 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第41张图片

 

 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第42张图片

 

 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第43张图片

最后按照逻辑把这些整理到下面就大功告成了

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第44张图片

 

 

怎么样,对树的初步了解感觉如何。

 

了解完树的概念和最基本的操作后

 

接下来,我们会对树更进一步的深入认识

 

 

 

---------------------------------------------------------------------------------------------------------------------------------------------------------

下面我们就来介绍二叉搜索树

那到底什么是二叉搜索树呢

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第45张图片

 

 

二叉搜索树的操作主要包括查找、插入和删除,查找是后两个的基础

 

我们先来看一下代码的直观实现

 

 

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;
}
 
BinTree Delete( BinTree BST, ElementType X ) 
{ 
    Position Tmp; 
 
    if( !BST ) 
        printf("要删除的元素未找到"); 
    else {
        if( X < BST->Data ) 
            BST->Left = Delete( BST->Left, X );   /* 从左子树递归删除 */
        else if( X > BST->Data ) 
            BST->Right = Delete( BST->Right, X ); /* 从右子树递归删除 */
        else { /* BST就是要删除的结点 */
            /* 如果被删除结点有左右两个子结点 */ 
            if( BST->Left && BST->Right ) {
                /* 从右子树中找最小的元素填充删除结点 */
                Tmp = FindMin( BST->Right );
                BST->Data = Tmp->Data;
                /* 从右子树中删除最小元素 */
                BST->Right = Delete( BST->Right, BST->Data );
            }
            else { /* 被删除结点有一个或无子结点 */
                Tmp = BST; 
                if( !BST->Left )       /* 只有右孩子或无子结点 */
                    BST = BST->Right; 
                else                   /* 只有左孩子 */
                    BST = BST->Left;
                free( Tmp );
            }
        }
    }
    return BST;
}

 

我们先一步一步拆解,查找作为后两个的基础当然至关重要

先来看看查找

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第46张图片

 

查找我们可以分为静态查找动态查找 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第47张图片

 

 

静态查找我们又有顺序查找二分查找两种实现方式

 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第48张图片

 

 

 树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第49张图片

 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第50张图片

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第51张图片

 

我们再来看看二分查找的例子

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第52张图片

 

 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第53张图片

 

 

 树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第54张图片

 

 

 

 

了解完二叉搜索树的遍历和查找操作,

我们再来看看它是怎么来实现插入删除操作的吧

 

                         插入

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第55张图片

 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第56张图片

 

 

                                           删除

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第57张图片

 

 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第58张图片

 

 

 

 

 

 最后我们来看一下利用二叉搜索树

来解决判断是否同一搜索树的问题

 

typedef struct TreeNode *Tree;
struct TreeNode{
        int v;
        Tree Left,Right;
        int flag;
};


Tree MakeTree(int N)
{                Tree T;
                  int i,V;

                 scanf(''%d'',&V);
                 T=NewNode(V);
                 for(i=1;i){
                              scanf(''%d'',&V);
                              T=Insert(T,V);
                }
                return T;
}

Tree NewNode(int V)
{         
Tree T=(Tree)malloc(sizeof(struct TreeNode));

       T->v=V;
       T->Left=T->Right=NULL;
       T->flag=0;
         return T;
}

Tree Insert(Tree T,int V)
{
   if(!T)T=NewNode(V);
   else{
      if(V>T->v)
           T->Right=Insert(T->Right,V);
      else
           T->Left=Insert(T->Left,V);
    }
    return T;
}

int check(Tree T,intV)
{
   if(T->flag){
     if(Vv)return check(T->Left,V);
     else if(V>T->v)return check(T->Right,V);
     else return 0;
}
   else{
                if(V==T->v){
                         T->flag=1;
                          return 1; 
                 }
                 else return 0;
      }
}

int Judge(Tree T,int N)
{
             int i,V,flag=0;
           //flag:0代表目前还一致,1代表已经不一致

              scanf(''%d'',&V);
              if(V!=T->v) flag=1;
              else T->flag=1;
              for(i=1;i){
                         scanf(''%d'',&V);
                         if((!flag)&&(!check(T,V)))flag=1;
              }
              if(flag)  return 0;
              else      return 1;
}

void ResetT(Tree T) //清楚T中各节点的flag标记
{
                 if(T->Left)   ResetT(T->Left);
                 if(T->Right) ResetT(T->Right);
                 T->flag=0;
}

void FreeTree(Tree T) //释放T的空间
{
              if(T->Left)   FreeTree(T->Left);
              if(T->Right) FreeTree(T->Right);
              free(T);
}

int main()
{
     int N,L,i;
     Tree  T;
     
   scanf(''%d'',&N);
   while(N){
     scanf(''%d'',&L);
     T=MakeTree(N);
     for(i=0;i){
           if(Judge(T,N))printf(''Yes\n'');
           else printf(''No\n'');
           ResetT(T); //清楚T中标记的flag
     }
     FreeTree(T);
     scanf(''%d'',&N);
     }
     return 0;
}                 

               
           

 

到此二叉搜索树就告一段落啦,你收获了多少呢

 

-----------------------------------------------------------------------------------------------------------------

 

二叉搜索树之后,我们来学习下一个新概念-平衡二叉树

同样我们也是先来同过代码直观地了解下这个AVL平衡二叉树

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;
}

 

我们先通过一个例子来演示一下

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第59张图片

 

 

 

 

那到底什么是平衡二叉树呢,又改怎么判断是还是不是呢

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第60张图片

 

 

思考一个问题:平衡二叉树的高度能达到logN吗

 

(ps:利用斐波那契序列)

 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第61张图片

 

 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第62张图片

 

 

 

 

平衡二叉树的调整(RR旋转为例)插入操作的具体步骤如下

 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第63张图片树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第64张图片

 

 

主要通过旋转来进行平衡二叉树的调整

旋转又可以分为 LL旋转  RR旋转   LR旋转  RL旋转   四种类型

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第65张图片

 

 

 

 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第66张图片

 

 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第67张图片

 

 

 

 

最后平衡二叉树还要注意平衡因子这个点

 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第68张图片

 

 

 

 至此,平衡二叉树我们也告一段落了

 

-------------------------------------------------------------------------

接下来我们介绍一下堆,不过这可不是我们之前所说的堆栈里的堆

为什么这么说呢,我们先来引入几个概念

 

 

一般我们会怎样来实现队列的优先级的呢,下面列举一些常见的方法

 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第69张图片

 

 

 

 

还记不记得我们之前稍微提过一下的完全二叉树的概念了,现在就派上用场了

 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第70张图片

 

 

 

 

好啦好啦,到目前为止我们可算引入堆的概念了(铺垫好像稍稍有点略长 噗)

我们再来认识下最大堆和最小堆吧(最大和最小永远是对难兄难弟呢)

 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第71张图片

 

 

数据结构我们都鼓励用抽象数据类型来表示的

 

堆当然也不会例外  具体如下

 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第72张图片

 

还是老样子,我们先构思以及搭建起框架

 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第73张图片

 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第74张图片

 

 

 

 

 

具体代码实现起来如下

 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第75张图片

 

 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第76张图片

 

 

其中哨兵可是贯穿堆操作的大部分过程喲

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第77张图片

 

细心的小伙伴们会发现,无论是以前还是现在,temp这个媒介

总是来无影去无踪地穿插于我们好多程序中,合理运用媒介是我们一直都得灵活变通的小技巧了

 

 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第78张图片

 实战实战堆堆堆-堆中的路径问题

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第79张图片

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第80张图片

 

这里我们看到著名的哨兵又粉墨登场咯

 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第81张图片

 

 

 

 

 

 

这次我们一反以往,最后再来看看构思完后完整的代码实现

前后比较下哪一种思维模式更适合独一无二的你哦

具体堆的构造和操作代码如下:

typedef struct HNode *Heap; /* 堆的类型定义 */
struct HNode {
    ElementType *Data; /* 存储元素的数组 */
    int Size;          /* 堆中当前元素个数 */
    int Capacity;      /* 堆的最大容量 */
};
typedef Heap MaxHeap; /* 最大堆 */
typedef Heap MinHeap; /* 最小堆 */
 
#define MAXDATA 1000  /* 该值应根据具体情况定义为大于堆中所有可能元素的值 */
 
MaxHeap CreateHeap( int MaxSize )
{ /* 创建容量为MaxSize的空的最大堆 */
 
    MaxHeap H = (MaxHeap)malloc(sizeof(struct HNode));
    H->Data = (ElementType *)malloc((MaxSize+1)*sizeof(ElementType));
    H->Size = 0;
    H->Capacity = MaxSize;
    H->Data[0] = MAXDATA; /* 定义"哨兵"为大于堆中所有可能元素的值*/
 
    return H;
}
 
bool IsFull( MaxHeap H )
{
    return (H->Size == H->Capacity);
}
 
bool Insert( MaxHeap H, ElementType X )
{ /* 将元素X插入最大堆H,其中H->Data[0]已经定义为哨兵 */
    int i;
  
    if ( IsFull(H) ) { 
        printf("最大堆已满");
        return false;
    }
    i = ++H->Size; /* i指向插入后堆中的最后一个元素的位置 */
    for ( ; H->Data[i/2] < X; i/=2 )
        H->Data[i] = H->Data[i/2]; /* 上滤X */
    H->Data[i] = X; /* 将X插入 */
    return true;
}
 
#define ERROR -1 /* 错误标识应根据具体情况定义为堆中不可能出现的元素值 */
 
bool IsEmpty( MaxHeap H )
{
    return (H->Size == 0);
}
 
ElementType DeleteMax( MaxHeap H )
{ /* 从最大堆H中取出键值为最大的元素,并删除一个结点 */
    int Parent, Child;
    ElementType MaxItem, X;
 
    if ( IsEmpty(H) ) {
        printf("最大堆已为空");
        return ERROR;
    }
 
    MaxItem = H->Data[1]; /* 取出根结点存放的最大值 */
    /* 用最大堆中最后一个元素从根结点开始向上过滤下层结点 */
    X = H->Data[H->Size--]; /* 注意当前堆的规模要减小 */
    for( Parent=1; Parent*2<=H->Size; Parent=Child ) {
        Child = Parent * 2;
        if( (Child!=H->Size) && (H->Data[Child]Data[Child+1]) )
            Child++;  /* Child指向左右子结点的较大者 */
        if( X >= H->Data[Child] ) break; /* 找到了合适位置 */
        else  /* 下滤X */
            H->Data[Parent] = H->Data[Child];
    }
    H->Data[Parent] = X;
 
    return MaxItem;
} 
 
/*----------- 建造最大堆 -----------*/
void PercDown( MaxHeap H, int p )
{ /* 下滤:将H中以H->Data[p]为根的子堆调整为最大堆 */
    int Parent, Child;
    ElementType X;
 
    X = H->Data[p]; /* 取出根结点存放的值 */
    for( Parent=p; Parent*2<=H->Size; Parent=Child ) {
        Child = Parent * 2;
        if( (Child!=H->Size) && (H->Data[Child]Data[Child+1]) )
            Child++;  /* Child指向左右子结点的较大者 */
        if( X >= H->Data[Child] ) break; /* 找到了合适位置 */
        else  /* 下滤X */
            H->Data[Parent] = H->Data[Child];
    }
    H->Data[Parent] = X;
}
 
void BuildHeap( MaxHeap H )
{ /* 调整H->Data[]中的元素,使满足最大堆的有序性  */
  /* 这里假设所有H->Size个元素已经存在H->Data[]中 */
 
    int i;
 
    /* 从最后一个结点的父节点开始,到根结点1 */
    for( i = H->Size/2; i>0; i-- )
        PercDown( H, i );
}

到这里堆也先暂时退场了,嘻嘻

-------------------------------------------------------------------------------------------------------------------------------------------------------

接下来我们来搞高中大学都是数学第一课的集合啦,这玩意可真贯穿好多阶段

相信大家都清楚集合是很啥玩意啦,但是咧但是咧,形式还是要走一遍的

何况不同地方的集合定义本来就千差万别,看看计算机科学里头的定义吧

当然依照惯例还是先代码警告哈哈

#define MAXN 1000                  /* 集合最大元素个数 */
typedef int ElementType;           /* 默认元素可以用非负整数表示 */
typedef int SetName;               /* 默认用根结点的下标作为集合名称 */
typedef ElementType SetType[MAXN]; /* 假设集合元素下标从0开始 */
 
void Union( SetType S, SetName Root1, SetName Root2 )
{ /* 这里默认Root1和Root2是不同集合的根结点 */
    /* 保证小集合并入大集合 */
    if ( S[Root2] < S[Root1] ) { /* 如果集合2比较大 */
        S[Root2] += S[Root1];     /* 集合1并入集合2  */
        S[Root1] = Root2;
    }
    else {                         /* 如果集合1比较大 */
        S[Root1] += S[Root2];     /* 集合2并入集合1  */
        S[Root2] = Root1;
    }
}
 
SetName Find( SetType S, ElementType X )
{ /* 默认集合元素全部初始化为-1 */
    if ( S[X] < 0 ) /* 找到集合的根 */
        return X;
    else
        return S[X] = Find( S, S[X] ); /* 路径压缩 */
}

 

 

 

 

 

下面是详细概念

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第82张图片

 

学了树当然得学以致用呀,父子关系得用上

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第83张图片

 

 

我们可以用数组来存这些树形式的集合

 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第84张图片

 

而且可以用负数的值来表示树的高度,初始值为-1

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第85张图片

是不是很神奇呢,负数 负树正高度,减法当加法算,哈哈,逆向思维

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第86张图片

 

 

有意思吧 嘻嘻-_-

 

 

 

 

 

 

 

 

集合的运算

 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第87张图片

 

 

 还有    并运算     哦

 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第88张图片

 

 

好啦好啦,大家三翻四次再学一次的集合也告一段落啦。

 

-----------------------------------------------------------------------------------------------------------------------

 

下面我们来讲讲File Transfer

慢慢来引出   按秩归并    和      路径压缩

两种大算法,优劣难以比较,貌似路径压缩好一丢丢吧貌似

 正好我们刚学完集合   

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第89张图片

集合简化表示如下

 树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第90张图片

 

下面我们连通问题为例

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第91张图片

 

 

要解这道题我们要怎么搭建这个程序框架呢

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第92张图片

 

 

有了思路之后再具化细节查漏补缺

 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第93张图片

我们为什么要用到按秩归并呢

 

 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第94张图片

 

 

废鸡巴话,当然是为了让这个时间复杂度更小一点提高效率啦

法一:按树高归并

 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第95张图片

 

 

 

 

 

 

 

法二:按规模归并

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第96张图片

 

  后面介绍下    路径压缩

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第97张图片

 

 

具体的时间复杂度效率压缩路径VS按秩归并  如下

 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第98张图片

 

 

 

 

 

*********************************************************************************************************************************

最后让我们来看下什么是哈夫曼树哈夫曼编码

很多小伙伴以前没听过这个词,当然我也没听过啦

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第99张图片

 

 

哈夫曼树有什么用呢,我们先来几个引例开开胃

下面是没有考虑成绩分布情况下我们正常平均分段查找的效率

 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第100张图片

 

 

那在知道学生成绩分布频率时我们有没有更好算法来提高效率呢

 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第101张图片

 

 

答案显然是肯定的   这就用到了我们最后的主角-哈夫曼树

 

那到底什么是哈夫曼树呢???

 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第102张图片

先来看看几个不同二叉树以及它们的权值

 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第103张图片

 

 

哈夫曼树是怎么构造的呢

 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第104张图片

 

 

 

具体代码实现如下 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第105张图片

 

 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第106张图片

 

 

再来看看哈夫曼树的特点 

 

 

 树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第107张图片

 

 

了解完哈夫曼树,我们再来看看什么叫哈夫曼编码树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第108张图片

 

编码经常会出现类似二义性这样的问题,那如何避免二义性呢

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第109张图片

 

 

一个方法是利用二叉树

 

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第110张图片

 

 

为了构造一颗编码代价最小的二叉树

 

所以我们就用到了哈夫曼树,这即是哈夫曼编码,大功告成!

树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩_第111张图片

 

 

-------------------------------------------------------------------------------------------------------------------

 

 

 

  至此,树的前世今生之缘也好,七姑六婆也罢,也都告一段落啦。

 

经过介绍前面这一大筐玩意,树在数据结构中的地位可想而知。

 

树也叫树状图,应用的领域非常广泛,适用范围更是浩瀚星辰。

 

在非线性结构中更多的代表的是一种单向的层次关系。

通过对树的学习,

我们会对非线性数据结构抽象数据类型有进一步的认识。

 

--------------------最后补充一些与树相关的计算机常用知识----------------

 

关于树的常用术语有下面这些:

 

  1. 节点的度:一个节点含有的子树的个数称为该节点的度;
  2. 树的度:一棵树中,最大的节点度称为树的度;
  3. 叶节点终端节点:度为零的节点;
  4. 非终端节点分支节点:度不为零的节点;
  5. 父亲节点父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点;
  6. 孩子节点子节点:一个节点含有的子树的根节点称为该节点的子节点;
  7. 兄弟节点:具有相同父节点的节点互称为兄弟节点;
  8. 节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
  9. 深度:对于任意节点n,n的深度为从根到n的唯一路径长,根的深度为0;
  10. 高度:对于任意节点n,n的高度为从n到一片树叶的最长路径长,所有树叶的高度为0;
  11. 堂兄弟节点:父节点在同一层的节点互为堂兄弟;
  12. 节点的祖先:从根到该节点所经分支上的所有节点;
  13. 子孙:以某节点为根的子树中任一节点都称为该节点的子孙。
  14. 森林:由m(m>=0)棵互不相交的树的集合称为森林;

 

 

 

树的种类主要有以下几种:

 

 

无序树:树中任意节点的子节点之间没有顺序关系,这种树称为无序树,也称为自由树;


有序树:树中任意节点的子节点之间有顺序关系,这种树称为有序树;


二叉树:每个节点最多含有两个子树的树称为二叉树;


完全二叉树:对于一颗二叉树,假设其深度为d(d>1)。

除了第d层外,其它各层的节点数目均已达最大值,且第d层所有节点从左向右连续地紧密排列,这样的二叉树被称为完全二叉树;

 

满二叉树:所有叶节点都在最底层的完全二叉树;


平衡二叉树(AVL树):当且仅当任何节点的两棵子树的高度差不大于1的二叉树;


排序二叉树(二叉查找树)(英语:Binary Search Tree)):也称二叉搜索树、有序二叉树;

 

霍夫曼树(又称哈夫曼树)带权路径最短的二叉树称为哈夫曼树或最优二叉树;


B树一种对读写操作进行优化的自平衡的二叉查找树,能够保持数据有序,拥有多于两个子树。

 

 

-----------------------------------------------------------------------------------end

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(树的前世今生之数据结构(慕课浙大)遍历二叉搜索树平衡二叉树哈夫曼树哈夫曼编码堆的路径集合的并查按秩归并与路径压缩)