文章目录
1.树
- 父节点: A节点是B节点的父节点
- 子节点: B节点是A节点的子节点
- 兄弟节点:B、C、D节点互为兄弟节点
- 根节点: 没有父节点的节点叫做根节点
- 叶子节点:没有子节点的节点,也叫叶节点
高度和深度相反,层数在深度+1
2.二叉树
- 二叉树每个节点最多两个叉
- 分别是左子节点和右子节点
2.1满二叉树
- 叶子节点全都在最底层,
- 除了叶子节点之外,每个节点都有左右两个子节点,
- 这种二叉树就叫做满二叉树。
2.2完全二叉树
- 叶子节点都在最底下两层,
- 最后一层的叶子节点都靠左排列,
- 并且除了最后一层,其他层的节点个数都要达到最大,
- 这种二叉树叫做完全二叉树。
3. 如何存储一棵二叉树?
3.1 链式存储
- 每个节点有三个字段,一个存数据,另外两个指向左右子节点的指针。
- 大多数二叉树是通过这种结构来实现
3.2 顺序储存
- 把下标i = 1 存在根节点
- 左子节点存在 2*i = 2 的位置
- 右子节点存在 2*i + 1 = 3 的位置
- 依次类推,B的左子节点,在 2* i = 2 * 2 =4,右子节点 在5 的位置
总结:
- 如果节点 X 存储在数组中下标为 i 的位置,
- 下标为 2 * i 的位置存储的就是左子节点,
- 下标为 2 * i + 1 的位置存储的就是右子节点。
- 反过来,下标为 i/2 的位置存储就是它的父节点。
- 如果是完全二叉树,数组存储最节省内存了
- 堆其实就是一种完全二叉树,最常用的存储方式就是数组。
4.二叉树遍历
经典三种遍历:前序遍历,中序遍历, 后序遍历
其中,前、中、后序,表示的是节点与它的左右子树节点遍历打印的先后顺序
- 前序遍历是指,对于树中的任意节点来说,先打印这个节点,然后再打印它的左子树,最后打印它的右子树。
- 中序遍历是指,对于树中的任意节点来说,先打印它的左子树,然后再打印它本身,最后打印它的右子树。
- 后序遍历是指,对于树中的任意节点来说,先打印它的左子树,然后再打印它的右子树,最后打印这个节点本身。
实际上,二叉树的前、中、后序遍历就是一个递归的过程。
前序遍历的递推公式:
preOrder(r) = print r->preOrder(r->left)->preOrder(r->right)
中序遍历的递推公式:
inOrder(r) = inOrder(r->left)->print r->inOrder(r->right)
后序遍历的递推公式:
postOrder(r) = postOrder(r->left)->postOrder(r->right)->print r
void preOrder(Node* root) {
if (root == null) return;
print root // 此处为伪代码,表示打印root节点
preOrder(root->left);
preOrder(root->right);
}
void inOrder(Node* root) {
if (root == null) return;
inOrder(root->left);
print root // 此处为伪代码,表示打印root节点
inOrder(root->right);
}
void postOrder(Node* root) {
if (root == null) return;
postOrder(root->left);
postOrder(root->right);
print root // 此处为伪代码,表示打印root节点
}
加粗样式
4.1 二叉树时间复杂度分析
从遍历图中可以看出,每个节点被访问两次 ,2n,时间复杂度O(n)
5. 思考题
- 给定一组数据,比如 1,3,5,6,9,10。你来算算,可以构建出多少种不同的二叉树?
产生的总排列数就不是n!卡塔兰数 - 我们讲了三种二叉树的遍历方式,前、中、后序。实际上,还有另外一种遍历方式,也就是按层遍历,你知道如何实现吗?
层序遍历,借用队列辅助即可,根节点先入队列,然后循环从队列中pop节点,将pop出来的节点的左子节点先入队列,右节点后入队列,依次循环,直到队列为空,遍历结束。
struct TreeNode {
DataType data; /* node data */
struct TreeNode *leftPtr; /* pointer to left subtree */
struct TreeNode *rightPtr; /* pointer to right subtree */
};
void FloorPrint_QUEUE(pTreeNode &Tree) //层序遍历_队列实现
{
queue < pTreeNode> q;
if (Tree != NULL)
{
q.push(Tree); //根节点进队列
}
while (q.empty() == false) //队列不为空判断
{
cout << q.front()->data << " → ";
if (q.front()->leftPtr != NULL) //如果有左孩子,leftChild入队列
{
q.push(q.front()->leftPtr);
}
if (q.front()->rightPtr != NULL) //如果有右孩子,rightChild入队列
{
q.push(q.front()->rightPtr);
}
q.pop(); //已经遍历过的节点出队列
}
}
6 .总结
1.两种特殊的二叉树
1)满二叉树
2)完全二叉树
2.二叉树定理
3.二叉树遍历
1)递归
2)迭代
满二叉树 --> 除叶子节点,其他节点都有两个非空子节点的二叉树
完全二叉树 --> 除最后一层,其余层都是满的
术语: 结点的度 --> 一个结点包含的子树个数
树叶 --> 叶子节点,终端结点
孩子结点 --> 相对于父节点(双亲结点)
祖先结点 --> 从根结点到父节点都是
子孙结点
兄弟结点
树的度 --> 使所有结点的度最大值(唯一性)
>> 二叉树特点 --> 树的度 <= 2
--> 是有序树
二叉树性质
定理1 --> 若根结点的层数为1,在二叉树的i层至多有2(i-1)次方个结点
定理2 --> k层二叉树最多有2(k)次方 - 1个结点
定理3 --> n个结点的完全二叉树的层数为[log2N] + 1
定理4 --> 二叉树度数为2节点的个数等于叶节点个数减1
7.C++实现二叉树
typedef char BinTreeDataType;
//二叉树数据结构
typedef struct BTNode
{
BinTreeDataType data;
BTNode * lchild;
BTNode * rchild;
}BTNode, *BinTree;
//层次建树,使用链表辅助
BinTree createBinTree(BinTree B, BinTreeDataType * cArr, int size);
//递归遍历
Status preOrder(BinTree B);
Status inOrder(BinTree B);
Status postOrder(BinTree B);
//非递归遍历
Status preOrderNot(BinTree B);
Status inOrderNot(BinTree B);
Status postOrderNot(BinTree B);
//层次遍历
Status sequenceOreder(BinTree B);
//层次建树,使用链表辅助
BinTree createBinTree(BinTree B, BinTreeDataType * cArr, int size)
{
if(0 >= size) return ERROR;
int i;
BinTree bArr = (BinTree)malloc(size * sizeof(BTNode));
for(i = 0; i < size; i++)
{
bArr[i].data = cArr[i];
bArr[i].lchild = NULL;
bArr[i].rchild = NULL;
}
LinkList L; //带头结点的链表
createList(L, bArr, size);
LNode * pFront, * pRear;
pFront = L->next;
pRear = L->next;
B = NULL;
while(NULL != pRear)
{
if(NULL == B)
{
B = &(pRear->data);
pRear = pRear->next;
}
else if(NULL == (pFront->data).lchild)
{
(pFront->data).lchild = &(pRear->data);
pRear = pRear->next;
}
else if(NULL == (pFront->data).rchild)
{
(pFront->data).rchild = &(pRear->data);
pRear = pRear->next;
}
else
{
pFront = pFront->next;
}
}
return B;
}
//递归遍历
Status preOrder(BinTree B)
{
if(NULL == B) return ERROR;
printf("%c ", B->data);
preOrder(B->lchild);
preOrder(B->rchild);
}
Status inOrder(BinTree B)
{
if(NULL == B) return ERROR;
inOrder(B->lchild);
printf("%c ", B->data);
inOrder(B->rchild);
}
Status postOrder(BinTree B)
{
if(NULL == B) return ERROR;
postOrder(B->lchild);
postOrder(B->rchild);
printf("%c ", B->data);
}
//非递归遍历
//在入栈前打印
Status preOrderNot(BinTree B)
{
BinTree root = B;
BTNode temp;
if(NULL == B) return ERROR;
SqStack S;
initStack(S, 1, 3);
while(root || !emptyStack(S))
{
if(root)
{
printf("%c ", root->data);
pushStack(S, *root);
root = root->lchild;
}
else
{
popStack(S, temp);
root = temp.rchild;
}
}
return OK;
}
//在出栈后打印
Status inOrderNot(BinTree B)
{
BinTree root = B;
BTNode temp;
if(NULL == B) return ERROR;
SqStack S;
initStack(S, 1, 3);
while(root || !emptyStack(S))
{
if(root)
{
pushStack(S, *root);
root = root->lchild;
}
else
{
popStack(S, temp);
printf("%c ", temp.data);
root = temp.rchild;
}
}
return OK;
}
//使用两个栈实现
Status postOrderNot(BinTree B)
{
BinTree root = B;
BTNode temp, temp1;
if(NULL == B) return ERROR;
SqStack S, S1;
initStack(S, 5, 5);
initStack(S1, 5, 5);
while(root || !emptyStack(S))
{
if(root)
{
pushStack(S, *root);
root = root->lchild;
}
else
{
getTopStack(S, temp);
getTopStack(S1, temp1);
if(!emptyStack(S) && !emptyStack(S1) && temp1.data == temp.data)
{
popStack(S, temp);
popStack(S1, temp1);
printf("%c ", temp.data);
}
else
{
popStack(S, temp);
if(NULL != temp.rchild)
{
pushStack(S, temp);
pushStack(S1, temp);
}
else
{
printf("%c ", temp.data);
}
root = temp.rchild;
}
}
}
return OK;
}
//层次遍历
Status sequenceOreder(BinTree B)
{
LinkList L;
if(NULL == B) return OVERFLOW;
enList(L, *B);
LNode * pFront = L->next;
BTNode * root = B;
while(pFront)
{
if(pFront->data.lchild)
enList(L, *(pFront->data.lchild));
if(pFront->data.rchild)
enList(L, *(pFront->data.rchild));
pFront = pFront->next;
}
travelList(L);
return OK;
}