演算法
資料結構
二叉搜尋樹(binary search tree)是經過一定地組織形成的有特定結構特徵的二叉樹,支援各種動態集合(dynamic set)操作(如insert、delete、maximum、minimum等等)。這些操作的時間複雜度與樹的高度成正比。一棵有n個節點的完全二叉樹(complete binary tree)的高度不超過lgn。因此,對於完全二叉樹的這些操作的最壞時間複雜度(worst-case time)為o(lgn)。但極端病態的二叉樹,形狀就與鍊錶是一樣的,那麼,各項操作的最壞時間複雜度將變為o(n)。
由隨機輸入資料構建的二叉搜尋樹的期望高度是o(lgn),也有一些經過更加複雜的構造規則而得到的二叉查詢樹(如紅黑樹、b樹等),對於一般的輸入都可以保持o(lgn)的期望高度。
二叉搜尋樹的結構特徵是:對於乙個節點x,它的鍵值為key[x],那麼x的左子樹中的任何乙個節點的鍵值,都不會大於key[x],x的右子樹中的任何乙個節點的鍵值都不會小於key[x]。
用c**進行實現乙個二叉搜尋樹,樹的節點只儲存int型的鍵值,並實現動態集合的基本操作,作為練習應該是ok了,呵呵。
先定義一些基本的結構,像樹的節點、對節點的基本操作,用於取節點的左右子節點和父節點、以及乙個樹「物件」:
cpp**
// 樹節點
typedef
struct
tagbstreenode bstreenode, *pbstreenode;
#define getleft(pnode) (pnode->left) // 左子節點
#define getright(pnode) (pnode->right) // 右子節點
#define getparent(pnode) (pnode->parent) // 父節點
// 二叉查詢樹「物件」
typedef
struct
tagbstree bstree, *pbstree;
二叉搜尋樹的節點插入操作
將乙個節點插入二叉搜尋樹,且要保持二叉搜尋樹的結構特徵,關鍵是要找到合適插入的位置。
所以,插入操作時,先進行位置地查詢,方法是從根開始,比較要插入的節點的鍵值與樹中的每乙個節點的鍵值,如果大於該節點,則下乙個比較的位置移到該節點的右子節點。否則,下乙個比較的位置移到該節點的左子節點上。這樣一直比較到樹的最「底下」的節點x(葉節點)。然後視鍵值,把需要插入的節點設定為 x的左子點,或是右子節點。**如下:
cpp**
void
tree_insert(pbstree ptree, pbstreenode pnode)
pnode->parent = pparent;
if(null == pparent)
else
else
} }
最大值與最小值。
對於二叉搜尋樹,很容易找到最大值和最小值。只需要從根節點開始,不斷地向左子節點移到,到葉子,就是最小值。從根節點開始,不斷地向右子節點移到,到葉子,就是最大值。
cpp**
pbstreenode tree_max(pbstreenode proot)
return
proot;
} pbstreenode tree_min(pbstreenode proot)
return
proot;
}
直接前趨
節點x的直接前趨節點y,就在樹中,鍵值比x小,但又最接近x的節點。
如果找到直接前趨節點呢?
1.首先,如果節點x有左子節點,那麼,x的直接前趨節點y一定是以x的左子節點為根的子樹中的最大鍵值節點。
證明,如下圖,圖中從x節點向上的路徑可能出現的兩種情況,我們稱1是向左,2是向右。
設x是黑色節點。從x向樹的上方攀登,只要是沿著右方向到達的節點(如果圖中2),都是鍵值比x大的節點(根據二叉查詢樹的性質,左子樹中的任何節點都小於根嘛),所以這些節點不可能是x的前趨(因為前趨節點的鍵值是小於x的)。如果沿著左方向到達的節點(如圖中1),那這個節點的鍵值確實比x的鍵值小,但是,它也絕不可能是x的直接前趨,因為x以及x的子節點,都是這個節點(z)的右子樹中的節點,所以z的鍵值一定小於x的左子樹中的任何乙個節點的鍵值,那麼它就失去作為x直接前趨的資格了(因為直接前趨是應該是鍵值最接近x的嘛)。對稱地,x是父節點的右節點時,情況也是一樣的。所以可以得出結論,如果節點x有左子節點,那麼x的直接前趨節點y一定是以x的左子節點為根的子樹中的最大鍵值節點。
2.如果x沒有左子樹呢?那麼,從上面的分析可知,x的直接前趨就一定是從x向上的路徑中,第一次「左」轉時的節點z。如果向上直到根節點的路徑都是右方向的,那麼這個x節點就沒有直接前趨節點了(也就是x是這二叉查詢樹最小的節點)。
直接後繼
直接後繼就只給出結論了:如果節點x有右子節點,那麼,x的直接前趨節點y一定是以x的左子節點為根的子樹中的最小鍵值節點。如果沒有右子節點,那麼x的直接後繼節點就是從x向上的路徑中第一次右轉時的節點。
證明與直接前趨是對稱的。
cpp**
pbstreenode tree_successor(pbstreenode proot)
pbstreenode pparent = getparent(proot);
while
((null != pparent) && (proot == getright(pparent)))
return
pparent;
} pbstreenode tree_predecessor(pbstreenode proot)
pbstreenode pparent = getparent(proot);
while
((null != pparent) && (proot == getleft(pparent)))
return
pparent;
}
查詢
其實二叉查詢樹的查詢,應該很容易理解了,就是讓x指向根,比較x的鍵值是否與要找的鍵值相等,是,那麼x就要找的節點,如果不是,那麼x就根據值的大小向左或向右移動,以此不斷地向下,直到樹的末尾。
cpp**
pbstreenode tree_search(pbstreenode proot,
intval_key)
return
proot;
}
刪除:
刪除乙個節點,最重要的也是想辦法在刪除動作後,仍然保持二叉查詢樹的特徵。
當然,我們會想用最容易的方法來保持它(複雜的話,就成有名有姓的樹了,像紅黑樹,在保證這二叉查基本特徵時,還維持了一些其它的特性,那就複雜鳥)。分三種情況:
1.如果被刪除的節點沒有子樹,好說,直接刪除就可以了,對二叉搜尋樹的基本特徵沒有任何影響。
2.如果被刪除的節點,只有乙個子樹呢?也好說,把那唯一的子樹掛到需要刪除節點的父節點上相應的子樹位置,然後刪除掉需要刪除的節點。也不會對二叉樹的基本特徵產生不良影響。
3.如果被刪除節點有兩個子樹呢?那就不好辦了,關鍵是要保住樹的基本性質不動搖,呵呵。怎麼辦呢?
我想,先化為已經解決的問題,那不就好辦了嗎?hoho,1和2是已經解決的,那麼化為1或2來解決嘛。設要刪除的子節點是x,那麼我們隨意選取乙個子樹,從底部開始,把這子樹上所有的節點,乙個乙個從樹上「摘下來」,存著。然後x就成為乙個只有一顆子樹的節點,然後就適用第2種情況啦。刪除完x後,再把存著的那些節點重新插入到樹上,不就得了。呵呵,笨辦法,但我確實這麼想過。
再想想其它辦法。要想不影響樹的特性,那麼最簡單的想法是如果能只動x以及x以下的節點,那麼樹的其它部分肯定是ok的。那只要處理x以及x的子樹就行了。想想,如果能在子樹中找乙個節點來替代x的位置,並保證新的子樹也是滿足二叉查詢樹要求的,這樣改動量可能就比較小了。那麼找哪個節點來代替它呢?當然是鍵值最接近x的,這樣二叉樹的特徵就比較容易保持嘛。鍵值最接近x的,上面已經說過了,就是直接前趨和直接後繼。正好,對於有兩個子樹的x來說,它的直接前趨和直接後繼都是在它的子樹中的,分別是左子樹的最大值、右子樹的最小值。而且,從子樹中取下這兩個節點(取下來幹嘛?代替需要刪除的x節點唄),也是比較容易的,因為「最大」「最小」值節點,最多擁有不超過乙個子節點(不然它絕對不夠格做最大或最小)。而沒有子節點和只有乙個子節點的節點刪除,是我們已經會啦~~~。好,那麼就取前趨或後繼就來代替需要刪除的節點,問題就解決了。
cpp**
pbstreenode tree_delete(pbstree ptree, pbstreenode pdel)
else
pbstreenode pchildofdel;
pchildofdel = (getleft(preldel)!= null ? getleft(preldel) : getright(preldel));
if(pchildofdel != null)
if(null == getparent(preldel))
else
else
} if(preldel != pdel)
return
preldel;
}
演算法導論 二叉搜尋樹
搜尋樹資料結構支援許多動態集合操作,包括search minimum maximum predecessor successor insert和delete等。因此,我們使用一棵搜尋樹既可以作為乙個字典又可以作為乙個優先佇列。二叉搜尋樹上的基本操作所花費的時間與這棵樹的高度成正比。對於乙個有n個結點...
演算法導論二叉搜尋樹
兩天寫完的,沒有認認真真的測試,可能有些bug,自己測試的資料能過,但是我的初始化有些問題,不知道該怎麼表示 二叉搜尋樹 include include using namespace std struct node void inorder search node root1 node tree ...
演算法導論筆記 12二叉搜尋樹
1 概念 二叉搜尋樹也叫二叉排序樹,它支援的操作有 search,minimum,maximum,predecessor,successor,insert,delete。所以,一顆二叉搜尋樹既可以作為乙個字典,又可以作為乙個優先佇列。二叉搜尋樹的基本操作時間與這棵樹的高度成正比。二叉搜尋樹的高度可以...