一、简介
二叉树是由 n(n>=0)个结点组成的有序集合,集合或者为空,或者是由一个根节点加上两棵分别称为左子树和右子树的、互不相交的二叉树组成。
二叉树有序是为了高效搜索,如果无序,在搜索的时候就只能遍历了 。并且无序的话,就只能手工创建树,不然程序无法获知结点的创建规则
二叉树不是树的一种特殊情形,尽管其与树有许多相似之处,但树和二叉树有两个主要差别:
①、树中结点的最大度数没有限制,而二叉树结点的最大度数为 2;
②、树的结点无左、右之分,而二叉树的结点有左、右之分。
1、二叉树性质
①、在非空二叉树中,第 i 层的结点总数不超过 2i - 1,i >= 1;
②、深度为 h 的二叉树最多有 2h - 1 个结点(h >= 1),最少有 h 个结点;
③、对于任意一棵二叉树,如果其叶结点数为 N0,而度数为 2 的结点总数为 N2,则 N0 = N2 + 1;
论证:每次添加一个新结点时,要么 N0、N2 不变,要么 N0、N2 同时 + 1.
④、具有 n 个结点的完全二叉树的深度为 [log2n] + 1。(注:[ ]表示向下取整)
⑤、有 N 个结点的完全二叉树各结点如果用顺序方式存储,则结点之间有如下关系:
若 I 为结点编号则如果 I > 1,则其父结点的编号为 I/2;
如果 2 * I <= N,则其左孩子的编号为 2 * I;若 2 * I > N,则无左孩子;
如果 2 * I + 1 <= N,则其右孩子的结点编号为 2 * I + 1;若2*I+1>N,则无右孩子。
⑥、给定 N 个节点,能构成 h(N) 种不同的二叉树。
h(N)为卡特兰数的第 N 项。h(n) = C(2*n,n)/(n+1)。
⑦、设有 i 个枝点,I 为所有枝点的道路长度总和,J 为叶的道路长度总和 J = I + 2i。
二、存储结构
1、孩子兄弟表示法
①、每个结点都有一个指向其第一个孩子的指针
②、每个结点都有一个指向其右侧兄弟的指针
特性:
①、能够表示任意的树形结构
②、每个结点包含一个数据成员和两个指针成员
③、孩子结点指针和兄弟结点指针构成树杈
typedef struct Node {
int data;
struct Node * child;
struct Node * brother;
} Node, Tree;
2、孩子表示法
每个结点都有一个指向左孩子的指针和一个指向右孩子的指针
typedef struct Node {
int data;
struct Node * lChild;
struct Node * rChild;
} Node, Tree;
孩子表示法
三、满二叉树
满二叉树如果二叉树中所有分支结点的度都为 2,并且叶子结点都在统一层次上,则二叉树为满二叉树。
四、完全二叉树
完全二叉树是由满二叉树而引出来的。
对于深度为 K 的,有 n 个结点的二叉树,当且仅当其每一个结点都与深度为 K 的满二叉树中编号从 1 至 n 的结点一一对应时称之为完全二叉树。
简而言之:完全二叉树从根结点到倒数第二层是满二叉树,最后一层可以不完全填充,其叶子结点都靠左对齐。
特性:
①、同样结点数的二叉树,完全二叉树的高度最小;
②、完全二叉树的叶子结点仅出现在最下边两层,并且最底层的叶子结点一定出现在左边,倒数第二层的叶子结点一定出现在右边。
③、完全二叉树中度为 1 的结点只有左孩子。
五、层次遍历
手工创建的无序二叉树各个结点的内容没有指定的顺序,可以引入一个队列,辅助逐层遍历二叉树。
二叉树遍历typedef struct TreeNode {
int data; //数据
struct TreeNode * lchild; // 左孩子指针
struct TreeNode * rchild; // 右孩子指针
} TreeNode, *Tree;
typedef struct QueueNode {
TreeNode * treeNode; // 数据
struct QueueNode * next; // 指向下一个结点的指针
} QueueNode;
/* 队列结构 */
typedef struct Queue {
struct QueueNode * front; // 队头
struct QueueNode * rear; // 队尾
} *Queue;
/// 初始化队列
Queue initQueue()
{
Queue queue = (Queue)malloc(sizeof(queue));
if (queue == NULL)
return NULL;
queue->front = NULL;
queue->rear = NULL;
return queue;
}
/// 空队列
bool isEmptyQueue(Queue queue)
{
return (queue == NULL || queue->front == NULL || queue->front->next == NULL || queue->front == queue->rear);
}
/// 分配一个队列结点
QueueNode * initQueueNode(TreeNode * treeNode)
{
QueueNode * queueNode = (QueueNode *)malloc(sizeof(QueueNode));
if (queueNode == NULL)
return NULL;
memset(queueNode, 0, sizeof(struct QueueNode));
queueNode->treeNode = treeNode;
queueNode->next = NULL;
return queueNode;
}
/// 入队
void insertQueueNode(Queue queue, TreeNode * treeNode)
{
if (queue == NULL)
queue = initQueue();
QueueNode * queueNode = initQueueNode(treeNode);
// 空队列
if (isEmptyQueue(queue)) {
queue->front = initQueueNode(NULL);
queue->front->next = queueNode;
queue->rear = queueNode;
}
// 非空队列
else {
queueNode->next = queue->rear->next;
queue->rear->next = queueNode;
queue->rear = queueNode;
}
}
/// 出队
QueueNode * removeNode(Queue queue)
{
// 空队列
if (isEmptyQueue(queue))
return NULL;
QueueNode * queueNode = queue->front->next;
// 如果已经是最后一个值
if (queueNode == queue->rear) {
queue->front = NULL;
queue->rear = NULL;
}
else {
queue->front->next = queueNode->next;
}
return queueNode;
}
/// 分配一个树结点
TreeNode * initNode(int data)
{
TreeNode * node = (TreeNode *)malloc(sizeof(TreeNode));
if(node == NULL)
return NULL;
// memset(node, 0, sizeof(struct TreeNode));
node->data = data;
node->lchild = NULL;
node->rchild = NULL;
return node;
}
/**
* 向二叉查找树中添加一个节点,使得新的二叉树还是二叉查找树。
* (非递归方法实现)
*/
void insertNode(Tree tree, int data)
{
// 空树
if (tree == NULL) {
tree = initNode(data);
}
else {
TreeNode * curNode = tree; // 当前位置结点
TreeNode * parent = NULL; // 保存父结点
bool isLeft = false;
while(curNode != NULL) {
parent = curNode;
isLeft = false;
// 比当前结点小,进入左子树
if(data < curNode->data) {
isLeft = true;
curNode = curNode->lchild;
}
// 比当前结点大,进入右子树
else if(data > curNode->data) {
curNode = curNode->rchild;
}
}
TreeNode * node = initNode(data);
if(isLeft) {
parent->lchild = node;
}
else {
parent->rchild = node;
}
}
}
/// 逐层遍历树
void printTree(Tree tree)
{
if (tree == NULL)
return;
// 用队列来辅助遍历
Queue queue = initQueue();
insertQueueNode(queue, tree);
// 非空队列
while (!isEmptyQueue(queue)) {
// 打印队头结点内容
printf("%d\t", queue->front->next->treeNode->data);
// 左结点入队
if (queue->front->next->treeNode->lchild != NULL)
insertQueueNode(queue, queue->front->next->treeNode->lchild);
// 右结点入队
if (queue->front->next->treeNode->rchild != NULL)
insertQueueNode(queue, queue->front->next->treeNode->rchild);
// 出队
removeNode(queue);
}
}
/// 插入法创建二叉树
void createTree(Tree tree, int value[], int len)
{
for(int i = 0; i < len; i++) {
insertNode(tree, value[i]);
}
}
/// 调用
{
int Array[] = { 2, 3, 4, 5, 6, 7, 8, 9 };
Tree tree = initNode(1);
createTree(tree, Array, 8);
printTree(tree);
}
1 2 3 4 5 6 7 8 9
六、按序遍历
1、先序遍历
①、访问根结点;
②、先序遍历左子树;
③、先序遍历右子树。
1 2 4 8 9 5 10 3 6 7
2、中序遍历
①、中序遍历左子树;
②、访问根结点;
③、中序遍历右子树。
8 4 9 2 10 5 1 6 3 7
3、后序遍历
①、后序遍历左子树;
③、后序遍历右子树。
②、访问根结点;
8 9 4 10 5 2 6 7 3 1
七、二叉树叠加
两棵二叉树重叠位置处的数据元素相加,返回一棵在堆空间创建的新的二叉树。
二叉树叠加八、二叉树线索化
线索化二叉树是将非线性的二叉树转换为线性的双向链表的过程。
二叉树的线索化能够反映某种二叉树的遍历次序(结点的先后访问次序)。
线索化二叉树的过程 二叉树线索化的实现通过某种遍历方式遍历二叉树,根据遍历次序将二叉树结点依次存储到辅助队列中,最后将辅助队列中保存的结点依次出队并连接,成为双向链表。
中序遍历:连接时,原二叉树结点的 lChild 指针作为双向链表结点的 pre 指针,指向结点的前驱;原二叉树结点的 rChild 结点作为双向链表结点的 next 指针,指向结点的后继。
结点连接
网友评论