//从下标0开始,返回的是存储公共祖先结点的数组下标
int find_sameFather(BSTNode[] node,BSTNode x,BSTNode y){
int i = x.data;
int j = y.data;
if(i<=2||j<=2)
return 0;
while(i!=j&&i>2&&j>2){
if(i<j){
j = j/2;
// if(j==i) return j;
}
else {
i =i/2;
// if(j==i) return i;
}
}
if(i==j)
return i;
else
return 0;
}
void InsertLevel(BiTree bt)
{
Stack s;Queue Q;
if(bt!=nullptr){
InitStack(S);
InitQueue(Q);
EnQueue(Q,bt);
while(!IsEmpty(Q)){
DeQueue(Q,p);
Push(S,p);
if(p->lchild!=nullptr)
Enqueue(Q,p->lchild);
if(p->rchild!=nullptr)
Enqueue(Q,p->rchild);
}
while(!IsEmpty(S))
{
Pop(S,p);
visit(p->data);
}
}
//但是实际上也不用开两个,就直接使用一个数组就行,物理存储都是一样的,只不过逻辑结构不同而已
#define MaxSize 100000
void InsertLevel(BiTree bt){
BiTree temp[MaxSize];
int top=0;
int begin=0;
p = T;
temp[top++]=bt;
while(begin!=top){//等到开始的结点控制位置和后面的重合就可以了
if(temp[begin]->lchild!=nullptr)
temp[top++]=temp[begin]->lchild;
if(temp[begin]->rchild!=nullptr)
temp[top++]=temp[begin]->rchild;
begin++;
}
for(int i=begin-1;i>=0;i--)
visit(temp[i]->data);
}
int Btdepth(BiTree T)
{
if(!T) return 0;
int front = -1,rear = -1;
int last = 0,level = 0;
BiTree Q[MaxSize];
Q[++rear]=T;//rear队尾指针,队尾指针实际上就是每层更新后的最后一个元素的位置啊
BiTree p;
while(front!=rear){
p = Q[++front]; //当前处理节点,当前访问到的结点
if(p->lchild!=nullptr)
Q[++rear]=p->lchild;
if(p->rchild!=nullptr)
Q[++rear]=p->rchild;
if(front == last)
//当前访问结点和当前层最右结点相等时才更新rear
//否则如果在if(front==last)外更新的话,front直到最后才能赶上rear(last)
//而且当前层遍历结束了也正好代表当前层的所子结点都已经进入队列,也就是当前的rear就是下一层的最右结点
//而且判断得在将当前访问结点的子节点入队列之后才能访问,否则每层都没有完全进入
{ level++;
last = rear;
}
}
return level
}
BiTree PreInCreat(ElemType A[],ElemType B[],int l1,int h1,int l2,int h2){
//A先序,B中序,l1,h2先序序列的开始和结束,l2,h2中序序列的开始和结束
root = (BiTNode*)malloc(sizeof(BiTNode));
root->data = A[l1];
/*
for(int i=l2;i<=h2;i++)
if(B[i] == A[l1]);
temp = i;
*/
for(int i=l2;B[i]!=root->data;i++);//上面的还得把整个数组B遍历完,这个到对应的位置就结束了
int llen = i-l2;//可以通过中序遍历知道左右子树的结点数
int rlen = h2-i;//即使是在先序遍历中一棵子树上的结点也是连续的排列在终先序序列中的
if(llen)
root->lchild=PreInCreat(A,B,l1+1,l1+llen,l2,l2+llen-1);
else//当分割成的长度为0的时候要赋值为空
root->lchild = nullptr;
if(rlen)
root->rchild=PreInCreat(A,B,h1-rlen+1,h1,h2-rlen +1,h2);
else
root->rchild=nullptr;
return root;
}
⚠️判断为完全二叉树的方法:采用层次遍历的算法,将所有结点加入队列(包括空结点),当遇到空结点时查看其后时候有非空结点,若有,则二叉树不是完全二叉树。(空结点后面如果有值存在一定不是完全二叉树)
[如果是完全二叉树变成顺序存储表示一定是一个连续的数组,那么就把按照二叉链表存储的二叉树转换为使用顺序存储表示然后看是否有空的。]
bool IsComplete(BiTree bt){
if(bt == nullptr) return true;
InitQueue(Q);
EnQueue(Q,bt);
BiTree p;
while(!IsEmpty(Q))
{
DeQueue(Q,p);
if(p){
EnQueue(Q,p->lchild);
EmQueue(Q,p->rchild);
}else{
while(!IsEmpty(Q)){
//当找到一个空结点之后直接将元素出队判断后面是否是非空元素,如果是,一定不是完全二叉树
//队列是先进先出的,所以才要队列非空不断出队判断出队结点是否正常
DeQueue(Q,p);
if(p)
return false;
}
}
return true;
}
//太麻烦了,咩有必要
bool IsComplete(BiTree bt)
{
if(bt==nullptr) return true;
BiTree Q[MaxSize];
int front=-1,rear=-1;
BiTree p = bt;
Q[++rear]=p;
while(front<rear){
p = Q[++front];
if(p->lchild!=nullptr)
Q[++rear]=p->lchild;
else
Q[++rear]=nullptr;
if(p->rchild!=nullptr)
Q[++rear]=p->rchild;
else
Q[++rear]=nullptr;
}
for(int i = 0;i<=rear;i++)
if(Q[i]==nullptr)
{
for(int j=i+1;j<=rear;j++)
if(Q[j]!=nullptr)
return false;
}
return true;
}
//方法一
int calculate(BiTree bt)
{
if(bt == nullptr)
return 0;
if(bt->rchild!=nullptr&&bt->lchild!=nullptr)
// 凡是有左右两个字节点的都会在这里+1
answer = calculate(bt->lchild)+calculate(bt->rchild)+1;//别忘了根结点如果有左右结点的话应该+1
else
answer =calculate(bt->lchild)+calculate(bt->rchild);
}
//方法二
int num =0;
int calculate(BiTree bt)
{
if(bt==nullptr)
return num;
if(bt->lchild!=nullptr&&bt->rchild!=nullptr)
num++;
calculate(bt->lchild);
calculate(bt->rchild);
}
BiTree swap(BiTree bt){
if(bt == nullptr) return bt;
/*
实际上这里的条件判断都没有用,如果根结点是nullptr的话直接从递归条件退出去了
交给退出条件就行,即使子节点有一个是空的也得换啊,所以也不用非得是两个都非空才能换
二叉树一个结点位置也是有影响的。
if(bt -> rchild!=nullptr&&bt->lchild!=nullptr)
BiTree temp;
temp = bt->rchild;
bt->rchild = bt->lchild;
bt->lchild = temp;
}
else if(bt->lchild!=nullptr)
transform(bt->lchild);
else if(bt->lchild!=nullptr)
transform(bt->rchild);
*/
swap(bt->lchild);
swap(bt->rchild);
BiTree temp;
temp = bt->lchild;
bt->lchild = bt->rchild;
bt->rchild = temp;
}
int i=1;
ElemType PreNode(BiTree bt,int k)
{
if(b==nullptr)
return '#';
if(i==k)
return b->data;
i++;
ch = PreNode(b->lchild,k);
if(ch!='#')
return ch;
ch = PreNode(b->rchild,k);
return ch;
}
//没有考虑bt为空的时候,但是这里说k大于1,所以应该不存在为空的情况
int find_k(Bitree bt,int& k)
{
if(k==1)
return bt->data;
else{
k--;
find_k(bt->lchild,k);
find_k(bt->rchild,k);
}
}
void delete_tree(BiTree bt){
if(bt == nullptr)
return;
delete_tree(bt->lchild);
delete_tree(bt->rchild);
BiTree p = bt;
delete p;
}
/*void condelete(BiTree bt,int x){
if(bt == nullptr) return;
condelete(bt->lchild,x);
if(bt->data == x)
delete_tree(bt);
condelete(bt->rchild,x);
}*/
//这里使用层次遍历更方便,因为一层出队的时候该层的所有子节点都进入队列,所以层次遍历便于找到某结点的父节点
//而且层次遍历从上层往下进行,不必进行回溯
void Search(BiTree bt,ElemType x){
BiTree Q[];
//当前二叉树不为空
if(bt){
//如果根结点即为x,则直接删除整棵树
if(bt->data == x)
{ delete_tree(bt);
return;}
//否则进行层次遍历的主体
InitQueue(Q);
EnQueue(Q,bt);
while(!IsEmpty(Q)){
DeQueue(Q,p);
//如果左子树不为空,则判断左子树的根结点是否值为x
if(p->lchild){
if(p->lchild->data==x){
delete_tree(p->lchild);
p->lchild = nullptr;//别忘了删除掉以该结点为根的子树后,将结点置空
}
else{
EnQueue(Q,p->lchild);//如果不是,入队继续处理
}
//如果右子树不为空,则判断右子树的根结点是否值是否为x
if(p->rchild){
if(p-rchild->data==x){
delete_tree(p->rchild);
p->rchild = nullptr;}
else{
EnQueue(Q,p->rchild);
}
}
}
}
(LLINK, INFO,RLINK)
,ROOT为指向该二叉树根结点的指针,p和q的最近公共祖先结点rtypedef struct{
Bitree t;
int tag;
}stack;
stack s[Maxsize],s1[Maxsize];
Bitree Ancestor(Bitree root,BitNode* p,BitNode* q){
int top = 0;
Bitree bt = root;
while(bt!=nullptr||top>0)//当不为空且当前栈不为空
{
while(bt!=nullptr&&bt!=p&&bt!=q){
while(bt!=nullptr){//沿左分枝向下遍历
s[++top].t = bt;
s[top].tag = 0;
bt = bt->lchild;
}
while(top!=0&&s[top].tag==1){
if(s[top].t == p)//堆栈复制
{
for(i =1;i<top;i++)
s1[i]=s[i];
top1 = top;
}
if(s[top].t == q)//找公共祖先
for(i = top;i>0;i--)
for(j =top1;j>0;j--)
if(s1[j].t == s[i].t)
return s[i].t;
}
top--;
}
//沿右分枝向下遍历
if(top!=0){
s[top].tag =1 ;
bt=s[top].t->rchild;
}
}
return nullptr;
}
⚠️之前想的是两个循环的嵌套是有顺序的,但是实际上忽略了一个问题就是,q的堆栈不是从p的堆栈继续(要是这样的话就不用求了,求到先遍历到的结点的父节点就是最近公共祖先了,但是这里先遍历到的p的堆栈还有可能退栈才能到q所以这里两层循环的顺序无所谓。
//self
int calculate_width(Bitree T){
if(T == nullptr) return 0;
if(T->rchild == nullptr && T->lchild == nullptr) return 0;
Bitree Q[Maxsize];
int front = -1,rear = -1;
int length = 0;
int last = rear;
Q[++rear] = T;
while(front!=rear){
Bitree q = Q[++front];
if(q->lchild!=nullptr) Q[++rear] = q->lchild;
if(q->rchild!=nullptr) Q[++rear] = q->rchild;
if(front == last){
int now = rear - last;
if(length < now) length = now;
}
}
return length;
}
//reference-another
//使用非环形队列,因为要把所有树的结点都保存在队列中,如果是环形队列可能会出现结点被覆盖的问题。
typedef struct{
BiTree data[Maxsize];//保存队列中的结点指针
int level[Maxsize];//保存data中相同下标结点的层次
int front,rear;
}Qu;//整个设置成一个数据结构为了进行遍历操作
int BiWidth(Bitree b){
Bitree p;
int k,max,i,n;
Qu.front = Qu.rear = -1;
//Qu.rear++;
Qu.data[++Qu.rear]=b;
Qu.level[Qu.rear]=1;//从树根开始为第一层,把每层的level进行设置
while(Qu.front <Qu.rear){//当队列不为空的时候循环操作
Qu.front++;
p = Qu.data[Qu.front];//取出当前结点,记录当前结点的level值,在下面儿子结点自然就是对当前值+1操作
k = Qu.level[Qu.front];
if( p->lchild!=nullptr){
//Qu.rear++;
Qu.data[++Qu.rear] = p->lchild;
Qu.level[Qu.rear] = k+1;
}
if( p->rchild!=nullptr){
//Qu.rear++;
Qu.data[++Qu.rear] = p->rchild;
Qu.level[Qu.rear] = k+1;
}
}
//所有的结点都不出队,通过一次层次遍历将每个结点的level记录下来
//就是一个数组找重复元素最多的个数
//然后遍历level数组,从第一层开始统计level相同的个数
max = 0; i = 0; k = 1;
//max始终记录最大值,i是数组遍历的下标控制,k代表当前统计的层数
while(i<=Qu.rear){
n = 0;//n记录每层的结点个数
while(i<Qu.rear&&Qu.level[i]==k){
n++;
i++;
}
k = Qu.level[i];//i加到当前不等k,则现在下标i对应的即是下一层的个数
if(n>max) max = n;
}
return max;
}
pre[l1...h1]
转换为后序序列post[l2...h2]
f(pre,l1,h1,post,l2,h2)=nullptr//l1>h1时,不做任何事
f(pre,l1,h1,post,l2,h2)=post[h2]=pre[l1];
//取中间位置,将左右子树互换,这个时候更新pre和post的头结点的时候要注意pre的头节点要路过,post的尾结点要路过
int half = (h1-l2)/2;
f(pre,l1+1,l1+half,post,l2,l2+half-1);
f(pre,l1+half+1,h1,post,l2+half,h2-1);
void PreToPost(Elemtype pre[],int l1,int h1,Elemtype post[],int l2,int h2){
if(h1<h1) return;
post[h2] = pre[l1];//key
int half = (h1-l2)/2;
PreToPost(pre,l1+1,l1+half,post,l2,l2+half-1);
PreToPost(pre,h1-half+1,h1,post,l2+half,h2-1);
}
lchild
和rchild
为nullptr
的结点,然后后面的结点直接使用rchild
域进行连接,最后一个结点的rchild
为nullptr
/*
void link_leaf(Bitree bt){
int front=-1,rear=-1;
Bitree tag = nullptr;
int flag = 0;
Bitree Queue[Maxsize];
Quere[++rear] = bt;
while(front!=rear){
Bitree p = Queue[++front];
if(p->lchild!=nullptr)
Queue[++rear] = p->lchild;
if(p->rchild!=nullptr)
Queue[++rear] = p->rchild;
if(p->lchild == p->rchild)
{ tag = p;break; flag = front;}
}
int i;
for(i = front;Queue[i]!=nullptr;i++)
{
Queue[i]->rchild = Queue[i+1];
}
Queue[i-1]->rchild = nullptr;
}
*/
LinkedList head,pre;
LinkedList InOrder(Bitree bt){
if(bt){
InOrder(bt->lchild);
if(bt->lchild==nullptr**bt->rchild==nullptr){
if(pre==nullptr){
head = bt;
pre = bt;
}else{
pre->rchild = bt;
pre = bt;
}
InOrder(bt->rchild);
pre->rchild = nullptr;
}
}
return head;
}
bool defer_similar(Bitree bt1, Bitree bt2){
if(bt1==nullptr&&bt2==nullptr) return true;
if((bt1->lchild == nullptr &&bt1->rchild==nullptr)&&(bt2->lchild==nullptr&&bt2->rchild==nullptr))
return true;
bool left = defer_similar(bt1->lchild,bt2->lchild);
bool right = defer_similar(bt1->rchild,bt2->rchild);
return (left&&right);
}
ThreadNode* InPostPre(ThreadTree t,ThreadNode* p){
ThreadTree q ;
if(p->rtag == 0)//右子女存在直接就是左子女
q = p->rchild;
else if(p->ltag == 0)//只有左子女存在,前驱是左子女
q = p->lchild;
else if(p->lchild == nullptr)//是中序遍历的第一个结点,没有前驱返回nullptr
q = nullptr;
else{
while(p->ltag == 1&& p->lchild!=nullptr)//顺着当前结点找其祖先,当ltag为1的时候当前结点是前驱
{ //一直找到一个有左孩子的结点,如果当前指向的是真正的左孩子结点,则返回该结点
p = p->lchild;
if(p->ltag == 0)
q = p->lchild;
else
q = nullptr;
}
}
return q;
}
typedef struct ThreadNode{
Elemtype data;
struct ThreadNode *lchild,*rchild;
int ltag,rtag;
}ThreadNode,*ThreadTree;
//中序遍历建立中序线索二叉树
void InThread(ThreadTree &p,ThreadTree &pre){
if(p!=nullptr)
InThread(p->lchild,pre);
if(p->lchild == nullptr)
{
p-lchild = pre;
p->ltag = 1;
}
if(p != nullptr&&pre->rchild == nullptr)
{
pre->rchild = p;
p->rtag =1 ;
}
pre = p;
InThread(p->rchild,pre);
}
void create_InThread(ThreadTree t){
ThreadTree pre = nullptr;
if(t!=nullptr){
InThread(t,pre);
pre->rchild = nullptr;
pre->rtag = 1;
}
}
叶结点
的带权路径长度之和。给定一棵二叉树T,采用二叉链表存储,结点结构:left weight right
其中叶结点的weight域保存该结点的非负权值。设root为指向T的根结点的指针,请设计求T的WPL的算法,要求:
(1)给出算法的基本设计思想
(2)给出二叉树结点的数据类型定义
//method one
//二叉树结点的数据类型
typedef struct Node{
int weight;//存储当前结点的权值
struct Node* lchild,* rchild;
}*Bitree,BiNode;
//使用层次遍历需要的队列类型
typedef struct {
Bitree Qu[Maxsize];
int level[Maxsize];//存储每个结点的层数,每个结点的路经长度=层数-1
int front=-1,rear = -1;
}Queue;
/*
使用二叉树的层次遍历,记录每个结点对应的路经长度值,直接使用Leaf结点的权重x对应的path长度
*/
int calculate_WPL(Bitree bt){
Queue queue;
int sum = 0;
//Bitree flag = nullptr;
if(bt==nullptr) return 0;
if(bt->rchild==nulltpr&&bt->lchild == nullptr) return bt->weight;
// k = 1;
queue.Qu[++queue.rear] = bt;
queue.level[queue.rear] = 1;
while(queue.front!=queue.rear){
Bitree p = queue.Qu[++front];
if(p-lchild != nullptr)
{
queue.Qu[++rear]=p->lchild;
queue.level[rear]=queue.level[front]+1;
}
if(p->rchild != nullptr)
{
queue.Qu[++rear]=p->rchild;
queue.level[rear]=queue.level[front]+1;
}
if(p->rchild==nullptr&&p->lchild==nullptr)
{
sum = sum + p->weight * (queue.level[front]-1);
}
}
return sum;
}
//method two 层次遍历的另一种写法
int wpl_levelorder(bitree root){
Bitree q[Maxsize];
int end1,end2;
end1= end2 = 0;
int wpl = 0,deep =0;
Bitree lastNode;
Bitree newlastNode;
lastNode = root;
newlastNode = nullptr;
q[end2++] = root;
while(end1!=end2){
Bitree t = q[end1++];
if(t->lchild==nullptr&&t->rchild==nullptr){
wpl+=deep*t->weight;
if(t->lchild!=nullptr){
q[end2++]=t->lchild;
newlastNode = t->lchild;
}
if(t->rchild!=nullptr)
{
q[end2++]=t->rchild;
newlastNode = t->rchild;
}
if(t==lastNode){
lastNode = newlastNode;
deep+=1;
}
}
return wpl;
}
//method three 先序遍历
int wpl(bitree root){
return wpl_PreOrder(root,0);
}
int wpl_preorder(bitree root,int deep){
static int wpl =0;
if(root->rchild==nullptr&&root->rchild==nullptr)
wpl+=deep*root->weight;
if(root->lchild!=nullptr)
wpl+=wpl_preorder(root->lchild,deep+1);
if(root->child!=nullptr)
wpl+=wpl_preorder(root->rchild,deep+1);
return wpl;
}
(a+b)*(c*(-d))和(a*b)+(-(c-d))
typedef struct node{
char data[10];
struct node *left,*right;
}BiTree;
思路:可以基于二叉树的中序遍历策略得到所需的表达式。表达式树中分支结点对应的子表达式的计算次序,由该分支结点所处的位置决定。为得到正确的中缀表达式,需要在生成遍历序列的同时,在适当位置添加必要的括号。显然,表达式的最外层(对应根结点)及操作数(对应叶结点)不需要添加括号。
除了根结点和叶结点之外,遍历到其它结点在遍历其左子树之前加上左括号,在遍历其右子树之后加上右括号
void BtreetoE(BTree *root){
BtreeToExp(root,1);//根的高度为1
}
void BtreeToExp(BTree *root,int deep){
if(root == nullptr) return;
else if(root->left == nullptr&&root->right==nullptr)
cout<<root->data<<endl;
else{//根结点通过deep=1来进行控制
if(deep>1) cout<<"(";
BtreetoExp(root->left,deep+1);
cout<<root->data;
BtreetoExp(root->right,deep+1);
if(deep>1) cout<<")";
}
}
tips:通过增加一个标识量来控制递归进行的某些条件,可能这个标识量没有特殊的意义但是在程序中起到控制数据流流动的功能,eg:上题的deep
19. 后序遍历
思路:因为后序递归遍历的二叉树顺序是先访问左子树,再访问右子树,最后访问根结点。当用堆栈来存储结点,必须分清返回根结点时,是从左子树返回的还是从右子树返回的。所以可以使用辅助指针r,其指向最近访问过的结点。也可以在结点中增加一个标志域,记录是否被访问。
void PostOrder(BiTree T){
InitStack(S);
p = T;
r = nullptr;
while(p||!IsEmpty(S)){
//p如果为空说明当前结点已经指向叶子节点的子节点了,但是该遍历还是要遍历
//而S栈为空则说明,当前的树遍历结束
if(p){//所以这里面才可以有关于P空或者不空的if-else
push(S,p);
p=p->lchild;
}
else{
GetTop(S,p);
if(p->rchild&&p->rchild!=r){
//先get到栈顶结点p,但是不知道栈顶结点是从左子树还是右子树得来的
//所以有p->rchild!=r,r中存储着刚访问的结点,如果等于r的话说明刚从右子树过来,可以访问了
//如果不是,将右结点压入堆栈
p = p->rchild;
push(S,p);
p = p->lchild;
}
else{
pop(S,p);
visit(p->data);
r=p;//但是r的改变是在访问完成一个结点之后的改变,因为根结点是最后一个
p = nullptr;
}
}
}
}
上面的思路利用:
当访问一个结点p时,栈中结点恰好是p结点的所有祖先结点,从栈底到栈顶结点再加上结点p就是一条从根结点到结点p的一条路径,求根结点到其他结点的路径和求两个结点的最近公共祖先都可以使用这个算法
typedef struct bitree{
int data;
struct bitree* lchild;
struct bitree* rchild;
}*Bitree;
typedef struct {
Bitree t;
int tag;
}stack;//使用tag表示返回到父节点时是从左子树返回还是从右子树返回
void search(Bitree bt,int x){
stack s[Maxsize];
int top =0;
while(bt!=nullptr||top>0)
{
while(bt!=nullptr&&bt->data!=x)
{
s[++top].t = bt;
//if(bt->lchild)实际上这里不需要判断同时也是直接赋空值给当前结点
s[top].tag = 0;
bt = bt->lchild;
}
while(bt->data==x){
for(int i=0;i<top+1;i++)
cout<<s[i].t->data;
}
while(top!=0&&s[top].tag==1)
top--;
//回溯的过程,在栈中的数据如果是之前已经从右子树返回的但是已经经过data==x的判断
//仍然在栈中出现的与当前要查找的结点路径无关,所以直接弹栈即可
if(top!=0){
s[top].tag=1;
bt = s[top].t->rchild;
}
}
}