文章目录
树
1 二叉树的存储结构
typedef struct _tree
{
int data;
struct _tree* left;
struct _tree* right;
}btree,*pbtree;
1 树的遍历
1 前序递归写法
#define MAX 100
void pre(btree*t)
{
if(t)
{
printf("%d",t->data);
pre(t->left);
pre(t->right);
}
}
2 前序非递归写法
void pre(btree* b)
{
首先
if(b!=NULL)
{
//栈初始化
top=1;
stack[top]=b;
while(top>0)
{
//出栈
p=stack[top];
top--;
//遍历根结点
printf("%d",p->data);
//右子树进栈
if(p->right)
{
top++;
stack[top]=p->right;
}
//左子树进栈
if(p->left)
{
top++;
stack[top]=p->left;
}
}
}
}
3 中序递归写法:
void in(btree* t)
{
if(t)
{
in(t->left);
printf("%d",t->data);
in(t->right);
}
}
4 中序非递归写法:
void in(btree*b)
{
btree*stack[MAX],*p;
int top=0;
p=b;
do
{
//遍历左子树
while(p)
{
top++;
stack[top]=p;
p=p->left;
}
if(top>0)
{
//遍历根结点
p=stack[top];
top--;
printf("%d",p->data);
//遍历右子树
p=p->right;
}
}while(p&&top);
}
5 后序递归写法:
void post(btree*t)
{
if(t)
{
post(t->left);
post(t->right);
printf("%d",t->data);
}
}
6 后序非递归写法:
void post(btree* b)
{
btree* stack[MAX],*p;
int tag[MAX],top=0;
p=b;
do
{
//遍历左子树
while(p)
{
top++;
stack[top]=p;
tag[top]=0;
p=p->left;
}
if(top>0)
{
p=stack[top];
//右子树遍历完成,再遍历根结点
if(tag[top]==1)
{
top--;
printf("%d",p->data);
}
//遍历右子树
if(top>0)
{
p=p->right;
tag[top]=1;
}
}
}while(p&&top);
}
7 层次遍历
分析:层次遍历相当于图的广度优先遍历,每一层从左往右遍历;
思路:利用队列先进先出的性质,设置一个队列存储待遍历的结点;
void broadorder(btree*t)
{
Queue q;
btree*pt=NULL;
if(!t)
{
return;
}
//初始化队列
EnQueue(q,t);
while(!isQueEMpty(q))
{
//出队
pt=Deque(q);
printf("%d",pt->data);
//将该结点的左右子树依次入队
if(pt->left)
{
Enque(q,pt->left);
}
if(pt->right)
{
Enque(q,pt->right);
}
return;
}
2 二叉排序树
1 性质:左<中<右;
2 插入算法:
void insert(btree*b,btree*s)
{
if(!b)
{
b=s;
}
else if(s->data==b->data)
{
return;
}
else if(s->data<b->data)
{
insert(b->left,s);
}
else if(s->data>b->data)
{
insert(b->right,s);
}
}
3 创建算法:
void create(btree*b)
{
int x;
btree*s;
b=NULL;
do
{
scanf("%d",&x);
s=(bnode*)malloc(sizeof(bnode));
s->data=x;
s->left=NULL;
s->right=NULL;
insert(b,s);
}while(x!=-1);
}
4 删除算法:
void delnode(btree*p)
{
btree*q,*s;
if(!p->right)
{
q=p;
p=p->left;
free(q);
}
else if(!p->left)
{
q=p;
p=p->right;
free(q);
}
else
{
q=p;
s=p->left;
while(s->right)
{
q=s;
s=s->right;
}
p->data=s->data;
if(q!=p)
{
q->right=s->left;
}
else
{
q->left=s->left;
}
free(s);
}
}
3 平衡二叉排序树
1 性质:一棵任意一结点的左子树和右子树的高度至多差1的二叉排序树;
2 红黑树:是平衡二叉树,优点是树到叶子结点深度一致,查找效率一样:
红黑树的性质:
1)结点是红色或黑色;
2)根结点是黑色;
3)每个红色结点的两个子节点都是黑色(从每个叶子到根的所有路径不能有两个连续的红结点;
4)从任一结点到其每个叶子的所有路径都包含相同数目的黑结点;
4 公共祖先
1 公共祖先:在树中某两个结点到根结点路径中重合的结点部分;
2 思路:要求常规树的公共祖先,需要利用树的后序遍历时栈中保存的结点为从根结点到当前结点的路径这样一个性质;
问题1:
假设二叉树采用链接存储结构进行存储,root指向根结点,p所指结点为任一给定的结点,编写一个求出从根结点到p所指结点之间路径的函数:
分析:
后序遍历整棵树,当遍历到p结点时,栈中的结点便是从根结点到p所指结点之间的路径;
void path(btree* root,btree*p)
{
btree*stack[MAX],*s;
int tag[MAX],top=0,find=0,i;
//后序遍历
s=root;
do
{
while(s)
{
top++;
stack[top]=s;
tag[top]=0;
s=s->left;
}
if(top>0)
{
s=stack[top];
if(tag[top]==1)
{
//如果为p,则开始打印栈中内容即路径
if(s==p)
{
for(i=1;i<=top;i++)
{
printf("%d",stack[i]->data);
find=1;
}
}
else
{
top--;
}
}
if(top>0&&!find)
{
p=p->right;
tag[top]=1;
}
}
}while(!find||(s&&top));
}
问题2:
假设二叉树采用链式存储结构存储,root指向根结点,p指向的结点和q所指结点为二叉树中两个结点,编写一个计算它们最近的公共祖先r所指结点的函数:
分析:
分别求出p与q到根结点的路径,比较两条路径,找出第一个相同的结点,即为所求的最近公共祖先;
btree*ancestor(btree*root,btree*p,btree*q)
{
btree* stack[MAX],*s,*ancr[MAX],*r;
int tag[MAX],top=0,top1,i,j,find=0;
s=root;
do
{
while(s)
{
top++;
stack[top]=s;
tag[top]=0;
s=s->left;
}
if(top>0)
{
s=stack[top];
if(tag[top]==1)
{
top--;
if(s==p)
{
//找到p到root的路径
for(top1=1,top1<=top;top1++)
{
ancr[top1]=stack[top];
}
}
if(s==q)
{
i=top;
//找到q到root的路径
while(!find)
{
j=top1;
//找出最近的公共祖先
while(j<0&&stack[i]!=ancr[j])
{
j--;
}
if(j>0)
{
find=1;
r=ancr[j];
}
else
{
j--;
}
}
}
}
}
if(top>0&&!find)
{
p=p->right;
tag[top]=1;
}
}while(!find||(s&&top));
return r;
}
问题3:
已知二叉排序树上两个结点的值,找出它们的最低公共祖先;
分析:
发现公共祖先结点的值大于左子结点的值,小于右子结点的值;
int FindLowestSharedAncestor(node*root,int val1,int val2)
{
node* curNode=root;
while(1)
{
//同时大于val1和val2,那么最近公共子结点在左边
if(curNode->val>val1&&curNode->val>val2)
{
curNode=curNode->left;
}
//同时小于val1和val2,那么最近公共子结点在右边
else if(curNode->val<val1&&
curNode->val<val2)
{
curNode=curNode->right;
}
//大于左结点而小于右结点,即为最近公共祖先
else
{
return curNode->val;
}
}
}
5 字典树
1 又称单词查找树,用于保存大量的字符串;
2 优点:
利用字符串的公共前缀来节约存储空间;
3 基本性质:
1)根结点不包含字符,除根结点以外每一个结点都只包含一个字符;
2)从根结点到某一结点,路径上经过的字符连接起来,为该结点对应的字符串;
3)每一个结点的所有子节点包含的字符都不相同;
问题1:
在一个树结构中,保存了几个字符串,假如已经包含了正确的字典,设计一个拼写纠错的程序:
思路:
程序以字典树为数据结构,存储整个字典,根据用户的输入,结合字典树来匹配;
每输入一个字母,沿字典树向下一层;
1)若可以顺利下行,则继续至结束,给出结果;
2)若该处不能匹配,纠错处理,给出拼写建议,继续至1;
算法:
1)在字典中查找单词:
由于有26个字母,则字典树采用26叉树组织,每个结点对应一个字母,查找就一个字母一个字母匹配,算法时间复杂度就是单词的长度;
2)纠错算法:
当输入的最后一个字母不能匹配时,提示出错;
当前字母前缺少一个字母:搜索树上两层到当前的匹配的作为建议;
当前字母拼写错误:当前字典树的下一个结点或当前字母的键盘上相邻的字母作为提示;
问题2:
有a,b两个文件,各存放50亿条url,每条url各占64字节,内存限制4GB,找出a,b,文件共同的url;
分析:
hush表的方法:内存不够,无法使用;
利用字典树;
typedef struct treeNode_
{
int flag;//1:表示为叶子结点,0:表示表示一个字符串;
char c;
struct treeNode_ *str[256];
}
思路:
首先读文件a,一行一个url,读的过程就是建立文件a的字典树的过程,每个url在树的叶子结点中的flag为1;
然后读b,每读一行url,从树的根结点开始往下查询,如果该url的最后一个字符能走到树的某条路径的叶子结点,说明该url在文件a中也存在,则记录下该url;
如果该url走到某条路径的叶子结点但不是url的最后一个字符,说明该url不在a中,则查询结束;
因为树公用了大量url的相同前缀,内存是足够的;
时间复杂度:插入和查询url的时间复杂度都是O(n);