zoukankan      html  css  js  c++  java
  • 浙大《数据结构》第三章:树(上)

    注:本文使用的网课资源为中国大学MOOC

    https://www.icourse163.org/course/ZJU-93001


    查找

    查找:根据某个给定的关键字K,从集合R中找出关键字与K相同的记录。

    静态查找:集合中的记录是固定的,没有插入删除的操作,只有查找;

    动态查找:集合中记录是动态变化的,除查找,还可能发生插入和删除。


    静态查找

    方法1:顺序查找

    int SequentialSearch( StaticTable *Tbl, ElementType K)
    {
        //在表Tbl[1]~Tbl[n]中查找关键字为K的数据元素
        int i;
        Tbl->Element[0] = K; //建立哨兵
        for (i=Tbl->Length; Tbl->Element[i]!=K; i--);
        return i; //查找成果则返回所在单元下标;不成功则返回0
    }
    

    顺序查找的时间复杂度为O(n)。


    方法2:二分查找

    int BinarySearch( StaticTable *Tbl, ElementType K)
    {
        int left, right, mid, NotFound = -1;
        
        left = 1; //初始左边界
        right = Tbl->Length; //初始右边界
        while ( left <= right )
        {
            mid = (left+right)/2; //计算中间元素坐标
            if ( K < Tbl->Element[mid] )
                right = mid-1; //调整右边界
            else if ( K > Tbl->Element[mid] )
                left = mid+1; //调整左边界
            else
                return mid; //查找成功,返回数据元素的下标
        }
        return NotFound; //查找不成功,返回-1
    }
    

    二分查找算法具有对数时间复杂度O(log(N))。


    树的定义

    树(Tree): n(n≥0)个结点构成的有限集合。当n=0时,称为空树,对于任意一棵非空树,它具备以下性质:

    • 树中有一个称为“根(root)”的特殊结点,用r表示;
    • 其余结点可分为若干个互不相交的有限集,其中每一个集合本身又是一棵树,称为原来树的子树(SubTree).
    • 子树是不相交的;
    • 除了根结点外,每个结点有且仅有一个父结点;
    • 一棵N个结点的树有N-1条边。

    树的基本术语

    1. 结点的度(Degree):结点的子树个数
    2. 树的度:树的所有结点中最大的度数
    3. 叶结点(Leaf):度为0的结点
    4. 父结点(parent):有子树的结点是其子树的根结点的父结点
    5. 子节点(child):若A结点是B结点的父结点,则称B结点是A结点的子结点;子结点也称为孩子结点
    6. 兄弟结点(sibling):具有同意父结点的各结点是彼此的兄弟结点
    7. 路径和路径长度:从结点(n_1)(n_k)的路径为一个结点序列(n_1,n_2,...,n_k)(n_i)(n_{i+1})的父结点。路径所包含的边的个数为路径的长度。
    8. 祖先结点(ancestor):沿树根到某一结点路径上的所有结点都是这个结点的祖先结点。
    9. 子孙结点(descendant):某一结点的子树中的所有结点是这个结点的子孙
    10. 结点的层次(level):规定根结点在1层,其他任意结点的层数是其父结点的层数加1。
    11. 树的深度(depth):树中所有结点中最大层次是这棵树的深度。

    二叉树及其存储结构

    定义

    二叉树T:一个有穷的结点组合

    • 这个集合可以为空
    • 若不为空,则它是由根结点和称为其左子树(T_L)和右子树(T_R)的两个不相交的二叉树组成。

    特殊的二叉树


    性质

    1. 一个二叉树第i层的最大结点数为:(2^{i-1},i geq 1.).
    2. 深度为K的二叉树有最大结点总数为(2^K-1,K geq 1.).
    3. 对于任何非空二叉树T,若(n_0)表示叶结点的个数(没有子树)、(n_2)是度为2的非叶结点个数(有左右两个子树),那么二者满足关系(n_0=n_2+1)
      (二叉树的结点总数为(n_0+n_1+n_2))

    抽象数据类型定义

    类型名称:二叉树

    数据对象集:一个有穷的结点集合。若不为空,则由根结点和其左、右二叉子树组成。

    操作集:(BT in BinTree),(Item in ElementType),重要操作有:

    Boolean IsEmpty( BinTree BT); //判别BT是否为空
    void Traversal( BinTree BT ); //遍历,按某顺序访问每一个结点
    BinTree CreatBinTree();//创建一个二叉树
    
    /* 常见的遍历方法 */
    void PreOrderTraversal( BinTree BT ); //先序:根-左-右
    void InOrderTraversal( BinTree BT ); //中序:左-根-右
    void PostOrderTraversal( BinTree BT ); //后序:左-右-根
    void LevelOrderTraversal( BinTree BT ); //层次遍历:从上到下,从左到右
    

    存储结构

    顺序存储结构

    完全二叉树:按从上到下、从左到右顺序存储n个结点的完全二叉树的结点父子关系:

    • 非根结点(序号i>1)的父结点的序号是(i/2)。
    • 结点(序号i)的左孩子结点序号是2i(需2i<=n,否则没有左孩子)
    • 结点(序号i)的右孩子结点序号是2i+1(需2i+1<=n,否则没有右孩子)

    链表存储结构

    typedef struct TreeNode *Position
    typedef Position BinTree
    struct TreeNode
    {
        ElementTyoe Data;
        BinTree Left;
        BinTree right;
    }
    

    二叉树的遍历

    递归遍历

    (1) 先序遍历

    遍历过程:

    1. 访问根结点
    2. 先序遍历其左子树
    3. 先序遍历其右子树
    void PreOrderTraversal( BinTree BT )
    {
        if ( BT )
        {
            printf("%d", BT->Data); //根结点
            PreOrderTraversal( BT->Left ); //左子树
            PreOrderTraversal( BT->Right ); //右子树
        }
    }
    

    (2) 中序遍历

    遍历过程:

    1. 中序遍历其左子树
    2. 访问根结点
    3. 中序遍历其右子树
    void InOrderTraversal( BinTree BT )
    {
        if ( BT )
        {
            InOrderTraversal( BT->Left ); //左子树
            printf("%d", BT->Data); //根结点
            InOrderTraversal( BT->Right ); //右子树
        }
    }
    

    (3) 后序遍历

    遍历过程:

    1. 后序遍历其左子树
    2. 后序遍历其右子树
    3. 访问根结点
    void PostOrderTraversal( BinTree BT )
    {
        if ( BT )
        {
            PostOrderTraversal( BT->Left ); //左子树
            PostOrderTraversal( BT->Right ); //右子树
            printf("%d", BT->Data); //根结点
        }
    }
    

    非递归遍历

    先序、中序和后序遍历过程中经过结点的路线是一样的,只是访问各结点的时机不同。

    • 先序遍历是第一次"遇到"该结点时访问
    • 中序遍历是第二次"遇到"该结点(此时该结点从左子树返回)时访问
    • 后序遍历是第三次"遇到"该结点(此时该结点从右子树返回)时访问

    非递归算法实现的基本思路:使用堆栈

    (1) 中序遍历

    1. 遇到一个结点,就把它压栈,并去遍历它的左子树;
    2. 当这个左子树遍历结束后,从栈顶弹出这个结点并访问它;
    3. 然后按其右指针再去中序遍历该结点的右子树;
    void InOrderTraversal( BinTree BT )
    {
        BinTree T = BT;
        Stack S = CreatStack(MaxSize); //创建并初始化堆栈S
        while (T || !IsEmpty(S))
        {
            while (T) //一直向左并将沿途结点压入堆栈
            {
                Push(S, T); 
                T = T->Left;
            }
            if ( !IsEmpty(S))
            {
                T = Pop(S); //结点弹出堆栈
                printf("%5d", T->Data); //打印结点
                T = T->Right; //转向右子树
            }
        }
    }
    

    (2) 先序遍历

    void PreOrderTraversal( BinTree BT )
    {
    	BinTree T = BT;
    	Stack S = CreateStack();  // 创建并初始化堆栈 S
    	while(T || !IsEmpty(S))  // 当树不为空或堆栈不空 
    	{  
    		while(T)
    		{     
    			Push(S,T);    // 压栈,第一次遇到该结点 
    			printf("%d",T->Data);  // 访问结点
    			T = T->Left;   // 遍历左子树 
    		}
    		if(!IsEmpty(S)) // 当堆栈不空 
    		{  
    			T = Pop(S);    // 出栈,第二次遇到该结点 
    			T = T->Right;  // 访问右结点 
    		}
    	} 
    } 
    

    (3) 后序遍历

    先序的访问顺序是root, left, right 假设将先序左右对调,则顺序变成root, right, left,暂定称之为“反序”。后序遍历的访问顺序为left, right,root ,刚好是“反序”结果的逆向输出。

    1. 反序遍历二叉树,具体方法为:将先序遍历代码中的left 和right 对调即可。数据存在堆栈S中。
    2. 在先序遍历过程中,每次Push节点后紧接着print结点。对应的,在反序遍历时,将print结点改为把当前结点Push到堆栈N中。
    3. 反序遍历完成后,堆栈N的压栈顺序即为反序遍历的输出结果。此时再将堆栈N中的结果pop并print,即为“反序”结果的逆向,也就是后序遍历的结果。
    void PostOrderTraversal( BinTree BT )
    {
        BinTree T = BT;
        Stack S = CreatStack();
        Stack N = CreatStack(); //创建并初始化栈N
        wihile ( T || !IsEmpty(S) )
        {
            while(T) /*一直向右并将沿途结点压入堆栈*/
            { 
                Push(S,T);
                Push(N,T);  //将遍历到的结点压栈,用于反向
                T = T->Right;  //这里是Right
            }
            if ( !IsEmpty(S) )
            {
                T = Pop(S);
                T = T->Left;
            }
        }
        while ( !IsEmpty(N) )
        {
            T = Pop(N);
            printf("%5d", T->Data); //将 N 栈中的数据依次弹出并打印
        }
    }
    

    层次遍历

    队列实现:

    1. 根结点入队;
    2. 从队列中取出一个元素,并访问该元素;
    3. 若该元素所指结点的左右孩子结点非空,则将其左右孩子的指针入队。
    void LevelTraversal( BinTree BT )
    {
        if ( !BT )
            return; //如果是空树直接返回
        BinTree T;
        Queue Q = CreatQueue(MaxSize); //创建并初始化队列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) 求二叉树的高度

    int PostOrderGetHeight( BinTree BT )
    {
        int HL, HR, MaxH;
        if ( BT )
        {
            HL = PostOrderGetHeight( BT->Left );
            HR = PostOrderGetHeight( BT->Right );
            MaxH = (HL>HR) ? HL :HR; // 取左右子树较大的深度
            return ( MaxH+1 );
        }
        else
            return 0;  // 空树深度为0
    }
    

    (2) 根据先序和中序遍历来确定一棵二叉树

    分析:

    1. 根据先序遍历序列的第一个结点确定根结点;
    2. 根据根结点在中序遍历序列中分割出左右两个子序列;
    3. 对左子树和右子树分别递归使用相同的方法继续分解;

    例如:

    前序:ABCDEFG

    中序:CBDAFEG

    先序遍历为"根左右",则 A 是根,对应可以划分出中序中:(CBD)A(FEG),CBD 为左子树,FEG 为右子树,

    再根据前序的 BCD,B 为根,划分出中序中(C(B)D)A(FEG),则 C D 分别是 B 的左右子树…最后可得树为:

         A
        /  
       B    E
      /   / 
     C   D F  G
    

    二叉树的同构

    题意理解

    给定两棵树T1和T2。如果T1可以通过若干次左右孩子互换就变成T2,则我们称这两棵树是“同构”的。

    输入格式:

    • 现在一行中给出概述的结点数,随后N行
    • 第i行对应编号第i个结点,给出该结点中存储的字母、其左孩子结点的编号、右孩子结点的编号
    • 如果孩子结点为空,则在相应的位置上给出“-”。

    输入样例:


    程序框架搭建

    int main()
    {
        Tree R1, R2; // 利用结构数组表示二叉树,静态链表
        
        R1 = BuildTree(T1); // 建立二叉树1
        R2 = BuildTree(T2); // 建立二叉树2
        if ( Isomorphic(R1, R2) ) // 判别是否同构并输出
            printf("Yes
    ");
        else
            printf("No
    ");
        
        return 0;
        
    }
    

    程序实现

    #include <stdio.h>
    #include <windows.h> //windows.h里定义了关于创建窗口,消息循环等函数S
    
    #define MaxTree 10
    #define Null -1
    
    typedef struct TreeNode
    {
        char item;
        int Left;
        int Right;              // 注:这里的left和right分别为输入数据的左右子结点索引
    } TNode;
    
    TNode T1[MaxTree], T2[MaxTree]; // 结构数组表示二叉树
    
    /* 创建树并返回根结点 */
    int BuildTree(TNode T[])
    {
        int i, N;
        int Root = 0;
        int check[MaxTree];
        char cl, cr;
        scanf("%d", &N);
        if ( !N )
            return Null;
        for (i = 0; i < N; i++)
            check[i] = 0;
        for (i = 0; i < N; i++)
        {
            printf("%d: ",i);
            scanf("
    %c %c %c", &T[i].item, &cl, &cr); // 注意输入时不要有空格,且字母要小写,eg.a12
            // scanf("
    "):为了避免下一次的scanf直接读取
    而结束,保证每次都等待键盘输入
            // 注意‘’和”“的区别,双引号括起来的是字符串指针,单引号括起来的才是字符
            if (cl != '-') 
            {
                T[i].Left = cl - '0';
                check[T[i].Left] = 1;
            }
            else
                T[i].Left = Null;
            if (cr != '-')
            {
                T[i].Right = cr - '0';
                check[T[i].Right] = 1;
            }
            else
                T[i].Right = Null;
        }
        for (i = 0; i < N; i++)
            if (!check[i])
                break;
        Root = i;
        printf("Done!
    ");
        return Root;
    }
    
    /* 判断二叉树是否重构 */
    int Ismorphic(int R1, int R2)
    {
        if ( R1 == Null && R2 == Null ) // 均为空
            return 1;
        if ( (R1 == Null && R2 != Null) || (R1 != Null && R2 == Null) ) // 其中一个为空
            return 0;
        if ( T1[R1].item != T2[R2].item ) // 两个子树的结点不同
            return 0;
        if ( T1[R1].Left == Null && T2[R2].Left == Null ) // 根结点均没有左子树
            return Ismorphic(T1[R1].Right, T2[R2].Right);
        if ( (T1[R1].Left != Null && T2[R2].Left != Null) && (T1[T1[R1].Left].item == T2[T2[R2].Left].item) )
            // 两树的左儿子不为空且值相等
            return (Ismorphic(T1[R1].Left, T2[R2].Left) && Ismorphic(T1[R1].Right, T2[R2].Right));
        else // 两树的左儿子不为空且值不等  或者某一个左儿子为空
            return (Ismorphic(T1[R1].Left, T2[R2].Right) && Ismorphic(T1[R1].Right, T2[R2].Left));
    }
    
    int main()
    {
        int R1, R2; // 利用结构数组表示二叉树,静态链表
    
        printf("input Tree:
    ");
        R1 = BuildTree(T1); // 建立二叉树1
        printf("T1 = %d
    ", R1);
    
        printf("input Tree:
    ");
        R2 = BuildTree(T2);     // 建立二叉树2
        printf("T2 = %d
    ", R2);
    
        if (Ismorphic(R1, R2)) // 判别是否同构并输出
            printf("Yes
    ");
        else
            printf("No
    ");
    
        system("pause"); //程序暂停,显示按下任意键继续
        return 0;
    }
    

    运行结果:

  • 相关阅读:
    Java内存管理以及各个内存区域详解
    python数据的存储和持久化操作
    Redis的安装及配置
    POI使用详解
    遍历Map的几种方法
    Quartz的cronTrigger表达式
    Java对XML文档的增删改查
    Solr系列二:Solr与mmseg4j的整合
    cms STW 的两个阶段
    GROUP BY 和 ORDER BY 同时使用问题
  • 原文地址:https://www.cnblogs.com/Superorange/p/12514512.html
Copyright © 2011-2022 走看看