樹形結構左右值存儲,移動節點詳解


最近做一個程序,用到樹形結構,並且要存儲到數據庫中。於是研究了一下樹形結構的左右值存儲。

左右值雖然取父祖節點和子孫節點,查找節點路徑非常方便,但要找某節點的父節點,子節點和兄弟節點就比較困難,所以還要需要一個層級維度方便確定父子和兄弟節點,也就是樹形結構中所說的樹的深度。

下面列舉一些普通的左右值算法,網上有大量的資料,就不細說了。

以下資料來自網上,錯誤的地方我已糾正

一、計算某節點的子孫節點數。

子孫節點數量 = (節點右值-節點左值-1)/2

二、查找某節點的所有子孫節點。

select * from tree where L > 節點左值 and R < 節點右值 order by L asc;

三、查找某節點的所有父祖節點。

select * from tree where L < 節點左值 and R > 節點右值 order by L desc;

四、某節點所處層級。

select count(*) from tree where L <= 節點左值 and R >= 節點右值

五、增加節點。需要為要增加的節點騰出左右值的空間。然后將新節點插入數據庫。在哪里增加?這就需要參照物,有下面四種情況。

1.在A節點下增加子節點B,B作為第一個子節點。

#更新受影響節點

update tree set L = L + 2 where L > A節點左值;

update tree set R = R + 2 where R > A節點左值;

#插入新增的節點

insert into tree (name, L, R) values('B', A節點左值+1, A節點左值+2);

2.在A節點下增加子節點B,B作為最后一個子節點。

#更新受影響節點

update tree set L = L + 2 where L >= A節點右值;

update tree set R = R + 2 where R >= A節點右值;

#插入新增的節點

insert into tree (name, L, R) values('B', A節點右值, A節點右值+1);

3.在A節點后面增加節點B, B作為A的兄弟節點。

#更新受影響節點

update tree set L = L + 2 where L > A節點右值;

update tree set R = R + 2 where R > A節點右值;

#插入新增的節點

insert into tree (name, L, R) values('B', A節點右值+1, A節點右值+2);

4.在A節點前面增加節點B,B作為A的兄弟節點。

#更新受影響節點

update tree set L = L + 2 where L >= A節點左值;

update tree set R = R + 2 where R >= A節點左值;

#插入新增的節點

insert into tree (name, L, R) values('B', A節點左值, A節點右值);

一個節點有左右值,兩個數。所以上面的算法是添加1個節點要加2,依次類推2個節點是2*2=4,3個 3*2 =6。

六、刪除A節點。先要計算出該節點及其所有子節點所占的左右值空間,作為常量,然后用這個常量更新其它節點的左右值。

常量 = A節點右值 – A節點左值 +1;

還有一個算法A所有子孫節點數量(含A節點) * 2

#刪除A節點及其子孫節點

delete from tree where L >= A節點左值 and R <= A節點右值;

#更新受影響的節點 A節點后的所有節點均受影響

update tree set R = R – 常量 where R > A節點右值;

update tree set L= L - 常量 where L > A節點右值;

七、移動節點。 這部分是我利用多張下面的圖形找出的規律,錯誤之處請高人指正。

重頭戲來了,這個網上講得大多含糊不清。移動節點簡單點就是先刪,后加。當然這個刪不是真刪,只是打個標記,表示刪除。但這樣有個弊端就是受影響的節點太多,數據量大時影響效率。經過幾天的試驗,找出了一個比較不錯的移動節點的算法。

第一步,先確定移動的方向,是左移還是右移。左移,節點左右值會變小; 右移,節點的左右值會變大。

第二步,根據移動的方向和兄弟節點的排序,計算出節點移動后的左右值。

第三步,根據移動后的左右值,計算出受影響節點的左右值范圍。

第四步,根據范圍更新受影響節點的左右值,常量為A節點右值 – A節點左值 + 1;。

第五步,計算節點及期子孫節點的偏移量節點原左右值 - 移動后的左右值。

第六步,根據節點的偏移量更新其子孫節點的左右值。

具體看代碼,代碼有詳細的注釋,此代碼經過初步測試未發現有問題

         /// <summary>

        /// 移動節點  有子孫節點的,會連子孫節點一起移動

        /// </summary>

        /// <param name="srcid">要移動的節點ID</param>

        /// <param name="targetid">移動后新的父節點</param>

        /// <param name="pos">兄弟節點位置 -1 最后 0 1最前 具體數字為順序號</param>

        public static void move(int moveid,int targetpid, int FamilyID,int pos=-1)

        {

            Point srcp = getLRval(moveid, FamilyID);           //取移動節點的原左右值

            Point targetp = getLRval(targetpid, FamilyID);     //取目標節點的左右值   移動節點新的父節點  

 

            if (targetp.X >= srcp.X && targetp.Y <= srcp.Y)

                throw (new Exception("不能移動到其本身節點或其子節點!"));

 

            int otheroffse = srcp.Y - srcp.X + 1;//其他受到影響節點要加或減的值 偏移值 要移動的節點數目 *2  右移減掉 左移加上

 

            bool IsLeft = (srcp.X - getLocation(FamilyID, targetpid, pos))>0;//判斷是左移還是右移 右移為負,左移為正 (移動前-移動后)不會出現等於0的情況

           

            int srcLevel = (int)dbop.GetSingle($"select nLevel from Relation where MID = {moveid} and HID = {FamilyID}"); //移動節點原層級

            int targetLevel = (int)dbop.GetSingle($"select nLevel from Relation where MID = {targetpid} and HID = {FamilyID}") + 1; //移動后層級

            int loffse = srcLevel - targetLevel;//層級差 偏移量

 

            int moffse = 0; //移動節點偏移量。右移為負,左移為正 (移動前-移動后)

 

            //獲取移動節點所有子孫節點的id,方便后面更新(不包含自身)

            int[] srcchildren =  getchildrenIDs(moveid,FamilyID);

            //獲取其他受影響節點 左移 原父左值變 新父右值變, 右移 原父右值變,新父左值變  

           

            List<string> sqlList = new List<string>();

            if (IsLeft)//左移 受影響的數據值范圍為 大於等於移動節點的新左值 ,小於原左值

            {

                Point newPoint = getNewPoint(targetpid, pos, otheroffse, FamilyID); //計算移動節點新位置的左右值

                moffse = srcp.X - newPoint.X; //計算移動節點及其子節點的偏移量

                //更新受影響的節點 右移減 左移加

                sqlList.Add($"update Relation set Nleft = Nleft + {otheroffse} where Nleft >= {newPoint.X} and Nleft < {srcp.X} and HID = {FamilyID} ");

                sqlList.Add($"update Relation set Nright = Nright + {otheroffse} where Nright >= {newPoint.X} and Nright < {srcp.X} and HID = {FamilyID}");

            }

            else //右移 受影響的數據值范圍為 大於移動節點的原右值 ,小於等於新右值

            {

                Point newPoint = getNewPoint(targetpid, pos, otheroffse, FamilyID,false); //計算移動節點新位置的左右值

                moffse = srcp.X - newPoint.X; //計算移動節點及其子節點的偏移量

                //更新受影響的節點 右移減 左移加

                sqlList.Add($"update Relation set Nleft = Nleft - {otheroffse} where Nleft > {srcp.Y} and Nleft <= {newPoint.Y} and HID = {FamilyID}");

                sqlList.Add($"update Relation set Nright = Nright - {otheroffse} where Nright > {srcp.Y} and Nright <= {newPoint.Y} and HID = {FamilyID}");

            }

            //更新移動節點及其子孫節點

            //左右值 層級

            string ids = String.Join(",", srcchildren) + "," + moveid;

            ids = ids.Trim(',');

            sqlList.Add($"update Relation set Nleft = Nleft - {moffse}, Nright = Nright - {moffse},nLevel = nLevel - {loffse} where MID IN ({ids}) and HID = {FamilyID}");

            //父ID

            sqlList.Add($"update Relation set PID = {targetpid} where MID = {moveid} and HID = {FamilyID}");

            dbop.ExecuteSqlTran(sqlList); //事務更新

 

        }

 

左移

移動前

圖片
 
移動后 
圖片
左移受影響的節點范圍為大於等於節點移動后的左值13,小於未移動的左值35
右移
 
移動前  
圖片
移動后
圖片
右移受影響的節點范圍為大於節點未移動的右值28,小於等於移動后的右值34 
上下移根據偏左還是偏移相當於是左右移。
上移
移動前
圖片
移動后
圖片
下移
移動前
圖片
移動后
圖片


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM