数据结构-树和二叉树

news2024/11/15 23:33:24


    树 和 二叉树 


1.树的概念 

    树 tree 
        是n(n>=0)个节点的有限集
        在任意的一个非空树中 
            (1)有且仅有一个特定的被称为 根(root) 的节点 
            (2)当n>1时, 其余的节点可分为m(m>0)个互不相交的有限集T1, T2, T3, .... 
                其中每一个集合本身又是一棵树, 并且称为 根的子树 (Subtree)


    树的节点 
        包含一个数据元素 以及 若干个指向子树的分支(保存的关系)

    
    节点的度 degree 
        节点拥有子树的个数 称为 节点的度 
            度为0的节点, 称为 叶子节点 或者 终端节点
            度不为0的节点, 称为 分支节点 或者 非终端节点


    节点的层次 level 
        从 根 开始定义起的, 根为第一层, 根的子树为第二层, 以此类推 ... 
        
        树中节点最大层次 称为 树的高度 或者 深度 depth 

2.二叉树 Binary 

    1)二叉树 
        是一种树形结构, 它的特点是 每一个节点至多有两个子树 
                                (即二叉树中不存在 度大于2 的节点)
            并且, 二叉树的子树有左右之分的, 其次序是不能颠倒 


    2)二叉树的5种形态  (见图示)
        (1)空树
        (2)只有一个根节点(没有子节点)
        (3)只有一个左子节点
        (4)只有一个右子节点
        (5)有左右两个子节点        


    ☆☆☆
    3)二叉树的性质 

        (1)在二叉树中, 第i层 至多有 2^(i-1) 个节点   (i>=1)

            证明:   数学归纳法 

                    当 n == 1 时, 1 
                    当 n == 2 时, 2
                    当 n == 3 时, 4
                    当 n == 4 时, 8 
                    ...
                       i   --->   2^(i-1) 

        
        (2)深度为k的二叉树中, 至多有 ( 2^k - 1 ) 个节点   (k>=1)

            证明:  由性质(1)可得 
                    1 + 2 + 4 + 8 + 16 + ... + 2^(k-1) = 2^k - 1

                        等比数列前n项和 : Sn = a1 * ( 1 - q^n ) / ( 1 - q ) 


        (3)对于任意一颗二叉树中, 如果其 叶子节点(度为0的节点)的个数为 n0, 度为2的节点的个数为 n2,
            则 n0 = n2 + 1 

            证明: 
                假设一个二叉树中的总的节点个数为N 
                    度为0的节点个数为n0
                    度为1的节点个数为n1
                    度为2的节点个数为n2

                    N = n0 + n1 + n2        (I)

                设这个二叉树的分支数为B 
                    从下往上看, 每一个节点都有一个向上的分支(除了根节点没有) 

                    B = N - 1               (II)

                    从上往下看, 一个节点如果有一个子节点,就会有一个向下的分支
                                如果有两个子节点,就会有2个向下的分支
                                如果没有子节点,就没有分支 

                    B = 0 * n0 + 1 * n1 + 2 * n2    
                    --> 
                        B = n1 + 2 * n2     (III)

                有(I)(II)(II)可得 
                                    B = N - 1
                    ==>   n1 + 2 * n2 = n0 + n1 + n2 - 1
                    ==>            n0 = n2 + 1 

                

            满二叉树:
                一个深度为k 且 有 2^k - 1 个节点的二叉树, 称为 满二叉树 
                    在不改变二叉树深度的情况下, 不能再额外的添加节点了

            完全二叉树:
                (1)除去最后一层, 为满二叉树
                (2)最后一层的节点的排序, 要从左边开始(中间不能有空洞)


        (4)如果对一个有n个节点的完全二叉树 从上至下,从左至右 
            给每一个节点进行编号, 从1开始编号 
            那么,如果一个节点的编号为i, 则 

                父节点的编号为      i/2
                左子节点的编号为    2*i 
                右子节点的编号为    2*i + 1 

            证明: 
                假设编号为i的节点, 在第k层 
                    那么第k层的第一个节点的编号为 2^(k-1) 

                    i这个节点 距离它所在的层次的第一个节点 中间间隔多少个节点? 
                        i - 2^(k-1)  

                    i的子节点在第k+1层 
                        左子节点的编号为  2^k + 2*( i - 2^(k-1) )  ==>  2*i 
                        右子节点的编号为  2*i + 1 

                    i的父节点在 第k-1层 
                        父节点的编号为    2^(k-2) + ( i - 2^(k-1) )/2 ==> i/2 


        (5)具有n个节点的完全二叉树的深度为 log2(n)向下取整 +1 

            证明: 
                假设深度为k 

                    2^(k-1) - 1 < n <= 2^k - 1
                    --> 2^(k-1) <= n < 2^k  

3.二叉树的代码实现 

    如何保存二叉树的节点? 
        保存数据 
        保存数据之间的关系
                对于数的节点来说, 父节点和子节点 


        1)顺序结构 
            用一组地址连续的空间 来保存二叉树   "数组" 

                如何去表示节点与节点之间的关系? 

                    完全二叉树的性质, 如果一个节点的编号为i, 则 
                        父节点的编号为      i/2
                        左子节点的编号为    2*i 
                        右子节点的编号为    2*i + 1 

                用数组来保存一颗完全二叉树 

                    #define MAX_LEN  1024
                    typedef int DataType;       //数据的类型

                    DataType SqBitree[MAX_LEN];     

                        SqBitree[1] 保存根节点的数据
                        SqBitree[2] 保存编号为2的节点
                        SqBitree[3] 保存编号为3的节点
                        ... 

                    缺点: 
                        数组的大小是固定的, 可能会造成空间的浪费,或者空间不足的情况 


        2)链式结构 
            用一组地址不连续的空间 来存储栈的一个二叉树

                二叉树的节点: 
                    数据域: 保存数据 
                    指针域: 至少有两个, 保存左子和右子的指针 

                    typedef int DataType;       //数据的类型

                    typedef struct BitNode      //二叉树节点的类型
                    {
                        DataType data;              //数据域 
                        struct BitNode * lchild;    //指针域 , 左子节点的指针     
                        struct BitNode * rchild;             //右子节点的指针 
                    } BitNode ;


    ☆☆☆
4.二叉树的遍历 

    如何按照某种搜索路径访问二叉树的每一个节点 
        使得每一个节点都能够被访问 且 仅访问一次 

        一般 左子节点的访问 在 右子节点的前面 

        根 左 右 : 先序遍历(先根遍历)
        左 根 右 : 中序遍历 
        左 右 根 : 后序遍历 


    1)先序遍历(先根遍历) 
        (1)先访问根节点 
        (2)再按照先序遍历的方式去访问根的左子树
        (3)再按照先序遍历的方式去访问根的右子树 

            F(t)    --> F表示用先序遍历的方法, t表示这个二叉树的根节点 

            F(t) ==> 
                    (1)判断这个树是否为空
                    (2)访问 根节点 printf
                    (3) F( t->lchild )
                    (4) F( t->rchild )


    2)中序遍历 
        (1)先按照中序遍历的方式去访问根的左子树
        (2)再访问根节点
        (3)再按照中序遍历的方式去访问根的右子树 


    3)后序遍历 
        (1)先按照后序遍历的方式去访问根的左子树
        (2)再按照后序遍历的方式去访问根的右子树
        (3)最后访问根节点


        练习: 
            1) 见图示 

            2) 已知一棵二叉树的先序和中序遍历,请画出这个树, 并写出其后序遍历
                先序遍历: EBADCFHGIKJ
                中序遍历: ABCDEFGHIJK

                    见图示 


5.二叉排序树 

    二叉排序树具有以下的性质: 
        (1)如果它的左子树不为空, 则左子树上的所有节点的值 都是小于 根节点的值
        (2)如果它的右子树不为空, 则右子树上的所有节点的值 都是大于 根节点的值 
        (3)它的左右子树 也分别是一棵二叉排序树 

        练习: 
            1)创建一个二叉排序树 
                实际上就是把一个节点加入到一个二叉排序树中,并保持其排序性
                    不断地 重复二叉排序树的插入操作 

                    BinaryTree.c   /  BinaryTree.h   

                    typedef char DataType;       //数据的类型

                    typedef struct BitNode      //二叉树节点的类型
                    {
                        DataType data;              //数据域 
                        struct BitNode * lchild;    //指针域 , 左子节点的指针     
                        struct BitNode * rchild;             //右子节点的指针 
                    } BitNode ;

                    //往一个二叉排序树中添加节点 

//往一个二叉排序树中添加节点 
BitNode * insert_node( BitNode *r , BitNode *pnew )
{
    if( r == NULL )     //从无到有 
    {
        return pnew;
    }

    //从少到多 
    //找到待插入的位置 
    BitNode * p = r;    //遍历指针 
    while( 1 )
    {
        if( pnew->data  >  p->data  )   //大于 往右 
        {
            if( p->rchild == NULL )     //如果右子为空,那么就添加进来
            {
                p->rchild = pnew;
                break;
            }
            p = p->rchild;
        }
        else if( pnew->data  <  p->data )   //小于  往左
        {
            if( p->lchild == NULL ) //如果左子为空, 那么就添加进来
            {
                p->lchild = pnew;
                break;
            }
            p = p->lchild;
        }
        else
        {
            printf("data repetitive! \n");
            break;
        }
    }

    //返回根节点
    return r;
}

                    //创建一个二叉排序树

//创建一个二叉排序树
BitNode * create_sort_tree()
{
    BitNode * r = NULL;     //保存根节点的指针

    //1.从键盘上获取输入的数据 
    char str[32] = {0};
    scanf("%s", str );
    int i = 0;

    while( str[i] )
    {
        //2.创建一个新的数据节点空间, 并初始化 
        BitNode * pnew = (BitNode *)malloc( sizeof(BitNode) );
        pnew->data = str[i];
        pnew->lchild = NULL;
        pnew->rchild = NULL;

        //3.把新节点加入到二叉树中
        r = insert_node( r , pnew );

        i++;
    }

    //4.返回根节点
    return r;
}

            2)用递归的方法, 实现二叉树的先序遍历\中序遍历\后序遍历 

//先序遍历
void pre_order( BitNode * r )
{
	if( r == NULL )
	{
		return ;
	}

	//(1)先访问根节点
	printf("%c ", r->data );

	//(2)再按照先序遍历的方式去访问根的左子树
	pre_order( r->lchild );

	//(3)再按照先序遍历的方式去访问根的右子树 
	pre_order( r->rchild );
}


//中序遍历
void mid_order( BitNode * r )
{
	if( r == NULL )
	{
		return ;
	}

	//(1)先按照中序遍历的方式去访问根的左子树
	mid_order( r->lchild );
	
    //(2)再访问根节点
    printf("%c ", r->data );
    
    //(3)再按照中序遍历的方式去访问根的右子树 
    mid_order( r->rchild );
    
}


//后序遍历 
void post_order( BitNode * r )
{
	if( r == NULL )
	{
		return ;
	}

	//(1)先按照后序遍历的方式去访问根的左子树
	post_order( r->lchild );
	
    //(2)再按照后序遍历的方式去访问根的右子树
	post_order( r->rchild );
		
    //(3)最后访问根节点
    printf("%c ", r->data );
}


            3)求一棵二叉树的高度(深度)

//求一棵二叉树的高度(深度)
int TreeHeight( BitNode * t )
{
	if( t == NULL )
	{
		return 0;
	}

	int l = TreeHeight( t->lchild );
	int r = TreeHeight( t->rchild );

	return l>r ? l+1 : r+1;
}

            4)删除二叉树中值为x的节点  (参考图示)


 
        5)销毁一棵二叉树 

//销毁一棵二叉树 
void destroy_tree( BitNode * r )
{
	while( r )
	{
		r = delete_node( r, r->data ); 
	}
}

        6)层次遍历 

//层次遍历 
void level_order( BitNode * r )
{
	if( r == NULL )
	{
		return ;
	}
	
    //1.初始化一个队列 
	CircleQueue *q = InitQueue();

    //2.把根节点入队
    EnQueue( q, r );

    while( ! IsEmpty( q ) )
    {
        //3.出队, 访问 
        ElemType d;		// BitNode * d;
        DeQueue( q, &d );
        printf("%c ", d->data );

        //4.再把出队元素的左子和右子全部入队 
        if( d->lchild )
        {
			EnQueue( q, d->lchild );
        }
        if( d->rchild ) 
        {
			EnQueue( q, d->rchild );
        }
    }
	putchar('\n');
    
    //5.销毁队列 
    DestroyQueue( q );
}

6.平衡二叉树 

    1)平衡二叉树 Balance Binary Tree   (又称为 AVL树 )

        它或者是一棵空树, 或者具有以下的性质: 
            它的左子树和右子树本身又是一棵平衡二叉树 
            且 左子树 和 右子树 的深度之差的绝对值不超过1 


        若平衡二叉树的节点的 平衡因子BF 的定义为
            该节点的 左子树的深度 减去 右子树的深度
            则 平衡二叉树上的所有节点的平衡因子的取值范围可能是 -1, 0, 1 
            只要有一个节点的平衡因子的绝对值大于1的,那么这棵树就是不平衡的

        平衡二叉树 --> 平衡的二叉排序树 来实现 


    2)平衡操作 

        (1)单向右旋平衡处理 SingleRotateWithRight 
            在不平衡的节点的左子节点的左边新增一个节点
            ==>对不平衡的节点做 单向右旋平衡处理 

AVLNode * SingleRotateWithRight( AVLNode *k2 )
{
	AVLNode * k1 = k2->lchild;

	//平衡处理 
	k2->lchild = k1->rchild;
	k1->rchild = k2;

	//更新树的深度 
	k2->h = MAX( Height(k2->lchild), Height(k2->rchild) ) + 1 ;
	k1->h = MAX( Height(k1->lchild), Height(k1->rchild) ) + 1 ;

	return k1;
}

  (2)单向左旋平衡处理 SingleRotateWithLeft 
            在不平衡的节点的右子节点的右边新增一个节点
            ==>对不平衡的节点做 单向左旋平衡处理 

     

AVLNode * SingleRotateWithLeft( AVLNode *k2 )
{
	AVLNode *k1 = k2->rchild;

	//平衡处理 
	k2->rchild = k1->lchild;
	k1->lchild = k2;

	//更新树的深度 
	k2->h = MAX( Height(k2->lchild), Height(k2->rchild) ) + 1 ;
	k1->h = MAX( Height(k1->lchild), Height(k1->rchild) ) + 1 ;

	return k1;
}

  (3)双向旋转(先左后右)平衡处理 DoubleRotateLeftRight
            在不平衡的节点的左子节点的右边新增一个节点
            ==> 先把 不平衡的节点的左子节点 做 单向左旋平衡处理 
                然后再对该 不平衡的节点 做 单向右旋平衡处理

       

AVLNode * DoubleRotateLeftRight( AVLNode * k3 )
{
	k3->lchild = SingleRotateWithLeft( k3->lchild );
	
	k3 = SingleRotateWithRight( k3 );
	
	return k3;
}

(4)双向旋转(先右后左)平衡处理 DoubleRotateRightLeft
            在不平衡节点的右子节点的左边新增一个节点
            ==> 先把 不平衡的节点的右子节点 做 单向右旋平衡处理 
                然后再对该 不平衡的节点 做 单向左旋平衡处理 

    

AVLNode * DoubleRotateRightLeft( AVLNode * k3 )
{
	k3->rchild = SingleRotateWithRight( k3->rchild );
	
	k3 = SingleRotateWithLeft( k3 );
	
	return k3;
}


    3)代码实现 

            typedef char DataType;      //数据的类型 

            typedef struct AVLNode      //平衡的二叉排序树的节点的数据类型
            {
                DataType data;              //数据域: 保存数据 
                struct AVLNode * lchild;    //指针域, 左子节点的指针 
                struct AVLNode * rchild;            //右子节点的指针 
                int h;                      //保存树的深度
            } AVLNode ;


        练习: 
            1)创建一个平衡二叉排序树 
                其实就是把一个节点 加入到一个平衡二叉排序树中, 并保持其平衡性 

                    AVL.c  /  AVL.h

                    //往一棵平衡二叉排序树t中 插入值为x的节点

AVLNode * insert_AVL_node( AVLNode *t, DataType x )
{
	if( t == NULL )		//从无到有 
	{
		//创建一个新节点, 并初始化
		AVLNode *pnew = (AVLNode *)malloc( sizeof(AVLNode) );
		pnew->data = x;
		pnew->lchild = NULL;
		pnew->rchild = NULL;
		pnew->h = 1;

		return pnew;
		
	}

	//从少到多
	if( x > t->data )	//大于 往右 
	{
		//大于, 把x插入到t的右子树中, 同时做平衡处理
		t->rchild = insert_AVL_node( t->rchild, x );

		//更新树的深度
		t->h = MAX( Height( t->lchild ) , Height( t->rchild ) ) + 1 ;

		//把x插入到右子树之后,如果不平衡, 需要做平衡处理
		if( Height( t->rchild ) - Height( t->lchild ) > 1  )
		{
			if( x > t->rchild->data )	//右子树的右边
			{
				//单向左旋平衡处理
				t = SingleRotateWithLeft( t );
			}
			else						//右子树的左边
			{
				//双向旋转(先右后左)平衡处理 
				t = DoubleRotateRightLeft( t );
			}
		}
		
	}
	else if( x < t->data )	//小于 往左 
	{
		//小于, 把x插入到t的左子树中, 同时做平衡处理
		t->lchild = insert_AVL_node( t->lchild, x );

		//更新树的深度
		t->h = MAX( Height( t->lchild ) , Height( t->rchild ) ) + 1 ; 

		//把x插入到左子树之后, 如果不平衡, 需要做平衡处理
		if( Height( t->lchild) - Height( t->rchild) > 1 )
		{
			if( x < t->lchild->data )	//左子节点的左边
			{
				//单向的右旋平衡处理
				t = SingleRotateWithRight( t );
			}
			else						//左子节点的右边 
			{	
				//双向旋转(先左后右)平衡处理
				t = DoubleRotateLeftRight( t );
			}
		}
	}

	//返回树的根节点
	return t;
}

                    //创建一个平衡二叉排序树

AVLNode * create_AVL()
{
	AVLNode * t = NULL;		//保存根节点 

	//从键盘上获取输入数据
	char str[32] = {0};
	scanf("%s", str );
	int i = 0;

	while( str[i] )
	{
		//把新的节点 加入到 平衡二叉树中 
		t = insert_AVL_node( t, str[i] );

		i++;
	}

	//返回 平衡二叉排序树的根节点
	return t;
}   

7.哈夫曼树 Huffman 

    1)哈夫曼树 

        又称为 最优二叉树 

            它是n个带权的叶子节点 构成的所有的二叉树中, 带权路径长度WPL最小的二叉树 

                (1)一棵树的带权路径长度的定义为 树中所有的叶子节点的带权路径长度之和
                (2)节点的带权路径长度规定 从根节点 到 该节点之间的路径长度 与 该节点上的权值 的乘积 


    2)哈夫曼编码 

        在电报通信中, 电文是以二进制0/1序列形式去发送, 每一个字符对应着一个二进制编码 
        为了缩短 电文的长度,采用不等长的编码方式,把使用频率高的字符用短编码
        使用频率低的字符用长编码

        我们把使用频率作为权值, 把每一个字符作为叶子节点来构建哈夫曼树
        每一个分支节点的左右分支分别用0和1进行编码, 这样就得到了每一个叶子节点的 哈夫曼编码

        使用的是叶子节点的编码, 所以每一个字符的编码都不可能是其他字符编码的前缀 


    3)构建哈夫曼树 

        假设有n个带权的叶子节点 w1, w2, w3, w4, ... wn , 则构建哈夫曼树的步骤: 

            第一步: 构建森林 
                    把每一个叶子节点 都当作是一棵独立的树(只有根结点)
                    这样就形成了森林 
                    把这片森林存放到 有序队列中 , 方便取出最小的节点 

            第二步: 
                    从 有序队列中, 取出两个最小的节点 
                    然后再创建一个新的节点 作为它们的父节点
                        父节点的权值 就是这两个子节点的权值之和 

            第三步: 
                    把新创建的父节点 加入到 有序队列中 

            重复 第二步和第三步, 直到这个有序队列中只剩下一个节点为止 
                最后剩下的这个节点 就是 哈夫曼树的根节点 


    4)代码实现  

            Huffman.c   /  Huffman.h    +  链式队列 

                typedef struct HTnode       //哈夫曼树的节点类型
                {
                    int weight;                 //数据域: 权值 
                    struct HTnode * left;       //指针域: 左子节点的指针
                    struct HTnode * right;              //右子节点的指针
                } HTnode ;

            
            LinkQueue.c 

/*
入队   ---> 有序队列 
	返回值:
	    1 入队成功
	    0 入队失败
*/
int EnQueue( LinkQueue *q, ElemType d )
{
    //不能入队的情况: 队列不存在 
    if( q == NULL )
    {
		return 0;
    }

    //创建一个新的数据节点空间, 并初始化
    Node * pnew = (Node *)malloc( sizeof(Node) );
    pnew->data = d;
    pnew->next = NULL;

    //入队 --> 插入有序
    if( q->num == 0 )  //从无到有
    {
		q->front = pnew;
		q->rear = pnew;
    }
    else    //从少到多 
    {
		//有序队列
		Node * p = q->front;	//遍历指针 
		Node * pre = NULL;		//指向p的前一个 
		
		while( p )
		{
			//找到第一个比它大的数的前面 (比较二叉树节点里面的权值)
			if( p->data->weight  >  pnew->data->weight )
			{
				break;
			}
			pre = p;
			p = p->next;
		}

		if( p != NULL )	//找到了 
		{
			if( p == q->front )
			{
				//头插 
				pnew->next = q->front;
				q->front = pnew;
			}
			else 
			{
				//中间插入 
				pre->next = pnew;
				pnew->next = p;
			}
		}
		else	//没有找到 
		{
			//尾插法
			q->rear->next = pnew;
			q->rear = pnew;
		}
    }
    q->num ++;

    return 1;
}
/*
	出队 
	返回值: 返回出队元素 
*/
Node * DeQueue( LinkQueue *q )
{
    //不能出队的情况: 队列不存在 || 队列为空 
    if( q == NULL || q->num == 0 )
    {
		return 0;
    }

    //出队
	Node * p = q->front;	//指向要出队的这个节点
	q->front = q->front->next;
	p->next = NULL;
	
	q->num --;

	if( q->num == 0 )	//仅剩的一个节点也被删除了,那么队尾rear也要被置NULL
	{
		q->rear = NULL;
	}

    return p;
}

            Huffman.c 

HTnode * create_Huffman()
{
    //从键盘上获取所有叶子节点的权值
    int w[MAX_LEN] = {0};
    int i;
    for( i=0; i<MAX_LEN; i++ )
    {
		scanf("%d", &w[i] );
    }

    //定义指针 保存哈夫曼树的根节点
    HTnode * r = NULL;

    //初始化一个有序队列
    LinkQueue * q = InitQueue();

    //1.构建森林 (为每一个权值 创建一个叶子节点),并且加入到 有序队列中 
	HTnode * hfmnode = (HTnode*)malloc( sizeof(HTnode) * MAX_LEN );
	for( i=0; i<MAX_LEN; i++ )
	{
		hfmnode[i].weight = w[i];
		hfmnode[i].left = NULL;
		hfmnode[i].right = NULL;

		//入队
		EnQueue( q, hfmnode+i );
	}

    while( QueueLength( q ) >= 2 )
    {
    	//2.把最小的两个节点出队, 构建父节点 
		HTnode * p1 = DeQueue( q )->data;
		HTnode * p2 = DeQueue( q )->data;

		HTnode * parent = (HTnode*)malloc( sizeof(HTnode) );
		parent->weight = p1->weight +p2->weight;
		parent->left = p1;
		parent->right = p2;
    	
		//3.把新创建的父节点 加入到有序队列中 
		EnQueue( q, parent );
    }

    //把最后剩下的节点出队, 该节点 就是 哈夫曼树的根节点 
    r = DeQueue( q )->data ;

    //销毁队列 
    DestroyQueue( q );

    //返回 哈夫曼树的根节点指针
    return r;
}


            打印出每一个叶子节点的哈夫曼编码 

//打印出每一个叶子节点的哈夫曼编码 
void print_Huffman_code( HTnode * t , int i )
{
   static int arr[32];	   //只初始化一次 

   HTnode * p = t; 
   if( p != NULL )
   {
	   if( p->left == NULL && p->right == NULL )   //叶子节点 
	   {
		   //打印编码
		   printf("%2d -- ", p->weight );

		   int j;
		   for( j=0; j<i; j++ )
		   {
			   printf("%d", arr[j] );
		   }
		   putchar('\n');
	   }
	   else 
	   {
		   arr[i] = 0;
		   print_Huffman_code( p->left , i+1 );

		   arr[i] = 1;
		   print_Huffman_code( p->right , i+1 );
	   }
   }
}


        

7.其他 

    1)树 转换成 二叉树     (参考图示)

        (1)树中所有相邻的兄弟节点 进行连接 
        (2)只保留原树的第一个子节点的连线,删除与其他子节点的连线 


    2)森林 转换成 二叉树    (参考图示)

        (1)树 转换成 二叉树 
        (2)第一棵树不动, 从第二棵树开始依次把当前二叉树 作为 前一个树的根节点的右子树 进行连接 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2155955.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java 入门指南:JVM(Java虚拟机)—— Java 类加载器详解

类加载器 类加载器&#xff08;Class Loader&#xff09;是 Java 虚拟机&#xff08;JVM&#xff09;的一部分&#xff0c;它的作用是将类的字节码文件&#xff08;.class 文件&#xff09;从磁盘或其他来源加载到 JVM 中。类加载器负责查找和加载类的字节码文件&#xff0c;并…

HTML和CSS做一个无脚本的手风琴页面(保姆级)

一、前言 使用HTML和CSS做一个无脚本的手风琴页面。让知识以自己喜欢的方式进入脑子&#xff0c;适用于很多场景&#xff0c;比如以下&#xff1a; 【注&#xff1a;图片源自百度】 二、HTML框架 使用外部样式表&#xff0c;将CSS文件用link标签引入 整体框架如下图&#x…

基于微信小程序的游泳馆管理系统--论文源码调试讲解

2 关键技术介绍 2.1 SSM框架 开发信息管理系统的主流框架是SSM&#xff08;Spring Spring MVC MyBatis&#xff09;&#xff0c;SSM框架web层使用Spring MVC框架&#xff0c;使传输前后端数据变得简单&#xff1b;对于业务层使用Spring作为轻量级控制反转和面向切面的容器框…

Leetcode面试经典150题-39.组合总数

给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target &#xff0c;找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 &#xff0c;并以列表形式返回。你可以按 任意顺序 返回这些组合。 candidates 中的 同一个 数字可以 无限制重复被选取 。如…

基于Ambari搭建hadoop生态圈+Centos7安装教程

当我们学习玩搭建hadoop的时候&#xff0c;未免也会遇见很多繁琐的事情&#xff0c;比如很多错误&#xff0c;需要解决。在以后公司&#xff0c;也不可能让你一个一个搭建hadoop&#xff0c;成千上万的电脑&#xff0c;你在一个个搭建&#xff0c;一个个报错&#xff0c;而且每…

深度学习与应用:人体关键点检测

实验二 深度学习与应用&#xff1a;人体关键点检测 1、 实验目的 了解人体关键点检测基础流程熟悉YOLOV7-pose模型结构掌握 YOLOv7-pose 模型的训练、Fine-tuning 以及推理的能力掌握YOLOV7-pose模型对实际问题的应用能力&#xff0c;了解如何在特定的场景和任务中应用该模型…

为什么git有些commit记录,只有git reflog可以看到,git log看不到?

文章目录 原因分析1. git log 只能显示 **可达的** 提交2. git reflog 记录所有引用的变更 常见导致 git log 看不到提交的原因1. git reset 操作2. git rebase 操作3. 分支删除4. git commit --amend5. 垃圾回收&#xff08;GC&#xff09;* 如何恢复 git log 看不到的提交&am…

QT实现升级进度条页面

一.功能说明 在Qt中实现固件升级的进度条显示窗口&#xff0c;你可以通过创建一个自定义的对话框&#xff08;Dialog&#xff09;来完成。这个对话框可以包含一个进度条&#xff08;QProgressBar&#xff09;、一些文本标签&#xff08;QLabel&#xff09;用于显示状态信息&am…

学生管理系统1.0版本

学生管理系统1.0版本有5个功能&#xff0c;即添加学生、删除学生、修改学生、查看全部学生、退出系统。 里面对添加重复学号、删除和修改不存在的学号等问题都有相应的解决办法。 代码区&#xff1a; Student.java package student;//快捷键Altinsert public class Student …

iOS 消息机制详解

应用 解决NSTimer、CADisplayLink循环引用。 二者都是基于runloop的定时器&#xff0c;由于处理事件内容不一样&#xff0c;runloop 每运行一次运行耗时就不一样&#xff0c;无法准确的定时触发timer的事件。 NSProxy 与 NSObject 如果继承自NSProxy 直接开始消息转发&…

【comfyUI工作流】一键生成专属欧美漫画!

现在你不需要在webui上手动设置一堆的参数 来将自己的照片转绘成欧美漫画插画 可以通过我制作的工作流一键完成转绘&#xff0c;更加效率便捷&#xff0c; 而且不需要你懂什么专业的AI绘画知识&#xff0c;会打开工作流&#xff0c;上传图片就可以 工作流特点 真实照片一键…

linux StarRocks 安装

一、检查服务器是否支持avx2&#xff0c;如果执行命令显示空&#xff0c;则不支持&#xff0c;那么安装后无法启动BE cat /proc/cpuinfo |grep avx2我的支持显示如下&#xff1a; 二、安装 docker run -p 9030:9030 -p 8030:8030 -p 8040:8040 -p 9001:9000 --privilegedtrue…

开源PHP导航网源码/精美简约网址导航收录网站/QQ技术导航程序

源码简介&#xff1a; 一款给力的开源PHP导航网源码&#xff0c;它不仅外观精美简约&#xff0c;还是个网址导航收录网站/QQ技术导航程序哦&#xff01; 在信息爆炸的时代&#xff0c;找网页就像大海捞针一样难。但是有了像PHP 导航网这样的神器&#xff0c;一切都变得简单了…

Gin框架入门(1)--路由搭建与Json处理

背景知识 为什么要使用Go框架 如果不使用框架&#xff0c;在创建服务器和调用端口时会遇到各种各样“奇怪”的问题&#xff08;就是出错的排查方向可能达到十几种&#xff09;&#xff0c;而且这些问题很难有相似性。同时作为适应于微服务的一门语言&#xff0c;代码的规范化…

【计算机组成原理】主存储器深度解析

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &#x1f4e2;本文由 JohnKi 原创&#xff0c;首发于 CSDN&#x1f649; &#x1f4e2;未来很长&#…

基于SpringBoot+Vue的时尚美妆电商网站系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 精品专栏&#xff1a;Java精选实战项目源码、Python精…

Windows下安装Neo4j流程

Neo4j简介 Neo4j 是一个基于图形结构的 NoSQL 数据库&#xff0c;专门用于存储和管理图数据。与传统的关系型数据库不同&#xff0c;Neo4j 使用 图&#xff08;graph&#xff09;的形式来表示数据&#xff0c;其中数据点&#xff08;称为 节点&#xff09;通过 边&#xff08;…

GUI编程19:贪吃蛇小游戏及GUI总结

视频链接&#xff1a;21、贪吃蛇之界面绘制_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1DJ411B75F?p21&vd_sourceb5775c3a4ea16a5306db9c7c1c1486b5 1.游戏中用的的图片素材 1.贪吃蛇游戏的主启动类StartGame&#xff1b; package com.yundait.snake;import j…

【ArcGISPro】配置模块

ArcGIS Pro 配置类似于加载项&#xff0c;但提供了扩展应用程序的其他方法。它可以帮助您设计更贴近您组织品牌和工作流的 ArcGIS Pro 版本。 托管配置是比 Add-in 更高级别的自定义。 配置可以提高加载项安全级别并添加非管理员指定的已知文件夹。 配置可以提供比插件更广泛…

全国832个贫困县名单及精准扶贫脱贫数据(2016-2020.11)

自党的十八大以来&#xff0c;通过全党全国各族人民的共同努力&#xff0c;中国成功实现了现行标准下9899万农村贫困人口的全部脱贫&#xff0c;832个贫困县全部摘帽。 摘帽名单 2016年-2020.11全国832个贫困县名单及精准扶贫脱贫数据整理&#xff08;大数据&#xff09;https…