目录
树概念及结构
树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因
为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
有一个特殊的结点,称为根结点,根节点没有前驱结点
除根节点外,其余结点被分成M(M>0)个互不相交的集合T1、T2、……、Tm,其中每一个集合Ti(1<= i
<= m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继
因此,树是递归定义的。
当然 ,二叉树也是递归定义的
任何一个二叉树都要拆成三个部分
1 根
2左子树
3右子数
二叉树遍历
二叉树遍历(Traversal)是按照某种特定的规则,依次对二叉树中的节点进行相应的操作,并且每个节点只操作一次。
按照规则,二叉树的遍历有:前序/中序/后序的递归结构遍历:
1. 前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。 根 左子树 右子树
2. 中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。
左子树 根 右子树
3. 后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。
左子树 右子树 根
递归实际上是把大问题拆成小问题 结束条件是NULL
其中N代表NULL
链式二叉树
三种遍历代码的实现
当前函数结束回到他调用的地方 也就是回到使用他的函数的语句,然后由于程序的顺序结构继续往下走
时间是一去不复返的 而空间可以重复利用。
函数调用和返回过程中,系统会自动管理函数栈帧的创建和销毁,我们不需要手动操作。当一个函数返回时,它对应的栈帧就会被弹出,控制权会返回到调用它的上一层函数。
Treesize函数的经典错误以及实现方法
经典错误 :每次递归调用都会建立新的栈帧 每次的size都是新的size。
int TreeSzie(TreeNode* root)
{
Int size=0;
If(root==NULL)
{
Return;
}
Size++
TreeSize(root->left);
TreeSize(root->right);
}
除非这里是搞一个静态的size,但是局部的静态化变量只会初始化一次(在局部函数中)
局部的静态变量虽然生命周期是在全局,但是作用域只在当前的函数。
实现方法:
第一种方法:
size当成全局变量放在外面即可 每次用size函数之前先把size初始化置零一次 然后遍历链式二叉树 不为空 size++
第二种方法:
逐层递归 类似与校长统计全校人数 校长找院长 院长找导员 导员找班长 班长找宿舍长 宿舍长数 然后返回数字 加上自身 后面逐层上报。依次往下找 找到可统计的最小单位,然后在加上自身逐层上报。
函数递归的独特理解
从前面两个函数的实现,我们不难看出递归是基于函数栈帧的开辟与销毁的,所谓递归,就是先“递”后“归”,递中有递,归中有归,可以想象的理解成套娃。函数递归与函数的顺序结构可谓是相辅相成,二位一体的,函数递归就是要把大问题分解成小问题去解决,且大问题与分解成的基本小问题的解决逻辑是统一的!把大问题分解到一个不能在分解的具有基本性质的小问题,而这个小问题具有一个特定的“返回值,返回条件”,递的过程实际上是大问题分解成诸多基本问题的过程,而归则是带有具体“答案”的小问题不断汇聚合体成大问题的过程。
一去一回,一递一归,先分解后汇合,两个互逆的过程的组合在整体的运行逻辑还是一致的!
一来一回却解决了许多大问题,递归思想的核心就在这里了!
当前函数结束回到他调用的地方 也就是回到使用他的函数的语句,然后由于程序的顺序结构继续往下走时间是一去不复返的 而空间可以重复利用。
函数调用和返回过程中,系统会自动管理函数栈帧的创建和销毁,我们不需要手动操作。当一个函数返回时,它对应的栈帧就会被弹出,控制权会返回到调用它的上一层函数。
因此基于其基本性质,我们发现函数递归时,需要注意两个大问题
1返回条件(取下娃娃壳子的条件)
2分治子问题(一个大娃娃能分成几个基本的小娃娃)
做好函数递归就需要多画递归展开图,理解函数栈帧的创建与销毁。
以上思路,我们来完成一个函数的递归
叶子结点的个数函数
分析可得:
子问题分治:左子树的叶子结点个数+右子树的叶子结点个数
返回条件:
1、空树 返回0
2、叶子返回1
实现:
求高度
不要写三目 因为只判断大小 不记录 会有重复计算(三目没理解)
fmax函数
分治子问题:
1空 返回0
2不是空 左子树和·右子树 大的那个加一
第k层的节点个数
分治子问题:
1空 返回0
2 不为空 且k==1,返回1
3 不为空 且k>1 返回左子树的k-1层+右子树的k-1层
层序遍历
Pop的是QueueNode 因此pop掉QueueNode,不会使front变成野指针,因为front是TreeNode*形的。 因为QueueNode的val是Treenode* val存的是Treenode的地址,通过val的解引用去访问原来二叉树中具体的值,这里巧妙的对队列的应用变形,以前队列存的是单纯的值,现在存的是地址,通过地址这个中介去间接的访问原二叉树中的值
每一层打印出来 打印一层换行,
LevelSize(用队列的size函数就可以,因为队列出一个结点就会进两个子节点,队列的大小就是这层的子节点个数) 每一层的数据个数,控制一层一层出。通过while循环来走
While(LevelSize--)
判断完全二叉树
用层序遍历,遇到空以后不能有非空,所有元素必须是连续的。
如果遇见空后面的全是空就是完全二叉树。
如果遇见空后面的有非空就不是完全二叉树
遇到空了,非空元素一定进入队列了。
其他的函数代码实现
#include"BinaryTree.h"
TreeNode* BuyTreeNode(int x)
{
TreeNode* node = (TreeNode*)malloc(sizeof(TreeNode));
assert(node);
node->data = x;
node->left = NULL;
node->right=NULL;
return node;
}
TreeNode* CreateTreeNode0()
{
TreeNode* node1 = BuyTreeNode(1);
TreeNode* node2 = BuyTreeNode(2);
TreeNode* node3 = BuyTreeNode(3);
TreeNode* node4 = BuyTreeNode(4);
TreeNode* node5 = BuyTreeNode(5);
TreeNode* node6 = BuyTreeNode(6);
/*TreeNode* node7 = BuyTreeNode(7);
//TreeNode* node8 = BuyTreeNode(8);*/
node1->left = node2;
node1->right = node4;
node2->left = node3;
node4->left = node5;
node4->right = node6;
return node1;
}
TreeNode* CreateTreeNode(char* a, int* pi)
{
if (a[*pi] == '#')
{
(*pi)++;
return NULL;
}
TreeNode* root = (TreeNode*)malloc(sizeof(TreeNode));
if (root == NULL)
{
perror("malloc fail");
exit(-1);
}
root->data = a[(*pi++)];
root->left = CreateTreeNode(a, pi);
root->right = CreateTreeNode(a, pi);
return root;
}
void TreeNodeprevorder(TreeNode* root)
{
if (root == NULL)
{
printf("N ");
return;
}
printf("%d ", root->data);
TreeNodeprevorder( root->left);
TreeNodeprevorder( root->right);
}
void TreeNodeinorder(TreeNode* root)
{
if (root == NULL)
{
printf("N ");
return;
}
TreeNodeinorder(root->left);
printf("%d ", root->data);
TreeNodeinorder(root->right);
}
void TreeNodepostorder(TreeNode* root)
{
if (root == NULL)
{
printf("N ");
return;
}
TreeNodepostorder(root->left);
TreeNodepostorder(root->right);
printf("%d ", root->data);
}
//int TreeNodeSize(TreeNode* root)
//{
// static int size = 0; //这种写法的size在递归时是逐渐累积的,因为size是静态变量,因此在每一次使用前需要初始化size=0
// if (root == NULL)
// {
// return 0;
// }
// size++;
// TreeNodeSize(root->left);
// TreeNodeSize(root->right);
//}
int TreeNodeSize(TreeNode* root)
{
if (root == NULL)
{
return 0;
}
return TreeNodeSize(root->left) + TreeNodeSize(root->right)+1;
}
int TreeNodeLeafSize(TreeNode* root)
{
if (root == NULL)
{
return 0;
}
if ((root->left == NULL) &&
(root->right == NULL))
{
return 1;
}
return TreeNodeLeafSize(root->left) + TreeNodeLeafSize(root->right);
}
int TreeHeight(TreeNode* root)
{
if (root == NULL)
{
return 0;
}
return fmax(TreeHeight(root->left), TreeHeight(root->right))+1;
}
int TreeLevelKSize(TreeNode* root, int k)
{
assert(k > 0);
if (k == 1)
{
return 1;
}
return TreeLevelKSize(root->left, k - 1) + TreeLevelKSize(root->right, k - 1);
}
TreeNode* TreeNodefind(TreeNode* root, int x)
{
if (root == NULL)
{
return NULL;
}
if (root->data == x)
{
return root;
}
TreeNode* ret1 = TreeNodefind(root->left, x);
if (ret1)
return ret1;
TreeNode* ret2 = TreeNodefind(root->right, x);
if (ret2)
return ret2;
return NULL;
}
void DestroyTree(TreeNode* root)
{
if (root == NULL)
{
return;
}
DestroyTree(root->left);
DestroyTree(root->right);
free(root);//外置root为空,要不然用二级指针。
}
void Levelorder(TreeNode* root)
{
Queue q;
QueueInit(&q);
if (root)
QueuePush(&q,root);
int levelsize = 1;
while (!QueueEmpty(&q))
{
while (levelsize--)
{
TreeNode* front = QueueFront(&q);
QueuePop(&q);
printf("%d ", front->data);
if(front->left)
QueuePush(&q,front->left);
if (front->right)
QueuePush(&q, front->right);
}
printf("\n");
levelsize = QueueSize(&q);
}
printf("\n");
QueueDestroy(&q);
}
bool TreeComplete(TreeNode* root)//遇到空之后就不能再遇见非空元素了,否则就不连续。就不是完全二叉树。
{
Queue q;
QueueInit(&q);
if (root)
QueuePush(&q, root);
while (!QueueEmpty(&q))
{
TreeNode* front = QueueFront(&q);
QueuePop(&q);
if (front == NULL)
break;
QueuePush(&q, front->left);
QueuePush(&q, front->right);
}
while (!QueueEmpty(&q))
{
TreeNode* front = QueueFront(&q);
QueuePop(&q);
if (front)
{
QueueDestroy(&q);
return false;
}
}
QueueDestroy(&q);
return true;
}
#include"Queue.h"
void QueueInit(Queue* pq)
{
assert(pq);
pq->phead = NULL;
pq->tail = NULL;
pq->size = 0;
}
void QueueDestroy(Queue* pq)
{
assert(pq);
QNode* cur = pq->phead;
while (cur)
{
QNode* next = cur->next;
free(cur);
cur = next;
}
pq->phead = NULL;
pq->tail = NULL;
pq->size = 0;
}
void QueuePush(Queue* pq,QDatatype x)
{
assert(pq);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloc fail");
return;
}
newnode->val = x;
newnode->next = NULL;
if (pq->tail == NULL)
{
pq->tail = pq->phead = newnode;
}
else
{
pq->tail->next = newnode;
pq->tail = newnode;
}
pq->size++;
}
void QueuePop(Queue* pq)
{
assert(pq);
assert(pq->phead);
QNode* tmp = pq->phead;
pq->phead = pq->phead->next;
free(tmp);
tmp = NULL;
if (pq->phead == NULL)
pq->tail = NULL;
pq->size--;
}
QDatatype QueueFront(Queue* pq)
{
assert(pq);
assert(pq->phead);
return pq->phead->val;
}
QDatatype QueueBack(Queue* pq)
{
assert(pq);
assert(pq->tail);
return pq->tail->val;
}
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->phead == NULL;
}
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}