数据结构整理——第四章 树

版权声明:个人整理,仅供参考,请勿转载,如有侵权,请联系[email protected] https://blog.csdn.net/mooe1011/article/details/88089462

考研时个人整理的,参考王道、严蔚敏等数据结构。

上一章——栈和队列:https://blog.csdn.net/mooe1011/article/details/88089325

1. 树的概念

  1. 结点的孩子个数为,最大度数称为树的度
  2. 第i层最多有结点数:二叉树为2i-1;m叉树有mi-1个结点(i>=1)
  3. 高度h最多有结点数:二叉树有2h-1(即满二叉树);m叉树有(mh-1)/(m-1)
  4. 平衡二叉树第一层N1=1,第二层N2=2,h层Nh=Nh-1+Nh-2+1
  5. n个结点最小高度:(完全)二叉树为⌊log2n⌋+1或者为⌈log2(n+1)⌉(满二叉树log2(n+1));m叉树为⌈logm(n(m-1)+1)⌉
  6. 总结点数=n0+n1+n2+n3+…=度总数(总分支数)+1=1+1* n1+2* n2+3* n3
  7. 叶子结点数:二叉树n0=1+n2;m叉树n0=1+n2+2* n3+…+(m-1)nm
  8. n个结点有 ( 2 n ) ! ( n + 1 ) ! n ! \dfrac{(2n)!}{(n+1)!n!} 种不同的二叉树(前序入栈,中序出栈)

2. 二叉树

  1. 二叉树可为空,度为2的树至少有3个结点;并且,度为2的有序树某个结点如果只有一个孩子结点,无须分左右次序
  2. 完全二叉树
    1. 若i<=⌊n/2⌋,则结点i为分支结点,左孩子编号为2i,如果i<=n/2-1,则右孩子编号为2i+1;否则为叶子结点
    2. 非根结点的双亲为⌊i/2⌋,i为偶数时,它是双亲的左孩子;i为奇数时,双亲为(i-1)/2,它是右孩子
    3. 若n为奇数,则分支结点都有左右孩子;为偶数时,则分支结点编号最大的结点为n/2,只有左孩子
    4. 叶子结点只能在最大两层上出现,排列该层最左边的位置上,如果有度为1的结点,只有一个,并且是左孩子
    5. 所在层次为⌊log2i⌋+1
    6. 一般来说根结点i为1,注意有些为0
  3. 二叉排序树,关键字:左子树<根结点<右子树;插入需比较次数为log2n
  4. 平衡二叉树,树上任一结点的左子树和右子树的深度差不超过1
  5. 存储结构——顺序和链式
    1. 二叉链表叶结点有2个空指针,度为1的有1个空指针,总共2n0+n1;总结点数=n0+n1+n2,又有n2=n0-1,所以n个结点含有n+1个空链域
    2. 结构
    typedef struct BiTNode{
        int data;
        struct BiTNode *lchild,*rchild;
    }BiTNode,*BiTree;

3. 二叉树的遍历和线索二叉树

  1. 先序PreOrder
    1. 先序和后序序列正好相反,即根左右和根右左相等(后序相反),则每层只有一个结点,其高度等于结点数
    中序后序类似,visit(T)位置不同
    void PreOrder(BiTree T){
        if (T!=NULL){
            visit(T);   或者 printf("%c",T->data);      //%c即为按字符输出
            PreOrder(T->lchild);
            PreOrder(T->rchild);
        }
    }
  1. 中序InOrder
    1. 最后一个结点如果是叶子结点,则一定是右孩子,并且一定是先序遍历的最后一个结点;如果是非叶子结点,则一定有左子树
    2. 某结点有左孩子,则前驱左子树最右下的结点;有右孩子,则后继右子树最左下结点
    3. abcdMefgh,M为根结点,则a、e是最左下结点,d、h是最右下的结点
  2. 后序PostOrder
    1. 用后序可以记录路径,从祖先结点开始,到访问该结点时,栈顶到栈底正好是该结点从双亲开始直到祖先的所有结点
    2. 结点是双亲的右孩子或者唯一左孩子时,后继为双亲
  3. 时间复杂度均为O(n)
  4. 中序/后序非递归
    void InOrder(BiTree T){
        InitStack(S);
        BiTree p=T;
        while(p||!IsEmpty(s)){
            if(p){
                Push(S,p);
                p=p->lchild;
            } else {
                Pop(S,p);
                visit(p-data);
                p=p->rchild;
            }
        }
    }
    
    void PostOrder(BiTree T){
        InitStack(S);
        BiTree p=T;
        r=NULL;     //记录最近访问过的结点
        while(p||!IsEmpty(S)){
            if(p){
                push(S,p);
                p=p->lchild;
            } else {        //遍历完左子树
                GetTop(S,p);
                if(p->rchild&&p->rchild!=r){        //右子树未访问过
                    p=p->rchild;
                    push(S,p);
                    p=p->lchild;
                } else {
                    pop(S,p);
                    visit(p->data);
                    r=p;        //记录访问过的结点
                    p=NULL;
                }
            }
        }
    }
  1. 层次遍历
    void LevelOrder(BiTree T){
        InitQueue(Q);
        BiTree p;
        EnQueue(Q,T);       //根结点入队
        while(!IsEmpty(Q)){
            DeQueue(Q,p);
            visit(p);
            if(p->lchild!=NULL)
                EnQueue(Q,p->lchild);
            if(p->rchild!=NULL)
                ENQueue(Q,p->rchild);
        }
    }
  1. 线索二叉树
    1. ltag 为0表示lchild指示左孩子; 为1指示前驱
    2. rtag 为0表示rchild指示右孩子; 为1指示后继
    3. 若左子树为空,先序线索化的空链域有2个(根结点左标示域和最后一个叶结点无后继)
    4. 先序查找后继简单后序查找前驱简单,仅叶子结点指示前驱、后继,其他结点都需要知道双亲结点
    5. 线索二叉树是链表结构,是一种物理结构
    typedef struct ThreadNode{
        int data;
        struct ThreadNode *lchild,*rchild;
        int ltag,rtag;
    }ThreadNode,*ThreadTree;
  1. 线索二叉树的构造(中序)
    void InThread(ThreadTree &p,ThreadTree &pre){
        if(p!=NULL){
            InThread(p->lchild,pre);
            if(p->lchild==NULL){
                p->lchild=pre;
                p->ltag=1;
            }
            if(pre!=NULL&&pre->rchild==NULL){
                pre->rchild=p;
                pre->rtag=1;
            }
            pre=p;
            InThread(p->rchild,pre);
        }
    }
    
    void CreatInThread(ThreadTree T){
        ThreadTree pre=NULL;
        if(T!=NULL){
            InThread(T,pre);
            pre->rchild=NULL;
            pre->rtag=1;
        }
    }
  1. 线索二叉树的遍历
    求中序第一个结点
    ThreadNode *Firstnode(ThreadNode *p){
        while(p->ltag==0) p=p->lchild;
        return p;
    }
    
    求下一个结点
    ThreadNode *Nextnode(ThreadNode *p){
        if(p->rtag==0)return Firstnode(p->rchild);       //递归求该点右子树最左下的结点
        else return p->rchild;      //这个rchild是后继结点
    }
    
    void Inorder(ThreadNode *T){
        for(ThreadNode *p=Firstnode(T);p!=NULL;p=Nextnode(p))
            visit(p);
    }

4. 树和森林

  1. 存储结构
  • 双亲表示法:求孩子结点时需要遍历整个结构
    #define Max_Tree_Size 100
    typedef struct{
        int data;
        int parent;
    }PTNode;
    typedef struct{
        PTNode nodes [Max_Tree_Size];
        int n;
    }PTree;
  • 孩子双亲表示法
    typedef struct CSNode{
        int data;
        struct CSNode *firstchild,*nextsibling;
    }CSNode,*CSTree;
  1. 树、森林转换二叉树
    1. 树转换规则:每个结点左指针指向第一个孩子结点,右指针指向相邻兄弟结点左孩子右兄弟),由于根没有兄弟,由树转换成二叉树没有右子树
    2. 森林转换规则:森林每棵树都转换成二叉树,第一棵树作为根结点,第二棵树转换为二叉树的右子树,第三棵为右子树的右子树,以此类推
    3. 森林转换成二叉树最后一棵树的右指针域为空,每个非叶子结点的最后一个孩子的右指针域也为空
    4. 二叉树转换森林:根及其左子树作为第一棵树的二叉树形式,右子树作为第二棵二叉树,直到产生最后一棵没有右子树的二叉树为止,二叉树转换成树或森林是唯一的
    5. 树的先根遍历与二叉树的先序遍历相同,后根遍历与二叉树的中序遍历相同,因此一棵树的先根遍历和后根遍历能唯一确定一棵树

5. 树与二叉树的应用

1. 二叉排列树(二叉查找树)

  • 左子树结点值<根结点值<右子树结点值,对其中序遍历,可得到递增的有序序列
  • 删除:
    1. 如果是叶子结点,直接删除
    2. 如果只有一棵子树,则直接成为删除结点父结点的子树
    3. 如果有左右子树,则令该点直接后继(或前驱)代替,然后该直接后继(或前驱)的位置转换为情况1、2、3
  • 高度为H的二叉排列树,插入删除的时间都是O(H),特别地,倾斜单支树效率最低。如果是平衡二叉树,平均查找长度达到O(log2n)
    非递归查找法
    BSTNode *BST_Search(BiTree T,int key,BSTNode *&p){
        p=NULL;
        while(T!=NULL&&key!=T->data){
            p=T;
            if(key<T->data) T=T->lchild;
            else T=T->rchild;
        }
        return T;
    }

    插入(新结点一定是叶子结点)

    int BST_Insert(BiTree &T,KeyType k){
        if(T==NULL){
            T=(BiTree)malloc(sizeof(BSTNode));
            T->key=k;
            T->lchild=T-rchild=NULL;
            return 1;
        } else if(k==T->key)
            return 0;
        else if(k<T->key)
            return BST_Insert(T->lchild,k);
        else
            return BST_Insert(T->rchild,k);
        
    }
    
    构造

    void Creat_BST(BiTree &T,KeyType str[],int n){
        T=NULL;
        int i=0;
        while(i<n){
            BST_Insert(T,str[i]);
            i++;
        }
    }

2. 哈夫曼树

  • 从根结点到任意结点经过的边数(路径长度),与该结点的权值的乘积称为带权路径长度。而所有叶子结点带权路径长度之和称为该树的带权路径长度(WPL)。而WPL最小的称为哈夫曼树或最优二叉树
  • 构造:
    1. 将N个权值为w1,w2,w3…的结点分别作为只含一个结点的二叉树,构成森林F
    2. 从F中选取两棵根结点权值最小的树作为新结点的左、右子树,新结点的权值为子树根结点之和
    3. 将新树加入F中,并删除刚选的两棵树
    4. 重复2)3),直到F中只剩一棵树为止
  • 特点
    1. 初始结点都成为叶子结点,并且权值越小的到根结点的路径长度越大
    2. N个叶结点(码字数)共新建了N-1个结点(N2=N-1),故结点总数为2N-1
    3. 每次构造都选择2棵树作为子树,因此不存在度为1的结点
    4. 子树左右顺序任意,因此哈夫曼树不唯一
    5. 度为m的哈夫曼树只有度为0和m的点

如果您看了这篇文章觉得有用的话,请随意打赏,您的支持将鼓励我继续创作,谢谢!

猜你喜欢

转载自blog.csdn.net/mooe1011/article/details/88089462