使用链式存储结构存储二叉树的相关操作:**
// 二叉树的创建
BTNode* CreateBinTree(BTDataType* array, int size, BTDataType invalid);
// 二叉树的拷贝
BTNode* CopyBinTree(BTNode* pRoot);
// 二叉树的销毁
void DestroyBinTree(BTNode** pRoot);
// 递归:前序遍历
void PreOrder(BTNode* pRoot);
// 递归:中序遍历
void InOrder(BTNode* pRoot);
// 递归:后序遍历
void PostOrder(BTNode* pRoot);
// 层序遍历
void LevelOrder(BTNode* pRoot);
// 获取二叉树中节点个数
int GetBinTreeSize(BTNode* pRoot);
// 获取二叉树中第K层节点个数
int GetKLevelNodeCount(BTNode* pRoot, int K);
// 获取二叉树中叶子节点个数
int GetLeafCount(BTNode* pRoot);
// 获取二叉树深度(高度)
int GetBinTreeHeight(BTNode* pRoot);
// 检测值为x的元素是否在二叉树中,在返回该节点的地址,否则返回NULL
BTNode* BinaryTreeFind(BTNode* pRoot, BTDataType x);
// 二叉树的镜像
void Mirror(BTNode* pRoot);
// 判断二叉树是否是完全二叉树
int BinaryTreeComplete(BTNode* pRoot);
二叉树链式结构结点定义(孩子表示法):
typedef char BTDataType;
typedef struct BTNode
{
struct BTNode* _pLeft;
struct BTNode* _pRight;
BTDataType _data;
}BTNode;
1.二叉树的创建
在这里对底层结构进行了一层简单的封装,为了用户更简单的理解和使用,用户只需要传入存放数据的array,元素个数size,和无效符号invalid就可以使用了
//申请新节点
BTNode* BuyBinTreeNode(BTDataType data)
{
BTNode* pNewNode = (BTNode*)malloc(sizeof(BTNode));
if (NULL == pNewNode)
{
assert(0);
return NULL;
}
pNewNode->_data = data;
pNewNode->_pLeft = NULL;
pNewNode->_pRight = NULL;
return pNewNode;
}
BTNode* _CreateBinTree(BTDataType* array, int size,int* index,BTDataType invalid)
{
BTNode* pRoot = NULL;
if (*index < size && invalid != array[*index])
{
//根节点
pRoot = BuyBinTreeNode(array[*index]);
//根的左子树
++(*index);
pRoot->_pLeft = _CreateBinTree(array, size, index,invalid);
//根的右子树
++(*index);
pRoot->_pRight = _CreateBinTree(array, size, index,invalid);
}
return pRoot;
}
//二叉树的创建
BTNode* CreateBinTree(BTDataType* array, int size, BTDataType invalid)
{
int index = 0;
return _CreateBinTree(array, size, &index, invalid);
}
2.二叉树的拷贝
BTNode* CopyBinTree(BTNode* pRoot)
{
BTNode* NewpRoot = NULL;
if (NULL == pRoot)
return 0;
NewpRoot = BuyBinTreeNode(pRoot->_data);
NewpRoot->_pLeft = CopyBinTree(pRoot->_pLeft);
NewpRoot->_pRight = CopyBinTree(pRoot->_pRight);
return NewpRoot;
}
3.二叉树的销毁
如果先删除根节点那么就找不到该节点的左右孩子了,所以必须将左右孩子先删除,才能删除根节点
//二叉树的销毁
void DestroyBinTree(BTNode** pRoot)
{
assert(pRoot);
if (*pRoot)
{
DestroyBinTree(&(*pRoot)->_pLeft);
DestroyBinTree(&(*pRoot)->_pRight);
free(pRoot);
}
}
4.前序、中序、后序遍历
- NLR:前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。
- LNR:中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。
- LRN:后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。
//前序遍历
void PreOrder(BTNode* pRoot)
{
if (pRoot)
{
printf("%c", pRoot->_data);
PreOrder(pRoot->_pLeft);
PreOrder(pRoot->_pRight);
}
}
//中序遍历
void InOrder(BTNode* pRoot)
{
if (pRoot)
{
InOrder(pRoot->_pLeft);
printf("%c", pRoot->_data);
InOrder(pRoot->_pRight);
}
}
//后序遍历
void PostOrder(BTNode* pRoot)
{
if (pRoot)
{
PostOrder(pRoot->_pLeft);
PostOrder(pRoot->_pRight);
printf("%c", pRoot->_data);
}
}
5.层序遍历
层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第2层上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。
在这里借助队列先入先出的特性来完成层序遍历:
- 定义一个队列
- 将头结点入队列
- 左孩子存在,则将左孩子入队列
- 右孩子存在,则将右孩子入队列
- 删除队头元素
// 层序遍历
void LevelOrder(BTNode* pRoot)
{
if (NULL == pRoot)
return 0;
Queue q; //定义一个队列
QueueInin(&q);
QueuePush(&q, pRoot); //将头结点入队列
while (!QueueEmpty(&q))
{
BTNode* pCur = QueueFront(&q);
printf("%c", pCur->_data);
if (pCur->_pLeft)
QueuePush(&q, pCur->_pLeft); //先将左孩子入队列
if (pCur->_pRight)
QueuePush(&q, pCur->_pRight); //再将右孩子入队列
QueuePop(&q); //删除队头元素
}
QueueDestroy(&q); //销毁队列
printf("\n");
}
6.二叉树中节点个数
二叉树中结点个数:左子树结点个数+右子树结点个数+1(根结点)
int GetBinTreeSize(BTNode* pRoot)
{
if (NULL == pRoot)
return 0;
return GetBinTreeSize(pRoot->_pLeft) + GetBinTreeSize(pRoot->_pRight) + 1;
}
7.二叉树中第K层节点个数
// 获取二叉树中第K层节点个数
int GetKLevelNodeCount(BTNode* pRoot, int K)
{
if (NULL == pRoot || K <= 0)
return 0;
if (1 == K)
return 1;
return GetKLevelNodeCount(pRoot->_pLeft, K - 1) + GetKLevelNodeCount(pRoot->_pRight, K - 1);
}
8.二叉树中叶子节点个数
// 获取二叉树中叶子节点个数
int GetLeafCount(BTNode* pRoot)
{
if (NULL == pRoot)
return 0;
//如果只有一个结点则返回1
if (NULL == pRoot->_pLeft && NULL == pRoot->_pRight)
return 1;
//
return GetLeafCount(pRoot->_pLeft) + GetLeafCount(pRoot->_pRight);
}
9.二叉树深度(高度)
树的深度=max(左子树深度,右子树深度)+1(根节点深度)
// 获取二叉树深度(高度)
int GetBinTreeHeight(BTNode* pRoot)
{
if (NULL == pRoot)
return 0;
int maxLeft = GetBinTreeHeight(pRoot->_pLeft); //左子树的深度
int maxRight = GetBinTreeHeight(pRoot->_pRight);//右子树的深度
if (maxLeft < maxRight)
return maxRight+1;
return maxLeft+1;
}
10.检测值为x的元素是否在二叉树中,在返回该节点的地址,否则返回NULL
// 检测值为x的元素是否在二叉树中,在返回该节点的地址,否则返回NULL
BTNode* BinaryTreeFind(BTNode* pRoot, BTDataType x)
{
BTNode* pRet = NULL;
if (NULL == pRoot)
return NULL;
if (pRoot->_data == x)
return pRoot;
if (pRet = BinaryTreeFind(pRoot->_pLeft, x))
return pRet;
return BinaryTreeFind(pRoot->_pRight, x);
}
11.二叉树的镜像
二叉树的镜像就是将从根节点开始的每一个节点的左右子节点交换位置
void Swap(BTNode* pLeft, BTNode* pRight)
{
BTNode* pTemp = pLeft;
pLeft = pRight;
pRight = pTemp;
}
// 二叉树的镜像
//递归方式
void Mirror(BTNode* pRoot)
{
if (pRoot)
{
Swap(&pRoot->_pLeft, &pRoot->_pRight);
Mirror(pRoot->_pLeft);
Mirror(pRoot->_pRight);
}
}
//非递归方式
void Mirror(BTNode* pRoot)
{
Queue q;
QueueInit(&q);
QueuePush(&q, pRoot);
while (!QueueEmpty(&q))
{
BTNode* pCur = QueueFront(&q);
Swap(&pRoot->_pLeft, &pRoot->_pRight);
if (pCur->_pLeft)
QueuePush(&q, pCur->_pLeft);
if (pCur->_pRight)
QueuePush(&q, pCur->_pRight)
QueuePop(&q);
}
}
12. 判断二叉树是否是完全二叉树
// 判断二叉树是否是完全二叉树
int BinaryTreeComplete(BTNode* pRoot) {
if (pRoot == NULL)
return 1;
Queue q;
QueueInit(&q);
QueuePush(&q, pRoot);
while (pRoot != NULL) {//若节点不为空,就进行出队列,并入队节点的左右孩子
pRoot = QueueFront(&q);
QueuePop(&q);
if (pRoot != NULL) {
QueuePush(&q, pRoot->_pLeft);
QueuePush(&q, pRoot->_pRight);
}
}
while (!QueueEmpty(&q)) {//节点为NULL但队列不空时
pRoot = QueueFront(&q);
QueuePop(&q);
if (pRoot == NULL)//在非空节点后又出现了NULL节点,说明不是完全二叉树
return -1;
}
return 1;
}
总结:二叉树的链式结构的相关操作,很多操作都是使用递归来完成的,这一切都是因为二叉树的特殊性,将一棵二叉树分为根节点、左子树和右子树三大部分,再结合具体的问题,就可以使用递归完成相关操作了