二叉树
前言:害,整了挺久,找了挺多的建树的C代码,结果,每一个用得上的
树的一种非线性结构,他的一个节点不止有一个前驱结点和一个后继结点,而是有多个
// 函数参数不支持对指针用取址好&
树
树的性质
1)有且仅有一个特定的称为根的结点
2)当n>1时,其余结点可分为m个互不相交的有限集合,其中每一个集合本身又是一颗树,称为根节点的子树(n个结点的树中只有n-1条边)
3)树中的一个结点的子结点的个数称为该结点的度,树中最大度数称为树的度
4)度大于0的结点称为分支节点,度为0的结点称为叶子结点
5)结点的层次,结点的高度(是树中结点的最大层数),结点的深度
6)有序树和无序数
7)树中两个结点之间的路径是由这两个结点之间所经过的结点序列构成的
8)路径长度:路径上所经历边的个数
9)森林:m棵互不相交的树的集合
10)树中的结点数等于所有结点的度数加1
11)度为m的树中第i层上至多有m**(i-1)个结点
12)高度为h的m叉结点至多有(m**h -1)/(m-1)个结点
13)具有n个结点的m叉树的最小高度为[logm (n(m-1)+1)]
满二叉树
一棵深度为k且有2的k次方-1结点的二叉树称为满二叉树
完全二叉树
深度为k,有n个结点的二叉树,当且仅当其每一个结点都与深度为k的满二叉树中编号从1到n的结点一一对应时,称为完全二叉树
建立二叉树
在此用的是树的链式存储结构
typedef struct BiTNode {
int data;
struct BiTNode* lchild, * rchild;
}BiTNode, * BiTree;
下面有两种方法可以建树
二级指针
// 二级指针,无返回值
void CreateTree(BiTree *root) {
char data;
scanf("%c", &data);
fflush(stdin);
if (data == '#') {
*root = NULL;
}
else {
*root = (BiTree)malloc(sizeof(BiTNode));
(*root)->data = data;
CreateTree(&(*root)->lchild);
CreateTree(&(*root)->rchild);
}
}
一级指针
// 一级指针,有返回值
BiTree CreateTree(BiTree root) {
int data;
scanf("%d", &data);
if (data == 0) {
root = NULL;
}
else {
root = (BiTree)malloc(sizeof(BiTNode));
root->data = data;
root->lchild = root->rchild = NULL;
root->lchild = CreateTree(root->lchild);
root->rchild = CreateTree(root->rchild);
}
return root;
}
五种遍历方法
// 先序遍历:根节点-左子树-右子树
void Traverse(BiTree root) {
if (root) {
printf("%d ", root->data);
Traverse(root->lchild);
Traverse(root->rchild);
}
}
// 中序遍历:左子树-根节点-右子树
void Traverse(BiTree root) {
if (root) {
Traverse(root->lchild);
printf("%d ", root->data);
Traverse(root->rchild);
}
}
// 中序遍历:左子树-右子树-根节点
void Traverse(BiTree root) {
if (root) {
Traverse(root->lchild);
Traverse(root->rchild);
printf("%d ", root->data);
}
}
// 中序遍历非递归算法
void Inorder(BiTree root) {
stack<BiTree> s;
s.push(root);
while (!s.empty() || root) {
if (root) {
s.push(root);
root = root->lchild;
}
else {
root = s.top();
printf("%d ", p->data);
s.pop();
root = root->rchild;
}
}
}
// 层序遍历
void levelOrder(BiTree T) {
queue<BiTree> q;
Bitree p;
q.push(p);
while(q.emtpy() == 1) {
q.pop(p);
if(p->lchild != nullptr) {
q.push(p->lchild);
}
if(p->rchild != nullptr) {
q.push(p->rchild);
}
}
}
下面来看看层序遍历
也正是这样一个结构
6
/ \
4 7
/ \
3 5
判断二叉树的深度
void PrintTree(BiTree root, int n) {
if (root != NULL) {
printf("元素%d在第%d层\n", root->data, n);
PrintTree(root->lchild, n+1);
PrintTree(root->rchild, n+1);
}
}
线索二叉树
由遍历二叉树可知,其是将二叉树中结点排列从一个线性序列,这实质上是对一个非线性结构进行线性化操作,使得每个结点在这些线性序列中有且只有一个直接前驱和一个直接后继。但是,当以二叉链表作为存储结构的时候,只能找到结点的左,右孩子信息,而不能得到结点在任一序列中的前驱和后继信息
如何找到?在每个结点上增加两个指针域,分布指示结点在任一遍历时得到的前驱和后继信息。
typedef struct BiTNode {
int data;
struct BiTree *lchild, *rchild;
int ltag, rtag;
}BiTNode, *BiTree;
// 在后序遍历过程中给他们加上前驱和后继结点信息
// 此处传入两个参数,一个二叉树根节点,一个为NULL,表示树遍历结点过程中前一个结点
void Posthreah(BiTree p, BiTree pre) {
if (p != NULL) {
Posthreah(p->lchild, pre);
Posthreah(p->rchild, pre);
// 查看是否左孩子,没有就指向前驱结点,ltag=1
if (p->lchild == NULL) {
p->lchild = pre;
p->ltag = 1;
}
// 首先判断pre是否为空,查看pre结点是否有右孩子,没有就指向后继结点
if (pre != NULL && pre->rchild == NULL) {
pre->rchild = p;
pre-rtag = 1;
}
// 让pre前进
pre = p;
}
}
树的存储结构
双亲表示法
以一组连续空间存储树的结点,同时在每个结点中附设一个指示器指示其双亲结点在链表中的位置
缺点:求结点的孩子时需要遍历整个结构
#define MAXSIZE 100
typedef struct PTNode { // 结点结构
int data; // 数据域
int parent; // 双亲位置域
}PTNode;
typedef struct { // 树结构
PTNode nodes[MAXSIZE];
int r, n; // 根的位置和节点数
}PTree;
孩子表示法
把每个结点的孩子结点排列起来,看成一个线性表,且以单链表作为存储结构,具有n个结点的有n个孩子链表(叶子的孩子链表为空表),查找更方便
缺点:不容易寻找双亲结点
typedfe struct CTNode { // 孩子结点
int child;
struct CTNode* next;
}*ChildPtr;
typedef struct {
int data;
ChildPtr firstchild; // 孩子链表头指针
}CTBox;
typedef struct {
CTBox node[MAXSIZE];
int n, r;
}CTree;
带双亲的孩子链表
将孩子表示法和双亲表示法结合
孩子兄弟表示法
链表中结点的两个链域分别指向该节点的第一个孩子结点和下一个兄弟结点
typedef struct CSNode {
int data;
struct CSNode *firstchild, *nextsibling;
}CSNode, *CSTree;
最优二叉树(赫夫曼树)
路径长度:从树中一个结点到另一个结点之间的分支构成两个结点之间的路径,路径上的分支数目乘坐路径长度,树的路径长度是从树根到每一个结点的路径长度之和
树的带权路径长度为树中所有叶子结点的带权路径长度之和,通常称作WPL
固定长度编码
相同长度进行编码
可变长度编码
不同长度二进制进行编码
二叉排序(查找)树(BST)
二叉排序树或者为空树,或者为非空树,当为空树时由如下特点:
- 若左子树非空,则左子树上所有结点关键字均小于根节点的关键字
- 若右子树非空,则右子树上所有结点关键字均大于根节点的关键字
- 左右子树本身也分布是一颗二叉排序树
查找
查找:二叉树非空时,查找根节点,若相等时则查找成功;若不等,则当小于结点值时,查找左子树;当大于根节点时,查找右子树。若都没有,查找失败
BSTNode *BST_Search(BiTree T, ElemType key) {
while (T != null && key!=T->data) {
if (key < T->data) {
T = T->lchild;
}
else {
T = T->rchild;
}
}
return T;
}
插入
插入:若二叉树为空,则直接插入节点;若非空,当值小于根节点时,插入左子树,否则插入右子树。当值和根节点相等不插入
#include <iostream>
#include <cstdio>
#include <cstdlib>
/*-------------------------------------------构建二叉树结构体------------------------------------*/
typedef struct BiTNode {
int val;
struct BiTNode *lchild, *rchild;
}BiTNode, *BiTree;
/*-------------------------------------------创建一棵BST树--------------------------------------*/
void CreateBST(BiTree &root, char data) {
if (root== nullptr) {
root = (BiTree)malloc(sizeof(BiTNode));
root->lchild = root->rchild = nullptr;
root->val = data;
}
else if (root->val == data) {
exit(0);
}
else if (root->val > data) {
CreateBST(root->lchild, data);
}
else if (root->val < data) {
CreateBST(root->rchild, data);
}
}
void Traverse(BiTree root) {
if (root) {
printf("%d ", root->val);
Traverse(root->lchild);
Traverse(root->rchild);
}
}
int main() {
BiTree root = nullptr;
int data;
scanf("%d", &data);
while (data != 0) {
CreateBST(root, data);
scanf("%d", &data);
}
Traverse(root);
return 0;
}
删除
若被删除节点是叶节点,则直接删除
若被删除节点只有一颗子树,则让他的子树称为父节点的子树,代替该结点
若被删除节点有两颗子树,则让z的中序序列直接后继代替z,并删去直接后继结点
平衡二叉树(AVL)
AVL,任意结点的平衡因子的绝对值不超过1
平衡因子=左子树高度-右子树高度
高度为h的最小平衡二叉树的结点数Nh
bool isBalanced(TreeNode* root) {
int balance, h;
Judge_AVL(root, balance, h);
if (balance == 1) return true;
else return false;
}
void Judge_AVL(TreeNode* root, int& balance, int& h) {
int hl = 0, bl = 0, hr = 0, br = 0;
if (root == NULL) {
balance = 1;
h = 0;
}
else if (root->left == NULL && root->right == NULL) {
balance = 1;
h = 1;
}
else {
Judge_AVL(root->left, bl, hl);
Judge_AVL(root->right, br, hr);
if (hr > hl) h = hr + 1;
else h = hl + 1;
if (abs(hr - hl) < 2 && br == 1 && bl == 1) {
balance = 1;
}
else {
balance = 0;
}
}
}
B和B+树
红黑树
五大法则:
- 结点有黑色和红色两种
- 根节点必须是黑色
- 叶子结点为不存数据且都是黑色
- 任意结点到叶子结点经过的黑色结点数目相同
- 不会有连续的红色结点