- 该合辑为笔者自b站自学的“C++数据结构与算法”课程学习记录,旨在将重要的学习要点、思考内容与部分代码进行记录,以便后续自行翻看,亦可给其他读者带来一些参考
- 内容基于笔者自身的理解或感悟,可能存在不妥当或是错误之处
- 系统环境:Win10,Visual Studio 2019
- 文中图片参考东北大学“数据结构与算法设计” (2020) 线上课程讲义
目录
1. BinaryTree 二叉树
一种非线性层次结构抽象模型
二叉树具有构成元素:
root:根节点,树的起点,如无根节点则为空树
nodes:节点,树的构成元素,根和叶也属于节点
branches:分支,一个节点可以有分支,也可以无分支,二叉树的节点最多有两个分支
leaves:叶,树的“末端”,叶节点无后继节点
用于描述树的构成元素的名词:
depth(of a node):深度,节点具有的往上数的代际数,根的深度为0
height(of a tree):高度,整个树的最大深度
degree(of a node):度,节点下一代的后代个数,二叉树最多为2
degree(of a tree):树中节点度的最大值
subtree:子树,以节点的后继节点为根的树
siblings:兄弟节点,共享一个父节点的节点互为兄弟节点
internal node:内部节点,至少有一个子节点的节点
external node:外部节点,无子节点的节点
ancestors:祖先,其父节点、祖父节点、曾祖父节点......
descendant:后代,其子节点、孙节点......
二叉树的特性
- 二叉树的度小于等于2,其子节点有左右位置的区分,不可互换位置
- 假设一个二叉树有n个节点,则这个二叉树可能呈现的结构的可能性共有 (2n!) / ((n+1)! n!) 种
- 高度为k的二叉树最多拥有2^(k+1) - 1 个节点
- 假设n0为二叉树中度为0的节点个数,n2为其度为2的节点个数,则具有等式 n0 = n2 + 1
Full binary tree 满二叉树
即达到 “高度为k的二叉树拥有2^(k+1) - 1 个节点” 要求的二叉树,从视觉上看既是该树没有“缺省的节点”,而一些外国教材对于满二叉树的定义有所不同,其为“只要二叉树中一个节点有子节点,则其子节点的个数必为2的二叉树”
满二叉树如果逐行从左至右依次增序标号,则其编号整除2得到的结果为父辈节点(根除外),编号*2为其左子树(若存在),编号*2+1为其右子树(若存在)
Complete binary tree 完全二叉树
与满二叉树不同的是,完全二叉树可以连续缺省最后一层的右侧元素
2. 二叉链表
用链表的形式来实现二叉树结构的表示
二叉链表用大量的空指针来表示节点无子
二叉链表的节点中花费内存空间来指向其parent
3. 二叉数组
用数组的形式来实现二叉树结构的表示
二叉树的数组实现利用了元素在内存中的位置关系来巧妙地表示节点之间的继承关系,但如果用二叉数组来表示退化的二叉树,则会浪费空间(用大量的φ来表示空元素)
4. Traversals 遍历
利用先根序(rLR)、中根序(LrR)与后根序(LRr)方法来遍历二叉树
- 先根序遍历:
- 中根序遍历:
- 后根序遍历:
5. 二叉树的非递归调用
用户自行管理Runtime Stack
- 二叉树的非递归调用让程序员自行管理运行时栈(explicit stack),并且其在用户线程内执行,不用频繁的开关中断交接权限,需要手动保存重要的历史节点地址进栈,需要时再弹出使用
- 可以设计一个Iterater class进行辅助
- Level order 层次遍历 / 广度遍历
6. 线索树
带有指针线索的二叉树
二叉树的叶节点有两个空指针,较为浪费空间,我们将这部分空间用来存储一定遍历规则下的前驱或后继节点的地址,则可以帮助提高二叉树遍历的效率,在二叉链表的空指针上添加的地址成为线索
left child:Pred为0时指向左子树地址,Pred为1时指向前驱节点
Pred:左标记,用来标记left child指向地址的情况
data:数据内容
Succ:右标记,用来标记right child指向地址的情况
right child:Succ为0时指向右子树地址,Succ为1时指向后继节点
Pre遍历下,有多少个nuptr就有多少个线索
Post遍历下,线索没有提供太大的帮助
In遍历下,不需栈,有一个“头结点”
7. 优先级队列
具有优先级区分的二叉树结构
基于Heap实现,借助无序链表时,插入可以达到O(1),删除为O(n),借助有序列表时插入为O(n),删除可以达到O(1)
Windows中将优先级分为0~31,共32个级别,其中系统进程优先级较高,占据16~31,用户进程占据0~15,其为PCB级支持,故想从数据结构方向实现优先级,可以考虑二叉堆
二叉堆使用完全二叉树实现,并具有有序特性,还可以分为大根堆(Max-Heap)和小根堆(Min-Heap),前者较为常用,插入和删除操作的时间复杂度均为O(log2n),优于链式线性阶
8. 哈夫曼树
最优解
路径,路径长度,权重,带权路径长度 (WPL, Path Length of a tree with Weights)
以权重序列作为树的叶来构建树,共有n个叶,2n-1个节点,n-1个度为2的节点,构造最小WPL形态的二叉树,其为哈夫曼树(可能不唯一),可用于找最优解、优化代码效率、创造前缀码用于通信等
9. 树(普通的)
普通树与森林转换为二叉树,则可使用二叉树相关方法进行操作
parent 表示
适合经常查找父节点的情形
children 表示
父节点具备一个指针,指向子节点链表的头部(子节点可以有多个),适合经常查找子节点的情形
parent - children 表示
结合上述两种方式的表示
First child - Next sibling 表示
将一个普通树以一定的规则转换成为一个唯一的二叉树,该二叉树无右子树,转换前的pre遍历结果与转换后相同,转换前的post遍历结果与转换后的in遍历结果相同
森林转二叉树
将多个普通树按照一定的规则转换成为一个二叉树,转换前的pre遍历结果与转换后相同,转换前的in遍历结果与转换后的in遍历结果相同
10. STL中的树
C++的STL中提供的功能
set
二叉搜索树,可以达到O(logn),元素不可重复,适合用于集合运算
priority queues
基于Heap,可自定义优先级
map
按关键字寻找集合元素,<key value, data>,例如:
#include <map>
map<string, string> m1;
m1.insert(pair<string, string>("apple", "a amall red fruit"));
cout << m1["apple"] << endl; //类似于数组的下标索引
附 二叉树 C++ 实现代码
用C++编程,实现简单的二叉树类
#include <iostream>
#include <algorithm>
#include <stack>
using namespace std;
// 定义二叉树节点类
class BiTreeNode {
public:
int data;
BiTreeNode* leftChild;
BiTreeNode* rightChild;
// 初始化列表,直接将 elem 置入data中
BiTreeNode(int elem) :data(elem) {}
};
// 二叉树类,借助节点类来实现
class BinaryTree {
private:
// 节点* 类型的根节点
BiTreeNode* root;
// 置空函数,运用递归思想,将current指向的二叉树回收
void makeEmpty(BiTreeNode* current);
// 重载方法Create,注意是引用参数,方便递归
void Create(BiTreeNode*& current);
// 重载递归实现
void PreOrder(BiTreeNode* current);
void InOrder(BiTreeNode* current);
void PostOrder(BiTreeNode* current);
public:
// 构造函数, 初始化列表构造空树
BinaryTree() :root(NULL) {}
// 析构方法
~BinaryTree();
// 对外提供Create接口,实现二叉树的创建
void Create();
// 对外提供三种遍历接口,先根,中根,后根遍历
void PreOrder();
void InOrder();
void PostOrder();
};
// 析构方法的实现
BinaryTree::~BinaryTree() {
// 将整个二叉树删除
makeEmpty(root);
}
// 置空方法的实现,传入的是节点类型的current指针
void BinaryTree::makeEmpty(BiTreeNode* current) {
// 递归条件,只要current不为空
if (current != NULL) {
// 递归删除左右子树
makeEmpty(current->leftChild);
makeEmpty(current->rightChild);
delete current;
}
}
// 创建树方法的实现,调用其重载方法
void BinaryTree::Create() {
Create(root);
}
void BinaryTree::Create(BiTreeNode*& current) {
int elem;
// 读入一个元素到elem中
cin >> elem;
// elem等于零,则是空树
if (elem == 0) {
current = NULL;
}
else {
current = new BiTreeNode(elem);
// 递归创建树
Create(current->leftChild);
Create(current->rightChild);
}
}
void BinaryTree::PreOrder() {
// 对以root为根的二叉树进行先根序遍历
PreOrder(root);
}
void BinaryTree::PreOrder(BiTreeNode* current) {
if (current != NULL) {
cout << current->data << " ";
PreOrder(current->leftChild);
PreOrder(current->rightChild);
}
}
void BinaryTree::InOrder() {
// 对以root为根的二叉树进行中根序遍历
InOrder(root);
}
void BinaryTree::InOrder(BiTreeNode* current) {
if (current != NULL) {
InOrder(current->leftChild);
cout << current->data << " ";
InOrder(current->rightChild);
}
}
void BinaryTree::PostOrder() {
// 对以root为根的二叉树进行后根序遍历
PostOrder(root);
}
void BinaryTree::PostOrder(BiTreeNode* current) {
if (current != NULL) {
PostOrder(current->leftChild);
PostOrder(current->rightChild);
cout << current->data << " ";
}
}
int main()
{
BinaryTree myTree;
myTree.Create();
myTree.PreOrder(); cout << endl;
myTree.InOrder(); cout << endl;
myTree.PostOrder(); cout << endl;
return 0;
}
The End