二叉搜尋樹(二叉排序樹)BST與雙向列表的轉換
目錄
1.二叉排序樹(Binary Sort Tree)
二叉排序樹又稱二叉查詢樹(Binary Search Tree) BST
二叉排序樹或者是一顆空樹,或者是滿足一下性質的一顆二叉樹:
- 若它的左子樹非空,則左子樹上所有結點的值均小於根結點的值;
- 若它的右子樹非空,則右子樹上所有結點的值均大於它的根結點的值;
- 它的左/右子樹分別為二叉排序樹
下面是初始序列建立二叉排序樹的過程:
{ 4, 2, 6, 1, 3, 5 }:
1.0 BST樹儲存結構
用二叉連結串列作為二叉排序樹的儲存結構!
typedef int KeyType; typedef int ElemType; /*二叉樹的結點儲存結構,二叉連結串列儲存結構*/ typedef struct BiTNode{ KeyType data; int multiplicity; struct BiTNode *lchild, *rchild; }BiTNode, *BiTree; /* BiTNode:是結構型別 BiTree:是指向結點BiTNode的指標型別 */
二叉排序樹的基本操作:
- 查詢key
- 插入結點
- 刪除結點
1.1BST查詢
/*在跟指標T所指的二叉排序樹中遞迴地查詢其關鍵字等於key的資料元素,
若查詢成功,則指標p指向該資料元素結點,並返回true,否則指標p指向查詢路徑
上訪問的最後一個結點並放回false,指標f指向T的雙親,其初始呼叫值為nullptr(空指標)*/
bool SearchBST(BiTree T, KeyType key, BiTree f, BiTree& p) { /*在跟指標T所指的二叉排序樹中遞迴地查詢其關鍵字等於key的資料元素, 若查詢成功,則指標p指向該資料元素結點,並返回true,否則指標p指向查詢路徑 上訪問的最後一個結點並放回false,指標f指向T的雙親,其初始呼叫值為nullptr*/ if (!T) { p = f; return false; } else if (key == T->data) { p = T; return true; } else if (key < T->data) { return SearchBST(T->lchild, key, T, p); } else if (key > T->data) { return SearchBST(T->rchild, key, T, p); } }//SearchBST
1.2BST樹插入節點
插入前要先搜尋待插入的位置,如果存在值key值相同的節點,則不插入,並返回false。
/*當二叉樹上不存在關鍵字等於e.key的資料元素時,插入e並返回true;
否則返回false;*/
bool InsertBST(BiTree& T, ElemType e) { /*當二叉樹上不存在關鍵字等於e.key的資料元素時,插入e並返回true; 否則返回false;*/ BiTree p; BiTree f = nullptr; if (!SearchBST(T, e, f, p))//指標p指向查詢路徑上訪問的最後一個結點並放回false { //BiTree s = (BiTree)malloc(sizeof(BiTNode)); BiTree s = new BiTNode; s->data = e; s->multiplicity = 1; s->lchild = s->rchild = nullptr; if (!p) {//被插入結點為新的根結點! T = s; } else if (e < p->data) {//被插入結點*s為當前結點的左孩子 p->lchild = s; } else if (e > p->data) {//被插入結點*s為當前結點的右孩子 p->rchild = s; } return true; } else {//樹中已經有關鍵字相同的結點,不再插入,該結點的重複度+1 p->multiplicity++; return false; } }
1.3中序遍歷BST樹得到有序序列
void printVal(BiTree T)
{
cout << T->data << " ";
}
void InOrderTraverseRecursive(BiTree T, void(*funcPtr)(BiTree))
{//對採用二叉連結串列表示的二叉樹的中序遍歷演算法
/*中序遍歷二叉樹T的遞迴演算法!*/
if (T)
{
InOrderTraverseRecursive(T->lchild, funcPtr);
funcPtr(T);
InOrderTraverseRecursive(T->rchild, funcPtr);
}
else
{
return;
}
}
//中序遍歷二叉樹
/*E2版本的中序遍歷結構更加清晰、易懂!*/
void InOrderTraverseLoopE2(BiTree T, void(*funcPtr)(BiTree))
{/*二叉樹T採用二叉連結串列儲存結構,中序遍歷T的非遞迴演算法,對每一個元素呼叫funcPtr函式*/
stack<BiTree> biTreeStack;//儲存結點指標的棧
BiTree p;
p = T;
while (p || !biTreeStack.empty())
{
if (p)
{//根指標進棧,然後遍歷左子樹
biTreeStack.push(p);
p = p->lchild;//這裡也是一直走到最下邊的結點
}
else
{//根指標退棧,訪問根結點,然後遍歷右子樹
p = biTreeStack.top();
biTreeStack.pop();
funcPtr(p);
p = p->rchild;
}
}
}
1.4刪除BST樹上的一結點
bool Delete(BiTree& p)
{//從二叉排序樹中刪除結點p,並重接它的左或右子樹
if (!p->rchild)
{
BiTree q = p;
p = p->lchild;
delete q;
}
else if (!p->lchild)
{
BiTree q = p;
p = p->rchild;
delete q;
}
else//左右子樹均不為空
{
BiTree q = p;
BiTree s = p->lchild;
while (s->rchild)
{//
q = s;
s = s->rchild;
}
p->data = s->data;//s指向被刪除結點的前驅
if (q != p)
{
q->rchild = s->lchild;//重接*q的右子樹
}
else
{
q->lchild = s->lchild;//重接*q的左子樹
}
delete s;
}
return true;
}
bool DeleteBSTKey(BiTree& T, KeyType key)
{/*若二叉排序樹T中存在關鍵字等於key的資料元素,則刪除該資料元素結點!*/
if (!T) return false;
else
{
if (key == T->data)
{
return Delete(T);
}
else if (key < T->data)
{
return DeleteBSTKey(T->lchild, key);
}
else if (key > T->data)
{
return DeleteBSTKey(T->rchild, key);
}
}
}//DeleteBST
1.5二叉排序樹記憶體的釋放_後序遍歷刪除每一個結點
bool deteteBiTNode(BiTree p)
{
if (!p)
{
return false;
}
delete p;
p = nullptr;
return true;
}
void PostOrderTraverseRecursive(BiTree T, bool(*funcPtr)(BiTree))
{//對採用二叉連結串列表示的二叉樹的後序遍歷演算法
/*後序遍歷二叉樹T的遞迴演算法!*/
if (T)
{
PostOrderTraverseRecursive(T->lchild, funcPtr);
PostOrderTraverseRecursive(T->rchild, funcPtr);
funcPtr(T);
}
else
{
return;
}
}
1.6二叉樹的後序遍歷_非遞迴演算法
參考我的另一篇博文:二叉樹_二叉連結串列儲存_前中後遍歷_棧:遞迴非遞迴遍歷_佇列:按層遍歷
後序遍歷的非遞迴演算法:以棧模擬遞迴的過程:
/*
二叉樹的後序遍歷--非遞迴實現
https://www.cnblogs.com/rain-lei/p/3705680.html
https://www.cnblogs.com/rain-lei/p/3705680.html
leetcode中有這麼一道題,非遞迴來實現二叉樹的後序遍歷。
二叉樹的後序遍歷順序為,root->left, root->right, root,
因此需要儲存根節點的狀態。顯然使用棧來模擬遞迴的過程,
但是難點是怎麼從root->right轉換到root。
方法1:判斷是否輪到棧頂p訪問法,設立剛訪問結點指標last
對於節點p可以分情況討論
1. p如果是葉子節點,直接訪問(輸出)
2. p如果有孩子,且孩子沒有被訪問過,則按照右孩子,左孩子的順序依次入棧
3. p如果有孩子,而且孩子都已經訪問過,則訪問p節點
如何來表示出p的孩是否都已經訪問過了呢?
最暴力的方法就是對每個節點的狀態進行儲存,
這麼做顯然是可以的,但是空間複雜度太大了。
我們可以儲存最後一個訪問的節點last,
如果滿足 (p->right==NULL && last ==p->left) || last=p->right,
那麼顯然p的孩子都訪問過了,接下來可以訪問p
*/
二叉樹的後序遍歷法1:區別棧頂結點p是否該訪問了之設立剛訪問結點指標法
void PostOrderTraverseE1(BiTree T, void(*funcPtr)(BiTree))
{
if (!T)
return;
stack<BiTree> biTreeStack;//儲存結點指標的棧
BiTree p = T;
BiTree last = T;
biTreeStack.push(p);
while (!biTreeStack.empty())
{
p = biTreeStack.top();
/*情況1:如果p是葉子結點,其左右子樹都為空,則可直接訪問p;
情況2:如果滿足 (p->right==NULL && last ==p->left) || last=p->right,
那麼顯然p的孩子都訪問過了,接下來可以訪問p
如果p的右子樹為空,並且p的左子樹已經訪問過了,即(p->right==NULL && last ==p->left)
那麼就可以訪問p了
如果p的右子樹也訪問過了即last=p->right,也可以訪問p了
*/
if ((p->lchild == nullptr&&p->rchild == nullptr) || (p->rchild == nullptr&&last == p->lchild) || (last == p->rchild))
{
funcPtr(p);//訪問p
last = p;//將剛才訪問的結點標記為p
biTreeStack.pop();//p出棧
}
else
{
if (p->rchild)
{//如果右子樹非空,則右子樹結點進棧
biTreeStack.push(p->rchild);
}
if (p->lchild)
{//如果左子樹非空,則左子樹結點進棧
biTreeStack.push(p->lchild);
}
}
}
}
二叉樹的後序遍歷法2:區別棧頂結點p是否該訪問了同一個結點兩次壓入兩次彈出法:
/*
法2:每個結點兩次壓入法
其實我們希望棧中儲存的從頂部依次是root->left, root->right, root,
當符合上面提到的條件時,就進行出棧操作。有一種巧妙的方法可以做到,
對於每個節點,都壓入兩遍,在迴圈體中,每次彈出一個節點賦給p,
如果p仍然等於棧的頭結點,說明p的孩子們還沒有被操作過,
應該把它的孩子們加入棧中,否則,訪問p。
也就是說,第一次彈出,將p的孩子壓入棧中,第二次彈出,訪問p。
*/
void PostOrderTraverseE2(BiTree T, void(*funcPtr)(BiTree))
{
if (T == NULL) return;
BiTree p = T;
stack<BiTree> sta;
sta.push(p);
sta.push(p);
while (!sta.empty())
{
p = sta.top();
sta.pop();
if (!sta.empty() && p == sta.top())
{
if (p->rchild) sta.push(p->rchild), sta.push(p->rchild);//C:逗號,運算子
if (p->lchild) sta.push(p->lchild), sta.push(p->lchild);
}
else
{
funcPtr(p);
}
}
}
2.將BST轉換成雙向列表
參考《劍指offer》P191
面試題36:二叉搜尋樹與雙向列表
題目:輸入一顆二叉搜尋樹,將該二叉搜尋樹轉換成一個排序有序的雙向列表。要求不能建立新的結點,只能通過調整樹中結點的指標,(沒有此要求的情況下,當然可以中序遍歷BST然後建立新的雙向連結串列結點,形參有序的雙向列表。
void ConvertNode(BiTNode* pNode, BiTNode** pLastNodeInList)
{
if (pNode == nullptr)
{
return;
}
BiTNode* pCurrent = pNode;
if (pCurrent->lchild != nullptr)
{
ConvertNode(pCurrent->lchild, pLastNodeInList);
}
pCurrent->lchild = *pLastNodeInList;
if (*pLastNodeInList != nullptr)
{
(*pLastNodeInList)->rchild = pCurrent;
}
*pLastNodeInList = pCurrent;
if (pCurrent->rchild != nullptr)
{
ConvertNode(pCurrent->rchild, pLastNodeInList);
}
}
BiTNode* Convert(BiTNode* pRootOfTree)
{
BiTNode* pLastNodeInList = nullptr;
ConvertNode(pRootOfTree, &pLastNodeInList);
//pLastNodeInList指向雙向列表的尾巴結點
//需求求得該雙向列表的頭結點
BiTNode* pHeadOfList = pLastNodeInList;
while (pHeadOfList != nullptr && pHeadOfList->lchild != nullptr)
{//向左走到雙向列表的頭
pHeadOfList = pHeadOfList->lchild;
}
return pHeadOfList;
}
2.1BST轉換成雙向列表測試:
#include "stdafx.h"
#include<stack>
#include<iostream>
using namespace std;
typedef int KeyType;
typedef int ElemType;
/*二叉樹的結點儲存結構,二叉連結串列儲存結構*/
typedef struct BiTNode{
KeyType data;
int multiplicity;
struct BiTNode *lchild, *rchild;
}BiTNode, *BiTree;
/*
BiTNode:是結構型別
BiTree:是指向結點BiTNode的指標型別
*/
int _tmain(int argc, _TCHAR* argv[])
{
int MyArray[6] = { 4, 2, 6, 1, 3, 5 };
BiTree T=nullptr;
for (int i = 0; i < 6; i++)
{
InsertBST(T, MyArray[i]);
}
InOrderTraverseLoopE2(T, printVal);//1 2 3 4 5 6
//bug! 2018年9月12日bugfixed!
BiTNode* p = Convert(T);
cout << endl;
while (p != nullptr )
{//向左走到雙向列表的頭
cout << p->data << " ";
p = p->rchild;
}
cout << endl;
system("pause");
return 0;
}
/*
輸出:
1 2 3 4 5 6
1 2 3 4 5 6
請按任意鍵繼續. . .
*/
3.BST轉雙向列表變種題
將二叉搜尋樹轉換成一個遞增的排序的雙向列表:
根據任意輸入建立一顆二叉搜尋樹,第一個原始是根結點原始,且元素可重複(通過在結點中設定元素的重複度multiplicity)
在轉換過程中不能建立新結點,只能調整指標,轉換完成後從頭到尾巴列印雙向列表:
二叉排序樹BST 二叉樹的遍歷 BST轉雙向列表