数据结构初阶------二叉树(2)

链式二叉树结构的实现

1、了解树的遍历方式

之前我们提到树的定义递归式的

所以实现二叉树之前我们先了解一下链式二叉树的遍历方式

前序遍历:将二叉树递归式地按照:根、左子树、右子树执行

中序遍历:将二叉树递归式地按照:左子树、根、右子树执行

后序遍历:将二叉树递归式地按照:左子树、右子树、根执行

以下图为例:

前序遍历:1,2,3,NULL,NULL,NULL,4,5,NULL,NULL,6,NULL,NULL

中序遍历:NULL,3,NULL,2,NULL,1,NULL.5,NULL,4,NULL,6,NULL

后序遍历:NULL,NULL,3,NULL,2,NULL,NULL,5,NULL,NULL,6,4,1

2、构建二叉树

节点结构每个父节点最多有两个孩子节点

typedef char BTDataType;


typedef struct BinaryTreeNode
{
	BTDataType val;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;

之前只是一个节点,现在还需要通过一个个节点来构建二叉树

有两种构建方式

手动构建

BTNode* BTCreate()
{

    BTNode* node1 = BuyNode(1);
    BTNode* node2 = BuyNode(2);
    BTNode* node3 = BuyNode(3);
    BTNode* node4 = BuyNode(4);
    BTNode* node5 = BuyNode(5);
    BTNode* node6 = BuyNode(6);

    node1->left = node2;
    node1->right = node4;
    node2->left = node3;
    node4->left = node5;
    node4->right = node6;

    return node1;

}

前序遍历构建

用前序把输入的字符串构建成二叉树  "#"代表NULL

输入的字符串需要传它的首元素地址过来,用参数接收

我们还需要一个数组下标来一次次访问数组元素

注意:要传下标的地址过来

因为不只只访问一个元素,下标在函数的递归过程中会不断改变

如果传的不是地址的话,你懂的(形参的改变不会影响实参)

 进入函数之后我们要判断数组当前元素是否为空,也就是"#"

为空的话那就不用存了,解引用下标指针然后++,返回NULL就行了

不是空呢,就要扩容了,不然没空间给节点存

扩容完把当前数组元素的值赋给当前节点的值

下标指针继续往后走

由于是前序遍历,按照其特点,上面内容为根部分,下面就是左右子树部分了

一个节点有两个指针连着它的左右孩子节点

那就左边孩子先调用该函数

然后右边孩子在调用该函数

这样层层深入,最后函数底部返回根节点,只要函数能走到最后那就能返回上一层函数

// 通过前序遍历的数组构建二叉树
BTNode* BTCreate(BTDataType* a,int* pi)
{
	assert(a);
	assert(pi);
    //#说明为空返回上一层函数栈帧
	if (a[(*pi)] == "#")
	{
        (*pi)++;
		return NULL;
	}
    //不是空,需要开辟一个节点的空间来存放
	BTNode* root = (BTNode*)malloc(sizeof(BTNode));	
	if (root == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
    //把数组的值赋给节点里的值
	root->val = a[(*pi)];
	(*pi)++;
    //往下递归
	root->left = BTCreate(a,pi);
	root->right = BTCreate(a,pi);
	//返回上一层
	return root;
}

3、二叉树的销毁

销毁二叉树能从根节点开始销毁吗?自然是不能的,销毁就找不到后面的子孙了

所以应该从叶子节点开始往根节点销毁,那就是最后再销毁根,这和我们上面提到的后序遍历思路是不是一样啊

所以我们采用后序遍历的思路,当然如果节点为空那就不用销毁了直接返回空就行

先走左孩子,再走有孩子,最后释放该节点,归零置空一条龙

    void BTDestory(BTNode* root)
    {
    	
    	if (root == NULL)
    	{
    		return;
    	}
    	//走到这说明根节点非空
        //后序递归,从叶子销毁到根
    	BTDestory(root->left);
    	BTDestory(root->right);
    	free(root);
    	root->val = 0;
    	root->left = NULL;
    	root->right = NULL;
    }

4、二叉树节点个数

下面我写一下常见的错误思路

int TreeSize(BTnode* root)
{
    //递归需要保证过程中size会保存
    static int size =0;
    //节点为空返回0
    if(root==NULL)
    {
        return 0;
    }
    else
    {
        //节点非空
        ++size;
    }
    TreeSize(root->left);
    TreeSize(root->left);
    return size;
}

缺点:static在静态区,只会初始化一次,多次调用会累加

就是这个函数只能用一次,第二次它就废了

优化:将size改为全局变量

int size = 0;
int TreeSize(BTnode* root)
{
    //节点为空返回0
    if(root==NULL)
    {
        return 0;
    }
    else
    {
        //节点非空
        ++size;
    }
    TreeSize(root->left);
    TreeSize(root->left);
    return size;
}

这种也有缺点,每次调用该函数都要把size置为0,而且不能多线程调用该函数

最优的解法是用分治的思路

让下属去管下属的下属,然后让他们一层层汇报上来(后序的思路)

节点为空返回0

非空那就先左孩子进入函数,进入函数时该节点(左孩子)为空返回0

然后有孩子进入函数,节点(右孩子)为空返回0

如果左右孩子为空但是节点本身不为空那就返回1

    int BTSize(BTNode* root)
    {
    	if (root == NULL)
    	{
    		return 0;
    	}
    	//左子树+右子树+根节点本身
    	return BTSize(root->left) + BTSize(root->right) + 1;
    }

5、二叉树叶子节点个数

叶子节点的特点是什么?左右节点都为空

如果节点为空那就不用找了直接返回0

那就要以左右节点都为空的为条件找节点

如果节点非空且左右孩子也非空,那就继续往下走直到走到叶子节点或空节点

    // 二叉树叶子节点个数
    int BTLeafSize(BTNode* root)
    {
    	if (root == NULL)
    	{
    		return 0;
    	}
    	//根节点非空    只有根节点(左右节点为空)
    	if (root->left == NULL && root->right == NULL)
    	{
    		return 1;
    	}
    	return BTLeafSize(root->left) + BTLeafSize(root->right);
    }

6、求第k层节点的个数

不知道k是多少,所以我们需要一个新的参数来存放k的值,k代表着第k层

换个思路总共有x层,从根开始,每往下走一层k就减1,当k减到1时就代表走到第k层(前序思路)

节点为空时直接返回0;当前节点k=1时就返回1

当前树的第k层=左子树的第k-1层+右子树的第k-1层

    int BTLevelKSize(BTNode* root, int k)
    {
    	assert(k > 0);
    	if (root == NULL)
    	{
    		return 0;
    	}
    	//当k等于1时说明正在第k层
    	if (k == 1)
    	{
    		return 1;
    	}
    	//不在k层往下走,然后k-1
    	return BTLevelKSize(root->left, k - 1) + BTLevelKSize(root->right, k - 1);
  }

7、二叉树查找值为x的节点

需要一个新的参数x,用来判断节点的值是否和值x一样

思路:还是前序遍历的思路

根节点为空直接返回,节点的值如果等于x,返回该节点(注意:返回值是指针要用指针接收)

创建一个新的指针变量用来存放值为x的节点的地址

左边找到了(指针不指向空),就返回该指针变量,找不到就到右边去找

右边也找不到就返回空

  BTNode* BTFind(BTNode* root, BTDataType x)
  {
  	if (root == NULL)
  	{
  		return NULL;
	}
    //节点非空    根
	if (root->val == x)
	{
		return root;
	}
    //创建指针
	BTNode* ret = NULL;
    //左
    ret = BTFind(root->left, x);
	if (ret != NULL)
	{
		return ret;
	}
    //右
	ret = BTFind(root->right, x);
	if (ret != NULL)
	{
		return ret;
	}
    //找不到
	return NULL;
}

8、前、中、后序遍历

思路前面讲过了

讲讲别的

递归的思路(二叉树)

把大问题分为子问题,返回条件(最小规模子问题)

(DFS)深度优先遍历:前序遍历      不严格来说:前、中、后序都算深度优先遍历

(BFS)广度优先遍历:层序遍历     一般用队列来配合实现

// 二叉树前序遍历 
void PrevOrder(BTNode* root)
{
	if (root==NULL)
	{
		return;
	}
	printf("%d ", root->val);
	PrevOrder(root->left); 
	PrevOrder(root->right);
}


// 二叉树中序遍历
void InOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	InOrder(root->left);
	printf("%d ", root->val);
	InOrder(root->right);
}


// 二叉树后序遍历
void PostOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	PostOrder(root->left);
	PostOrder(root->right);
	printf("%d ", root->val);
}

9、二叉树的层序遍历

层序遍历顾名思义就是一层一层地往下遍历

我们可以使用队列的结构来辅助完成这一目标,队列的特点是先进先出

通过这个特点我们可以先往队列里入根节点,在根节点出队列之前用一个指针变量来获取队列的队头(根节点)用来打印,入完趁着根节点还没被删除,如果其左、右孩子非空的话,利用其存放指向其左、右孩子节点的指针去将其左、右孩子拉入队列中,最后删除队头

后面同理,边入队列边出队列直到队列为空(没有节点可入)时,遍历结束

void LevelOrder(BTNode* root)
{
	DL q;
	DLInit(&q);
	if (root)
		DLPush(&q, root);
	while (!DLEmpty(&q))
	{
		BTNode* front = DLFront(&q);
		printf("%d ", front->val);
		if (front->left)
			DLpush(&q, front->left);
		if (front->right)
			DLpush(&q, front->right);
		DLPop(&q);
	}
	printf("\n");


	DLDestroy(&q);
}

10、判断二叉树是否为完全二叉树

二叉树的特点,非空节点是连续的,就是完全二叉树

由于需要判断有无空节点,需要用队列录入NULL节点,思路是层序遍历的思路

层序遍历一层层入数据,一边入一边出,当队头为空时就条出循环;

这时遇到空节点了,但是不知道是正常情况的空节点(二叉树走到最后面全是空节点)

还是说遇到"断点"了,非空节点中插了个空节点

接下来二叉树继续往后遍历,如果说还有非空节点说明是非空节点中插了个空节点,返回假

如果遍历完还没遇到非空节点说明是走到最后面,返回真

bool BinaryTreeComplete(BTNode* root)
{
	DL q;
	DLInit(&q);
	if (root)
		DLPush(&q, root);
	while (!DLEmpty(&q))
	{
		BTNode* front = DLFront(&q);
		if (front == NULL)
			break;
		DLpush(&q, front->left);
		DLpush(&q, front->right);
		DLPop(&q);
	}
	//走到这已经遇到空节点
	while (!DLEmpty(&q))
	{
		BTNode* front = DLFront(&q);
		DLPop(&q);
		if (front != NULL)
		{
			return false;
			DLDestroy(&q);
		}
		DLDestroy(&q);
	}
	return true;
	DLDestroy(&q);
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值