学习笔记7:树的实现和代码

一、树的基本概念

1.树的定义

树是n个结点的有限集,当 n = 0 n=0 n=0时,称为空树,树只有一个根结点,根结点没有前驱,除根结点之外的所以结点有且只有一个前驱,树中所有结点可以有一个或多个后继。

2.基本术语

  1. 祖先:从根结点到某一指定结点路径上的所有结点

  2. 父结点:若一个结点含有子结点,则这个结点称为其子结点的父结点

  3. 子结点:一个结点含有的子树的根结点称为该结点的子结点

  4. 兄弟结点:拥有共同父结点的结点互称为兄弟结点

  5. 结点的度: 一个结点含有子结点的个数

  6. 树的度:树中结点的最大度数称为数的度

  7. 分支结点/叶子结点:度大于0的结点称为分支结点,度等于0的结点称为叶子结点

  8. 树的高度/深度:树从上至下层数逐层累加称为深度,从下至上逐层累加称为高度

  9. 有序树/无序树:树的任意结点的子结点是否存在顺序关系。

3.树的性质

  1. 树中的结点树等于所有结点的度数加1

  2. 度为 m m m的树中第 i i i层上之多有$m^{i-1} $ 个结点

  3. 高度为 h h h m m m叉树至多有 ( m h − 1 ) / ( m − 1 ) (m^h-1)/(m-1) (mh1)/(m1)个结点

  4. 具有 n n n个结点的 m m m叉树的最小高度为 log ⁡ m ( n ( m − 1 ) + 1 ) \log_m(n(m-1)+1) logm(n(m1)+1)

二、二叉树

1.定义

树任意结点至多只能有两颗子树的树称为二叉树,且二叉树的子树有左右之分,次序不能颠倒,二叉树也以递归的形式定义,其任意结点左右子树也是一颗二叉树。

2.性质

  1. 非空二叉树上的叶子结点数等于度为2的结点数加1,即 n 0 = n 2 + 1 n_0=n_2+1 n0=n2+1
  2. 非空二叉树上第 k k k层上之多有 2 k − 1 2^{k-1} 2k1个结点
  3. 高度为 h h h的二叉树之多有 2 h − 1 2^h-1 2h1个结点

3.满二叉树

定义:一颗高度为 h h h,且含有 2 h − 1 2^h-1 2h1个结点的二叉树称为满二叉树,即每层都含有最多的结点。

对满二叉树按层序编号:约定编号从根结点(根结点编号为1)起,自上而下,从左向右。对与编号为 i i i的结点,双亲为 [ i / 2 ] [i/2] [i/2](向下取整),左孩子为 2 i 2i 2i,右孩子为 2 i + 1 2i+1 2i+1

4.完全二叉树

定义:高度为 h h h,有 n n n个结点的二叉树,其每个结点都与高度为 h h h的满二叉树中编号为 1 1 1~ n n n的结点一一对应时,称为完全二叉树。

特点:

  1. i ≤ [ n / 2 ] i\le[n/2] i[n/2],则结点 i i i为分支结点,否则为叶子结点
  2. 只可能有一个度为1的结点,且该结点只有左孩子
  3. 按层序编号后,一旦某结点(编号为 i i i)为叶子结点或只有左孩子,则编号大于 i i i的结点均为叶子结点。
  4. n n n为奇数,则每个分支结点都有左孩子和右孩子;若 n n n为偶数,则编号最大的分支结点(编号为 n / 2 n/2 n/2只有左孩子,没有右孩子)

5.二叉树的存储结构

顺序存储

只适合存储满二叉树,存储一般的二叉树空间利用率较低。

//顺序存储
#define MaxSize 100
struct TreeNode{
   ElemType value; //结点中的数据元素
   bool isEmpty;   //结点是否为空
}

链式存储

二叉树主要采用的存储方式

//链式存储
typedef struct BiTNode{
	int data; 
	struct BiTNode *lchild,*rchild;
	//struct BiTNode *parent;	可以添加一个父结点指针,建立三叉链表 
}BiTNode,	//强调链表 
*BiTree;	//强调结点 

6.二叉树的遍历

先序遍历

//1.先序遍历(根左右) 
void PreOrder(BiTree T){
	if(T != NULL){
		visit(T);
		PreOrder(T->lchild);
		PreOrder(T->rchild);
	}
} 

中序遍历

//2.中序遍历(左根右) 
void InOrder(BiTree T){
	if(T != NULL){
		InOrder(T->lchild);
		visit(T);
		InOrder(T->rchild);
	}
}

后序遍历

void PostOrder(BiTree T){
	if(T != NULL){
		PostOrder(T->lchild);
		PostOrder(T->rchild);
		visit(T);
	}
}

层序遍历

//建立一个辅助队列
typedef struct LinkNode{
	BiTNode *data;  		
	struct LinkNode *next;	
}LinkNode; 

typedef struct{
	LinkNode *front,*rear; 
}LinkQueue;


//进行层序遍历
void LevelOrder(BiTree){
	LinkQueue Q;	//创建辅助队列Q 
	InitQueue(Q);	//初始化辅助队列 
	BiTree p;		//创建根结点 
	EnQueue(Q,T);	//将根结点入队
	
	//队头元素不空则循环 
	while(isEmpty(Q) != NULL){
		
		//1.队头结点出队 
		DeQueue(Q,p);
		
		//2.访问出队结点 
		visit(p);
		
		//3.左孩子入队 
		if(p->lchild != NULL)
			EnQueue(Q,p->lchild);
			
		//4.右孩子入队 
		if(p->rchild != NULL)
			EnQueue(Q,p->rchild);
	}
} 

7.线索二叉树

初始化

typedef struct ThreadNode{
	int data;
	struct ThreadNode *lchild,*rchild;
	int ltag,rtag;	//左右线索标志 
}ThreadNode,*ThreadTree;	

//创建全局变量pre,指向当前访问的结点的前驱
ThreadNode *pre = NULL;

中序线索化二叉树

//访问函数
void visit(ThreadNode *q){
	if(q->lchild == NULL){
		q->lchild = pre;
		q->ltag = 1;
	}
	
	if(pre != NULL && pre->rchild = NULL){
		pre->rchild = pre;
		pre->rtag = 1;
	}
	pre = q;
} 


//中序遍历函数
void InThread(ThreadTree T){
	if(T != NULL){
		InThread(T->lchild);
		visit(T);
		InThread(T->rchild);	
	}
} 


//进行中序线索化
void CreateInThread(ThreadNode T){
	//将pre初始为NULL 
	pre = NULL;
	
	//判断二叉树是否为空,非空则进行线索化		
	if(T != NULL){
		
		//中序线索化二叉树T 
		InThread(T);
		
		//处理遍历的最后一个结点 
		if(pre->rchild == NULL)
			pre->rtag = 1; 
	} 
}

//中序线索二叉树中找中序后继
//找中序线索二叉树中中序序列下的第一个结点
ThreadNode* Firstnode(ThreadNode* p){		
    while(p->ltag==0) 
        p = p->lchild;	//最左下结点(不一定是叶结点)
    return p;
}
 
//找中序线索二叉树中节点p在中序序列下的后继
ThreadNode* Nextnode(ThreadNode* p){
    if(p->rtag == 0) 
        return Firstnode(p->rchild);
    return p->rchild;   //rtag==1直接返回后继线索
}

//中序线索二叉树中找中序前驱
//找到以p为根的子树中,最后一个被中序遍历的结点
ThreadNode* Lastnode(ThreadNode* p){
    //循环找到最右下结点(不一定是叶结点)
    while(p->rtag == 0) 
        p = p->rchild;
    return p;
}
 
//在中序线索二叉树中找到节点p的前驱结点
ThreadNode* Prenode(ThreadNode* p){
    //左子树中最右下结点
    if(p->ltag == 0) 
        return Lastnode(p->lchild);
    return p->lchild;   //ltag==1直接返回前驱
}

先序线索化二叉树

//访问函数
void visit(ThreadNode *q){
	if(q->lchild == NULL){
		q->lchild = pre;
		q->ltag = 1;
	}
	
	if(pre != NULL && pre->rchild = NULL){
		pre->lchild = pre;
		pre->ltag = 1;
	}
	pre = q;
} 
 
void PreThread(ThreadTree T){
	if(T != NULL){		
		visit(T);
		if(T->ltag == 0) 
			PreThread(T->lchild);
		PreThread(T->rchild);	
	}
} 


//进行先序线索化
void CreatePreThread(ThreadNode T){
	//将pre初始为NULL 
	pre = NULL;
	
	//判断二叉树是否为空,非空则进行线索化		
	if(T != NULL){
		
		//先序线索化二叉树T 
		PreThread(T);
		
		//处理遍历的最后一个结点 
		if(pre->rchild == NULL)
			pre->rtag = 1; 
	} 
}

//找先序线索二叉树中节点p在先序序列下的后继
ThreadNode* Nextnode(ThreadNode* p){
    if(p->rtag == 0){
        if(p->lchild!=NULL) 
            return p->lchild;
        return p->rchild;
    }
    return p->rchild;   //rtag==1直接返回后继线索
}

后序线索化二叉树

//访问函数
void visit(ThreadNode *q){
	if(q->lchild == NULL){
		q->lchild = pre;
		q->ltag = 1;
	}
	
	if(pre != NULL && pre->rchild = NULL){
		pre->rchild = pre;
		pre->rtag = 1;
	}
	pre = q;
} 
 
void PostThread(ThreadTree T){
	if(T != NULL){		
		PostThread(T->lchild);
		PostThread(T->rchild);
		visit(T);	
	}
} 

//进行后序线索化
void CreatePostThread(ThreadNode T){
	//将pre初始为NULL 
	pre = NULL;
	
	//判断二叉树是否为空,非空则进行线索化		
	if(T != NULL){
		
		//后序线索化二叉树T 
		PostThread(T);
		
		//处理遍历的最后一个结点 
		if(pre->rchild == NULL)
			pre->rtag = 1; 
	} 
}

//找后序线索二叉树中节点p在后序序列下的前驱
ThreadNode* Prenode(ThreadNode* p){
    if(p->ltag == 0){
        if(p->rchild!=NULL) 
            return p->rchild;
        return p->lchild;
    }
    return p->lchild;   //ltag==1直接返回前驱线索
}

三、树和森林

1.树的存储结构

双亲表示法

//双亲表示法 (顺序存储) 
//1.树的结点定义 
typedef struct{
	int data;
	int parent;		//双亲位置域 
}PTNode; 

//2.树的类型定义 
typedef struct{
	int PTNode nodes[Maxsize];	//双亲表示 
	int n;		//结点数 
}PTree; 

孩子表示法

//孩子表示法(顺序+链式存储) 
struct CTNode{
	int child;	//孩子结点在数组中的位置 
	struct CTNode *next;	//下一个孩子	 
}; 

typedef struct{
	int data;
	struct CTNode *firstChild;	//第一个孩子 
}CTBox; 

typedef struct{
	CTBox nodes[MaxSize];
	int n,r;	//结点数和根的位置 
}CTree; 

孩子兄弟表示法

//孩子兄弟表示法(链式结构) 
typedef struct{
	int data;
	struct CSNode *firstChild,*nextsibling;	//第一个孩子和右兄弟指针 
}CSNode,*CSTree; 

2.树,森林,二叉树之间的相互转换

  1. 森林和树相互转换:将森林中各个树的根结点之间视为兄弟关系

  2. 树和二叉树相互转换:将树中的兄弟结点视为右孩子

  3. 森林和二叉树相互转换:将森林中的每颗树按照2中的步骤转换为二叉树,再将不同树的根结点视为右孩子

3.树,森林的遍历

先根遍历:若树非空,先访问根结点,再依次对每棵子树进行先根遍历(对应二叉树的先序遍历)

后根遍历:若树非空,先依次对每棵子树进行后根遍历,最后再返回根结点(对应二叉树的中序遍历)

四、二叉排序树(BST)

1.定义

左子树结点值<跟结点值<右子树结点值

typedef struct BSTNode{
   int key;
   struct BSTNode *lchild, *rchild;
}BSTNode, *BSTree;

2.查找

//在二叉排序树中查找值为key的结点(非递归)
//最坏空间复杂度:O(1)
BSTNode *BST_Search(BSTree T, int key){
   while(T!=NULL && key!=T->key){        //若树空或等于跟结点值,则结束循环
      if(key<T->key)       //值小于根结点值,在左子树上查找
         T = T->lchild;
      else                  //值大于根结点值,在右子树上查找
         T = T->rchild;
   }
   return T;
}

//在二叉排序树中查找值为key的结点(递归)
//最坏空间复杂度:O(h)
BSTNode *BSTSearch(BSTree T, int key){
   if(T == NULL)
      return NULL;
   if(Kry == T->key)
      return T;
   else if(key < T->key)
      return BSTSearch(T->lchild, key);
   else 
      return BSTSearch(T->rchild, key);
}

3.插入

//在二叉排序树中插入关键字为k的新结点(递归)
//最坏空间复杂度:O(h)
int BST_Insert(BSTree &T, int k){
   if(T==NULL){           			//原树为空,新插入的结点为根结点
      T = (BSTree)malloc(sizeof(BSTNode));
      T->key = k;
      T->lchild = T->rchild = NULL;
      return 1;						//插入成功
   }
   else if(K == T->key)             //树中存在相同关键字的结点,插入失败
      return 0;
   else if(k < T->key)                 
      return BST_Insert(T->lchild,k);
   else 
      return BST_Insert(T->rchild,k);
}

4.建立

//按照str[]中的关键字序列建立二叉排序树
void Crear_BST(BSTree &T, int str[], int n){
   T = NULL;                     //初始时T为空树
   int i=0;
   while(i<n){
      BST_Insert(T,str[i]);     //依次将每个关键字插入到二叉排序树中
      i++;
   }
}

五、平衡二叉树(AVL)

定义:任意结点的左右子树的高度差的绝对值不超过1

//平衡二叉树
typedef struct AVLNode{
   int key;         //数据域
   int balance;     //平衡因子
   struct AVLNode *lchild; *rchild; 
}AVLNode, *AVLTree;

平衡二叉树插入新结点后,如何调整不平衡的方法

  1. LL: 在A结点的左孩子的左子树中插入导致不平衡

​ 调整: A的左孩子结点右上旋

  1. RR: 在A结点的右孩子的右子树中插入导致不平衡

​ 调整: A的右孩子结点左上旋

  1. LR: 在A结点的左孩子的右子树中插入导致不平衡

​ 调整: A的左孩子的右孩子,先左上旋再右上旋

  1. RL: 在A结点的右孩子的左子树中插入导致不平衡

​ 调整: A的右孩子的左孩子,先右上旋再左上旋

查找效率分析(树高为 h h h)

最好时间复杂度 O ( l o g 2 h ) O(log_2h) O(log2h)

最坏时间复杂度 O ( h ) O(h) O(h)

六、哈夫曼树

  1. 带权路径长度:从根结点到该结点之间的路径长度与该结点的权的乘积。
  2. 哈夫曼树的定义:带权路径最短的树。
  3. 哈夫曼树的构造。
  4. 哈夫曼编码:由哈夫曼树得到的二进制前缀编码称为哈夫曼编码。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值