树
一种非线性的数据结构,是以分支关系定义的层次结构。
比如人类社会中的族谱,社会机构关系
重点:
- 二叉树的存储,相关操作
- 树,森林,二叉树的转换关系
树的基本术语
- 树:是n个节点的有限级,0 == n为空树,不讨论
- 根结点:树的最顶层结点,一棵树仅有一个
- 子树:一棵树除根节点外,剩余的互不相交的有限级,每一个集合本身又是一颗树,被称为根的子树
- 结点的度:树的结点包含一个数组元素及若干个指向其子树的分支,结点拥有的子树成为结点的度
- 叶子结点:结点的度为0,被称为叶子结点或终端结点
- 分支结点:结点的度部不为0,被称为分支结点或非终端结点,也被称为内部结点
- 树的度:是指树内各结点度的最大值
- 密度:一颗树中结点的总数
- 孩子,双亲,兄弟,祖先结点:结点的子树被称为该结点的孩子,该结点是孩子结点的双亲结点,共同双亲的互为兄弟结点,从双亲结点往上都被称为孩子结点的祖先结点
- 层数,深度,高度,密度:从根节点开始定义,根为第一层,根的孩子为第二层,树中结点的最大层数被称为树的深度或高度,所有结点的数量
- 有序树和无序树:将树种结点的各子树看成从左到右是有序次,即不能交换则称该树为有序树,否则称为无序树
- 森林:若干棵互不相交的竖的集合称为森林,对树中每个结点而言,其指数集合就是森林
就逻辑结构而言,任何一颗树都是一个二元组Tree = (root,F),其中root是数据元素,称为树的根结点,F是若干棵子树构成的森林
二叉树的定义和性质(背)
定义:
一种特殊的树形结构,每个结点最有有两颗子树(二叉树不存在度大于2的结点),二叉树的子树有左右之分,顺序不能颠倒
满二叉树:若一棵树的层数为k,则它的结点数是2^k-1,则这棵树是满二叉树
完全二叉树:若一棵树的层数为k,则它的前k-1层的结点数是2^(k-1)-1,第k层的结点按照从左往右顺序排序
性质:
- **在二叉树的第i层上最多有2^(i-1)个结点 **
- 深度为k的二叉树,最多有2^k - 1个结点
- 对于任何一颗二叉树,如果叶子结点数为n0,度为2结点的数量为n2,则n0 = n2+1
- 具有n个结点的完全二叉树的深度为log2(n)+1
- 如果对一颗有n个结点完全二叉树,结点按照从上到下,从左到右的顺序排序为1~n。
- i>1时 i/2就是它的双亲结点
- 2*i 是 i 的左子树,2*i+1 是 i 的右子树
二叉树不同型式
二叉树的遍历
- 前序:根 左 右
- 判断二叉树是否为空,若二叉树为空,则不操作
- 访问根结点
- 前序遍历左子树
- 前序遍历右子树
- 中序:左 根 右
- 判断二叉树是否为空,若二叉树为空,则不操作
- 中序遍历左子树
- 访问根结点
- 中序遍历右子树
- 后序:左 右 根
- 判断二叉树是否为空,若二叉树为空,则不操作
- 后序遍历左子树
- 后序遍历右子树
- 访问根结点
- 层序:从上到下 从左到右,需要与队列结构配合
二叉树的顺序存储
前提:由于顺序存储需要根据元素的相对位置确定关系,所以先把二叉树补成完全二叉树,之前空的点可以用特殊值代表,并把完全二叉树按照层序遍历结果存储到数组中。
只有补成了完全二叉树才能根据性质5的公式对二叉树进行相关操作
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <math.h>
#define PARENT_TREE(i) ((i + 1) / 2 - 1)
#define LEFT_TREE(i) ((i + 1) * 2 - 1)
#define RIGHT_TREE(i) ((i + 1) * 2)
typedef struct TreeArray
{
char *arr;
size_t cnt;
} TreeArray;
// 创建树
TreeArray *create_tree(const char *str)
{
TreeArray *tree = malloc(sizeof(TreeArray));
tree->cnt = strlen(str);
tree->arr = malloc(tree->cnt);
strncpy(tree->arr, str, tree->cnt);
return tree;
}
// 初始化树
void init_tree(TreeArray *tree, const char *str)
{
free(tree->arr);
tree->cnt = strlen(str);
tree->arr = malloc(tree->cnt);
strncpy(tree->arr, str, tree->cnt);
}
// 销毁树
void destroy_tree(TreeArray *tree)
{
free(tree->arr);
free(tree);
}
// 清空树
void clear_tree(TreeArray *tree)
{
free(tree->arr);
tree->cnt = 0;
}
// 判断是否是空树
bool empty_tree(TreeArray *tree)
{
return 0 == tree->cnt || '#' == tree->arr[0];
}
// 计算树的深度
size_t hight_tree(TreeArray *tree)
{
size_t hight = 1;
while (pow(2, hight) < tree->cnt)
hight++;
return hight;
}
// 访问根结点
char root_tree(TreeArray *tree)
{
if (empty_tree(tree))
return '#';
return tree->arr[0];
}
// 访问指定的结点
char value_tree(TreeArray *tree, size_t index)
{
if (index >= tree->cnt)
return '#';
return tree->arr[index];
}
// 给指定的位置赋值
bool assign_tree(TreeArray *tree, size_t index, char value)
{
if (index >= tree->cnt)
return false;
tree->arr[index] = value;
return true;
}
// 访问双亲结点
char parent_tree(TreeArray *tree, size_t index)
{
if (0 == index || PARENT_TREE(index) >= tree->cnt)
return '#';
return tree->arr[PARENT_TREE(index)];
}
// 访问左子树结点
char left_child_tree(TreeArray *tree, size_t index)
{
if (LEFT_TREE(index) >= tree->cnt)
return '#';
return tree->arr[LEFT_TREE(index)];
}
// 访问右子树结点
char right_child_tree(TreeArray *tree, size_t index)
{
if (RIGHT_TREE(index) >= tree->cnt)
return '#';
return tree->arr[RIGHT_TREE(index)];
}
// 访问左兄弟结点
char left_sigling_tree(TreeArray *tree, size_t index)
{
int left = LEFT_TREE(PARENT_TREE(index));
if (0 == index || left >= tree->cnt || left == index)
return '#';
return tree->arr[left];
}
// 访问右兄弟结点
char right_sigling_tree(TreeArray *tree, size_t index)
{
int right = RIGHT_TREE(PARENT_TREE(index));
if (0 == index || right >= tree->cnt || right == index)
return '#';
return tree->arr[right];
}
// 插入子树 lr(left true,right false)
bool insert_tree(TreeArray *tree, size_t index, bool lr, char value)
{
size_t pos = lr ? LEFT_TREE(index) : RIGHT_TREE(index);
if (pos >= tree->cnt || '#' != tree->arr[pos])
return false;
tree->arr[pos] = value;
return true;
}
bool delete_tree(TreeArray *tree, size_t index, bool lr)
{
// 计算出要删除的子树的下标
size_t pos = lr ? LEFT_TREE(index) : RIGHT_TREE(index);
// 判断要删除的子树下标是否合法
if (pos >= tree->cnt)
return false;
// 计算出要删除结点的左右子树下标
size_t pos_left = LEFT_TREE(pos), pos_right = RIGHT_TREE(pos);
// 判断删除的结点是否有左子树,如果有则删除失败
if (pos_left < tree->cnt && '#' != tree->arr[pos_left])
return false;
// 判断删除的结点是否有右子树,如果有则删除失败
if (pos_right < tree->cnt && '#' != tree->arr[pos_right])
return false;
tree->arr[pos] = '#';
return true;
}
// 前序遍历二叉树
void _pre_order_tree(TreeArray *tree, int index)
{
// 根
if (index >= tree->cnt || '#' == tree->arr[index])
return;
printf("%c ", tree->arr[index]);
// 左
_pre_order_tree(tree, LEFT_TREE(index));
// 右
_pre_order_tree(tree, RIGHT_TREE(index));
}
// 前序遍历
void pre_order_tree(TreeArray *tree)
{
_pre_order_tree(tree, 0);
printf("\n");
}
void _in_order_tree(TreeArray *tree, int index)
{
if (index >= tree->cnt || '#' == tree->arr[index])
return;
// 左
_in_order_tree(tree, LEFT_TREE(index));
// 根
printf("%c ", tree->arr[index]);
// 右
_in_order_tree(tree, RIGHT_TREE(index));
}
// 中序遍历
void in_order_tree(TreeArray *tree)
{
_in_order_tree(tree, 0);
printf("\n");
}
void _post_order_tree(TreeArray *tree, int index)
{
if (index >= tree->cnt || '#' == tree->arr[index])
return;
// 左
_post_order_tree(tree, LEFT_TREE(index));
// 右
_post_order_tree(tree, RIGHT_TREE(index));
// 根
printf("%c ", tree->arr[index]);
}
// 后序遍历
void post_order_tree(TreeArray *tree)
{
_post_order_tree(tree, 0);
printf("\n");
}
// 层序遍历
void level_order_tree(TreeArray *tree)
{
for (int i = 0; i < tree->cnt; i++)
{
if ('#' != tree->arr[i])
printf("%c ", tree->arr[i]);
}
printf("\n");
}
int main(int argc, const char *argv[])
{
TreeArray *tree = create_tree("FCEADHG##B###M");
level_order_tree(tree);
printf("---------------------\n");
printf("%d\n", hight_tree(tree));
printf("%c\n", right_sigling_tree(tree, 4));
insert_tree(tree, 0, true, 'X');
printf("%d\n", delete_tree(tree, 1, false));
return 0;
}
二叉树的链式存储
- 需要给每个度不为2的结点补充一些空白的子结点,使树中除了空白结点,其他结点的度全为2,然后以前,中,后序的方式遍历二叉树,然后以遍历的结果创建二叉树
- 不需要对二叉树做任何改变,以前序+中序或中序+后的序的遍历结果创建二叉树
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
typedef struct TreeNode
{
char data;
struct TreeNode *left;
struct TreeNode *right;
} TreeNode;
TreeNode *create_node(char data)
{
TreeNode *node = malloc(sizeof(TreeNode));
node->data = data;
node->left = NULL;
node->right = NULL;
return node;
}
// 方式1
TreeNode *create_tree(const char **str)
{
if ('#' == **str)
return NULL;
TreeNode *root = create_node(**str);
*str += 1;
root->left = create_tree(str);
*str += 1;
root->right = create_tree(str);
return root;
}
// 方式二
TreeNode *pre_in_create_tree(char *pre, char *in, int len)
{
if (len <= 0)
return NULL;
TreeNode *root = create_node(*pre);
int pos = 0;
while (*pre != in[pos])
pos++;
root->left = pre_in_create_tree(pre + 1, in, pos);
root->right = pre_in_create_tree(pre + 1 + pos, in + 1 + pos, len - 1 - pos);
return root;
}
// 方式二
TreeNode *post_in_create_tree(char *post, char *in, int len)
{
if (len <= 0)
return NULL;
TreeNode *root = create_node(post[len - 1]);
int pos = 0;
while (post[len - 1] != in[pos])
pos++;
root->left = post_in_create_tree(post, in, pos);
root->right = post_in_create_tree(post + pos, in + pos + 1, len - 1 - pos);
}
size_t height_tree(TreeNode *root)
{
if (NULL == root)
return 0;
size_t lh = height_tree(root->left);
size_t rh = height_tree(root->right);
return lh > rh ? lh + 1 : rh + 1;
}
size_t density_tree(TreeNode *root)
{
if (NULL == root)
return 0;
return 1 + density_tree(root->left) + density_tree(root->right);
}
void pre_order_tree(TreeNode *root)
{
if (NULL == root)
return;
printf("%c ", root->data);
pre_order_tree(root->left);
pre_order_tree(root->right);
}
void in_order_tree(TreeNode *root)
{
if (NULL == root)
return;
in_order_tree(root->left);
printf("%c ", root->data);
in_order_tree(root->right);
}
void post_order_tree(TreeNode *root)
{
if (NULL == root)
return;
post_order_tree(root->left);
post_order_tree(root->right);
printf("%c ", root->data);
}
void level_tree(TreeNode *root)
{
size_t density = density_tree(root), front = 0, rear = 0;
TreeNode *queue[density];
queue[rear++] = root;
while (front != rear)
{
TreeNode *node = queue[front++];
printf("%c ", node->data);
if (NULL != node->left)
queue[rear++] = node->left;
if (NULL != node->right)
queue[rear++] = node->right;
}
}
int main(int argc, const char *argv[])
{
const char *pre_str = "FCA##DB###EH##GM###";
TreeNode *pre_root = create_tree(&pre_str);
pre_order_tree(pre_root);
printf("\n");
/*
char post[] = "ADCHGEF";
char in[] = "ACDFHEG";
TreeNode* root = post_in_create_tree(post,in,7);
pre_order_tree(root);
printf("%d\n",height_tree(root));
printf("%d\n",density_tree(root));
*/
level_tree(pre_root);
return 0;
}
搜索二叉树
又名有序二叉树,二叉查找树
左子树不空,左子树上所有结点的值小于根节点
右子树不空,右子树上所有结点的值大于根节点
优点:
缺点:
-
二叉搜索树的元素添加的顺序会影响二叉搜索树的形状
二叉搜索树的形状会影响他的操作效率
在极端情况下二叉搜索树可能会成单支装分布,遍历速度接近于单项链表,这种情况出现的原因就是因为添加的元素基本有序
代码
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
typedef struct TreeNode
{
int val;
struct TreeNode* left;
struct TreeNode* right;
size_t height;
size_t density;
}TreeNode;
TreeNode* create_node(int val)
{
TreeNode* root = malloc(sizeof(TreeNode));
root->val = val;
root->left = NULL;
root->right = NULL;
root->height = 1;
root->density = 1;
return root;
}
void auto_tree(TreeNode* root)
{
if(NULL == root)
return;
int lh = 0 , rh = 0 , ld = 0 , rd = 0;
if(NULL != root->left)
{
auto_tree(root->left);
lh = root->left->height;
ld = root->left->density;
}
if(NULL != root->right)
{
auto_tree(root->right);
rh = root->right->height;
rd = root->right->density;
}
root->height = lh > rh ? lh+1 : rh+1;
root->density = ld + rd + 1;
}
void _add_tree(TreeNode** root,TreeNode* node)
{
if(NULL == *root)
{
*root = node;
return;
}
if((*root)->val > node->val)
_add_tree(&(*root)->left,node);
else
_add_tree(&(*root)->right,node);
auto_tree(*root);
}
void add_tree(TreeNode** root,int val)
{
_add_tree(root,create_node(val));
}
TreeNode** _query_tree(TreeNode** root,int val)
{
if(NULL == *root)
return root;
if(val == (*root)->val)
return root;
if(val <= (*root)->val)
return _query_tree(&(*root)->left,val);
else
return _query_tree(&(*root)->right,val);
}
bool query_tree(TreeNode* root,int val)
{
TreeNode** node = _query_tree(&root,val);
return NULL != *node;
}
void _del_tree(TreeNode** root)
{
TreeNode *temp = *root;
if(NULL == temp->left)
*root = temp->right;
else if(NULL == temp->right)
*root = temp->left;
else
{
*root = temp->left;
_add_tree(root,temp->right);
}
free(temp);
}
bool del_tree(TreeNode** root,int val)
{
TreeNode** node =_query_tree(root,val);
if(NULL == *node)
return false;
_del_tree(node);
auto_tree(*root);
return true;
}
bool modify_tree(TreeNode** root,int old,int val)
{
TreeNode** node = _query_tree(root,old);
if(NULL == *node)
return false;
_del_tree(node);
_add_tree(root,create_node(val));
}
void _in_order_tree(TreeNode* root)
{
if(NULL == root)
return;
_in_order_tree(root->left);
printf("%d ",root->val);
_in_order_tree(root->right);
}
void in_order_tree(TreeNode* root)
{
_in_order_tree(root);
printf("\n");
}
void level_order_tree(TreeNode* root)
{
if(NULL == root)
return;
TreeNode* queue[root->density];
int front = 0 , rear = 0;
queue[rear++] = root;
while(front != rear)
{
int cnt = rear - front;
while(cnt--)
{
TreeNode* node = queue[front++];
printf("[v:%d h:%d d:%d] ",node->val,node->height,node->density);
if(NULL != node->left)
queue[rear++] = node->left;
if(NULL != node->right)
queue[rear++] = node->right;
}
printf("\n");
}
}
// 访问第k个小的数,按大小值的顺序访问
void _assign_tree(TreeNode* root,int k,int* pos,int* ptr)
{
if(NULL == root)
return;
_assign_tree(root->left,k,pos,ptr);
if(++*pos == k)
*ptr = root->val;
_assign_tree(root->right,k,pos,ptr);
}
int assign_tree(TreeNode* root,int k)
{
if(k < 1)
return -1;
int pos = 0 , val = -1;
_assign_tree(root,k,&pos,&val);
return val;
}
int main(int argc,const char* argv[])
{
TreeNode* root = NULL;
for(int i=0; i<10; i++)
{
add_tree(&root,rand()%100);
}
in_order_tree(root);
modify_tree(&root,77,99);
in_order_tree(root);
level_order_tree(root);
return 0;
}
平衡二叉树(AVL树)
首先是一棵二叉搜索树
二叉搜索树在极端情况下(插入的序列是有序的),二叉搜索树将退化成单链表,时间复杂度会退化为线性O(n)
可以通过随机化建立二叉搜索树来尽量避免这种情况,在多次删除时候,只将左或右子树前提,则会导致树往一遍沉。
会将树的平衡性破坏,提高他的操作时间复杂度。
优点:
- 能够让二叉搜索树以最佳的状态进行操作 时间复杂度为O(log2n),避免了二叉搜索树的单支状分布
缺点:
-
平衡二叉树在创建,添加,删除时,为了让二叉树保持平衡,需要进行大量的左旋,右旋,高度的计算,所以平衡二叉树在创建时,添加删除数据时,速度比较慢
因此,平衡二叉树适合使用在数据量大,但数据量稳定没有大量的添加删除动作,大多数情况下都是在查询
不平衡的二叉树调整为二叉树
前提:检查二叉树是否平衡,从叶子结点向根节点遍历
不平衡二叉树的四种情况
A A A A
/ / \ \
B B B B
/ \ / \
C C C C
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define TreeHight(node) (NULL==(node)?0:(node)->height)
typedef struct TreeNode
{
int val;
struct TreeNode* left;
struct TreeNode* right;
size_t height;
}TreeNode;
// 创建结点
TreeNode* create_node(int val)
{
TreeNode* root = malloc(sizeof(TreeNode));
root->val = val;
root->left = NULL;
root->right = NULL;
root->height = 1;
return root;
}
// 调整树的高度
void auto_height_tree(TreeNode* root)
{
if(NULL == root)
return;
auto_height_tree(root->left);
auto_height_tree(root->right);
int lh = TreeHight(root->left);
int rh = TreeHight(root->right);
root->height = lh > rh ? lh+1 : rh+1;
}
void right_rotate_tree(TreeNode** rpp)
{
TreeNode* A = *rpp;
TreeNode* B = A->left;
TreeNode* t = B->right;
*rpp = B;
B->right = A;
A->left = t;
}
void left_rotate_tree(TreeNode** rpp)
{
TreeNode* A = *rpp;
TreeNode* B = A->right;
TreeNode* t = B->left;
*rpp = B;
B->left = A;
A->right = t;
}
void auto_balance_tree(TreeNode** rpp)
{
if(NULL == *rpp)
return;
auto_balance_tree(&(*rpp)->left);
auto_balance_tree(&(*rpp)->right);
int lh = TreeHight((*rpp)->left);
int rh = TreeHight((*rpp)->right);
if(lh-rh > 1)
{
if(TreeHight((*rpp)->left->left) >= TreeHight((*rpp)->left->right))
right_rotate_tree(rpp);
else
{
left_rotate_tree(&(*rpp)->left);
right_rotate_tree(rpp);
}
auto_balance_tree(rpp);
}
else if(lh-rh < -1)
{
if(TreeHight((*rpp)->right->right) >= TreeHight((*rpp)->right->left))
left_rotate_tree(rpp);
else
{
right_rotate_tree(&(*rpp)->right);
left_rotate_tree(rpp);
}
auto_balance_tree(rpp);
}
else
(*rpp)->height = lh > rh ? lh+1 : rh+1;
}
bool is_balance_tree(TreeNode* root)
{
if(NULL == root)
return true;
bool flag1 = is_balance_tree(root->left);
bool flag2 = is_balance_tree(root->right);
return flag1 && flag2 && 1 >= abs(TreeHight(root->left)-TreeHight(root->right));
}
void _in_order_tree(TreeNode* root)
{
if(NULL == root)
return;
_in_order_tree(root->left);
printf("%d ",root->val);
_in_order_tree(root->right);
}
void in_order_tree(TreeNode* root)
{
_in_order_tree(root);
printf("\n");
}
void level_order_tree(TreeNode* root)
{
if(NULL == root)
return;
TreeNode* queue[100];
int front = 0 , rear = 0;
queue[rear++] = root;
while(front != rear)
{
int cnt = rear - front;
while(cnt--)
{
TreeNode* node = queue[front++];
printf("[v:%d h:%d] ",node->val,node->height);
if(NULL != node->left)
queue[rear++] = node->left;
if(NULL != node->right)
queue[rear++] = node->right;
}
printf("\n");
}
}
// 添加结点
void _add_tree(TreeNode** rpp,TreeNode* node)
{
if(NULL == *rpp)
{
*rpp = node;
return;
}
if(node->val < (*rpp)->val)
_add_tree(&(*rpp)->left,node);
else
_add_tree(&(*rpp)->right,node);
}
// 添加元素
void add_tree(TreeNode** rpp,int val)
{
// 创建结点并添加
_add_tree(rpp,create_node(val));
// 调整树的高度
auto_height_tree(*rpp);
// 让树自动平衡
auto_balance_tree(rpp);
}
// 初始化二权树
TreeNode* init_tree(int* arr,int size)
{
TreeNode* root = NULL;
// 循环把所有的元素创建结点并添加到树中
for(int i=0; i<size; i++)
{
_add_tree(&root,create_node(arr[i]));
}
// 调整树的高度
auto_height_tree(root);
// 让树自动平衡
auto_balance_tree(&root);
return root;
}
int main(int argc,const char* argv[])
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
TreeNode* root = NULL;
for(int i=0; i<10; i++)
{
add_tree(&root,arr[i]);
}
printf("%d\n",is_balance_tree(root));
return 0;
}
红黑树和AVL树的区别(面试)
- AVL树在物理上就是平衡的,所以在创建,添加,删除时速度比较慢,但他的查询速度接近平衡二叉树的极限
- 红黑树是一种特殊的AVL树,它的物理结构不是严格的平衡,而是接近平衡,从根节点所有的叶子结点的速度大致相同,它的非绝对平衡使用节约很多左旋,右旋的次数,因此它创建,添加,删除时速度比AVL树要快,查询速度接近AVL树