基于HTML的高度平衡的二叉树的算法图示

本文通过HTML5画布展示如何使用AVLTree算法在LeetCode题目中构建平衡二叉树,包括插入过程中的旋转操作和可视化结果。博主分享了从数据预处理到网页动态展示的完整步骤,期待大佬实现在线演示平台。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言-------------------------------------------------------------------------------------------------------------------------

leetcode有人专门画了算法的图示,我用html的画布,画出建立一个平衡二叉树的过程,抛砖引玉。

基本思路:

1 因为网页端不好执行算法,所以先用leetcode或者visual studio,算好数据

2 在网页端根据数据,画出算法的过程

若有谁有钱,可以买一个服务器,前台输入一串数字,服务器返回计算的结果,然后在页面显示出来。

甚至可以专门建一个网站,用来图示各种算法。

坐等有钱又有闲的大佬,实现网站后来@我。

步骤--------------------------------------------------------------------------------------------------------------------------

1  1. 两数之和 - 力扣(LeetCode) (leetcode-cn.com)

     打开链接,复制粘贴下方的代码,并复制执行后的结果

//高度平衡的二叉树

//二叉树结点:数据域,指针域,平衡因子
struct Node
{
    Node(int n=0,Node * pl=nullptr,Node * pr=nullptr):data(n),l(pl),r(pr),bf(0){}
    int data;
    Node *l,*r;
    int bf;
};

//前向声明
class AVLTree;
ostream& operator<<(ostream& os,AVLTree& tree);

//高度平衡的二叉树类定义
class AVLTree
{
  friend ostream& operator<<(ostream& os,AVLTree& tree);  
  public:
  bool Insert(int n,int *xuanzhaun){return Insert(root,n,xuanzhaun);}
  //根据{7,6,5,4,3,2,1},建立高度平衡的二叉树
  //若是不平衡的二叉树,会建立出一颗单支树,搜索效率低下
  AVLTree(initializer_list<int> l)
  {
      root=nullptr;
      //0,1,2,3,4分别对应["不旋转","左旋(2,1)","先右后左(2,-1)","右旋(-2,-1)","先左后右(-2,1)"]
      int XuanZhuan=0;
      vector<int> xz;
      cout<<"var Trees=["<<endl;
      for(auto& n:l)
      {
          XuanZhuan=0;
          Insert(n,&XuanZhuan);
          xz.push_back(XuanZhuan);
          //打印出一棵树
          cout<<*this<<','<<endl;
      }
      cout<<']'<<endl;
      //打印出插入过程中,每一步是否发生旋转的数组
      cout<<"var XuanZhuan=[";
      for(auto& n:xz)
      {
          cout<<n<<',';
      }
      cout<<"]"<<endl;
  }
  private:
  //入参.bf=2
  void RotateL(Node *& ptr)
  {
    //指向原来不平衡的根节点
    Node * subL=ptr;
    //小弟上位
    ptr=subL->r;
    //两个X
    subL->r=ptr->l;
    ptr->l=subL;
    //1,2,0
    ptr->bf=subL->bf=0;
  }
  void RotateR(Node *& ptr)
  {
    //指向原来不平衡的根节点
    Node * subR=ptr;
    //小弟上位
    ptr=subR->l;
    //两个X
    subR->l=ptr->r;
    ptr->r=subR;
    //-1,-2,0
    ptr->bf=subR->bf=0;
  }
  
   void RotateLR(Node *& ptr)
  {
    //三个结点
    Node * subR=ptr,*subL=subR->l;
    ptr=subL->r;
    
    //先左旋,两个X
    subL->r=ptr->l;
    ptr->l=subL;


     //后右旋,两个X
     subR->l=ptr->r;
     ptr->r=subR;

    //插入新结点后,ptr->bf只可能是-1,1,如果是0不会调用该函数
    //h,h-1
    if(ptr->bf==-1)
    {
      subL->bf=0;
      subR->bf=1;
    }
    //h-1,h
    else  
    {
      subL->bf=-1;
      subR->bf=0;
    }
    ptr->bf=0;
  }

   void RotateRL(Node *& ptr)
  {
    //三个结点
    Node * subL=ptr,*subR=subL->r;
    ptr=subR->l;
    
    //先右旋,两个X
    subR->l=ptr->r;
    ptr->r=subR;


     //后左旋,两个X
     subL->r=ptr->l;
     ptr->l=subL;

    //插入新结点后,ptr->bf只可能是-1,1,如果是0不会调用该函数
    //h,h-1
    if(ptr->bf==-1)
    {
      subL->bf=0;
      subR->bf=1;
    }
    //h-1,h
    else  
    {
      subL->bf=-1;
      subR->bf=0;
    }
    ptr->bf=0;
  }
  Node* root;
  bool Insert(Node *& ptr,int n,int *xuanzhaun)
  {
     Node *pr=nullptr,*p=ptr,*q=nullptr;
     int d;
     stack<Node*> st;
     while(p)
     {
       if(p->data==n) return false;
       //pr是路径,也是父节点
       pr=p;
       st.push(pr);
       if(n<p->data) p=p->l;
       else p=p->r;
     }
     //p是叶节点的空子女域的拷贝(不是引用)
     p=new Node(n);
     if(pr==NULL) {ptr=p;return true;}
     if(n<pr->data) pr->l=p;
     else  pr->r=p;
     //-------------------------------------------
     //只有一个结点时,根结点bf=0
     //后续每次添加结点,需要更新父节点路径的bf
     //新添加的叶节点的bf是0,其无子女
     while(!st.empty())
     {
       pr=st.top();
       st.pop();
       if(p==pr->l) pr->bf--;
       else pr->bf++;
       if(pr->bf==0) break;//pr高度没变
       //pr高度变了,但还是平衡的,继续考察父节点
       else if(pr->bf==1 || pr->bf==-1)
       p=pr;
       else if(pr->bf==2)
       {
         //同号单旋
         if(p->bf==1)
         {
           RotateL(pr);
           *xuanzhaun=1;
         }
         else
         {
           RotateRL(pr);
            *xuanzhaun=2;
         }
         break;
       }
       else if(pr->bf==-2)
       {
         //同号单旋
         if(p->bf==-1)
         {
           RotateR(pr);
           *xuanzhaun=3;
         }
         else
         {
           RotateLR(pr);
           *xuanzhaun=4;
         }
         break;
       }
     }

     //while跳出的时机不同
     if(st.empty())
     ptr=pr;
     else
     {
       q=st.top();
       if(q->data>pr->data) q->l=pr;
       else q->r=pr;
     }
     return true;
  }
};

//子女域存放子女在输出数组中的索引
ostream& operator<<(ostream& os,AVLTree& tree)
{
    int index=1;
    cout<<'[';
    //层次遍历,只有非空的指针可以入栈
    queue<Node*> q;
    Node* temp=nullptr;
    if(tree.root)
    q.push(tree.root);
    while(!q.empty())
    {
      //temp一定非空  
      temp=q.front();
      cout<<"{data:"<<temp->data<<','<<"bf:"<<temp->bf<<",l:";
      if(temp->l)
      {
        cout<<index++<<",r:";
        q.push(temp->l);
      }
      else
      {
        cout<<-1<<",r:"; 
      }
      if(temp->r)
      {
        cout<<index++<<"},";
        q.push(temp->r);
      }
      else
      {
        cout<<-1<<"},"; 
      }
      //出一个,进一大堆
      q.pop();
    }
    cout<<']';
    return os;
}

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
      //算法的输入------------------------------------------------------------------
      initializer_list<int> l{7,6,5,4,3,2,1};

      //图示所需的数据---------------------------------------------------------------
      //var RawInput=[7,6,5,4,3,2,1] 
      cout<<"var RawInput=[";
      for(auto & n:l)
      cout<<n<<',';
      cout<<']'<<endl;

      //var Trees ,var XuanZhuan
      AVLTree MyAVLTree(l);


      vector<int> laji;
      return laji;
    }
};

2 新建AVLTree.html,复制粘贴下方的代码,并把leetcode的代码执行结果替换到相应的位置

<!DOCTYPE html>
<html>
<head> 
<meta charset="utf-8"> 
<title>RBTree</title> 
<style>
    .InputWihth
    {
        width:50px; 
    }
</style> 

<script>

//图示输入:来自leetcode或者VS
var RawInput=[7,6,5,4,3,2,1,]
var Trees=[
[{data:7,bf:0,l:-1,r:-1},],
[{data:7,bf:-1,l:1,r:-1},{data:6,bf:0,l:-1,r:-1},],
[{data:6,bf:0,l:1,r:2},{data:5,bf:0,l:-1,r:-1},{data:7,bf:0,l:-1,r:-1},],
[{data:6,bf:-1,l:1,r:2},{data:5,bf:-1,l:3,r:-1},{data:7,bf:0,l:-1,r:-1},{data:4,bf:0,l:-1,r:-1},],
[{data:6,bf:-1,l:1,r:2},{data:4,bf:0,l:3,r:4},{data:7,bf:0,l:-1,r:-1},{data:3,bf:0,l:-1,r:-1},{data:5,bf:0,l:-1,r:-1},],
[{data:4,bf:0,l:1,r:2},{data:3,bf:-1,l:3,r:-1},{data:6,bf:0,l:4,r:5},{data:2,bf:0,l:-1,r:-1},{data:5,bf:0,l:-1,r:-1},{data:7,bf:0,l:-1,r:-1},],
[{data:4,bf:0,l:1,r:2},{data:2,bf:0,l:3,r:4},{data:6,bf:0,l:5,r:6},{data:1,bf:0,l:-1,r:-1},{data:3,bf:0,l:-1,r:-1},{data:5,bf:0,l:-1,r:-1},{data:7,bf:0,l:-1,r:-1},],
]
var XuanZhuan=[0,0,3,0,3,3,3,]


//手动变量-------------------------------------    
var XCoordinate=300;
var YCoordinate=100;
var NodeRadius=20

//初始化变量-------------------------------------
var DataPosAdjust=NodeRadius/4
var XOffSet=50
var YOffSet=50
var CurNewNode=1
var TreeNode=Trees[CurNewNode-1]
var NewNode=RawInput[CurNewNode]
var TreeNode2=Trees[CurNewNode]

 //流程变量-------------------------------------  
 var i=0;
 var j=0;
 var k=0;
 var  NodeStr="";
 var MinNewNode=1
 var MaxNewNode=RawInput.length-1
 var XuanZhuanText=["不旋转","左旋(2,1)","先右后左(2,-1)","右旋(-2,-1)","先左后右(-2,1)"]

 function Previous() {
    if(CurNewNode>MinNewNode)
    {
        CurNewNode--;
        TreeNode=Trees[CurNewNode-1]
        TreeNode2=Trees[CurNewNode]
        ctx.fillStyle="azure";
        ctx.fillRect(0,0,1200,600);
        ctx.fillStyle="black";
        PreOrder(TreeNode,0,XCoordinate,YCoordinate)
        PreOrder(TreeNode2,0,XCoordinate+600,YCoordinate)
        NewNode=RawInput[CurNewNode]
        document.getElementById("NewNode").innerHTML="NewNode: "+NewNode+"   "+XuanZhuanText[XuanZhuan[CurNewNode]];  
    }
    
}

function Next() {
    if(CurNewNode<MaxNewNode)
    {
        CurNewNode++;
        TreeNode=Trees[CurNewNode-1]
        TreeNode2=Trees[CurNewNode]
        ctx.fillStyle="azure";
        ctx.fillRect(0,0,1200,600);
        ctx.fillStyle="black";
        PreOrder(TreeNode,0,XCoordinate,YCoordinate)
        PreOrder(TreeNode2,0,XCoordinate+600,YCoordinate)
        NewNode=RawInput[CurNewNode]
        document.getElementById("NewNode").innerHTML="NewNode: "+NewNode+"   "+XuanZhuanText[XuanZhuan[CurNewNode]];  
    }
    
}

</script>
</head>


<body>


<canvas id="myCanvas"  width="1200" height="600" style="border:1px solid #c3c3c3;background-color: azure;">
您的浏览器不支持 HTML5 canvas 标签。
</canvas>
<p align="center">
    <span><button type="button" onclick="Next()">Next</button></span>
    <span><button type="button" onclick="Previous()">Previous</button></span>
    <span  id="NewNode">垃圾</span>
</p>



<script>

//依次画根结点,左子女,右子女
//前序遍历树并
function PreOrder(tree,index,x,y)
{
    //结点
    ctx.beginPath();
    ctx.arc(x,y,NodeRadius,0,2*Math.PI);
    ctx.stroke();
    //数据域
    ctx.font="10px Arial";
    NodeStr=""
    NodeStr+=tree[index].data
    NodeStr+=':'
    NodeStr+=tree[index].bf
    ctx.fillText(NodeStr,x-DataPosAdjust-5,y+DataPosAdjust);
   
   if(index==0)
   {
    //左子树
    if(tree[index].l!=-1)
    {
     //画指针
     ctx.moveTo(x-NodeRadius/1.414,y+NodeRadius/1.414);
     ctx.lineTo(x-XOffSet-100+NodeRadius/1.414,y+YOffSet-NodeRadius/1.414);
     ctx.stroke();   
     PreOrder(tree,tree[index].l,x-XOffSet-100,y+YOffSet); 
    }
    //右子树
    if(tree[index].r!=-1)
    {
     //画指针
     ctx.moveTo(x+NodeRadius/1.414,y+NodeRadius/1.414);
     ctx.lineTo(x+XOffSet+100-NodeRadius/1.414,y+YOffSet-NodeRadius/1.414);
     ctx.stroke();  
     PreOrder(tree,tree[index].r,x+XOffSet+100,y+YOffSet); 
    }
   }
   else
   {
    //左子树
    if(tree[index].l!=-1)
    {
     //画指针
     ctx.moveTo(x-NodeRadius/1.414,y+NodeRadius/1.414);
     ctx.lineTo(x-XOffSet+NodeRadius/1.414,y+YOffSet-NodeRadius/1.414);
     ctx.stroke(); 
     PreOrder(tree,tree[index].l,x-XOffSet,y+YOffSet); 
    }
    //右子树
    if(tree[index].r!=-1)
    {
     //画指针
     ctx.moveTo(x+NodeRadius/1.414,y+NodeRadius/1.414);
     ctx.lineTo(x+XOffSet-NodeRadius/1.414,y+YOffSet-NodeRadius/1.414);
     ctx.stroke();  
     PreOrder(tree,tree[index].r,x+XOffSet,y+YOffSet); 
    }
   }


}
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
PreOrder(TreeNode,0,XCoordinate,YCoordinate)
PreOrder(TreeNode2,0,XCoordinate+600,YCoordinate)


document.getElementById("NewNode").innerHTML="NewNode: "+NewNode+"   "+XuanZhuanText[XuanZhuan[CurNewNode]];  


</script>

</body>
</html>

3 在浏览器打开AVLTree.html

输入是{7,6,5,4,3,2,1}

插入5时,本来是单支树,7(平衡因子-2)->6(-1)->5

(-2,-1)满足书中,右旋的条件

右旋之后树平衡了

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值