树和二叉树
一、基本概念
根结点: 树的最顶层的结点,一棵树有且仅有一个。
子树: 一棵树除根结点外,剩余的是若干个互不相交的有限集,每一个集合本身又是棵树,称为根的子树。
结点的度: 树的结点包含一个数据元素及若干个指向其子树的分支,结点拥有的子树数称为结点的度。
叶子结点: 度为0的结点。
分支结点: 结构的度不为0,被称为分支结点或非终端结点,也被称为内部结点。
树的度: 是指树内各结点度的最大值。
密度: 指的是一棵树中,所有结点的总数。
孩子、双亲、兄弟、祖先、子孙: 结点的子树称为该结点的孩子,而该结点是孩子结点的双亲,拥有共同双亲的结点互为兄弟,从双亲结点往上,直到根结点都称为孩子结点的祖先结点,以某结点为根的子树中的任一结点都被称为该结点的子孙。
层数、深度、高度: 从根结点开始定义,根为第一层、根的孩子为第二层依次类推,树中结点的最大层数被称为树的深度或高度,双亲在同一层的结点互为堂兄弟。
有序树和无序树: 将树中结点的各子树看成从左到右是有序次,即不能交换(顺序有意义,表达一些含义),则称该树为有序树,否则称为无序树。
森林: 若干个棵互不相交的树的集合称为森林,对树中每个结点而言,其子树集合就是森林。
二、二叉树
二叉树:
是一种特殊的树型结构,也就是每个结点最多有两棵子树(二叉树中不存在度大于2的结点),并且二叉树的子树有左右之分,顺序不能颠倒。
满二叉树:
若一棵树的层数为k,它总结点数是(2^k)-1,则这棵树就是满二叉树。
完全二叉树:
若一棵树的层数为k,它前k-1层的总结点数是2^(k-1)-1(前k-1层是满二叉树),第k层的结点按照从左往右的顺序排列,则这棵树就是完全二叉树。
二叉树的性质:
性质1:
在二叉树的第i层上,最多有2^(i-1) [i=1,2,3…n]个结点。
性质2:
深度为k的二叉树,最多有(2^k)-1[k=1,2,3…n]个节点。
性质3:
对于任何一棵二叉树,如果叶子结点的数量为n0,度为2结点的数量为n2,则n0=n2+1;
性质4:
具有n个结点的完全二叉树的高度为⌊ log2n ⌋+1。
性质5:
如有一个n个结点的完全二叉树,结点按照从上到下从左到右的顺序排序为1~n。
1、i > 1时,i/2就是它的双亲结点。
2、i*2是i的左子树,当i*2>n时,则i没有左子树。
3、2*i+1是i的右子树,2*i+1>n时,则i没有右子树。
三、二叉树的实现
1、顺序存储
由于顺序存储是根据元素的相对位置来确定,所以需要将二叉树补为完全二叉树,可以用一些特殊值来表示空白位置,然后就可以通过性质5的公式来计算对应位置,要注意下标和性质中序号的转换。
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<string.h>
#include<math.h>
#define LEFT_INDEX(index) ((((index)+1)*2)-1)
#define RIGHT_INDEX(index) (((index)+1)*2)
#define PARENT_INDEX(index) (((index)+1)/2-1)
typedef struct TreeArray
{
char* arr;
size_t cnt;
}TreeArray;
//str是完全二叉树的层序遍历结果
TreeArray* create_tree(const char* str)
{
TreeArray* tree=malloc(sizeof(TreeArray));
tree->cnt=strlen(str);
tree->arr=malloc(tree->cnt);
//只拷贝cnt个字符 不一定以'\0'结束
strncpy(tree->arr,str,tree->cnt);
return tree;
}
//销毁
void destroy_tree(TreeArray* tree)
{
free(tree->arr);
free(tree);
}
//访问左子树
char _left_tree(TreeArray* tree,int index)
{
// index是下标
int li=LEFT_INDEX(index);
if(li>=tree->cnt)
return '#';
return tree->arr[li];
}
char left_tree(TreeArray* tree,char val)
{
for(int i=0;i<tree->cnt;i++)
{
if(tree->arr[i]==val)
return _left_tree(tree,i);
}
return '#';
}
//访问右子树
char _right_tree(TreeArray* tree,int index)
{
int ri=RIGHT_INDEX(index);
if(ri>=tree->cnt)
return '#';
return tree->arr[ri];
}
char right_tree(TreeArray* tree,char val)
{
for(int i=0;i<tree->cnt;i++)
{
if(tree->arr[i]==val)
return _right_tree(tree,i);
}
return '#';
}
//访问双亲
char _parents_tree(TreeArray* tree,int index)
{
int pi=PARENT_INDEX(index);
if(index<0)
return '#';
return tree->arr[pi];
}
char parents_tree(TreeArray* tree,char val)
{
for(int i=0;i<tree->cnt;i++)
{
if(tree->arr[i]==val)
return _parents_tree(tree,i);
}
return '#';
}
//计算高度
size_t height_tree(TreeArray* tree)
{
for(int h=1;h<tree->cnt;h++)
{
if(pow(2,h)-1 >= tree->cnt)
return h;
}
}
//计算密度
size_t density_tree(TreeArray* tree)
{
size_t count=0;
for(int i=0;i<tree->cnt;i++)
{
count+=tree->arr[i]!='#';
}
return count;
}
//前序遍历
void _pre_tree(TreeArray* tree,int index)
{
if(0>index||index>=tree->cnt)
return;
if('#' == tree->arr[index])
return;
printf("%c ",tree->arr[index]);
_pre_tree(tree,LEFT_INDEX(index));
_pre_tree(tree,RIGHT_INDEX(index));
}
void pre_tree(TreeArray* tree)
{
_pre_tree(tree,0);
printf("\n");
}
//中序遍历
void _mid_tree(TreeArray* tree,int index)
{
if(0>index||index>=tree->cnt)
return;
if('#' == tree->arr[index])
return;
_mid_tree(tree,LEFT_INDEX(index));
printf("%c ",tree->arr[index]);
_mid_tree(tree,RIGHT_INDEX(index));
}
void mid_tree(TreeArray* tree)
{
_mid_tree(tree,0);
printf("\n");
}
//后序遍历
void _post_tree(TreeArray* tree,int index)
{
if(0>index||index>=tree->cnt)
return;
if('#' == tree->arr[index])
return;
_post_tree(tree,LEFT_INDEX(index));
_post_tree(tree,RIGHT_INDEX(index));
printf("%c ",tree->arr[index]);
}
void post_tree(TreeArray* tree)
{
_post_tree(tree,0);
printf("\n");
}
int main(int argc,const char* argv[])
{
const char* str="HCDGF##Z";
TreeArray* tree=create_tree(str);
printf("%c\n",right_tree(tree,'D'));
printf("%c\n",parents_tree(tree,'D'));
printf("%d\n",height_tree(tree));
printf("%d\n",density_tree(tree));
pre_tree(tree);
mid_tree(tree);
post_tree(tree);
return 0;
}
2、链式存储
有两种创建链式二叉树的方式:
方式一: 需要给每个度不为2的结点补充一些空白子结点,使树中除了空白结点其它结点的度全为2,然后以前序的方式遍历二叉树,然后以遍历的结果创建二叉树。
方式二: 不需要对二叉做任何改变,以前序+中序或中序+后序的遍历结果创建二叉树。
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
typedef struct TreeNode
{
char val;
struct TreeNode* left;
struct TreeNode* right;
}TreeNode;
TreeNode* create_node(char val)
{
TreeNode* node = malloc(sizeof(TreeNode));
node->val = val;
node->left = NULL;
node->right = NULL;
return node;
}
TreeNode* _create_tree(const char* str,int* ip)
{
if('#' == str[*ip])
return NULL;
TreeNode* node = create_node(str[*ip]);
*ip += 1;
node->left = _create_tree(str,ip);
*ip += 1;
node->right = _create_tree(str,ip);
return node;
}
TreeNode* create_tree(const char* str)
{
int index = 0;
return _create_tree(str,&index);
}
//前序 中序 后序类似 只需要改变下位置
void pre_tree(TreeNode* tree)
{
if(NULL == tree)
return;
printf("%c ",tree->val);
pre_tree(tree->left);
pre_tree(tree->right);
}
int main(int argc,const char* argv[])
{
TreeNode* tree = create_tree("HCGZ###F##D##");
pre_tree(tree);
return 0;
}
以中序+后序为例
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<string.h>
typedef struct TreeNode
{
char val;
struct TreeNode* left;
struct TreeNode* right;
}TreeNode;
TreeNode* create_node(char val)
{
TreeNode* node=malloc(sizeof(TreeNode));
node->val=val;
node->left=NULL;
node->right=NULL;
return node;
}
TreeNode* post_mid_tree(char* post,char* mid,size_t len)
{
if(0>=len)
return NULL;
TreeNode* node=create_node(post[len-1]);
int ri=0;
while(ri<len&& mid[ri]!=post[len-1])
ri++;
node->left=post_mid_tree(post,mid,ri);
node->right=post_mid_tree(post+ri,mid+ri+1,len-ri-1);
}
void pre_tree(TreeNode* tree)
{
if(NULL==tree)
return;
printf("%c ",tree->val);
pre_tree(tree->left);
pre_tree(tree->right);
}
int main(int argc,const char* argv[])
{
char post[] = "74258631";
char mid[] = "47215386";
TreeNode* tree = post_mid_tree(post,mid,strlen(post));
pre_tree(tree);
return 0;
}
四、二叉搜索树
二叉查找树(Binary Search Tree),又称二叉搜索树,二叉排序树。它具有如下性质:
1、对于任意节点,其左子树中的值都小于该节点的值,右子树中的值都大于该节点的值。
2、左子树和右子树也都是二叉搜索树。
对于这样有规律的树,我们就可以进行快速的插入删除查找等操作了。
插入:
向二叉搜索树中插入一个新的节点时,需要从根节点开始比较节点的值,并根据比较结果选择左子树或右子树继续进行比较,直到找到一个空的位置插入新节点。
删除:
删除节点时,首先需要找到待删除的节点。如果待删除的节点没有子节点,可以直接删除即可。如果待删除的节点有一个子节点,可以用子节点替换待删除的节点。如果待删除的节点有两个子节点,可以找到其右子树中的最小节点,即右子树中的最左节点,将其值复制到待删除节点,并删除该最小节点。查找:
查找操作在二叉搜索树中非常高效。从根节点开始,若目标值等于当前节点的值,则找到了目标节点。若目标值小于当前节点的值,则继续在左子树中查找,若目标值大于当前节点的值,则继续在右子树中查找,直到找到目标节点或遍历到空节点。缺点:极端情况下会影响树的形状,如插入的数据有序,他就会呈单支状分布,进而降低效率。
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
typedef struct TreeNode
{
int val;
struct TreeNode* left;
struct TreeNode* right;
}TreeNode;
TreeNode* create_node(int val)
{
TreeNode* node = malloc(sizeof(TreeNode));
node->val = val;
node->left = NULL;
node->right = NULL;
return node;
}
void destroy_tree(TreeNode* tree)
{
if(NULL == tree)
return;
destroy_tree(tree->right);
destroy_tree(tree->left);
printf("%d ",tree->val);
free(tree);
}
/*
void insert_tree(TreeNode** tree,int val)
{
if(NULL == *tree)
{
*tree = create_node(val);
return;
}
if(val < (*tree)->val)
insert_tree(&(*tree)->left,val);
else
insert_tree(&(*tree)->right,val);
}
*/
TreeNode* _insert_tree(TreeNode* tree,TreeNode* node)
{
if(NULL == node)
return tree;
if(NULL == tree)
return node;
//小于根节点的值,成为左子树
if(node->val < tree->val)
tree->left = _insert_tree(tree->left,node);
else
tree->right = _insert_tree(tree->right,node);
return tree;
}
TreeNode* insert_tree(TreeNode* tree,int val)
{
return _insert_tree(tree,create_node(val));
}
TreeNode* query_tree(TreeNode* tree,int key)
{
if(NULL == tree)
return NULL;
if(key < tree->val)
return query_tree(tree->left,key);
if(key > tree->val)
return query_tree(tree->right,key);
return tree;
}
void mid_tree(TreeNode* tree)
{
if(NULL == tree)
return;
mid_tree(tree->left);
printf("%d ",tree->val);
mid_tree(tree->right);
}
TreeNode* delete_tree(TreeNode* tree,int key)
{
if(NULL == tree)
return NULL;
if(key < tree->val)
{
tree->left = delete_tree(tree->left,key);
return tree;
}
if(key > tree->val)
{
tree->right = delete_tree(tree->right,key);
return tree;
}
tree->left = _insert_tree(tree->left,tree->right);
TreeNode* tmp = tree->left;
free(tree);
return tmp;
/*
if(NULL == tree)
return NULL;
if(key < tree->val)
tree->left = delete_tree(tree->left,key);
if(key > tree->val)
tree->left = delete_tree(tree->right,key);
if(NULL == tree->left && NULL == tree->right)
{
free(tree);
return NULL;
}
if(NULL == tree->right)
{
TreeNode* tmp = tree->left;
free(tree);
return tmp;
}
if(NULL == tree->left)
{
TreeNode* tmp = tree->right;
free(tree);
return tmp;
}
TreeNode* min = tree->right;
while(NULL != min->left)
min = min->left;
int tmp = tree->val;
tree->val = min->val;
min->val = tmp;
tree->right = delete_tree(min,key);
return tree;
*/
}
int main(int argc,const char* argv[])
{
TreeNode* tree = NULL;
int arr[10];
for(int i=0; i<10; i++)
{
arr[i] = rand()%100;
tree = insert_tree(tree,arr[i]);
printf("%d ",arr[i]);
}
printf("\n");
/*
//mid_tree(tree);
printf("\n");
for(int i=0; i<10; i++)
{
tree = delete_tree(tree,arr[i]);
mid_tree(tree);
printf(" del %d\n",arr[i]);
}
*/
destroy_tree(tree);
return 0;
}