二叉树链式结构及实现
数据结构和接口函数
typedef int BTDataType;
typedef struct BinaryTreeNode
{
BTDataType _data;
struct BinaryTreeNode* _left;
struct BinaryTreeNode* _right;
}BTNode;
接口函数
BTNode* BuyNode( BTDataType x);//新节点的创建
BTNode* CreatBinaryTree();//自己手动创建一个二叉树
void PreOrder(BTNode* root);//前序遍历
void InOrder(BTNode* root);//中序遍历
void PosOrder(BTNode* root);//后序遍历
int TreeSize(BTNode* root);//树结点个数
int BinaryTreeLeafSize(BTNode* root);// 二叉树叶子节点个数
int TreeHeight(BTNode* root);//树的高度
int TreeKLevel(BTNode* root, int k);//第k层的节点个数
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);//查找二叉树结点并返回
void BinaryTreeLevelOrder(BTNode* root);// 层序遍历
int BinaryTreeComplete(BTNode* root);//是否为完美二叉树
void BinaryTreeDestory(BTNode** root);//二叉树的销毁
新结点的创建和手动创建一个二叉树
BTNode* BuyNode(BTDataType x)//新节点的创建
{
BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
if (newnode == NULL)
{
perror("BuyNode malloc fail");
return NULL;
}
newnode->data = x;
newnode->_left = NULL;
newnode->_right = NULL;
return newnode;
}
手动创建一个二叉树
BTNode* CreatBinaryTree()//自己手动创建一个二叉树
{
//结点的创建
BTNode* node1 = BuyNode(1);
BTNode* node2 = BuyNode(2);
BTNode* node3 = BuyNode(3);
BTNode* node4 = BuyNode(4);
BTNode* node5 = BuyNode(5);
BTNode* node7 = BuyNode(7);
//结点的链接
node1->_left = node2;
node1->_right = node3;
node2->_left = node4;
node2->_right = node5;
node3->_right = node7;
return node1;
}
该树的结构
前序遍历
先看代码吧,思路比较简单。
void PreOrder(BTNode* root)//前序遍历
{
if (root == NULL)
{
printf("NULL ");
return;
}
printf("%d ", root->data);
PreOrder(root->_left);
PreOrder(root->_right);
}
前序、中序、后序遍历用递归实现的思路很简单,那前序来举例子。先访问根节点然后,如果根节点为空,就打印NULL;根节点不为空的话,就访问该根节点的左子树,接下来把左子树当成根节点来重新去进入PreOrder函数中去,然后反复循环直到遇到左子树为空,一步一步的返回访问右子树。递归还是画图最清晰了,来看图。
中序遍历
void InOrder(BTNode* root)//中序遍历
{
if (root == NULL)
{
printf("NULL ");
return;
}
PreOrder(root->_left);
printf("%d ", root->data);
PreOrder(root->_right);
}
后序遍历
void PosOrder(BTNode* root)//后序遍历
{
if (root == NULL)
{
printf("NULL ");
return;
}
PreOrder(root->_left);
PreOrder(root->_right);
printf("%d ", root->data);
}
二叉树的高度
递归就是一个分治思想,我的理解就是把问题拆分成一个个小问题去想,拆成最基础的一个小问题。
思路:拿最1、2、3最基础的二叉树来想,二叉树的高度的高度就是根节点左子树或右子树的高度+1
但是当当树变成了以下的情况,根节点1的高度就是取左子树和右子树中高度最大的+1
大方向有了,那么再考虑一下特殊情况,如果根节点为空的时候,就为空树,则返回0。
那么上代码
int TreeHeight(BTNode* root)//树的高度
{
if (root == NULL)
{
return 0;
}
//这里不建议采取以下的思路来写,因为这样左子树、右子树的高度算出来进行比较之后,因为没有用变量将这两个值存起来,然后后面+1返回的时候还要调用一次左子树或者右子树这样很浪费时间复杂度。
/*return TreeHeight(root->_left) > TreeHeight(root->_left)?
TreeHeight(root->_left)+1:
TreeHeight(root->_left);*/
int LeftHeight = TreeHeight(root->_left);
int RightHeight = TreeHeight(root->_right);
//这里用的三目操作符
return LeftHeight > RightHeight ?
LeftHeight + 1 :
RightHeight + 1;
}
二叉树的节点个数
先拿最基本的1、2、3子树来思考问题,那么这个二叉树的结点就是左子树的结点+右子树的结点+1(自身);当根节点为空的时候返回0。
上代码
int TreeSize(BTNode* root)//树结点个数
{
if (root == NULL)
{
return 0;
}
return TreeSize(root->_left) + TreeSize(root->_right) + 1;
}
二叉树的叶子节点个数
思路:左子树叶子结点个数+右子树叶子结点个数,如果根节点为空返回0,空树没有叶子结点;根节点不为空,没有左右子树的时候返回1。
突然发现二叉树的递归就像现在这阶层型社会把当下的问题不断的去推给自己的下一级,哈哈哈。
上代码:
int BinaryTreeLeafSize(BTNode* root)// 二叉树叶子节点个数
{
if (root == NULL)
{
return 0;
}
if (root->_left == NULL && root->_right == NULL)
{
return 1;//结点的左右子树都是空的话就返回1
}
return BinaryTreeLeafSize(root->_left) + BinaryTreeLeafSize(root->_right);
}
二叉树第k层节点个数
思路:第k层的节点个数等于左子树的第k-1层的结点加上右子树k-1层的结点。当k=1的时候返回1;有一种情况是k还没递减到1,遇到空了,那么返回0;
int TreeKLevel(BTNode* root, int k)//第k层的节点个数
{
assert(k > 0);//没有第0层
if (k == 1)
{
return 1;
}
return TreeKLevel(root->_left, k - 1)
+TreeKLevel(root->_right, k - 1);
}
查找二叉树的某个节点
思路:直接用前序的思路遍历二叉树,找到x返回根节点,然后注意函数的返回值。
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)//查找二叉树结点并返回 ps:注意返回值
{
if (root == NULL)
{
return NULL;//空的时候返回NULL
}
if (root->data == x)
{
return root;
}
BTNode* lret = BinaryTreeFind(root->_left, x);
if (lret)
{
return lret;
}
BTNode* rret = BinaryTreeFind(root->_right, x);
if (rret)
{
return rret;
}
return NULL;
}
层序遍历
思路:用队列来的先进先出的思路来实现,如果根节点不为空则入队,接下来取队头,然后将队头出队列,接下来把队头不为空的孩子结点入队列,然后反复循环该操作。
直接上代码:
//队列代码
typedef struct BinaryTreeNode* QDataType;//数据类型这里队列数据类型为二叉树结构体指针
typedef struct QueueNode
{
struct Queue* next;//下一个节点
QDataType data;//数据
}QNode;
typedef struct Queue
{
QNode* head;
QNode* tail;
int size;
}Queue;
void QueueInit(Queue* pq)//初始化
{
assert(pq);
pq->head = pq->tail = NULL;
pq->size = 0;
}
void QueuePush(Queue* pq,QDataType x)//插入(尾插)
{
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloc\n");
return;
}
//新节点的初始化
newnode->data = x;//数据的写入
newnode->next = NULL;//因为是尾插 所以尾指针为NULL
//判断队列是否为NULL,为空就得修改头指针
if (pq->head == NULL)//如果插入前队列为空
{
assert(pq->tail == NULL);//如果head为空 tail不为空就有问题
pq->head = pq->tail = newnode;
}
else //插入前队列不为空
{
pq->tail->next = newnode;
pq->tail = pq->tail->next;//尾节点向后遍历
}
pq->size++;//队列长度自增1
}
void QueuePop(Queue* pq)//删除(头删)
{
assert(pq);
assert(pq->head);//空队列不能删除
QNode* cur = pq->head;
pq->head = pq->head->next;
free(cur);
cur = NULL;
pq->size--;
if (pq->head == NULL)//队列中只有一个元素删除了之后 head为NULL,但是此时tail为野指针
{
pq->tail = NULL;
}
}
bool QueueEmpty(Queue* pq)//判断队列是否为空
{
assert(pq);
return pq->size == 0;
}
QDataType QueueFront(Queue* pq)//取队头的数据
{
assert(pq);
assert(!QueueEmpty(pq));//队列不为空
return pq->head->data;
}
void QueueDestroy(Queue* pq)//销毁
{
assert(pq);
QNode* cur = pq->head;
while (cur)
{
QNode* next = cur->next;
free(cur);
cur = next;
}
pq->head = pq->tail = NULL;//防止野指针的生成
pq->size = 0;
}
// 层序遍历
void BinaryTreeLevelOrder(BTNode* root)
{
Queue q;
QueueInit(&q);//创建队列
if (root)
{
QueuePush(&q, root);//队列不为空就入栈
}
while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);//取队头结点
printf("%d,", front->data);//打印队头数据
QueuePop(&q);//出队
//如果有左右孩子就将左右孩子入栈
if (front->_left)
{
QueuePush(&q, front->_left);
}
if (front->_right)
{
QueuePush(&q, front->_right);
}
}
QueueDestroy(&q);
}
判断二叉树是否为完美二叉树
完美二叉树的性质:空结点用层序遍历的话必须是连续的
思路:用层序遍历的原理遍历二叉树然后将所有结点都存放在队列里面,包括空结点,直到pop到第一个空结点的时候,跳出遍历循环,接下来遍历剩下的队列,如果队列中有空结点,那么不是完全二叉树,如果没有空结点则是完全二叉树。
int BinaryTreeComplete(BTNode* root)//是否为完美二叉树
{
// write code here
Queue q;//创建一个队列
QueueInit(&q);//队列的初始化
if (root == NULL)
{
return false;//空树
}
else//不是空树则入队
{
QueuePush(&q, root);
//层序遍历找空结点
while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
QueuePop(&q);
if (front == NULL)
{
//找到空结点跳出循环
break;//完全二叉树的层序遍历非空结点是连续的,如果遇到空结点,判断后面的结点是否有非空结点,有非空结点的话就不是完全二叉树。
}
else {//空结点也入队列,用来寻找空结点
QueuePush(&q, front->_left);
QueuePush(&q, front->_right);
}
}
//用层序遍历剩下的判断是否有非空结点
while (!QueueEmpty(&q))
{
struct TreeNode* front = QueueFront(&q);
if (front)
{
//若有非空结点则不是完二叉树
return false;
}
QueuePop(&q);
}
QueueDestroy(&q);
return true;
}
```
# 二叉树销毁
思路:遍历数组然后一个一个的释放结点的空间,那么用那种遍历最合适呢,显然是后序遍历,其他遍历方式都不是最后删除根节点,如果将根节删除了其他的结点就不能访问了。
```c
void BinaryTreeDestory(BTNode** root)//二叉树的销毁
{
assert(root);
//用后序遍历
BinaryTreeDestory((*root)->_left);
BinaryTreeDestory((*root)->_right);
free(*root);
root = NULL;
}
这里用二级指针就是防止野指针的形成。
```