最近做一個程序,用到樹形結構,並且要存儲到數據庫中。於是研究了一下樹形結構的左右值存儲。
左右值雖然取父祖節點和子孫節點,查找節點路徑非常方便,但要找某節點的父節點,子節點和兄弟節點就比較困難,所以還要需要一個層級維度方便確定父子和兄弟節點,也就是樹形結構中所說的樹的深度。
下面列舉一些普通的左右值算法,網上有大量的資料,就不細說了。
以下資料來自網上,錯誤的地方我已糾正
一、計算某節點的子孫節點數。
子孫節點數量 = (節點右值-節點左值-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
上移

移動后


移動后
