算法建树
A. 二叉树的中序、前序、后序的递归、非递归遍历算法,应包含建树的实现
#include "stdlib.h"
#include "iostream"
using namespace std;
#define ok 1
#define null 0
typedef char telemtype;
typedef struct bitnode
{telemtype data;<br> struct bitnode *lchild,*rchild;<br> }bitnode,*bitree;/* void visit(bitnode *p)
{printf("%c",p->data);<br> }*/
//先序建树
bitree createbitree(void)
{char ch;bitnode *t;<br> scanf("%c",&ch);<br> if(ch=='#')<br> t=null;<br> else<br> {t=(bitnode *)malloc(sizeof(bitnode));<br> t->data=ch;<br> t->lchild=createbitree();<br> t->rchild=createbitree();<br> } return t;
}
//中序递归遍历
void inorder(bitree T){
if(T)
{inorder(T->lchild);<br> printf("%c",T->data);<br> inorder(T->rchild);}
} //先序递归遍历
void preorder(bitree T)
{if(T)<br> {<br> printf("%c",T->data);<br> preorder(T->lchild);<br> preorder(T->rchild);<br>}
}//后序递归遍历
void Post_order(bitree T)
{if(T)<br> {<br> Post_order(T->lchild);<br> Post_order(T->rchild);<br> printf("%c",T->data);<br>}
}//先序非递归遍历二叉树
void PreTree(bitree T)
{
bitree p,node[20];
int top=0;
p=T;
do
{
while(p!=NULL)
{
cout<<p->data<<" ";
node[top]=p;
top++;
p=p->lchild; }
if(top>0){top--;p=node[top];p=p->rchild;}
}while(top>0||p!=NULL);
cout<<endl;}//中序非递归遍历二叉树
void InTree(bitree T)
{
bitree p,node[20];
int top=0;
p=T;
do
{
while(p!=NULL)
{
node[top]=p;
top++;
p=p->lchild; }
if(top>0){
cout<<p->data<<" ";
top--;p=node[top];
p=p->rchild;}
}while(top>0||p!=NULL);
cout<<endl;}//后序非递归遍历二叉树
void PostTree(bitree T)
{
bitree p,node[20];
int top=0;
p=T;
do
{
while(p!=NULL)
{
cout<<p->data<<" ";
node[top]=p;
top++;
p=p->lchild; }
if(top>0){top--;p=node[top];p=p->rchild;}
}while(top>0||p!=NULL);
cout<<endl;}void leaf(bitree T,int &count)
{
if(T)
{
if(T->lchild==NULL&&T->rchild==NULL)
count++;
else
{
leaf(T->lchild,count);
leaf(T->rchild,count);
}
}}
int deepth(bitree T)
{
int leftdep,rightdep;
if(!T)
return 0;
else
{
leftdep=deepth(T->lchild);
rightdep=deepth(T->rchild);
if(leftdep>rightdep)
return leftdep+1;
else
return rightdep+1;
}
}
void level_order(bitree T)
{}
//输出菜单
void print_menu(){
cout<<"请选择你要进行的操作:"<<endl;
cout<<"1.先序建立二叉树! "<<" "<<"2.中序递归遍历二叉树!"<<endl;
cout<<"3.先序递归遍历二叉树!"<<" "<<"4.后序递归遍历二叉树!"<<endl;
cout<<"5.先序非递归遍历二叉树!"<<" "<<"6.中序非递归遍历二叉树!"<<endl;
cout<<"7.后序非递归遍历二叉树!"<<" "<<"8.求叶子结点个数!"<<endl;
cout<<"9.求树的深度! "<<" "<<"10.层序遍历二叉树!"<<endl;
cout<<"11.退出!"<<endl;
}
void main()
{
bitree t;
int k,c=0,d;
print_menu();
cin>>k;
while(k!=11)
{
switch(k)
{
case 1:
cout<<"请输入树的先序字符序列,空树以#表示"<<endl;
t=createbitree();
cout<<"建树成功"<<endl;
break;
case 2:
inorder(t);
break;
case 3:
preorder(t);
break;
case 4:
Post_order(t);
break;
case 5:
PreTree(t);
break;
case 6:
InTree(t);
break;
case 7:
PostTree(t);
break;
case 8:
leaf(t,c);
cout<<"叶子结点数为:"<<c<<endl;
break;
case 9:
d=deepth(t);
cout<<"树的深度为:"<<d<<endl;
break;
case 10:
level_order(t);
break;
case 11:
exit(0);
default:
cout<<"不存在您选择的项,请重新输入:"<<endl;
} //switch
print_menu();
cin>>k;
}//while
}//main
B. 给定权3,4,5,6,7,8,9,试用算法构造一棵最优二叉树,画出这棵树并计算出...
建树步骤:
3
4
5
6
7
8
9
7
5
6
7
8
9
7
11
7
8
9
11
14
8
9
11
14
17
25
17
42
建立后的最优二叉树是这样滴:(线和箭头自己连一下吧汗~)
42
25
17
11
14
8
9
5
6
7
7
3
4
权(WPL):3*4+4*4+5*3+6*3+7*3+8*2+9*2=116
C. PyOD主要算法(KNN、IForest 和 MCD)的原理及使用
https://pyod.readthedocs.io/en/latest/pyod.models.html
Python Outlier Detection(PyOD)是当下最流行的Python异常检测工具库(toolkit)。该工具库的主要亮点包括:
对于特征空间中的一个样本,如果与之最相似的(即特征空间中距离最近的)k个样本中的大多数都属于某一类别,则该样本的分类结果也是这个类别。
https://www.cnblogs.com/lesleysbw/p/6074662.html
① 什么叫做KD_tree
K:K邻近查询中的k;D:空间是D维空间(Demension)tree:二叉树
② 建树过程
K-D tree的建立就是分裂空间的过程
首先,我们对整个区间 [1 , 15] 建树:先计算区间中所有点在第一维(也就是 x 坐标)上的方差:
平均值 : ave_1 =5.4
方差 : varance_1 =9.04
再计算区间中所有点在第二维(也就是 y 坐标)上的方差:
平均值:ave_2 =6.8
方差:varance_2 =10.96
明显看见,varance_2 > varance_1 ,那么我们在本次建树中, 分裂方式 :split_method =2 , 再将所有的点按照第2维的大小 从小到大排序 ,得到了新的点的一个排列:
(4,2) (1,4) (5,8) (7,9) (10,11)
取中间的点作为分裂点 sorted_mid =(5,8)作为根节点,再把区间 [1 , 2] 建成左子树 , [4 , 5] 建成右子树,此时,直线 : y = 8 将平面分裂成了两半,前面一半给左儿子,后面一半给了右儿子,如图:
建左子树 [1, 3] 的时候可以发现,这时候 第一维的方差大 ,分裂方式就是1 ,把区间 [ 1, 2 ] 中的点按照 第一维 的大小,从小到大排序 ,取 中间点(1,4) 根节点,再以区间 [ 2, 2] 建立右子树 得到节点 (4,2)
建右子树 [4 , 5] 的时候可以发现,这时还是第一维的方差大, 于是,我们便得到了这样的一颗二叉树 也就是 K-D tree,它把平面分成了如下的小平面, 使得每个小平面中最多有一个点 :
③ 查询过程:
查询,其实相当于我们要将一个点“添加”到已经建好的 K-D tree 中,但并不是真的添加进去,只是找到他应该 处于的子空间 即可,所以查询就显得简单的。
每次在一个区间中查询的时候,先看这个区间的 分裂方式 是什么,也就是说,先看这个区间是按照哪一维来分裂的,这样如果这个点对应的那一维上面的值比根节点的小,就在根节点的左子树上进行查询操作,如果是大的话,就在右子树上进查询操作。
每次回溯到了根节点(也就是说,对他的一个子树的查找已经完成了)的时候,判断一下,以该点为圆心,目前 找到的最小距离为半径 ,看是否和分裂区间的那一维所构成的平面相交,要是相交的话,最近点可能还在另一个子树上,所以还要再查询另一个子树,同时,还要看能否用根节点到该点的距离来更新我们的最近距离。为什么是这样的,我们可以用一幅图来说明:
https://github.com/YinghongZhang/BallTree-MIPS
① 原理
为了改进KDtree的二叉树树形结构,并且沿着笛卡尔坐标进行划分的低效率,ball tree将在一系列嵌套的超球体上分割数据。也就是说: 使用超球面而不是超矩形划分区域 。虽然在构建数据结构的花费上大过于KDtree,但是在 高维 甚至很高维的数据上都表现的很高效。
球树递归地将数据划分为 由质心C和半径r定义的节点 ,使得节点中的每个点都位于由r和C定义的超球内。通过使用三角不等式来减少邻居搜索的候选点数量。
② 建树过程
选择一个距离当前圆心最远的观测点A,和距离A最远的观测点B,将圆中所有离这两个点最近的观测点都赋给这两个簇的中心,然后计算每一个簇的中心点和包含所有其所属观测点的最小半径。对包含n个观测点的超圆进行分割,只需要线性的时间。
③ 查询
使用ball tree时,先自上而下找到包含target的叶子结点(c, r),从此结点中找到离它最近的观测点。这个距离就是 最近邻的距离的上界 。检查它的 兄弟结点 中是否包含比这个上界更小的观测点。方法是: 如果目标点距离兄弟结点的圆心的距离d > 兄弟节点所在的圆半径R + 前面的上界r,则这个兄弟结点不可能包含所要的观测点 。否则,检查这个兄弟结点是否包含符合条件的观测点。
用一个随机超平面来切割数据空间, 直到每个子空间里面只有一个数据点为止。切割次数的多少可用来区分异常。
https://www.jianshu.com/p/5af3c66e0410
iForest 由t个iTree孤立树组成,每个iTree是一个二叉树,其实现步骤如下:
可以看到d最有可能是异常,因为其最早就被孤立(isolated)了。
获得t个iTree之后,iForest 训练就结束,然后我们可以用生成的iForest来评估测试数据了。对于一个训练数据x,我们令其遍历每一棵iTree,然后计算x最终落在每个树第几层(x在树的高度),得到x在每棵树的高度平均值。获得每个测试数据的average path length后,我们可以设置一个阈值,低于此阈值的测试数据即为异常。
IForest具有线性时间复杂度。
IForest不适用于特别高维的数据。
最小协方差行列式(Minimum Covariance Determinant)
https://max.book118.com/html/2017/1217/144650709.shtm
论文《Minimum covariance determinant and extensions》中有更详细描述。
论文《A Fast Algorithm for the Minimum Covariance Determinant Estimator》有更详细描述。
D. 编写递归算法,求二叉树的结点个数和叶子数
00DLR(liuyu *root) /*中序遍历 递归函数*/
{if(root!=NULL)
{if((root->lchild==NULL)&&(root->rchild==NULL)){sum++; printf("%d\n",root->data);}
DLR(root->lchild);
DLR(root->rchild); }
return(0);
}
法二:
int LeafCount_BiTree(Bitree T)//求二叉树中叶子结点的数目
{
if(!T) return 0; //空树没有叶子
else if(!T->lchild&&!T->rchild) return 1; //叶子结点
else return Leaf_Count(T->lchild)+Leaf_Count(T->rchild);//左子树的叶子数加
上右子树的叶子数
}//LeafCount_BiTree
注:上机时要先建树!例如实验二的方案一。
① 打印叶子结点值(并求总数)
思路:先建树,再从遍历过程中打印结点值并统计。
E. 哈夫曼算法中频度建树应该用什么排序
最优二叉树概念
1.树的路径长度
树的路径长度是从树根到树中每一结点的路径长度之和。在结点数目相同的二叉树中,完全二叉树的路径长度最短。
2.树的带权路径长度(Weighted Path Length of Tree,简记为WPL)
结点的权:在一些应用中,赋予树中结点的一个有某种意义的实数。
结点的带权路径长度:结点到树根之间的路径长度与该结点上权的乘积。
树的带权路径长度(Weighted Path Length of Tree):定义为树中所有叶结点的带权路径长度之和,通常记为:
【数据结构】树:哈夫曼树及其应用 - 八月照相馆 - 八月照相馆
其中:
n表示叶子结点的数目
wi和li分别表示叶结点ki的权值和根到结点ki之间的路径长度。
树的带权路径长度亦称为树的代价。
3.最优二叉树或哈夫曼树
在权为wl,w2,…,wn的n个叶子所构成的所有二叉树中,带权路径长度最小(即代价最小)的二叉树称为最优二叉树或哈夫曼树。
【例】给定4个叶子结点a,b,c和d,分别带权7,5,2和4。构造如下图所示的三棵二叉树(还有许多棵),它们的带权路径长度分别为:
(a)WPL=7*2+5*2+2*2+4*2=36
(b)WPL=7*3+5*3+2*1+4*2=46
(c)WPL=7*1+5*2+2*3+4*3=35
其中(c)树的WPL最小,可以验证,它就是哈夫曼树。
【数据结构】树:哈夫曼树及其应用 - 八月照相馆 - 八月照相馆
注意:
① 叶子上的权值均相同时,完全二叉树一定是最优二叉树,否则完全二叉树不一定是最优二叉树。
② 最优二叉树中,权越大的叶子离根越近。
③ 最优二叉树的形态不唯一,WPL最小
构造最优二叉树
1.哈夫曼算法
哈夫曼首先给出了对于给定的叶子数目及其权值构造最优二叉树的方法,故称其为哈夫曼算法。其基本思想是:
(1)根据给定的n个权值wl,w2,…,wn构成n棵二叉树的森林F={T1,T2,…,Tn},其中每棵二叉树Ti中都只有一个权值为wi的根结点,其左右子树均空。
(2)在森林F中选出两棵根结点权值最小的树(当这样的树不止两棵树时,可以从中任选两棵),将这两棵树合并成一棵新树,为了保证新树仍是二叉树,需要增加一个新结点作为新树的根,并将所选的两棵树的根分别作为新根的左右孩子(谁左,谁右无关紧要),将这两个孩子的权值之和作为新树根的权值。
(3)对新的森林F重复(2),直到森林F中只剩下一棵树为止。这棵树便是哈夫曼树。
用哈夫曼算法构造哈夫曼树的过程见【动画演示】。
注意:
① 初始森林中的n棵二叉树,每棵树有一个孤立的结点,它们既是根,又是叶子
② n个叶子的哈夫曼树要经过n-1次合并,产生n-1个新结点。最终求得的哈夫曼树中共有2n-1个结点。
③ 哈夫曼树是严格的二叉树,没有度数为1的分支结点。
2.哈夫曼树的存储结构及哈夫曼算法的实现
(1) 哈夫曼树的存储结构
用一个大小为2n-1的向量来存储哈夫曼树中的结点,其存储结构为:
#define n 100 //叶子数目
#define m 2*n-1//树中结点总数
typedef struct { //结点类型
float weight; //权值,不妨设权值均大于零
int lchild,rchild,parent; //左右孩子及双亲指针
}HTNode;
typedef HTNode HuffmanTree[m]; //HuffmanTree是向量类型
注意:
因为C语言数组的下界为0,故用-1表示空指针。树中某结点的lchild、rchild和parent不等于-1时,它们分别是该结点的左、右孩子和双亲结点在向量中的下标。
这里设置parent域有两个作用:其一是使查找某结点的双亲变得简单;其二是可通过判定parent的值是否为-1来区分根与非根结点。
(2)哈夫曼算法的简要描述
在上述存储结构上实现的哈夫曼算法可大致描述为(设T的类型为HuffmanTree):
(1)初始化
将T[0..m-1]中2n-1个结点里的三个指针均置为空(即置为-1),权值置为0。
(2)输人
读人n个叶子的权值存于向量的前n个分量(即T[0..n-1])中。它们是初始森林中n个孤立的根结点上的权值。
(3)合并
对森林中的树共进行n-1次合并,所产生的新结点依次放人向量T的第i个分量中(n≤i≤m-1)。每次合并分两步:
①在当前森林T[0..i-1]的所有结点中,选取权最小和次小的两个根结点[p1]和T[p2]作为合并对象,这里0≤p1,p2≤i-1。
② 将根为T[p1]和T[p2]的两棵树作为左右子树合并为一棵新的树,新树的根是新结点T[i]。具体操作:
将T[p1]和T[p2]的parent置为i,
将T[i]的lchild和rchild分别置为p1和p2
新结点T[i]的权值置为T[p1]和T[p2]的权值之和。
注意:
合并后T[pl]和T[p2]在当前森林中已不再是根,因为它们的双亲指针均已指向了T[i],所以下一次合并时不会被选中为合并对象。