二叉树的概念:
一颗二叉树是节点的有限集合。二叉树由左子树和右子树组成,每一个非空的子树都可以称作一个独立的二叉树。
二叉树的特点:
- 每个节点最多有两颗子树,即树的度最大为2;
- 子树右左右之分,次序不能颠倒。
二叉树的分类:
满二叉树:不存在度为1的节点。只可能度为0和2
完全二叉树:具有N个节点的结构与满二叉树的前n个节点的结构相同。称为完全二叉树。
二叉树的性质:
(1)若规定根节点的层数为1,则一颗非空二叉树的第i层上最多有2^(i - 1)个节点。
(2)若规定只有根节点的二叉树深度为1,则深度为K的二叉树的最大节点数是2^K - 1。
(3)对任何一个二叉树,如果其叶节点个数为n0,度为2的非叶节点个数为n2,则有n0 = n2 + 1,n总 = n0 + n1 + n2;
(4)具有n个节点的完全二叉树的深度k为log2(n + 1)上取整。
(5)对于具有n个节点的完全二叉树,入股按照从上到下,从左到右的顺序对所有节点从0开始编号,则对于序号为i的节点有,
- 若i > 0,双亲序号:(i - 1)/ 2 ;i = 0, 为根节点,无双亲节点
- 若2i + 1 < n,左孩子编号为2i + 1,否则无左孩子。
- 若2i + 1 < n,右孩子编号为2i + 2,否则无右孩子。
链式存储:
优点:能很好的利用存储空间。适用于任何二叉树的存储
常见二叉树节点的定义:通常使用左右孩子表示法来描述二叉树
typedef BTNode{ struct BTNode* LChild; struct BTNode* RChild; DataType data; };双亲表示法:
typedef BTNode{ struct BTNode* Parent; struct BTNode* LChild; struct BTNode* RChild; DataType data; };二叉树的基本操作:
(1)创建二叉树
(2)先序遍历二叉树
(3)中序遍历二叉树
(4)后序遍历二叉树
(5)拷贝一颗二叉树
(6)层序遍历
(7)二叉树的销毁
(8)二叉树的镜像非递归
(9)求二叉树中节点的个数
(10)求二叉树中叶子节点的个数
(11)求二叉树中第K层节点的个数
(12)求二叉树的高度
(13)检测一颗二叉树是否为完全二叉树
(14)根据数据获取二叉树中的节点
(15)检测一个节点是否在二叉树中
(16)前序遍历的非递归实现
(17)中序遍历的非递归实现
(18)后续遍历的非递归实现
(19)二叉树的镜像递归实现
具体实现:
(1)创建二叉树(必须遵循先序遍历规则)
思路:
- 先将数据按先序遍历的顺序存储在一个数组中。
- 创建根节点,将数据赋值给根节点。
- 创建左子树
- 创建右子树
- 遇到无用的操作符,该节点不被创建,并且函数返回。
代码如下:
void BTNodeCreate(BTDataType* array, PBTNode* pRoot, int length, int *index, BTDataType invalid) { if (*index < length && array[*index] != invalid) { *pRoot = (PBTNode)malloc(sizeof(BTNode)); if (*pRoot == NULL) return; (*pRoot)->LChild = NULL; (*pRoot)->RChild = NULL; (*pRoot)->data = array[*index]; ++(*index); BTNodeCreate(array, &(*pRoot)->LChild, length, index, invalid); ++(*index); BTNodeCreate(array, &(*pRoot)->RChild, length, index, invalid); } }
(2)先序遍历二叉树
思路:先遍历根节点,再遍历左子树,再遍历右子树
void PreOrder(PBTNode pRoot) { if (pRoot) { printf("%c ", pRoot->data); PreOrder(pRoot->LChild); PreOrder(pRoot->RChild); } }
(3)中序遍历二叉树
void InOrder(PBTNode pRoot) { if (pRoot) { InOrder(pRoot->LChild); printf("%c ", pRoot->data); InOrder(pRoot->RChild); } }
(4)后序遍历二叉树
void PostOrder(PBTNode pRoot) { if (pRoot) { PostOrder(pRoot->LChild); PostOrder(pRoot->RChild); printf("%c ", pRoot->data); } }
(5)拷贝一颗二叉树(先序遍历规则)
思路:先拷贝根节点,再拷贝左子树,再拷贝右子树。
void CopyBinTree(PBTNode pRoot, PBTNode* newRoot) { if (pRoot) { *newRoot = (PBTNode)malloc(sizeof(BTNode)); if (*newRoot == NULL) return; (*newRoot)->LChild = NULL; (*newRoot)->RChild = NULL; (*newRoot)->data = pRoot->data; CopyBinTree(pRoot->LChild, &(*newRoot)->LChild); CopyBinTree(pRoot->RChild, &(*newRoot)->RChild); } }
(6)层序遍历
思路:先遍历根节点,每遇到一个节点都先依次判断他的左右孩子是否为空,然后将其左孩子和右孩子加入到队列中。然后将队头节点出队列
// 层序遍历二叉树 void LevelOrder(PBTNode pRoot) { Queue* q = (Queue*)malloc(sizeof(Queue)); QueueInit(q); QueuePush(q, pRoot); PBTNode tmp = NULL; while (!QueueEmpty(q)) { PBTNode cur = QueueFront(q); printf("%c ", cur->data); if (cur->LChild != NULL) QueuePush(q, cur->LChild); if (cur->RChild != NULL) QueuePush(q, cur->RChild); QueuePop(q, &tmp); } }
(7)二叉树的销毁(必须遵循后续遍历规则,否则会出现空指针异常)
思路:要销毁一颗二叉树,应该先删除他的左右子树,再删除根节点。
void DetroyBinTree(PBTNode* pRoot) { if (*pRoot) { DetroyBinTree(&(*pRoot)->LChild); DetroyBinTree(&(*pRoot)->RChild); free(*pRoot); *pRoot = NULL; } }
(8)二叉树的镜像非递归
思路:和层序遍历一样,当前节点入队列,左孩子入队列,右孩子入队列,交换队头节点的左右孩子
// 二叉树的镜像---非递归 void MirrorBinTreeNor(PBTNode pRoot) { Queue* q = (Queue*)malloc(sizeof(Queue)); QueueInit(q); QueuePush(q, pRoot); PBTNode tmp = NULL; while (!QueueEmpty(q)) { PBTNode cur = QueueFront(q); if (cur->LChild != NULL) QueuePush(q, cur->LChild); if (cur->RChild != NULL) QueuePush(q, cur->RChild); swapBTNodeNode(&(cur->LChild), &(cur->RChild)); QueuePop(q, &tmp); } }
(9)求二叉树中节点的个数
思路:总节点个数等于 == 左孩子个数 + 右孩子个数 + 根节点个数(1),递归结束条件,当前节点为空,返回0(优化:当左右孩子为空时,直接返回,可以大幅减少递归深度);
// 求二叉树中结点的个数 int BinTreeSize(PBTNode pRoot) { if (pRoot == NULL) return 0; if (pRoot->LChild == NULL && pRoot->RChild == NULL) return 1; return BinTreeSize(pRoot->LChild) + BinTreeSize(pRoot->RChild) + 1; }
(10)求二叉树中叶子节点的个数
思路:叶子节点个数 == 叶子节点中左孩子的个数 + 叶子节点中右孩子的个数
// 求二叉树中叶子结点的个数 int BinTreeLeaf(PBTNode pRoot) { if (pRoot == NULL) return 0; if (pRoot->LChild == NULL && pRoot->RChild == NULL) return 1; return BinTreeLeaf(pRoot->LChild) + BinTreeLeaf(pRoot->RChild); }
(11)求二叉树中第K层节点的个数
思路:和求叶子节点个数类似,第K层中左孩子个数 + 第K层中右孩子个数;只是将结束条件变成了K == 1;(K最小为1时,节点为根节点,个数为1)
// 求二叉树中第K层结点的个数 int BinTreeKLevelNode(PBTNode pRoot, int K) { if (K <= 0) return 0; if (K == 1 && pRoot != NULL) return 1; if (pRoot == NULL) return 0; return BinTreeKLevelNode(pRoot->LChild, K - 1) + BinTreeKLevelNode(pRoot->RChild, K - 1); }
(12)求二叉树的高度
思路:判断当前节点是否为空,为空返回0,不为空返回1,将返回值赋值给high,比较左右子树的较大值,取大的。
// 求二叉树的高度 int BinTreeHeight(PBTNode pRoot) { int leftHigh, rightHigh; if (pRoot == NULL) return 0; if (pRoot->LChild == NULL && pRoot->RChild == NULL) return 1; leftHigh = BinTreeHeight(pRoot->LChild); rightHigh = BinTreeHeight(pRoot->RChild); return leftHigh > rightHigh ? leftHigh + 1 : rightHigh + 1; }
(13)检测一颗二叉树是否为完全二叉树
思路:完全二叉树的某个节点之前一定是一颗满二叉树。所有的节点度都为2。
找到这个节点。
这个节点的可能情况为:只有左孩子,没有左孩子也没有右孩子。
利用层序遍历,从上到下从左到右,就可以找到这个点。如果在这个点之后出现了任何一个节点有孩子,那这个二叉树就不是完全二叉树。
// 检测一棵树是否为完全二叉树 int IsCompleteBinTree(PBTNode pRoot) { if (pRoot == NULL) return 0; // 标识是否找到了这个特殊的节点。 int flag = 0; PBTNode cur; Queue q; QueueInit(&q); QueuePush(&q, pRoot); while (!QueueEmpty(&q)) { QueuePop(&q, &cur); if (flag == 1 && (cur->LChild != NULL || cur->RChild != NULL)) return 0; if (cur->LChild != NULL && cur->RChild != NULL && flag == 0) { QueuePush(&q, cur->LChild); QueuePush(&q, cur->RChild); } if (cur->LChild == NULL && cur->RChild != NULL) return 0; if ((cur->RChild == NULL && cur->LChild == NULL) || (cur->RChild == NULL && cur->LChild != NULL)) { // 找到分界点。 flag = 1; } } return 1; }
(14)根据数据获取二叉树中的节点
思路:先找左孩子,没找到(node == NULL)才找右孩子。
// 在二叉树中查找值为data的结点,找到返回该结点,否则返回空 PBTNode Find(PBTNode pRoot, BTDataType data) { PBTNode node; if (pRoot == NULL) return NULL; if (pRoot->data == data) return pRoot; node = Find(pRoot->LChild, data); if (node == NULL) { // 没找到就找右子树 Find(pRoot->RChild, data); } }
(15)检测一个节点是否在二叉树中
思路:和获取二叉树中的节点相同,但是要判断pNode是否为空。
// 检测一个节点是否在二叉树中 int IsNodeInBinTree(PBTNode pRoot, PBTNode pNode) { PBTNode node; if (pNode == NULL) return 0; if (pRoot == NULL) return 0; if (pRoot == pNode) return 1; node = IsNodeInBinTree(pRoot->LChild, pNode); if (node == NULL) { // 没找到就找右子树 IsNodeInBinTree(pRoot->RChild, pNode); } }
(16)前序遍历的非递归实现
思路:先将当前节点的右孩子入栈,然后一直往左遍历(左孩子不入栈),如果遇到的节点为空,就出栈。并将当前节点设置为栈中的节点。
// 循环先序遍历二叉树 void PreOrderNor(PBTNode pRoot) { if (pRoot == NULL) return; PBTNode cur = pRoot; Stack s; StackInit(&s); while (1) { printf("%c ", cur->data); if (cur->RChild != NULL) StackPush(&s, cur->RChild); cur = cur->LChild; if (cur == NULL) { int ret = StackPop(&s, &cur); if (ret == 0) return; } } }
(17)中序遍历的非递归实现
思路:
(1)一直找到当前树的最左节点,并将遇到的节点入栈。
(2)出栈打印左孩子,判断当前节点有无右孩子,无继续出栈,若有,则继续第一步
//循环中序遍历二叉树 void InOrderNor(PBTNode pRoot) { if (pRoot == NULL) return; PBTNode cur = pRoot; PBTNode tmp = NULL; Stack s; StackInit(&s); while (1) { while (cur != NULL) { StackPush(&s, cur); cur = cur->LChild; } // ret表示pop的状态,0表示空栈 int ret = StackPop(&s, &tmp); if (!ret) return; printf("%c ", tmp->data); if (tmp->RChild) cur = tmp->RChild; } }
(18)后续遍历的非递归实现
思路:
(1)将当前节点入栈,将当前节点的右孩子入栈,直到找到当前树的最左节点。
(2)看最左节点有无右孩子,有跳到(1),无则打印这个最左节点。
(3)出栈,将出栈的这个节点赋值给当前节点,判断有无左右孩子,有跳到(1)
//循环后续遍历二叉树 void PostOrderNor(PBTNode pRoot) { if (pRoot == NULL) return; Stack s; StackInit(&s); PBTNode cur = pRoot; // 当前节点 PBTNode tmp = NULL; // 用来记录上一次出栈的节点,以便判断这个节点是否应该继续向下遍历 PBTNode preTmp = NULL; //放入根节点 StackPush(&s, cur); while (1) { // 一直向左,入栈顺序为,cur cur->RChild cur->LChild while (cur != NULL) { if (cur->RChild) StackPush(&s, cur->RChild); if (cur->LChild) StackPush(&s, cur->LChild); cur = cur->LChild; } if (cur == NULL) { tmp = StackTop(&s); //栈为空退出 if (tmp == NULL) return; //判断当前点的子节点是否是上一次遍历的。如果是则代表这颗树的子节点已经遍历完了,就直接输出当前节点 if (tmp->LChild == preTmp || tmp->RChild == preTmp) { printf("%c ", tmp->data); StackPop(&s, &tmp); preTmp = tmp; continue; } // 不直接遍历则看他的左右孩子是否为空,不为空就入栈 if (tmp->LChild || tmp->RChild) { cur = tmp; preTmp = tmp; continue; } //叶子节点,直接打印。 StackPop(&s, &tmp); preTmp = tmp; printf("%c ", tmp->data); } } }
(19)二叉树的镜像递归实现
思路:遇到一个非空的节点就交换他们的左右孩子。
// 二叉树的镜像---递归 void MirrorBinTree(PBTNode pRoot) { if (pRoot) { swapBTNodeNode(&(pRoot->LChild), &(pRoot->RChild)); MirrorBinTree(pRoot->LChild); MirrorBinTree(pRoot->RChild); } }
以上程序的运行结果:
完整代码在:github.com/Niceug/Algorithm/