数据结构-----AVL树的插入删除操作
對于AVL的插入和刪除,主要利用的就是上篇文章所述的四種旋轉操作,根據插入后不同的結構選用不同的方式復原平衡。
再次聲明一下,http://www.cnblogs.com/QG-whz/p/5167238.html這篇文章講解的比較好,我也是從鏈接處的大神那學習的,這里只用來復習。
首先對于插入操作,有以下幾個步驟:
步驟1:根據二叉樹的性質:大的向右找,小的向左找。逐個比較要插入的值和節點的值的大小關系,找到應該插入的位置。
步驟2:在插入的位置新申請一個節點,并返回該節點。
步驟3:判斷是否出現不平衡的情況,通過旋轉操作恢復平衡。
其次明確插入函數的實現方式:利用遞歸,假設當前結點為pnode,要插入的值為key。
步驟1:比較權值
? ? ? ? 如果key > pnode->key。那么執行insert(pnode->rightChild);
? ? ? ? 如果key < pnode->key。那么執行insert(pnode->leftChild);
步驟2:處理不平衡
? ? ? ? 如果在右子樹中插入,則可能會需要左旋操作或者先右旋后左旋操作。
? ? ? ? 如果在左子樹種插入,則可能會需要右旋操作或者先左旋后右旋操作。
然后應該明確插入操作的參數和其返回值:
既然使用了遞歸,很明顯參數應該是當前節點pnode和要插入的值key。理解為在以節點pnode為根節點的子樹中插入新節點,該節點的權值為key。
返回值是當前子樹的根節點。
所以插入操作的大體結構應該長這個樣子:
template<class T> AVLTreeNode<T>* AVLTree<T>::insert(AVLTreeNode<T>* &pnode, const T& key) {if(pnode == NULL){pnode = new AVLTreeNode<T>(key);}else{if(key > pnode->key){pnode->rightChild = insert(pnode->rightChild, key);if(height(pnode->rightChild) - height(pnode->leftChild) == 2){if(key > pnode->rightChild->key)pnode = leftRotation(pnode);else if(key < pnode->rightChild->key)pnode = rightLeftRotation(pnode);}}else if(key < pnode->key){pnode->leftChild = insert(pnode->leftChild, key);if(height(pnode->leftChild) - height(pnode->rightChild) == 2){if(key < pnode->leftChild->key)pnode = rightRotation(pnode);else if(key > pnode->leftChild->key)pnode = leftRightRotation(pnode);}}}return pnode; }
接下來是刪除操作,刪除操作和插入都是利用了遞歸的思想。
首先思路如下:
步驟1:根據二叉樹的定義,找到要刪除的節點。該節點的key值和參數key相等。
步驟2:比較待刪除的節點的左右子樹的高度。
? ? ? ? 如果左子樹的高度大,則將左子樹中key值最大的節點移動到待刪除的節點處。
? ? ? ? 如果右子樹的高度大,則將右子樹種key值最小的節點移動到待刪除的節點處。
其次明確刪除操作的實現方式:利用遞歸,假設當前結點為pnode,要刪除的節點權值應為key。
步驟1:比較權值。
? ? ? ? 如果key > pnode->key,那么執行remove(pnode->rightChild, key);
? ? ? ? 如果key < pnode->key,那么執行remove(pnode->leftChild, key);
? ? ? ? 如果key == pnode->key,那么刪除該節點。
刪除操作
步驟1:
? ? ? ? 如果height(pnode->leftChild) > height(pnode->rightChild),那么尋找其左子樹中key值最大的那個節點plnode。
? ? ? ? 如果height(pnode->leftChild) < height(pnode->rightChild),那么尋找其右子樹中key值最小的那個節點prnode。
步驟2:
? ? ? ? 若在左子樹中查找,則令pnode->key = plnode->key,隨后利用遞歸在pnode的左子樹中刪除plnode節點,即remove(pnode->leftChild, plnode->key)。
? ? ? ? 若在右子樹中查找,則令pnode->key = prnode->key,隨后利用遞歸在pnode的右子樹中刪除prnode節點,即remove(pnode->rightChild, prnode->key)。
然后應該明確刪除操作的參數和其返回值。
參數應為當前結點pnode和要刪除的節點的值key。理解為在以節點pnode為根節點的子樹中刪除權值為key的節點。
返回值為當前子樹的根節點pnode。
所以刪除操作大體上長這個樣子:
template<class T> AVLTreeNode<T>* AVLTree<T>::remove(AVLTreeNode<T>* &pnode, const T& key) {if(pnode != NULL){if(pnode->key == key){if(pnode->leftChild != NULL && pnode->rightChild != NULL){ if(height(pnode->leftChild) > height(pnode->rightChild)){AVLTreeNode<T>* plnode = maximun(pnode->leftChild);pnode->key = plnode->key;pnode->leftChild = remove(pnode->leftChild, plnode->key);}else{AVLTreeNode<T>* prnode = minimun(pnode->rightChild);pnode->key = prnode->key;pnode->rightChild = remove(pnode->rightChild, prnode->key);}}else{AVLTreeNode<T>* temp = pnode;if(pnode->leftChild != NULL)pnode = pnode->leftChild;else if(pnode->rightChild != NULL)pnode = pnode->rightChild;delete temp;return pnode;}}else if(key > pnode->key){pnode->rightChild = remove(pnode->rightChild, key);if(height(pnode->leftChild) - height(pnode->rightChild) == 2){if(height(pnode->leftChild->leftChild) > height(pnode->leftChild->rightChild))pnode = rightRotation(pnode);elsepnode = leftRightRotation(pnode);}}else{pnode->leftChild = remove(pnode->leftChild, key);if(height(pnode->rightChild) - height(pnode->leftChild) == 2){if(height(pnode->rightChild->rightChild) > height(pnode->rightChild->leftChild))pnode = leftRotation(pnode);elsepnode = rightLeftRotation(pnode);}}return pnode;}return NULL; }在沒有找到要刪除的節點的時候,需要利用遞歸再次調用刪除函數。
又因為以待刪除的那個節點為根節點的子樹中,替換掉的節點是高度大的一方,所以這個子樹的平衡不會被破壞。
但是因為確實少了一個節點,高度很有可能會改變,所以其父節點的平衡很有可能就被破壞了,需要重新改變樹的結構。
不過在判斷需要什么樣的旋轉操作時與插入操作有點不同,它是比較當前節點的左節點的左節點和當前節點的左節點的右節點的高度差,或者是當前節點的右節點的左節點和當前節點的右節點的右節點的高度差。具體的實現可以參考上述代碼。
下面說一下為什么插入操作和刪除操作利用的是遞歸而不是迭代。
考慮一下,在插入或刪除操作后需要進行左旋或者右旋來重塑樹的結構,具體向哪個方向旋轉是根據它在父節點的左邊還是右邊。同時在插入刪除后,改變樹的結構是從插入位置開始,或者從刪除位置開始逐步向上進行重塑的。即應該先考慮當前父節點開始的子樹結構是否需要重塑,完成后再考慮以父節點的父節點開始的子樹結構是否需要重塑。
簡單來說,就是從發生改變的那個節點到整個樹的根節點的這條路上經過的所有節點都需要判斷一下是否需要重塑。
所以如果利用迭代的話,那么每一個節點都需要記錄,而且每一個節點是其父節點的左節點還是其右節點也需要記錄。
但是如果利用遞歸的話,在遞歸的調用空間中,我們默認就記錄下了這些節點,也記錄下了每一個節點在其父節點的左邊還是右邊,所以利用遞歸實現起來是比較方便的。
總結
以上是生活随笔為你收集整理的数据结构-----AVL树的插入删除操作的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 数据结构-----AVL树的旋转操作
- 下一篇: 数据结构-----图的拓扑排序和关键路径