数据结构——二叉树(3)

165 阅读5分钟

链式存储结构

若不是完全二叉树,链式结构更适合存储数据

前、中、后序遍历

任何一棵树,都要分成根、左子树、右子树

前序遍历:访问根结点的操作发生在遍历其左右子树之前

中序遍历:访问根结点的操作发生在遍历其左右子树之间

后序遍历:访问根结点的操作发生在遍历其左右子树之后

前序:根、左子树、右子树

中序:左子树、根、右子树

后序:左子树、右子树、根

区别:访问根的时机不同

例:以#代表空树,正确的遍历顺序如下:

image.png

任何一棵树都要分成根、左子树、右子树.

子树也要分成根、左子树、右子树.

前序遍历:1 2 3 # # # 4 5 # # 6 # #

中序遍历:# 3 # 2 # 1 # 5 # 4 # 6 #

后序遍历:# # 3 # 2 # # 5 # # 6 4 1

定义二叉树的结点

typedef char BTDataType;
typedef struct binaryTreeNode
{
	struct binaryTreeNode* left;//指向左孩子结点
	struct binaryTreeNode* right;//指向右孩子结点
	BTDataType val;
}BTNode;

递归形式的前、中、后序遍历

//前序遍历—— 根、左子树、右子树
void preOrder(BTNode* root)
{
	//空树打印#
	if (root == NULL)
	{
		printf("# ");
		return;
	}
	//访问根结点
	printf("%c ", root->val);
	//遍历左子树
	preOrder(root->left);
	//遍历右子树
	preOrder(root->right);
}

//中序遍历——左子树、根、右子树
void inOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("# ");
		return;
	}
	inOrder(root->left);
	printf("%c ", root->val);
	inOrder(root->right);
}

//后序遍历——左子树、右子树、根
void postOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("# ");
		return;
	}
	postOrder(root->left);
	postOrder(root->right);
	printf("%c ", root->val);
}

根据字符串前序构建二叉树

来源:www.nowcoder.com/practice/4b…

//申请二叉树新结点
BTNode* buyBinaryTreeNewNode(BTDataType x)
{
	BTNode* newNode = (BTNode*)malloc(sizeof(BTNode));
	assert(newNode);
	newNode->left = NULL;
	newNode->right = NULL;
	newNode->val = x;
	return newNode;
}

BTNode* createBinaryTree2(char* str, int* index)
{
	//如果遍历到'#',返回空树
	if (str[*index] == '#')
	{
		(*index)++;
		return NULL;
	}
	//先构建根结点
	BTNode* root = buyBinaryTreeNewNode(str[*index]);
	(*index)++;
	//再构建左子树和右子树
	root->left = createBinaryTree2(str, index);
	root->right = createBinaryTree2(str, index);
	return root;
}

二叉树的销毁

void BinaryTreeDestroy(BTNode* root)
{
	if (root == NULL)
		return;
	BinaryTreeDestroy(root->left);
	BinaryTreeDestroy(root->right);
	free(root);
}

二叉树的结点树

int binaryTreeSize(BTNode* root)
{
	//如果是空树,空树的结点数是0
	//二叉树的结点数 = 根结点( 1 )+ 它左子树的结点数和右子树的结点数
	return root == NULL ? 0 : 1 + 
		binaryTreeSize(root->left) + binaryTreeSize(root->right);
}

二叉树的叶子结点数

int binaryTreeLeafSize(BTNode* root)
{
	//如果是空树,叶结点数为0
	if (root == NULL)
		return 0;
	//如果这棵树的左子树是空,右子树也是空,这棵树的根结点就是叶子结点
	if (root->left == NULL && root->right == NULL)
		return 1;
	//否则求这棵树的左子树叶子结点数+右子树叶子结点数
	return binaryTreeLeafSize(root->left) + binaryTreeLeafSize(root->right);
}

求第k层结点数

int treeKLevel(BTNode* root, int k)
{
	assert(k >= 1);
	//如果是空树,结点数为0
	if (root == NULL)
		return 0;
	//如果k = 1,根结点就是这一层的结点
	if (k == 1)
		return 1;
	//否则求左子树的第(k-1)层结点数+右子树的(k-1)层结点数
	return treeKLevel(root->left, k - 1) + treeKLevel(root->right, k - 1);
}

求二叉树的深度

int treeDepth(BTNode* root)
{
	//空树的深度是0
	if (root == NULL)
		return 0;
	//求出左子树的深度和右子树的深度
	int leftTreeDepth = treeDepth(root->left);
	int rightTreeDepth = treeDepth(root->right);
	//比较两棵子树的深度,返回较大的深度+原树的根深度
	return leftTreeDepth > rightTreeDepth ? leftTreeDepth + 1 : rightTreeDepth + 1;
}

二叉树中查找值为x的结点

BTNode* binaryTreeFind(BTNode* root, BTDataType x)
{
	//如果是空树,没找到
	if (root == NULL)
		return NULL;
	//找到后返回给 调用该函数的函数
	if (root->val == x)
		return root;
	//如果这棵树的根结点不是,先去左子树找,找到了直接返回
	BTNode* leftTreeFind = binaryTreeFind(root->left, x);
	if (leftTreeFind != NULL)
		return leftTreeFind;
	//再去右子树找,找到了直接返回
	BTNode* rightTreeFind = binaryTreeFind(root->right, x);
	if (rightTreeFind != NULL)
		return rightTreeFind;
	//左右子树都没有找到
	return NULL;
	/*每一次return都是返回给递归调用该函数的上一层*/
}

层序遍历(需要队列)

思路:将根结点入队列,取队头数据打印出来,再出队头数据,

然后将队头的左孩子和右孩子入队列,上一层出队列,下一层有序入队列.

注意:二叉树的空结点不进队列.

image.png

void levelOrder(BTNode* root)
{
	if (root == NULL)
		return;
	Queue q;
	QueueInit(&q);
        //将根节点入队列
	QueuePush(&q, root);
	while (!QueueEmpty(&q))
	{
                //取队头数据,并打印
		BTNode* cur = QueueFront(&q);
		printf("%c ", cur->val);
                //出队头数据
		QueuePop(&q);
                //将队头的左孩子和右孩子入队列
		if (cur->left)
			QueuePush(&q, cur->left);
		if (cur->right)
			QueuePush(&q, cur->right);
	}
	QueueDestroy(&q);
}

判断是否为完全二叉树

完全二叉树:前(n-1)层是满的,第N层不一定满,但从左到右一定是连续的.

思路:用层序遍历的方法遍历二叉树,但把NULL一起入队列。

如果是完全二叉树,结点之间是连续的,NULL后面不会有其它非空结点.

所以当遍历到NULL时退出遍历,看队列是否还存有二叉树的非空结点.

image.png

bool isCompleteTree(BTNode* root)
{
	if (root == NULL)
		return true;
	Queue q;
	QueueInit(&q);
	QueuePush(&q,root);
	BTNode* cur = QueueFront(&q);
        //层序遍历二叉树,直到遍历到NULL结束
	while (cur != NULL)
	{
                //出队头数据
		QueuePop(&q);
                //将下一层的结点入队列
		QueuePush(&q, cur->left);
		QueuePush(&q, cur->right);
                //更新队头数据
		cur = QueueFront(&q);
	}
        //遍历到NULL后,看队列是否还存有二叉树的非空结点
	while (!QueueEmpty(&q))
	{
		if (QueueFront(&q) != NULL)
		{
			QueueDestroy(&q);
			return false;
		}
		QueuePop(&q);
	}
	QueueDestroy(&q);
	return true;
}