#个人自用
目录
概念:树是存储具有一对多关系的数据的逻辑结构,其在形状上表现为一棵倒着的树
在树中,用结点/节点存储数据(不区分结点与节点的区别)
节点的分类:
根节点:没有前驱节点
叶子节点:没有后继节点
内部节点:其余节点
度/阶:表示节点分了多少个叉
节点的度:该节点子数目/分叉数目
树的度:max(所有节点的度)
假如树的度是n,则称树为n叉树
树的高度和深度:在数值上是一样的
存储结构:数据 + 关系
树(常规)
双亲表示法 / 父亲表示法
存储方式:顺序存储方式存数据 => 数组
p.s.存数据时,存父亲下标
优点:方便找父亲
缺点:不方便找孩子
结构体组成:数据 + 父亲节点的下标
struct node {
int data;
int fi;//父 father_i,若fi==-1,认为是根节点
}t[1000];
int size;//数据数量
初始化
//初始化
void init(int r)
{
//加入根节点
t[1].data = r;
t[1].fi = -1;
size = 1;
}
操作
//查找数的下标
int find(int fx)
{
for (int i = 1; i <= size; i++)
{
if (t[i].data == fx)
return i;
}
return -1;
}
//增
void insert(int x, int fx)
{
size++;
t[size].data = x;
int fx_i = find(fx);
if (fx_i == -1)
printf("父不存在\n");
else
{
t[size].fi = fx_i;
}
}
//找父
void find_fa(int x)
{
int x_i = find(x);
int fa_i = t[x_i].fi;
if (fa_i == -1)
printf("该节点是根节点\n");
else
printf("%d\n", t[fa_i]);
}
//找子
void find_ch(int x)
{
int x_i = find(x);
int sum = 0;
for (int i = 1; i <= size; i++)
{
if (t[i].fi == x_i)
{
sum++;
printf("%d ", t[i].data);
}
}
if (sum == 0)
printf("无子");
printf("\n");
}
参考语言
int main()
{
int n;//节点总数
scanf("%d", &n);
int root;//根节点数据
scanf("%d", &root);
init(root);
int x, fx;//fx是x的父
for (int i = 2; i <= n; i++)
{
scanf("%d%d", &x, &fx);
insert(x, fx);
}
scanf("%d", &x);
find_ch(x);
find_fa(x);
}
孩子表示法
存储方式:顺序存储方式存数据 => 数组
p.s.链表来存储子数据
结构体组成:
树:数据 + 孩子节点
孩子链表:数据 + 下一个孩子
//孩子链表的节点结构
typedef struct chNode {
int data;
struct chNode* next;
}chNode;
//树的结构
struct Tree {
int data;
chNode* first;
}t[1000];
int size;//数据数量
初始化
//初始化
void init(int r, int n)
{
t[1].data = r;
size = 1;
t[1].first = NULL;
}
操作
//查找数的下标
int find(int x)
{
for (int i = 1; i <= size; i++)
{
if (t[i].data == x)
return i;
}
return -1;
}
//增
void insert(int x, int fx)
{
size++;
t[size].data = x;//存放数据
t[size].first = NULL;
//建立关系
int fx_i = find(fx);
if (fx_i != -1)
{
chNode* s = (chNode*)malloc(sizeof(chNode));
s->data = x;
//采用头插法
s->next = t[fx_i].first;
t[fx_i].first = s;
}
}
//找父
void find_fa(int x)
{
chNode* s = NULL;
int flag = 0;
for (int i = 1; i <= size; i++)
{
s = t[i].first;
while (s != NULL)
{
if (s->data == x)
{
printf("%d\n", t[i].data);
flag = 1;
return;
}
s = s->next;
}
}
if (flag == 0)
printf("无父,为根节点\n");
}
//找子
void find_ch(int x)
{
int x_i = find(x);
chNode* s = t[x_i].first;
if (s == NULL)
printf("无子\n");
else
{
while (s != NULL)
{
printf("%d ", s->data);
s = s->next;
}
}
}
参考语言
int main()
{
int n;//节点总数
scanf("%d", &n);
int root;//根节点数据
scanf("%d", &root);
init(root, n);
int x, fx;//fx是x的父
for (int i = 2; i <= n; i++)
{
scanf("%d%d", &x, &fx);
insert(x, fx);
}
scanf("%d", &x);
find_ch(x);
find_fa(x);
}
孩子兄弟表示法 / 二叉存储法
存储方式:二叉链表
结构体组成:数据 + 第一个孩子节点 + 右边第一个亲兄弟节点
typedef struct Node {
int data;
struct Node* first;//指向第一个孩子
struct Node* bro;//指向右边第一个亲兄弟
}tNode, * TreeList;
初始化
//初始化
TreeList init(int root)
{
TreeList r = (tNode*)malloc(sizeof(tNode));
if (r != NULL)
{
r->data = root;
r->first = r->bro = NULL;
return r;
}
}
操作
//查找
//1.与根节点对比,若无,则2
//2.在子代找 or 在兄弟中找
tNode* find(TreeList r, int fx)
{
tNode* ans = NULL;
if (r == NULL)
return NULL;
if (r->data == fx)
return r;
/*if (r == NULL || r->data == fx)
return r;*/
if (r->first != NULL)//在子代中找
{
ans = find(r->first, fx);
if (ans != NULL && ans->data == fx)
return ans;
}
if (r->bro != NULL)//在兄弟中找
{
ans = find(r->bro, fx);
if (ans != NULL && ans->data == fx)
return ans;
}
return NULL;//若不存在fx,则返回NULL
}
//插入
void insert(TreeList r, int x, int fx)
{
tNode* f = find(r, fx);
if (f != NULL)
{
tNode* s = (tNode*)malloc(sizeof(tNode));
s->data = x;
s->first = NULL;
if (f->first==NULL)//s是长子
{
f->first = s;
s->bro = NULL;
}
else
{
//头插
s->bro = f->first;
f->first = s;
}
}
else
{
printf("该父节点不存在,插入失败\n");
return;
}
}
//找子
void find_ch(TreeList r, int x)
{
tNode* p = find(r, x);
tNode* fir = p->first;
if (fir == NULL)
printf("无子\n");
else
{
p = fir;
while (p != NULL)
{
printf("%d\n", p->data);
p = p->bro;
}
}
}
//找父
//改结构:加一个指向父亲节点的指针
参考语言
int main()
{
int n;//节点个数
int root;//根节点数据
TreeList r = NULL;
scanf("%d%d", &n, &root);
r = init(root);
int x, fx;//子数据,父数据
for (int i = 02; i <= n; i++)
{
scanf("%d%d", &x, &fx);
insert(r, x, fx);
}
scanf("%d", &x);
find_ch(r, x);
}
二叉树
性质
p.s.默认根节点是第一层
1)在二叉树的第i层,最多有2^(i-1)个节点
2)深度为k的二叉树,最多有(2^k)-1个节点
3)在一棵二叉树中,叶子节点的数目 比 度为2的节点数目 多一个
度为0的节点/叶子节点:n0
度为1的节点:n1
度为2的节点:n2
(1)节点总数:n=n0+n1+n2;
(2)节点总数:n=1 + 0*n0+1*n1+2*n2(孩子节点)
∴n0=n2+1,得证
4)具有n个节点的 完全二叉树 的深度/高度为logn+1 (其中logn向下取整)(若log底数不给出,默认为2)
具有n个节点的 完全二叉树 的深度/高度为log(n+1) (向上取整)
假设完全二叉树深度为k,k=logn+1(其中logn向下取整)
1+(2^(k-1))-1 <= n < (2^k)-1+1
2^(k-1) <= n < (2^k)
k-1 <= logn < k
∴k=logn+1(其中logn向下取整)得证
同理, (2^(k-1))-1 < n <= (2^k)-1
2^(k-1) < n+1 <= (2^k)
k-1 < log(n+1) <= k
∴k=log(n+1)(其中logn向上取整)得证
5)对于一颗 完全二叉树 进行编号,编号规则:根节点是1,然后从上到下,从左到右。
除根节点,节点i的父亲节点编号为i/2(向下取整)。
除叶子节点,节点i的左孩子节点编号为2*i,右孩子节点编号为2*i+1
补充:任何一个二叉树都可以加NULL节点,从而补成一颗完全二叉树
=>存储方式:顺序存储(基于数组)(缺点:存非完全二叉树,会有空间浪费)
两种特殊的二叉树:
1)满二叉树:深度为k,且节点个数是(2^k)-1的二叉树
2)完全二叉树:对满二叉树,从下向上,从右到左一次删除若干个节点,剩下的树就能成为完全二叉树
p.s.满二叉树是完全二叉树
存储
存储方式:
1.顺序存储,基于数组:存非完全二叉树,会有空间浪费
a[i]父亲:a[i/2]
a[i]左孩子:a[2*i]
a[i]右孩子:a[2*i+1]
2.基于二叉链表
结构体组成:数据 + 左孩子节点 + 右孩子节点
typedef struct BTNODE {
int data;
struct BTNODE* left;
struct BTNODE* right;
}BTNode, * BTTree;
int flag;//0为左孩子,1为右孩子
初始化
//初始化
BTTree init(int x)
{
BTTree r = (BTNode*)malloc(sizeof(BTNode));
if (r == NULL)
printf("分配失败\n");
else
{
r->data = x;
r->left = r->right = NULL;
}
return r;
}
操作
//查找-递归
BTNode* find(BTTree r, int fx)
{
if (r == NULL || r->data == fx)
return r;
if (r->left != NULL)
{
BTNode* f = find(r->left, fx);
if (f != NULL && f->data == fx)
return f;
}
if (r->right != NULL)
{
BTNode* f = find(r->right, fx);
if (f != NULL && f->data == fx)
return f;
}
return NULL;
}
//插入
void insert(BTTree r, int x, int fx, int flag)
{
BTNode* f = find(r, fx);
if (f == NULL)
printf("父亲节点不存在\n");
else
{
BTTree s = (BTTree)malloc(sizeof(BTNode));
s->data = x;
s->left = s->right = NULL;
if (flag == 0)
f->left = s;
else
f->right = s;
}
}
//找子
void find_ch(BTTree r, int x)
{
BTNode* p = find(r, x);
if (p != NULL)
{
if (p->left != NULL)
printf("左:%d\n", p->left->data);
if (p->right != NULL)
printf("右:%d\n", p->right->data);
}
}
//找父
//加父亲指针
参考语言
int main()
{
int n, x;
scanf("%d", &n);//输入节点个数n
BTTree r = NULL;
scanf("%d", &x);//根节点x
r = init(x);
for (int i = 2; i <= n; i++)
{
int fx;
scanf("%d %d %d", &x, &fx, &flag);
insert(r, x, fx, flag);
}
scanf("%d", &x);
find_ch(r, x);
}
遍历
概念:沿着某条路径,对树中的节点都遍历(访问)一次,有且仅遍历一次
由于树是非线性结构,每个节点有两个后继节点。当访问完一个节点后,会有不同的选择,可以得到不同的访问顺序。
层序遍历
遍历顺序:从上往下,依次输出每一层(从左往右)的节点
实现:当访问一个节点时,存下该节点的孩子,之后进行遍历时,直接去存放的地方找
声明队列 -> 根节点入队 -> 循环 -> 队列空,结束
建立链式队列
//链式队列
typedef struct QNode {
int data;
struct QNode* next;
}qNode, * qLink;
qLink front, rear;//队头指针&队尾指针
//初始化
void initQNode()
{
qLink q = (qNode*)malloc(sizeof(qNode));
if (q == NULL)
printf("队列分配失败\n");
else
{
front = rear = q;
front->next = NULL;
}
return;
}
//入队
void enQueue(int x)
{
qNode* s = (qNode*)malloc(sizeof(qNode));
s->data = x;
s->next = NULL;//新节点插入到链尾
rear->next = s;
rear = s;
}
//判空
int empty()
{
if (front->next == NULL)
return 1;//空
return 0;//非空
}
//出队
int deQueue()
{
if (!empty())//判空
{
qNode* q = front->next;//队头
front->next = q->next;
int x = q->data;//保留出队元素
if (front->next == NULL)//若原队列有且仅有一个结点,则删除变空,需要处理尾指针
rear = front;
free(q);
q = NULL;
return x;
}
else
printf("队空\n");
}
层序遍历
//层序遍历
void levelunderBTTree(BTTree r)
{
int x;
BTNode* q = NULL;
initQNode();//初始化队列
if (r == NULL)
{
printf("空树\n");
return;
}
enQueue(r->data);//根节点数据入队
while (!empty())
{
x = deQueue();
printf("%d\n", x);
q = find(r, x);
if (q->left != NULL)
enQueue(q->left->data);
if (q->right != NULL)
enQueue(q->right->data);
}
}
参考语言(存储部分见上)
int main()
{
int n, x;
scanf("%d", &n);//输入节点个数n
BTTree r = NULL;
scanf("%d", &x);//根节点x
r = initBT(x);
for (int i = 2; i <= n; i++)
{
int fx;
scanf("%d %d %d", &x, &fx, &flag);
insert(r, x, fx, flag);
}
levelunderBTTree(r);
}
深度优先遍历
非递归版:栈的构建
//整个节点入栈
typedef struct stackNode {
struct BTNode* data;
struct stackNode* next;
}mystack;
mystack* s = NULL;
//初始化
void initStack()
{
s = (mystack*)malloc(sizeof(mystack));
if (s != NULL)
s->next = NULL;
}
//入栈
void ppush(BTNode* k)
{
mystack* p = (mystack*)malloc(sizeof(mystack));
if (p == NULL)
{
printf("分配失败\n");
return;
}
p->data = k;
p->next = s->next;
s->next = p;
}
//判空
int empty()
{
if (s->next == NULL)
return 1;
return 0;
}
//出栈:返回栈顶元素,将其在栈中删除
BTNode* ppop()
{
if (empty())
{
printf("栈空\n");
return NULL;
}
BTNode* k = s->next->data;
mystack* p = s->next;
s->next = p->next;
free(p);
p = NULL;
return k;
}
先序遍历
遍历顺序:根 左子树 右子树
1)递归版
//先序遍历
void perOrder(BTTree r)
{
if (r == NULL)
return;
printf("%d ", r->data);
if (r->left != NULL)
perOrder(r->left);
if (r->right != NULL)
perOrder(r->right);
}
2)非递归版:栈
//先序遍历
void PerOrder(BTTree r)
{
initStack();
if (r == NULL)
return;
ppush(r);
while (!empty())
{
BTNode* node = ppop();
printf("%d ", node->data);
if (node->right != NULL)
ppush(node->right);
if (node->left != NULL)
ppush(node->left);
}
}
中序遍历
遍历顺序:左子树 根 右子树
1)递归版
//中序遍历
void inOrder(BTTree r)
{
if (r == NULL)
return;
if (r->left != NULL)
inOrder(r->left);
printf("%d ", r->data);
if (r->right != NULL)
inOrder(r->right);
}
2)非递归版:栈
//中序遍历
void InOrder(BTTree r)
{
initStack();
if (r == NULL)
return;
BTNode* node = r;
BTNode* k = NULL;
while (!empty() || node != NULL)
{
if (node != NULL)//向左
{
ppush(node);
node = node->left;
}
else
{
k = ppop();//取出栈顶
printf("%d ", k->data);
node = k->right;
}
}
}
后序遍历
遍历顺序:左子树 右子树 根
1)递归版
//后序遍历
void postOrder(BTTree r)
{
if (r == NULL)
return;
if (r->left != NULL)
postOrder(r->left);
if (r->right != NULL)
postOrder(r->right);
printf("%d ", r->data);
}
2)非递归版:栈
//后序遍历
void PostOrder(BTTree r)
{
initStack();
if (r == NULL)
return;
BTNode* node = r;
BTNode* pre = NULL;
BTNode* k = NULL;
while (!empty() || node != NULL)
{
if (node != NULL)//向左
{
ppush(node);
node = node->left;
}
else
{
k = s->next->data;//栈顶元素
if (k->right != NULL && pre != k->right)
node = k->right;
else
{
k = ppop();
printf("%d ", k->data);
pre = k;
node = NULL;//返回上一个父亲节点
}
}
}
}
线索化
若一个节点的左孩子指针域为空,则将该指针域指向其前驱节点;
若一个节点的右孩子指针域为空,则将该指针域指向其后继节点;
优点:加快了找前驱和后继的速度
应用场景:计算机网络-CIDR,选择下一跳 最长前缀匹配:线索化
树的存储
typedef struct BTNODE {
int data;
struct BTNODE* left;
struct BTNODE* right;
int lflag, rflag;//标记左右指针指向的是孩子节点(0),还是前驱后继(1)
}BTNode, * BTTree;
int flag;//0为左孩子,1为右孩子
BTTree pre = NULL;
//初始化
BTTree init(int x)
{
BTTree r = (BTNode*)malloc(sizeof(BTNode));
if (r == NULL)
printf("分配失败\n");
else
{
r->data = x;
r->left = r->right = NULL;
}
return r;
}
//查找-递归
BTNode* find(BTTree r, int fx)
{
if (r == NULL || r->data == fx)
return r;
if (r->left != NULL && r->lflag == 0)
{
BTNode* f = find(r->left, fx);
if (f != NULL && f->data == fx)
return f;
}
if (r->right != NULL && r->rflag == 0)
{
BTNode* f = find(r->right, fx);
if (f != NULL && f->data == fx)
return f;
}
return NULL;
}
//插入
void insert(BTTree r, int x, int fx, int flag)
{
BTNode* f = find(r, fx);
if (f == NULL)
printf("父亲节点不存在\n");
else
{
BTTree s = (BTTree)malloc(sizeof(BTNode));
s->data = x;
s->left = s->right = NULL;
if (flag == 0)
{
f->left = s;
f->lflag = 0;
}
else
{
f->right = s;
f->rflag = 0;
}
}
}
访问
//访问
void visit(BTTree r)
{
if (r->left == NULL)
{
r->left = pre;
r->lflag = 1;//加标记
}
if (pre != NULL && pre->right == NULL)
{
pre->right = r;
pre->rflag = 1;//加标记
}
pre = r;
}
深度优先遍历
//先序遍历
void perOrder(BTTree r)
{
if (r == NULL)
return;
visit(r);
if (r->left != NULL && r->lflag == 0)
perOrder(r->left);
if (r->right != NULL && r->rflag == 0)
perOrder(r->right);
}
//中序遍历(可不加lflag和rflag的判断)
void inOrder(BTTree r)
{
if (r == NULL)
return;
if (r->left != NULL && r->lflag == 0)
inOrder(r->left);
visit(r);
if (r->right != NULL && r->rflag == 0)
inOrder(r->right);
}
//后序遍历
void postOrder(BTTree r)
{
if (r == NULL)
return;
if (r->left != NULL && r->lflag == 0)
postOrder(r->left);
if (r->right != NULL && r->rflag == 0)
postOrder(r->right);
visit(r);
}
寻找前驱后继
//中序线索化找前驱后继
//找后继
BTNode* find_pre(BTTree r, int k)
{
BTNode* x = find(r, k);
if (x->rflag == 1)
return x->right;
else if (x->rflag == 0)
{
BTTree p = x->right;
while (p->left != NULL && p->lflag == 0)
p = p->left;
return p;
}
}
//找前驱
BTNode* find_post(BTTree r, int k)
{
BTNode* x = find(r, k);
if (x->lflag == 1)
return x->left;
else if (x->lflag == 0)
{
BTNode* p = x->left;
while (p->right != NULL && p->rflag == 0)
p = p->right;
return p;
}
}
语言参考
int main()
{
int n, x;
scanf("%d", &n);//输入节点个数n
BTTree r = NULL;
scanf("%d", &x);//根节点x
r = init(x);
for (int i = 2; i <= n; i++)
{
int fx;
scanf("%d %d %d", &x, &fx, &flag);
insert(r, x, fx, flag);
}
inOrder(r);
scanf("%d", &x);
BTNode* ans = find_pre(r, x);
printf("%d\n", ans->data);
}
二叉排序树(BST)
特殊的二叉树,又称 二叉排序树/二叉搜索树/二叉查找树(BST:Binary Search Tree)
定义/性质:在二叉树的基础上,增加以下约束/规则,构成BST:
1)如果树中一个节点(x)的左子树非空,则左子树所有节点的值都小于该节点(x)的值
2)如果树中一个节点(x)的右子树非空,则右子树所有节点的值都大于该节点(x)的值
3)左右子树也满足BST树的规则 --> 递归定义
用处/特点:
1)BST的中序遍历序列有序(递增)
2)减少查找次数
建立:
1)在空树的基础上,先用第一个数据建立根节点
2)执行n-1次插入操作
结构体组成
typedef struct BSTNODE {
int data;
struct BSTNODE* left;
struct BSTNODE* right;
}BSTNode, * BSTTree;
操作
//初始化
BSTTree init(int x)
{
BSTTree r = (BSTNode*)malloc(sizeof(BSTNode));
if (r == NULL)
printf("分配失败\n");
else
{
r->data = x;
r->left = r->right = NULL;
}
return r;
}
//插入-非递归
void insert(BSTTree r, int x)
{
BSTNode* s = (BSTNode*)malloc(sizeof(BSTNode));
s->data = x;
s->left = s->right = NULL;
BSTNode* p = r;
BSTNode* pre = NULL;
while (p != NULL)
{
if (x < p->data)
{
pre = p;
p = p->left;
}
else
{
pre = p;
p = p->right;
}
}
if (x < pre->data)
pre->left = s;
else
pre->right = s;
}
//插入-递归
BSTTree insert_BST(BSTTree r, int x)
{
if (r == NULL)
{
BSTNode* s = (BSTNode*)malloc(sizeof(BSTNode));
s->data = x;
s->left = s->right = NULL;
return s;
}
if (x < r->data)
r->left = insert_BST(r->left, x);
else
r->right = insert_BST(r->right, x);
return r;
}
//中序遍历
void inOrder(BSTTree r)
{
if (r == NULL)
return;
if (r->left != NULL)
inOrder(r->left);
printf("%d ", r->data);
if (r->right != NULL)
inOrder(r->right);
}
//查找
BSTTree search(BSTNode* r, int x)
{
BSTNode* p = r;
while (p != NULL)
{
if (x < p->data)
p = p->left;
else if (x > p->data)
p = p->right;
else if (x == p->data)
return p;
}
return NULL;
}
//查找:以r为根的树中最靠右的节点
BSTNode* find_p(BSTTree r)
{
BSTNode* p = r;
while (p->right != NULL)
p = p->right;
return p;
}
//删除
BSTNode* del(BSTTree r, int x)
{
if (x < r->data)
r->left = del(r->left, x);
else if (x > r->data)
r->right = del(r->right, x);
else if (x == r->data)
{
if (r->left != NULL && r->right != NULL)
{
BSTNode* p = find_p(r->left);//左子树中最靠右的
r->data = p->data;
r->left = del(r->left, p->data);
}
else//只有一个孩子
{
BSTNode* ch = NULL;
if (r->left != NULL)//度为1
ch = r->left;
else//度为1/0
ch = r->right;
free(r);
r = NULL;
return ch;
}
}
return r;
}
参考语言
int main()
{
int n, x;
scanf("%d", &n);//输入节点个数n
BSTTree r = NULL;
scanf("%d", &x);//根节点x
r = init(x);
for (int i = 2; i <= n; i++)
{
scanf("%d", &x);
insert(r, x);
//r = insert_BST(r, x);
}
r = del(r, 1);
inOrder(r);
printf("\n");
}
AVL树
AVL树,又称 平衡二叉树 / 二叉平衡树
平衡因子:一个节点的平衡因子是其左右子树的高度差。
概念:在BST树的基础上,每个节点的平衡因子的绝对值≤1
结构体组成
typedef struct AVLNODE {
int data;
int h;//高度
struct AVLNODE* left;
struct AVLNODE* right;
}AVLNode, * AVLTree;
功能函数
//比大小
int MAX(int a, int b)
{
return a > b ? a : b;
}
//算高度
int geth(AVLTree ro)
{
if (ro == NULL)
return 0;
else
return ro->h;
}
初始化
//初始化
AVLTree init(int x)
{
AVLTree r = (AVLNode*)malloc(sizeof(AVLNode));
if (r == NULL)
printf("分配失败\n");
else
{
r->data = x;
r->left = r->right = NULL;
r->h = 1;
}
return r;
}
四种失衡调整函数
p.s.假设最小失衡子树的根节点是A
LL:往A的左孩子的左子树中插入一个节点,导致A的左子树比右子树高
调整策略:以A为中心右旋
//LL-右旋
AVLTree LL_rotation(AVLNode* x)
{
AVLNode* y = x->left;
x->left = y->right;
y->right = x;
x->h = MAX(geth(x->left), geth(x->right)) + 1;
y->h = MAX(geth(y->left), geth(y->right)) + 1;
return y;
}
RR:往A的右孩子的右子树中插入一个节点,导致A的右子树比左子树高
调整策略:以A为中心左旋
//RR-左旋
AVLTree RR_rotation(AVLNode* x)
{
AVLNode* y = x->right;
x->right = y->left;
y->left = x;
x->h = MAX(geth(x->left), geth(x->right)) + 1;
y->h = MAX(geth(y->left), geth(y->right)) + 1;
return y;
}
LR:往A的左孩子的右子树中插入一个节点,导致A的左子树比右子树高
调整策略:①以A->left为中心进行左旋,转化成LL;②按照LL情况操作;
//LR
AVLTree LR_rotation(AVLNode* x)
{
x->left = RR_rotation(x->left);
x = LL_rotation(x);
return x;
}
RL:往A的右孩子的左子树中插入一个节点,导致A的右子树比左子树高
调整策略:①以A->right为中心进行右旋,转化成LL;②按照RR情况操作;
//RL
AVLTree RL_rotation(AVLNode* x)
{
x->right = LL_rotation(x->right);
x = RR_rotation(x);
return x;
}
操作
1)查找(同BST)
2)插入
①进行BST的插入;
②算插入节点的所有祖先节点的平衡因子 => 高度差
节点x的高度差x->h=max(x->left->h,x->right->h)+1
节点x的平衡因子:abs(x->left->h-x->right->h)
③判断失衡,有节点失衡之后进行旋转操作,优先调整最小失衡子树
4种调整类型:LL LR RR RL
//插入-递归
AVLTree insert_BST(AVLTree root, int x)
{
if (root == NULL)
{
AVLNode* s = (AVLNode*)malloc(sizeof(AVLNode));
s->data = x;
s->left = s->right = NULL;
s->h = 1;
return s;
}
if (x < root->data)
{
root->left = insert_BST(root->left, x);
//判断r是否失衡:LL LR
if (geth(root->left) - geth(root->right) > 1)
{
AVLNode* l = root->left;
if (x < l->data)//LL
root = LL_rotation(root);
else//LR
root = LR_rotation(root);
}
}
else if (x > root->data)
{
root->right = insert_BST(root->right, x);
//判断r是否失衡:RR RL
if (geth(root->right) - geth(root->left) > 1)
{
AVLNode* r = root->right;
if (x > r->data)//RR
root = RR_rotation(root);
else//RL
root = RL_rotation(root);
}
}
root->h = MAX(geth(root->left), geth(root->right)) + 1;
return root;
}
3)删除
①执行BST的删除操作
②判断失衡:在x的子树中删除一个节点,失衡节点是x节点
当在x的左子树中删除一个节点时,左子树高度-1,导致失衡
=> 右子树一棵树也随左子树删了一个节点,但又插回来了,此时就变成插入导致的失衡了
等价于:
在x节点的一边的子树中删除了一个节点,就等价于在其另一边子树插入了一个节点导致的失衡
左删除 相当于 右插入:RR RL
右删除 相当于 左插入:LL LR
//查找:以r为根的树中最靠右的节点
AVLNode* find_p(AVLTree r)
{
AVLNode* p = r;
while (p->right != NULL)
p = p->right;
return p;
}
//删除
AVLNode* del(AVLTree r, int x)
{
if (r == NULL)
return NULL;
if (x < r->data)
{
r->left = del(r->left, x);
//判断失衡:RR RL
if (geth(r->right) - geth(r->left) > 1)
{
AVLNode* ans = r->right;
if (geth(ans->right) >= geth(ans->left))
r = RR_rotation(r);
else
r = RL_rotation(r);
}
}
else if (x > r->data)
{
r->right = del(r->right, x);
//判断失衡:LL LR
if (geth(r->left) - geth(r->right) > 1)
{
AVLNode* ans = r->left;
if (geth(ans->left) >= geth(ans->right))
r = LL_rotation(r);
else
r = LR_rotation(r);
}
}
else if (x == r->data)
{
if (r->left != NULL && r->right != NULL)
{
AVLNode* p = find_p(r->left);//左子树中最靠右的
r->data = p->data;
r->left = del(r->left, p->data);
//判断失衡:RR RL
if (geth(r->right) - geth(r->left) > 1)
{
AVLNode* ans = r->right;
if (geth(ans->right) >= geth(ans->left))
r = RR_rotation(r);
else
r = RL_rotation(r);
}
}
else//只有一个孩子
{
AVLNode* ch = r;
if (r->left != NULL)//度为1
r = r->left;
else//度为1/0
r = r->right;
free(ch);
ch = NULL;
return r;
}
}
r->h= MAX(geth(r->left), geth(r->right)) + 1;
return r;
}
参考语言
int main()
{
int n, x;
scanf("%d", &n);//输入节点个数n
AVLTree root = NULL;
scanf("%d", &x);//根节点x
root = init(x);
for (int i = 2; i <= n; i++)
{
scanf("%d", &x);
root = insert_BST(root, x);
}
inOrder(root);
del(root, 4);
inOrder(root);
}
算法
基于树形结构的算法
并查集
概念:利用树形结构来处理一些不相交的集合的合并和查询问题
int n, m, p;
int f[5005];
//查找-非递归
int find(int x)
{
while (f[x] != x)
x = f[x];
return x;
}
//查找-递归
int find2(int x)
{
if (f[x] == x)
return x;
else
return f[x] = find2(f[x]);//路径压缩
}
int main()
{
int x, y, px, py;
scanf("%d %d %d", &n, &m, &p);
//初始化fi=i
for (int i = 1; i <= n; i++)
f[i] = i;
for (int i = 1; i <= m; i++)
{
scanf("%d%d", &x, &y);
px = find(x);
py = find(y);
f[px] = py;
}
for (int i = 1; i <= p; i++)
{
scanf("%d%d", &x, &y);
px = find(x);
py = find(y);
if (px == py)
printf("Yes\n");
else
printf("No\n");
}
}
哈夫曼树
1.节点i的路径长度:从根节点到节点i的路径上所经过的边数
2.树的路径长度:所有节点的路径长度之和为该树的路径长度
内部路径长度:所有内部节点的路径长度之和
外部路径长度(树的路径长度):所有叶子节点的路径长度之和
3.节点i的带权路径长度:节点i的路径长度*节点i权值
4.树的带权路径长度(WPL):所有叶子节点的带权路径长度之和
概念:给出n个带权值的节点,可再自行加入若干不确定的节点。用这n个节点全做叶子节点,自行加入的节点做内部节点去建立一棵树。其中WPL最小的一颗二叉树成为哈夫曼树(最优二叉树)
应用场景:通过编码进行数据压缩
哈夫曼编码 属于 变长编码
变长编码 比 定长编码 省空间
构建:对于给定的n个节点(带权值且只做叶子节点)构造哈夫曼树
树的合并,一开始认为这n个节点是n棵树
结构体构成
typedef struct {
int w;//权值
int f;//父亲节点的下标
int left, right;//左右孩子节点的下标
}HuffmanNode, * HuffmanTree;
操作
//寻找权值最小的两个根节点的下标
void find(HuffmanTree t, int x, int* w1, int* w2)
{
//先找最小
int minn = 0;
for (int i = 1; i <= x; i++)
{
if (t[i].f == i)
{
minn = i;
break;
}
}
for (int i = 1; i <= x; i++)
{
if (t[i].f == i)
{
if (t[i].w < t[minn].w)
minn = i;
}
}
*w1 = minn;//最小根节点的下标保存在w1
//找第二小
for (int i = 1; i <= x; i++)
{
if (t[i].f == i && i != (*w1))
{
minn = i;
break;
}
}
for (int i = 1; i <= x; i++)
{
if (t[i].f == i && i != (*w1))
{
if (t[i].w < t[minn].w)
minn = i;
}
}
*w2 = minn; //第二小根节点的下标保存在w1
}
//建树
HuffmanTree createHuffmanTree(int* w, int n)
{
int m = 2 * n;//数组大小
HuffmanTree t = (HuffmanTree)malloc(sizeof(HuffmanNode) * m);
for (int i = 0; i < m; i++)//初始化
{
t[i].f = t[i].right = t[i].left = 0;
t[i].w = 0;
}
for (int i = 1; i <= n; i++)
{
t[i].w = w[i - 1];
t[i].f = i;
}
int w1, w2;//权值最小的两个根节点的下标
for (int i = n + 1; i < m; i++)
{
find(t, i - 1, &w1, &w2);
t[w1].f = t[w2].f = i;
t[i].f = i;
t[i].w = t[w1].w + t[w2].w;
t[i].left = w1;
t[i].right = w2;
}
return t;
}
//编码
char** createHuffmanCode(HuffmanTree t, int n)
{
char* temp = (char*)malloc(sizeof(char) * n);//0~n-1
char** code = (char*)malloc(sizeof(char*) * n);
int start;//一开始放编码的位置
int pos, p;
for (int i = 1; i <= n; i++)
{
start = n - 1;
temp[start] = '\0';
pos = i;//当前节点
p = t[pos].f;//父
while (t[pos].f != pos)
{
start--;
if (t[p].left == pos)
temp[start] = '0';
else if (t[p].right == pos)
temp[start] = '1';
pos = p;
p = t[pos].f;
}
code[i - 1] = (char*)malloc(sizeof(char) * (n - start));
strcpy(code[i - 1], &temp[start]);
}
free(temp);
temp = NULL;
return code;
}
参考语言
int main()
{
char s[8] = { 'A','B','C','D','E','F','G','H' };
int w[8] = { 5,29,7,8,14,23,3,11 };
int* a = (int*)malloc(sizeof(int) * 10);
HuffmanTree t = createHuffmanTree(w, 8);//建树
char** code = createHuffmanCode(t, 8);
for (int i = 0; i <= 8; i++)
printf("%c : %s\n", s[i], code[i]);
}