第二十五天(数据结构:树)
树树一个有n(n >= 0)个节点组成一个有限集合1 有且仅有一个被称为根节点(root)的节点2 当n > 1,除了根节点之外,剩余的m个节点互不相交,分别都是一颗颗的树这些树我们称为子树,每一个节点实际上都是一棵树(子树)的根树的每一个节点都包含两个部分1 数据元素2 节点和节点之间的关系,不像原先的链表,关系都是一个方向上面1个节点可能有n个节点和它有关系(因此它就不是一个线性表)每个节点的关系数量我们称为这个节点的度度:出度(我可以到别人),树里面我们暂时只考虑出度 入度(别人到我)在树里面如果一个节点和别人没有关系了(出度为0),这种节点我们称之为终端节点(叶子节点)树的层次(深度):从根开始,根为第一层,根的儿子为第二层,儿子的儿子为第三层......不能乱了辈分二叉树 -- 一种树的形态,每个节点的度(出度)最多只有2,如果你的树满足这一点这棵树就被称为二叉树,二叉树我们就可以分大小两边根据这个大小我们可以分为左右关系,左边的被称为左儿子右边的被称为右儿子二叉树有5种形态1 空树2 只有一个根节点3 只有一个左儿子4 只有一个右儿子5 有左右儿子二叉树的基本性质1 在二叉树的第i(i从1开始)层上面最多有 2 ^ (i - 1)2 深度为k的二叉树最多有 2 ^ k - 1 个节点3 对于任何一个二叉树,有如下关系n0为度为0的节点个数 n1为度为1的节点个数 n2为度为2的节点个数 n为总节点数n = n2 + n1 + n0n = 分支数(出度的个数) + 1分支数 = 2 * n2 + 1 * n1 n = 2 * n2 + 1 * n1 + 1so:2 * n2 + 1 * n1 + 1 = n2 + n1 + n0-> n2 + 1 = n0->这个性质对于任何一个多叉树都是适用的假设你是一个m叉树n = n0 + n1 + n2 + n3 + .... + nmn = 分支数(出度的个数) + 1分支数 = m * nm + (m - 1) * n(m - 1) + .... + 3 * n3 + 2 * n2 + 1 * n1n0 + n1 + n2 + n3 + .... + nm = m * nm + (m - 1) * n(m - 1) + .... + 3 * n3 + 2 * n2 + 1 * n1 + 1n0 = 1 + n2 + 2n3 + 3n4 + .... + (m - 1)nm ----> 可以直接套公式的二叉树的两种特殊的形态满二叉树:深度为k的二叉树有 2 ^ k - 1 个节点,这颗二叉树就是一颗满二叉树不增加深度的情况,不能往这棵树里面添加节点完全二叉树:1 除去最后一层是一个满二叉树2 最下面的那一层所有的节点都是尽左排,一个节点的左边是不能添加新的节点满二叉树是一个特殊的完全二叉树 4 对于一个有n个节点的完全二叉树从上到下,从左往右,从1开始进行编号根为1 根的左儿子为2 根的右儿子为3 ......假设有一个节点的编号为 i 则,这个节点的父节点的编号为 i / 2如果它有左儿子,那么它左儿子的编号为 2 * i如果它有右儿子,那么它右儿子的编号为 2 * i + 15 具有n个节点的完全二叉树的深度为 log2(n) 向下取整 + 1假设深度为k2 ^ (k - 1) <= n < 2 ^ k计算机如何来使用二叉树首先我们应该要保存二叉树1 数据2 左右儿子(关系)1 顺序结构来保存二叉树由于完全二叉树是可以表示左右儿子关系和父亲关系的因此完全二叉树才能用数组存储就是上面的第4个性质如果这棵树不是完全二叉树呢?数组保存不了,因此需要将不是完全二叉树的二叉树补全为完全二叉树才能使用数组保存,这么一来浪费就有点多了因此顺序结构存储二叉树就不是那么方便2 链式结构保存1 有数据域2 有指针域 -> 至少要有两个指针 lchild rchildstruct TreeNode{Tree_Datatype data;//树的数据struct TreeNode * lchild;//指向它的左儿子struct TreeNode * rchild;//指向它的右儿子};二叉树的遍历:遍历:根据某一种规则对集合里面所有元素的访问(必须访问1次,并且只能访问一次)二叉树的遍历有三种(左边在右边的前面)1 先序(根)遍历 -> 根 左 右首先:先访问根节点再次:以相同的规则继续访问根节点的左儿子最后:以相同的规则继续访问根节点的右儿子2 中序(根)遍历 -> 左 根 右首先以相同的规则访问它的左孩子访问根节点最后以相同的规则继续访问根节点的右儿子3 后序(根)遍历 -> 左 右 根首先以相同的规则访问它的左孩子再以相同的规则继续访问根节点的右儿子访问根节点练习:一棵二叉树的先序为:EBADCFHGIKJ中序为:ABCDEFGHIJK请画出这棵树先中序、中后序能确定这棵树但是先后序不一定能确定这棵树建立一棵二叉树:建立树之前我们需要先确定左边是什么关系,右边是什么关系这种确定关系的二叉树我们叫二叉排序树一般是根绝大小来进行确定的一般左边为小右边为大示例代码为左小右大
.h与.c
#ifndef __LISTTREE_H__
#define __LISTTREE_H__#include <stdio.h>
#include <stdlib.h>
#include "ListStack.h"
#include "ArrayQueue.h"typedef char ListTreeDataType;//树的节点类型
typedef struct ListTreeNode
{ListTreeDataType _data;int _cnt;//记录这个元素的访问次数//int _datanum;struct ListTreeNode * _lchild,* _rchild;
}ListTreeNode;//将这数据加入到这棵树里面去
ListTreeNode * ListTree_Add(ListTreeNode *root,const ListTreeDataType data);
//我想以递归的形式插入数据 你应该要怎么思考
ListTreeNode * ListTree_Insert(ListTreeNode *root,const ListTreeDataType data);//遍历这棵树
//先序
void ListTree_PreOrder(ListTreeNode *root);
//中序
void ListTree_MidOrder(ListTreeNode *root);
//后序
void ListTree_PostOrder(ListTreeNode *root);
//求树高 层次
int ListTree_Level(ListTreeNode *root);//以非递归的形式写先序中序后序 -> 利用栈
void ListTree_PreOrder_Stack(ListTreeNode *root);
//非递归的中序 和先序比较只需要改一点点 先序是入栈前访问,中序是出栈访问
void ListTree_MidOrder_Stack(ListTreeNode *root);
//非递归的后序访问
void ListTree_PostOrder_Stack(ListTreeNode *root);//树的层次遍历 利用队列 我们在层次遍历的时候可以确定辈分,并且我可以求得这个数的宽度
//问怎么求宽度? 在一层里面最多元素的元素个数就是数的宽度
int ListStack_LevelOrder(ListTreeNode *root);//写一个销毁函数
void ListTree_Destory(ListTreeNode **root);#endif
#include "ListTree.h"//这个节点建立出来是一个独立的节点
static ListTreeNode *ListTree_Node_create(const ListTreeDataType data)
{ListTreeNode * ptr = (ListTreeNode *)calloc(1,sizeof(ListTreeNode));//创建节点ptr ->_data = data;return ptr;
}//将这数据加入到这棵树里面去
ListTreeNode * ListTree_Add(ListTreeNode *root,const ListTreeDataType data)
{if(!root)//如果这棵树为空的,则data就是根节点{return ListTree_Node_create(data);}//寻找插入位置 大的往右边去 小的往左边去 相等(树里面是没有相同的两个节点的)//相等的情况我们有两种解决办法 1 直接丢弃 2 节点里面弄一个num表示这个数据有几个//现在按第一种解决方案 直接丢弃ListTreeNode * ptr = root;//有一个遍历指针 去找我们的插入位置while(1){if(data > ptr ->_data)//数据比较大 往右边找{if(ptr ->_rchild == NULL)//找到插入位置了{ptr ->_rchild = ListTree_Node_create(data);break;}ptr = ptr ->_rchild;}else if(data < ptr ->_data)//数据比较小 往左边找{if(ptr ->_lchild == NULL)//找到插入位置了{ptr ->_lchild = ListTree_Node_create(data);break;}ptr = ptr ->_lchild;}else//直接丢弃不要了{break;}}return root;
}//遍历这棵树
//先序
void ListTree_PreOrder(ListTreeNode *root)
{if(!root)return;//首先:先访问根节点printf("%c ",root ->_data);///再次:以相同的规则继续访问根节点的左儿子ListTree_PreOrder(root ->_lchild);//最后:以相同的规则继续访问根节点的右儿子ListTree_PreOrder(root ->_rchild);
}
//中序
void ListTree_MidOrder(ListTreeNode *root)
{if(!root)return;//首先以相同的规则访问它的左孩子ListTree_MidOrder(root ->_lchild);//访问根节点printf("%c ",root ->_data);//最后以相同的规则继续访问根节点的右儿子ListTree_MidOrder(root ->_rchild);
}//后序
void ListTree_PostOrder(ListTreeNode *root)
{if(!root)return;//首先以相同的规则访问它的左孩子ListTree_PostOrder(root ->_lchild);//再以相同的规则继续访问根节点的右儿子ListTree_PostOrder(root ->_rchild);//访问根节点printf("%c ",root ->_data);
}//求树高 层次
int ListTree_Level(ListTreeNode *root)
{if(!root)return 0;//树高 = 左右儿子中间最高的那个高度 + 1int h_l = ListTree_Level(root ->_lchild);int h_r = ListTree_Level(root ->_rchild);return h_l > h_r ? h_l + 1 : h_r + 1;
}//销毁就是一个后序
static void ListTree_Destory_shixian(ListTreeNode *root)
{if(!root)return;ListTree_Destory_shixian(root ->_lchild);ListTree_Destory_shixian(root ->_rchild);//销魂它自己root ->_lchild = root ->_rchild = NULL;printf("free %c\n",root ->_data);free(root);
}//写一个销毁函数
void ListTree_Destory(ListTreeNode **root)
{ListTree_Destory_shixian(*root);//让那边置空*root = NULL;
}//我想以递归的形式插入数据 你应该要怎么思考
ListTreeNode * ListTree_Insert(ListTreeNode *root,const ListTreeDataType data)
{if(!root)//如果这棵树为空的,则data就是根节点{return ListTree_Node_create(data);}//如果数据比较小 我就一相同的规则插入到它的左子树上面去if(root ->_data > data){root ->_lchild = ListTree_Insert(root ->_lchild,data);}//如果数据比较大 我就一相同的规则插入到它的右子树上面去else if(root ->_data < data){root ->_rchild = ListTree_Insert(root ->_rchild,data);}return root;
}//以非递归的形式写先序中序后序 -> 利用栈
void ListTree_PreOrder_Stack(ListTreeNode *root)
{if(!root)return;//我需要一个栈ListStack * st = ListStack_init(100);//总纲:入栈前访问,数据先访问再入栈ListTreeNode * ptr = root;while(1){//1 将左边的所有全部入栈(从根开始,根的左孩子,根的左孩子的左孩子,根的左孩子的左孩子的左孩子....一直弄到没有左孩子为止)while(ptr){printf("-%c ",ptr ->_data);ListStack_push(st,(LS_DataType)ptr);ptr = ptr ->_lchild;}if(ListStack_empty(st))// 栈空退出break;//2 出栈,转到出栈元素的右孩子回到第一步ptr = (ListTreeNode *)ListStack_top(st);//先获取栈顶元素ListStack_pop(st);//出栈ptr = ptr ->_rchild;//转到它的右孩子}printf("\n");ListStack_destory(&st,NULL);}//非递归的中序 和先序比较只需要改一点点 先序是入栈前访问,中序是出栈访问
void ListTree_MidOrder_Stack(ListTreeNode *root)
{if(!root)return;//我需要一个栈ListStack * st = ListStack_init(100);//总纲:入栈前访问,数据先访问再入栈ListTreeNode * ptr = root;while(1){//1 将左边的所有全部入栈(从根开始,根的左孩子,根的左孩子的左孩子,根的左孩子的左孩子的左孩子....一直弄到没有左孩子为止)while(ptr){ListStack_push(st,(LS_DataType)ptr);ptr = ptr ->_lchild;}if(ListStack_empty(st))// 栈空退出break;//2 出栈,转到出栈元素的右孩子回到第一步ptr = (ListTreeNode *)ListStack_top(st);//先获取栈顶元素ListStack_pop(st);//出栈printf("+%c ",ptr ->_data);ptr = ptr ->_rchild;//转到它的右孩子}printf("\n");ListStack_destory(&st,NULL);}//非递归的后序访问
void ListTree_PostOrder_Stack(ListTreeNode *root)
{if(!root)return;//我需要一个栈ListStack * st = ListStack_init(100);//后序是它的左右孩子都访问完毕之后太轮到他访问//因此我们需要叠加一个访问的次数ListTreeNode * ptr = root;while(1){//1 将左边所有全部入栈,入栈的时候访问次数置1while(ptr){ptr ->_cnt = 1;//让它的左孩子先弄完ListStack_push(st,(LS_DataType)ptr);ptr = ptr ->_lchild;}//LOOP://栈空退出if(ListStack_empty(st))// 栈空退出break;//2 出栈,出栈的时候就有情况了ptr = (ListTreeNode *)ListStack_top(st);//先获取栈顶元素ListStack_pop(st);//出栈// 如果出栈元素的访问次数是1 说明它的右边还没有弄完,将它的右边弄完之后才能轮到它//因此需要将它的访问次数 ++,然后将它入栈,转到这个入栈元素右边回到第1步if(ptr ->_cnt == 1){ptr ->_cnt++;ListStack_push(st,(LS_DataType)ptr);ptr = ptr ->_rchild;}else{ //如果出栈元素的访问次数变成了2,说明它的左右两边全部弄完了,轮到它访问了 访问之printf("=%c ",ptr ->_data);//访问它的时候说明左右孩子都弄完了 所有不能回到第1步//而第一步的循环条件是 while(ptr) 我将ptr = null,第一步就过掉了ptr = NULL;//goto LOOP;//这也可以}}printf("\n");ListStack_destory(&st,NULL);}//树的层次遍历 利用队列 我们在层次遍历的时候可以确定辈分,并且我可以求得这个数的宽度
//问怎么求宽度? 在一层里面最多元素的元素个数就是数的宽度
//返回树宽
int ListStack_LevelOrder(ListTreeNode *root)
{if(!root)return 0;ArrayQueue * qu = ArrayQueue_init(100);//1 将根节点入队ArrayQueue_push(qu,(AQ_DataType)root);int shangyibei = 1;//老祖宗有一个 就是根节点 这个n是确定这一辈里面有多少人int xiayibei = 1;int max = shangyibei;int beifen = 1;while(1){shangyibei = xiayibei;//下一辈的人转成长辈xiayibei = 0;//下一辈的人就变成0了printf("第%d辈:",beifen);for(int i = 0;i < shangyibei;i++)//为了将长辈这一波的人全部出去 为了确定它的下一辈有多少人 一个长辈确定两个孩子{//2 出队,将出队元素的左右孩子全部入队(左右需要存在才能入队)ListTreeNode * ptr = (ListTreeNode * )ArrayQueue_front(qu);ArrayQueue_pop(qu);printf("%c ",ptr ->_data);if(ptr ->_lchild){xiayibei++;ArrayQueue_push(qu,(AQ_DataType)ptr ->_lchild);}if(ptr ->_rchild){xiayibei++;ArrayQueue_push(qu,(AQ_DataType)ptr ->_rchild);}//循环第二步就可以了 队空退出}if(ArrayQueue_empty(qu))break;printf(" ");beifen++;if(xiayibei > max)//更新最大值max = xiayibei;}printf("\n");ArrayQueue_destory(&qu,NULL);return max;
}