《算法笔记》- 第9章整理
| 二叉树
注意区分二叉树和度为2的树的区别。二叉树是严格区分左右子树的。
(一)存储结构与基本操作
//定义
struct node {
typename data;
node* lchild;
node* rchild;
};
//生成一个新节点
node* newNode(int data) { //data为结点权值
node* Node = new node;
Node->data = data;
Node->lchild = Node->rchild = NULL;
return Node;
}
//查找 & 修改
void search(node* root, int x, int newData) {
if(root == NULL) return;
if(root->data == x) root->data = newData; //找到,修改
search(root->lchild, x, newData);
search(root->rchild, x, newData);
}
//插入
void insert(node* &root, int x) { //注根节点用引用
if(root == NULL) { //空树,查找失败,插入位置
root = newNode(x);
return;
}
//递归插入
if(左子树) insert(root->lchild, x);
else insert(root->rchild, x);
}
//构造二叉树
node* create(int data[], int n) {
node* root = NULL;
for(int i = 0; i < n; i++) //逐个插入
insert(root, data[i]);
return root;
}
附注1:判断是否需要加引用
如果函数需要新建结点,则加;若只是遍历或修改已有结点的信息,则不加;
(二)完全二叉树
数组存储:
- 1、根节点必须在
1
号位 (0
号位左孩子错误); - 2、对于完全二叉树中的任一结点(编号
X
),其左孩子编号一定是2X
,右孩子编号一定是2X+1
; - 3、数组中元素存放的位置正好是层序遍历系列;
- 4、判断某个结点是够为叶节点:若其左孩子结点编号
2X > n
(结点总数) 即为叶节点
(三)遍历
- 1、注意层序遍历时队列中的元素是
node*
而非node
,因为队列中保存的只是原元素的一个副本(修改无法成功) - 2、要求计算每个结点所处层次时,在二叉树结构体中添加一个结构体变量即可
(四)二叉树静态存储
申请一个大小为结点上限个数的node
型数组即可!
使用:当所给数据直接为结点编号信息时,静态存储方式在输入时就可直接静态指定建树,比较方便;
struct node {
int data;
int lchild;
int rchild;
}Node[maxn]; //结点数组
//新节点
int index = 0;
int newNode(int data) { //静态指定即可
Node[index].data = data;
Node[index].lchild = -1; //以-1或maxn表示空
Node[index].rchild = -1;
return index++;
}
//查找 & 修改
void search(int root, int x, int newData) {
if(root == -1) return;
if(x == Node[root].data) //找到, 修改
Node[root].data = newData;
//递归查找
search(Node[root].lchild, x, newData);
search(Node[root].rchild, x, newData);
}
//插入
void insert(int &root, int x) { //记得加引用
if(root == -1) { //空树,查找失败,插入位置
root = newNode(x);
return;
}
//递归
if(左子树) insert(Node[root].lchild, x);
else insert(Node[root].rchild, x);
}
//二叉树建立
int create(int data[], int n) {
int root = -1;
for(int i = 0; i < n; i++)
insert(root, data[i]);
return root;
}
| 树
既可以用静态存储,也可以用指针操作;更推荐静态存储方法
(一)静态存储
结点是特别多时,强烈推荐用静态存储;可以在输入时就建树,之后操作也很方便
<练习题>: PAT. A1106 || PAT. A1090 || PAT. A1079 || PAT. A1094 || PAT. A1004 || PAT. A1053 ||
struct node{
int data;
int layer; //记录层号
vector<int> child; //存储子节点的下标【直接用数组只能开到maxn大小,必然空间溢出】
}Node[maxn];
//新建结点
int index = 0;
int newNode(int data) {
Node[index].data = data;
Node[index].child.clear(); //清空子节点
return index++;
}
//先根遍历
void pre_order(int root) {
printf("%d ", Node[root].data);//此处访问
for(int i = 0; i < Node[root].child.size(); i++)//访问子节点
pre_order(Node[root].child[i]);
}
//层序遍历 【加入对层号的求解:结构体node中增加layer变量】
void layer_order(int root) {
queue<int> Q; //存放数组下标
Q.push(root);
Node[root].layer = 0; //根节点层号为0
while(!Q.empty()) {
int front = Q.front(); //获取队首元素
Q.pop();
printf("%d", Node[front].data); //此处访问
//子节点入队列
for(int i = 0; i < Node[front].child.size(); i++) {
int child = Node[front].child[i];
Node[child].layer = Node[front].layer + 1; //更新子节点层号
Q.push(child);
}
}
}
| 二叉查找树(BST)
注:对BST
进行中序遍历,遍历结果是有序的。
【练习题】 PAT. A1043 || PAT. A1064 || PAT. A1099 ||
//定义(与一般二叉树一致)
struct node{
int data;
node* lchild;
node* rchild;
};
//生成新结点(与一般二叉树一致)
node* newNode(int data) {
node* root = new node;
root->data = data;
root->lchild = root->rchild = NULL;
return root;
}
//查找
void search(node* root, int x) {
if(root == NULL) return; //查找失败
if(x == root->data) printf("%d", root->data); //查找成功
else if(x < root->data)
search(root->lchild, x); //左子树搜索
else
search(root->rchild, x); //右子树搜索
}
//插入 【就是在查找失败处插入即可】
void insert(node* &root, int x) { //记得加引用
if(root == NULL) { //查找失败,插入
root = newNode(x);
return;
}
if(x == root->data) printf("%d", root->data); //查找成功
else if(x < root->data)
insert(root->lchild, x); //左子树搜索
else
insert(root->rchild, x); //右子树搜索
}
//构造BST (与一般二叉树完全一样)
node* create(int data[], int n) {
node* root = NULL;
for(int i = 0; i < n; i++) //逐个插入
insert(root, data[i]);
return root;
}
//寻找最大(最小)结点
node* findMax(node* root) { //用于寻找前驱
while(root->rchild)
root = root->rchild; //不断向右,直到没有右孩子
return root;
}
node* findMin(node* root) { //用于寻找后继
while(root->lchild)
root = root->lchild; //不断向左,直到没有左孩子
return root;
}
//删除以root为根结点的树中权值为x的结点
//递归实现【当然也可以优化为非递归】
void delete_node(node* &root, int x) { //记得加引用
if(root == NULL) return; //不存在权值为x的结点
if(x == root->data) { //找到欲删除结点
if(root->lchild == NULL && root->rchild == NULL) //叶节点
root = NULL;
else if(root->lchild){ //左子树非空
node* pre = findMax(root->lchild); //找root前驱pre
root->data = pre->data; //用前驱覆盖root
delete_node(root->lchild, pre->data); //在左子树中删除pre(递归)
} else { //右子树非空
node* next = findMin(root->rchild);
root->data = next->data;
delete_node(root->rchild, next->data);
}
} else if(x < root->data)
delete_node(root->lchild, x);
else
delete_node(root->rchild, x);
}
优化删除操作 – 非递归
思路:
1、(前提)在结点定义中额外记录每个结点父结点地址;
2、在找到欲删除结点root
的后继结点next
后,假设next
的父结点是S
,显然结点next
是S
的左孩子(因为findMin()
),那么由于next
一定没有左子树,便可直接把next
的右子树代替为next
成为S
的左子树;
3、(前驱同理)在找到欲删除结点root
的前驱结点pre
后,假设pre
的父结点是S
,显然pre
是S
的右孩子(因为findMax()
),那么由于pre
一定没有右子树,便可直接把pre
的左子树代替pre
成为S
的右子树代码实现
只需在原有基础上改变两句,但是每个结点的父结点信息需要在建树时赋值。
//删除以root为根结点的树中权值为x的结点
void delete_node(node* &root, int x) { //记得加引用
if(root == NULL) return; //不存在权值为x的结点
if(x == root->data) { //找到欲删除结点
if(root->lchild == NULL && root->rchild) //叶节点
root = NULL;
else if(root->lchild){ //左子树非空
node* pre = findMax(root->lchild); //找root前驱pre
root->data = pre->data; //用前驱覆盖root
pre->farther->rchild = pre->lchild; //优化递归
} else { //右子树非空
node* next = findMin(root->rchild);
root->data = next->data;
next->farther->lchild = next->rchild; //优化递归
}
} else if(x < root->data)
delete_node(root->lchild, x);
else
delete_node(root->rchild, x);
}
| 平衡二叉树(AVL)
(一)、定义
1、AVL
树是一棵BST
树
2、对AVL
树的每一个结点,其左子树与右子树高度之差的绝对值不超过1
其中,平衡因子 = 左子树高度 - 右子树高数
AVL
树作为BST
树的一种,当每次删除或插入结点后,调整树的结构,使树的高度始终保持在O(logn)
的级别,这样查询操作的时间复杂度仍然是O(logn)
。
//定义(添加树高变量)
struct node{
int data, height; //权值,树高
node* lchild;
node* rchild;
};
//生成新节点
node* new_node(int data) {
node* root = new node;
root->data = data;
root->height = 1; //树高初始为1
root->lchild = root->rchild = NULL;
return root;
}
//获取树高
int get_height(node* root){
if(root == NULL) return 0; //空树
return root->height;
}
//计算结点root的平衡因子 (通过子树高计算)
//(平衡因子无法作为属性保存在树结构体中,因为凭借子树的平衡因子计算不出根的平衡因子,无法实现回溯。)
int get_balance_factor(node* root) {
return get_height(root->lchild) - get_height(root->rchild); //左高 - 右高
}
//更新结点root的高度
void update_height(node* root){ //左高与右高的较大者 + 1
root->height = max(get_height(root->lchild), get_height(root->rchild)) + 1;
}
(二)基本操作(查找、插入、建立)
//删除操作太过复杂,只讨论查找、插入、建立!
//查找(AVL就是BST一种,故与BST完全相同)
void search(node* root, int x) {
if(root == NULL) return; //查找失败
if(x == root->data) printf("%d", root->data); //查找成功
else if(x < root->data)
search(root->lchild, x); //左子树搜索
else
search(root->rchild, x); //右子树搜索
}
//左旋
void L(node* &root) { //记得加引用
node* temp = root->rchild;
root->rchild = temp->lchild;
temp->lchild = root;
update_height(root); //两句更新顺序不能反
update_height(temp);
root = temp;
}
//右旋(与左旋为互逆操作)
void R(node* &root) { //记得加引用
node* temp = root->lchild;
root->lchild = temp->rchild;
temp->rchild = root;
update_height(root); //更新顺序不能反
update_height(temp);
root = temp;
}
//插入 【就是在查找失败处插入即可】(在BST的基础上加入旋转调整)
//先执行插入函数,等函数返回后,判断结点是否失衡,若有则根据失衡类型进行旋转;
//整个过程呈现为从下到上的回溯;
void insert(node* &root, int x) { //记得加引用
//查找失败,插入(判断失衡和旋转调整在上一层函数)
if(root == NULL) {
root = new_node(x);
return;
}
//插入左侧
if(x < root->data) {
insert(root->lchild, x); //先插入;等返回后,先更新高度,再检查、调整
update_height(root);
//检查是否有失衡结点
if(get_balance_factor(root) == 2) { //插入左侧只可能 左高 > 右高
if(get_balance_factor(root->lchild) == 1) { //LL型
R(root); //右旋即可
} else if(get_balance_factor(root->lchild) == -1) { //LR型
L(root->lchild); //先左旋后右旋
R(root);
}
}
}
//插入右侧
else {
insert(root->rchild, x); //先插入等返回后检查
update_height(root);
//检查是否有失衡结点
if(get_balance_factor(root) == -2) { //插入左侧只可能 左高 < 右高
if(get_balance_factor(root->rchild) == -1){ //RR型
L(root); //左旋
} else if(get_balance_factor(root->rchild) == -1) { //RL型
R(root->rchild); //先右旋在左旋
L(root);
}
}
}//else
}//insert
//构造AVL (与一般二叉树完全一样)
node* create(int data[], int n) {
node* root = NULL;
for(int i = 0; i < n; i++)
insert(root, data[i]);
return root;
}
| 并查集
| 堆
| 哈夫曼树
*
*
*
* 整理自《算法笔记》!!