当前位置: 首页 > news >正文

数据结构—第五章 树与二叉树

(一)树的基本概念

基本术语:

  • 兄弟:有相同双亲
  • 堂兄弟:双亲不同,在同一层的结点
  • 树的度:树中结点最大度数(孩子个数)
  • 分支节点:度大于0的结点

(二)二叉树

二叉树的定义及其主要特征

特殊的二叉树:

  • 满二叉树:每层都含有最多的结点
  • 完全二叉树:除了最底层右端连续的结点可以不存在
  • 二叉排序树:左子树小于根节点编号,右子树大于根节点编号
  • 平衡二叉树:左子树与右子树高度差≤1
  • 正则二叉树:树中只有度为0或2的结点

性质:

  • 二叉树的子树有左右之分,其次序不能任意颠倒
  • n0=n2+1,度为0的结点个数=度为2的结点个数+1
  • n1=1/0,度为1的结点个数只可能是1或0

二叉树的顺序存储结构和链式存储结构

顺序存储:用一组连续的单元依次自上而下、自左至右存储完全二叉树上的结点(完全二叉树),普通的树通常只能添加一些并不存在的结点,让其与二叉树上的结点对照

链式存储:二叉链表至少包含3个域:数据域data,左指针域lchild,右指针域rchild

typedef struct BiTNde{ElemType data;struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;

二叉树的遍历

先序遍历、中序遍历、后序遍历,即visit(T)写在哪一行

// 先序遍历
void Order(BiTree T){if(T!=NULL){visit(T);Order(T->lchild);Order(T->rchild);}
}

层次遍历:自上而下、从左至右逐层遍历,代码用队列

void LevelOrder(BiTree T){InitQueue(q);BiTree p;EnQueue(q,T);while(!IsEmpty(q)){DeQueue(q,p);visit(p);if(p->lchild!=NULL)EnQueue(q,p->lchild);if(p->rchild!=NULL)EnQueue(q,p->rchild);}
}

先/前序序列:|根|左子树的先序序列|右子树的先序序列|

中序序列:|左子树的中序序列|根|右子树的中序序列|

后续序列:|左子树的后序序列|右子树的后序序列|根|

先序序列、后序序列、层序序列两两组合,无法唯一确认一棵树

线索二叉树的基本概念和构造

lchildltagdatartagrchild

ltag/rtag:0表示指示结点孩子,1表示只是结点的前驱/后继

// 中序线索二叉树
void InThread(ThreadTree &p,ThreadTree &pre){		// pre表示前驱结点if(p!=NULL){InThread(p->lchild,pre);			// 线索化左子树if(p->lchild==NULL){p->lchild=pre;p->ltag=1;}if(pre!=NULL&&pre->rchild==NULL){pre->rchild=p;pre->rtag=1;}pre=p;								// 标记当前结点即最后一次访问的结点InThread(p->rchild,pre);			// 线索化右子树}
}
void CreateInThread(ThreadTree T){ThreadTree pre=NULL;if(T!=NULL){InThread(T,pre);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);else reurn p->rchild;
}
// 不含头结点的中序遍历算法
void Inorder(ThradNode *T){for(ThreadNode *p=Firstnode(T);p!=NULL;p=Nextnode(p))visit(p);
}

(三)树、森林

树的存储结构

1.双亲表示法:根节点下标为0,其伪指针域为-1

#define MAX_TREE_SIZE 100
typedef struct{			// 描述一个结点ElemType data;int parent;			// 记录双亲小标
}PTNode;
typedef struct{			// 描述整个树PTNode nodes[MAX_TREE_SIZE];int n;				// 结点数
}PTree;

2.孩子表示法:每个结点的孩子结点视为一个链表

/*
0 R -> 1 -> 2 ->3
1 A -> 4 -> 5
2 B
3 C ->6
...
*/

3.孩子兄弟表示法(也称二叉树表示法)

每个节点包括:结点值、指向结点第一个孩子的指针、指向结点下一个兄弟结点的指针

typedef struct CSNode{ElemType data;struct CSNode *firstchild,*nextsibling;
}CSNode,*CSTree;

森林与二叉树的转换

1.树转换为二叉树:每个结点的左指针指向它的第一个孩子,右指针指向它在树中相邻右兄弟(左孩子右兄弟

2.森林转换为二叉树:森林中各棵树的根也可视为兄弟关系

树和森林的遍历

树的遍历:

  • 先根遍历:先遍历根,再遍历子树
  • 后根遍历:先遍历子树(同亲一层的话从左至右),再遍历根

森林的遍历:

  • 先序遍历:先遍历根,再遍历子树,遍历完一棵树后遍历下一棵树
  • 中序遍历:先遍历子树,再遍历根

树的后根遍历 / 森林的中序遍历对应于二叉树的中序遍历。

(四)树与二叉树的应用

哈夫曼树和哈夫曼编码

哈夫曼树定义:

  • 结点的带权路径长度:从树的根到一个结点的路径长度与该结点上权值的乘积
  • 树的带权路径长度WPL:所有叶节点的带权路径长度之和
  • 哈夫曼树(最优二叉树):带权路径长度(WPL)最小的二叉树

哈夫曼树性质:

  • 每次选取两棵根节点权值最小的树
  • 共构造n-1个结点,因此哈夫曼树结点总数为2n-1

哈夫曼编码定义:

  • 固定长度编码:每个字符用等长的二进制位表示
  • 可变长度编码:每个字符用不等长的二进制位表示
  • 非常有效的数据压缩编码,权值为它出现的频度或次数

并查集及其应用

通常用树的双亲表示法作为并查集的存储结构

#define SIZE 100
int S[Size];
void Initial(int S[]){for(int i=0;i<SIZE;i++)S[i]=-1;
}
// Find操作
int Find(int S[],int x){while(S[x]>=0)x=S[x];return x;
}
// Union操作
void Union(int S[],int Root1,int Root2){Root1=Find(Root1),Root2=Find(Root2);			// 先找到两个根节点if(Root1==Root2&&Roo1!=-1)return;			// 因为用双亲表示法,根指向-1S[Root1]=Root2;
}
// 改进的Find操作
int Find(int S[],int x){int root=x;while(S[root]>=0)root=S[root];while(x!=root){			// 压缩路径int t=S[x];S[x]=root;x=t;}return root;
}
// 改进的Union操作
void Union(int S[],int Root1,int Root2){Root1=Find(Root1),Root2=Find(Root2);			// 先找到两个根节点if(Root1==Root2&&Roo1!=-1)return;if(S[Root2]>S[Root1]){			// 小树合并到大树,这里树根都是负数S[Root1]+=S[Root2];S[Root2]=Root1;}else{S[Root2]+=S[Root1];S[Root1]=Root2;}
}

不是很能看懂王道的代码,平时算法写的并查集代码:

int Find(int x){if(x==fa[x])return x;return fa[x]=find(fa[x]);
}
void Union(int u,int v){u=Find(u),v=Find(v);if(u==v)return;fa[u]=v;
}

小试牛刀:hdu3635、食物链、虫子的生活

堆及其应用

堆的本质是「完全二叉树」,堆只约束父与子的关系,不约束左右子节点的大小关系(如大根堆中,左子节点可大于或小于右子节点)

类型堆序性规则结构特点
大根堆(Max Heap)任意父节点的数值 ≥ 其左右子节点的数值根节点是整个堆的最大值
小根堆(Min Heap)任意父节点的数值 ≤ 其左右子节点的数值根节点是整个堆的最小值

堆的核心操作围绕 “维护堆序性” 展开,主要包括 插入删除堆顶,两者的时间复杂度均为 O(log n)

插入操作:将新节点加入堆的末尾,再 “向上调整”

  1. 将新节点插入数组的最后一位(对应完全二叉树的最后一个位置,保证结构完整);
  2. 设新节点下标为i,对比其与父节点i/2的数值:
  • 若新节点值 < 父节点值(小根堆),则交换两者位置;
  • 更新 i 为父节点下标,重复步骤 2,直到 i=0(根节点)或父节点值 ≤ 新节点值。
2. 删除堆顶操作(Remove the root)

目标:删除堆顶(根节点,最小值 / 最大值),再 “向下调整”(Heapify Down),维护堆序性。
步骤

  1. 将堆顶(数组下标 0)与数组最后一位节点交换;
  2. 删除数组最后一位节点(原堆顶,此时已移到末尾,删除后不影响剩余结构);
  3. 设当前需要调整的节点下标为i=0,对比其与左右子节点(2i、2i+1)的数值:
  • 找到左右子节点中值最小的节点(小根堆),记其下标为 min_child
  • 若当前节点值 > min_child 的值,交换两者位置;
  • 更新 imin_child,重复步骤 3,直到 i 无左子节点(2i ≥ n)或当前节点值 ≤ 所有子节点值。

http://www.xdnf.cn/news/1389691.html

相关文章:

  • 机器学习算法全景解析:从理论到实践
  • vue3 鼠标移上去 显示勾选框 选中之后保持高亮
  • 自然语言提取PDF表格数据
  • 马斯克杀入AI编程!xAI新模型Grok Code Fast 1发布,深度评测:速度、价格与API上手指南
  • Vue3 + Spring Boot 项目中跨域问题的排查与解决
  • CS144 lab3 tcp_sender
  • 自动驾驶中的传感器技术36——Lidar(11)
  • 《生成式AI消费级应用Top 100——第五版》| a16z
  • uni-app 跨平台项目的 iOS 上架流程:多工具组合的高效协作方案
  • driver.js实现前端页面引导
  • 【Flask】测试平台开发,集成禅道
  • 渗透测试学习笔记
  • dm8_静默安装简单快速
  • 基于EB的K3XX_GPT定时器中断的实现方法
  • 音视频直播卡顿分析与优化:技术原理、实践案例与未来趋势
  • Java 流(Stream)、文件(File)和IO
  • 基于 Python asyncio 和币安 WebSocket 打造高频加密货币预警机器人
  • 【Spring Cloud Alibaba】前置知识
  • 订餐后台项目-day02数据库模型定义笔记
  • 从0开始学习Java+AI知识点总结-28.Linux部署
  • Java 8核心特性详解:从Lambda到Stream的革命性升级
  • lesson49:HTML基础标签全解析:从入门到精通的网页构建指南
  • SQL Server 查看备份计划
  • Cursor不能读取.env文件解决办法(**/.env、**/env.*)
  • 华为认证全解析:价值详解、含金量解读(2025最新版)
  • 安全月报 | 傲盾DDoS攻击防御2025年8月简报
  • CRYPT32!CryptMsgUpdate函数分析之CRYPT32!PkiAsn1Decode函数的作用是得到pci
  • 达梦数据库-归档日志(一)
  • JavaScript 入门教程
  • 《Linux 网络编程六:数据存储与SQLite应用指南》