1、二叉樹的構造
#include <iostream>
#include <cassert>
#include <queue>
#include <stack>
using namespace std;
template <class T>
struct TreeNode
{
TreeNode(const T& value = T())
:_value(value)
,_lchild(0)
,_rchild(0)
{}
T _value;//節點的值
TreeNode<T>* _lchild;//左孩子
TreeNode<T>* _rchild;//右孩子
};
template <class T>
class BinaryTree
{
public:
typedef TreeNode<T> Node;
BinaryTree()//無參構造函數
:_root(NULL)
{}
BinaryTree(const T* a,size_t size,const T& invalid)//構造函數
{
assert(a);
size_t index = 0;
_root = CreatTree(a,size,invalid,index);
}
BinaryTree(const BinaryTree<T>& b)//拷貝構造
{
_root = Copy(b._root);
}
//現代寫法的賦值運算符重載1
BinaryTree& operator=(const BinaryTree<T>& b)
{
if (this != &b)
{
Node* tmp = Copy(b._root);
Destroy(_root) ;
_root = tmp;
//swap(_root,tmp);
}
return *this;
}
////現代寫法的賦值運算符重載2
//BinaryTree& operator=(BinaryTree<T> b)
//{
// swap(b._root,_root);
// return *this;
//}
~BinaryTree()//析構
{
if (NULL != _root)
{
Destroy(_root);
_root = NULL;
}
}
protected:
//按照先序遍歷遞歸建樹
Node* CreatTree(const T* a,size_t size,const T& invalid,size_t& index)//注意index的傳值方式
{
assert(a);
Node* root = NULL;
if (a[index] != invalid && index < size)//按先序遍歷建樹
{
root = new Node(a[index]);
root->_lchild = CreatTree(a,size,invalid,++index);
root->_rchild = CreatTree(a,size,invalid,++index);
}
return root;
}
//拷貝對象
Node* Copy(Node* root)
{
Node* tmp = NULL;
if(root)
{
tmp = new Node(root->_value);
tmp->_lchild = Copy(root->_lchild);
tmp->_rchild = Copy(root->_rchild);
}
return tmp;
}
//釋放空間
void Destroy(Node*& root)
{
if(root)//用後序遍歷方式釋放空間
{
Destroy(root->_rchild);
Destroy(root->_lchild);
delete root;
root = NULL;
}
}
private:
Node* _root;//根節點
};
2、遍歷二叉樹
二叉樹的遍歷方式一共分爲四種:先序遍歷、中序遍歷、後序遍歷和層序遍歷,而前三種遍歷又分爲遞歸遍歷和非遞歸遍歷,層序遍歷則是藉助隊列先進先出的特性來輔助完成。
【遞歸遍歷二叉樹】
void preorder(Node* root)//先序遍歷打印樹的各個節點
{
if (root)
{
cout<<root->_value<<" "; //訪問當前節點的值
preorder(root->_lchild); //遞歸遍歷左子樹
preorder(root->_rchild); //遞歸遍歷右子樹
}
}
void inorder(Node* root)//中序遍歷打印樹的各個節點
{
if (root)
{
preorder(root->_lchild); //遞歸遍歷左子樹
cout<<root->_value<<" "; //訪問當前節點的值
preorder(root->_rchild); //遞歸遍歷右子樹
}
}
void postorder(Node* root)//後序遍歷打印樹的各個節點
{
if (root)
{
preorder(root->_lchild); //遞歸遍歷左子樹
preorder(root->_rchild); //遞歸遍歷右子樹
cout<<root->_value<<" "; //訪問當前節點的值
}
}
void levelorder(Node* root)//層序遍歷打印樹的各個節點
{
queue<Node*> q;
if (root)
{
q.push(root);//將根節點插進隊列
}
while(!q.empty())
{
Node* front = q.front();
q.pop();
cout<<front->_value<<" ";
if (front->_lchild)
{
q.push(front->_lchild);
}
if (front->_rchild)
{
q.push(front->_rchild);
}
}
}
【非遞歸遍歷二叉樹】
提供給外部的接口:
public:
void PreOrder()//先序遍歷打印樹的各個節點
{
cout<<"先序遍歷:";
preorderR(_root);
cout<<endl;
}
void InOrder()//中序遍歷打印樹的各個節點
{
cout<<"中序遍歷:";
inorderR(_root);
cout<<endl;
}
void PostOrder()//後序遍歷打印樹的各個節點
{
cout<<"後序遍歷:";
postorderR(_root);
cout<<endl;
}
void LevelOrder()//層序遍歷打印樹的各個節點
{
cout<<"層序遍歷:";
levelorder(_root);
cout<<endl;
}
算法實現:
void preorderR(Node* root)//先序遍歷打印樹的各個節點
{
Node* cur = root;
stack<Node*> s;
while (!s.empty() || cur)//只要當前節點和棧不同時爲空,就說明樹沒遍歷完
{
while(cur)//先序遍歷,遇到樹根節點直接訪問數據並將其壓棧
{
cout<<cur->_value<<" ";
s.push(cur);
cur = cur->_lchild;
}
Node* top = s.top();//取出棧頂元素,到此說明此節點以及其左子樹已經訪問過了
s.pop();
cur = top->_rchild;//以子問題的方式去訪問右子樹
}
cout<<endl;
}
void inorderR(Node* root)//中序遍歷打印樹的各個節點
{
Node* cur = root;
stack<Node*> s;
while(!s.empty() || cur)//只要當前節點和棧不同時爲空,就說明樹沒遍歷完
{
while(cur)//中序遍歷,遇到樹根節點直接將其壓棧
{
s.push(cur);
cur = cur->_lchild;
}
Node* top = s.top();//取出棧頂元素,到此說明此節點的左子樹已經訪問過了
cout<<top->_value<<" ";//訪問棧頂元素(即根節點)
s.pop();
cur = top->_rchild;//以子問題的方式去訪問右子樹
}
cout<<endl;
}
void postorderR(Node* root)//後序遍歷打印樹的各個節點
{
Node* cur = root;
Node* prev = NULL;//上一個訪問過的數據
stack<Node*> s;
while(!s.empty() || cur)//只要當前節點和棧不同時爲空,就說明樹沒遍歷完
{
//後序遍歷,遇到樹根節點直接將其壓棧
while(cur)
{
s.push(cur);
cur = cur->_lchild;
}
Node* top = s.top();//取棧頂元素,但不一定能訪問
//當節點右子樹爲空或已經訪問過時對其直接進行訪問
if (top->_rchild==NULL || top->_rchild==prev)
{
cout<<top->_value<<" ";
prev = top;//將prev更新爲已經訪問的節點
s.pop();
}
else//以子問題的方式去訪問右子樹
{
cur = top->_rchild;
}
}
cout<<endl;
}
void levelorder(Node* root)//層序遍歷打印樹的各個節點
{
queue<Node*> q;
if (root)
{
q.push(root);//將根節點插進隊列
}
while(!q.empty())
{
Node* front = q.front();
q.pop();
cout<<front->_value<<" ";
if (front->_lchild)
{
q.push(front->_lchild);
}
if (front->_rchild)
{
q.push(front->_rchild);
}
}
}
3、求二叉樹的高度(深度)
外部接口:
size_t Depth()//求樹的深度
{
cout<<"depth:";
return depth(_root);
}
算法實現:
size_t depth(Node* root)//求樹的深度
{
if (NULL == root)
{
return 0;
}
else
{
return depth(root->_lchild)>depth(root->_rchild)?
(depth(root->_lchild)+1):(depth(root->_rchild)+1);
}
}
4、求二叉樹中節點的個數
外部接口:
size_t Size()//求樹中的節點個數
{
cout<<"size:";
return size(_root);
}
算法實現:
size_t size(Node* root)//求樹中的節點個數
{
size_t count = 0;
if (NULL == root)
{
count = 0;
}
else
{
//當前節點 = 左子樹節點 + 右子樹節點 + 1
count = size(root->_lchild) + size(root->_rchild)+ 1;
}
return count;
}
5、求二叉樹中葉子節點的個數
外部接口:
size_t GetLeafSize()
{
cout<<"leaf_size:";
return getleafsize(_root);
}
算法實現:
size_t getleafsize(Node* root)//求葉節點的個數
{
if (NULL == root)//空樹
{
return 0;
}
if (NULL == root->_lchild && NULL == root->_rchild)//左葉節點右節點均爲空,即
{
return 1;
}
else//左子樹的葉節點+右子樹的葉節點
{
return getleafsize(root->_lchild)+getleafsize(root->_rchild);
}
}
6、求二叉樹第K層的節點個數(假設根節點爲第1層)
外部接口:
size_t GetKLevelSize(size_t k)//樹中第k層的節點個數
{
cout<<k<<"_level_size:";
return getklevelsize(_root,k);
}
算法實現:
size_t getklevelsize(Node* root,size_t k)//樹中第k層的節點個數
{
assert(k>0);
size_t count = 0;
if (NULL == root)
{
return 0;
}
if (k == 1)
{
count++;
}
else
{
count = getklevelsize(root->_lchild,k-1)
+ getklevelsize(root->_rchild,k-1);
}
return count;
}
7、判斷一個節點是否在二叉樹中
外部接口:
bool Find(const T& data) //判斷一個值爲的節點是否在當前二叉樹中
{
return _Find(_root,data);
}
算法實現:
bool _Find(Node* root,const T& data) //查找指定數據的節點
{
if (NULL == root)
{
return false;
}
else if (root->_data == data)
{
return true;
}
else
{
bool isIn = false;
if (root->_left && !isIn)
isIn = _Find(root->_left,data);
if (root->_right && !isIn)
isIn = _Find(root->_right,data);
return isIn;
}
}
8、求兩個節點的最近公共祖先
外部接口:
Node* sameAncester(Node* n1,Node* n2) //求兩個節點的最近公共祖先
{
//return _sameAncester1(_root,n1,n2);
//return _sameAncester2(_root,n1,n2);
return _sameAncester3(_root,n1,n2);
}
算法實現:
Node* _sameAncester1(Node*& root,Node* n1,Node* n2)//求兩個節點的最近公共祖先(時間複雜度:O(N^2))
{
assert(n1 && n2);
//保證兩個節點都在當前樹中,且當前樹不爲空
if (root && Find(n1->_data) && Find(n2->_data))
{
//其中一個節點爲根節點,直接返回根節點
if (root->_data == n1->_data || root->_data == n2->_data)
{
return root;
}
//兩個節點:1、都在左子樹 2、都在右子樹 3、一個在左子樹,一個在右子樹
else
{
Node* cur = root;
//在當前樹的左子樹去找n1和n2節點,如果沒找到就一定在右子樹
bool tmp1 = _Find(cur->_left,n1->_data);
bool tmp2 = _Find(cur->_left,n2->_data);
//n1和n2都在左子樹
if (tmp1 && tmp2)
{
return _sameAncester1(cur->_left,n1,n2);
}
//n1和n2都在右子樹
else if(!tmp1 && !tmp2)
{
return _sameAncester1(cur->_right,n1,n2);
}
//n1與n2一個在左子樹一個在右子樹
else
{
return root;
}
}
}
return NULL;
}
bool _GetPath2(Node* root,Node* cur,stack<Node*>& s)//在根爲root的樹中查找節點cur的路徑
{
if (NULL == root || NULL == cur)//樹爲空
return false;
s.push(root); //將當前節點入棧
if (cur->_data == root->_data) //找到路徑
{
return true;
}
if (_GetPath2(root->_left,cur,s))//在左子樹中找路徑
{
return true;
}
if (_GetPath2(root->_right,cur,s))//在右子樹中找路徑
{
return true;
}
s.pop();
return false;
}
Node* _sameAncester2(Node*& root,Node* n1,Node* n2)//求兩個節點的最近公共祖先(時間複雜度:O(N))
{
//樹爲空
if (NULL == root)
{
return NULL;
}
stack<Node*> s1;
stack<Node*> s2;
//用棧s1和s2分別保存節點n1和節點n2的路徑
_GetPath2(root,n1,s1);
_GetPath2(root,n2,s2);
//將多餘的路徑刪除
while (s1.size() > s2.size())
{
s1.pop();
}
while(s1.size() < s2.size())
{
s2.pop();
}
//找s1與s2中不同的節點
while(s1.top() != s2.top())
{
s1.pop();
s2.pop();
}
return s1.top();
}
bool _GetPath3(Node* root,Node* cur,vector<Node*>& v)
{
if (NULL == root || NULL == cur)//樹爲空
return false;
v.push_back(root);
if (cur->_data == root->_data) //找到路徑
{
return true;
}
else
{
bool ret = false;
if (root->_left && !ret)//在左子樹中找路徑
{
ret = _GetPath3(root->_left,cur,v);
}
if (root->_right && !ret)//在右子樹中找路徑
{
ret = _GetPath3(root->_right,cur,v);
}
if (ret == false && v.size() != 0)//如果不是正確路徑上的節點,就去除該節點
{
v.pop_back();
}
return ret;
}
}
Node* _sameAncester3(Node*& root,Node* n1,Node* n2)
{
//樹爲空
if (NULL == root)
{
return NULL;
}
//定義容器
vector<Node*> v1;
vector<Node*> v2;
//用容器v1和v2分別保存節點n1和節點n2的路徑
_GetPath3(root,n1,v1);
_GetPath3(root,n2,v2);
////從下往上找
////定義指向查找節點的迭代器指針
//vector<Node*>::iterator it1 = --v1.end();
//vector<Node*>::iterator it2 = --v2.end();
////將多餘的路徑節點刪除掉
//while(v1.size() > v2.size())
//{
// --it1; //相應的迭代器要向前移
// v1.pop_back();
//}
//while(v2.size() > v1.size())
//{
// --it2; //相應的迭代器要向前移
// v2.pop_back();
//}
////找v1與v2中不同的節點
//while(it1 >= v1.begin() && it2 >= v2.begin())
//{
// if (*it1 == *it2)
// {
// return *it1;
// }
// --it1;
// --it2;
//}
//從上往下找
//定義指向查找節點的迭代器指針
vector<Node*>::iterator it1 = v1.begin();
vector<Node*>::iterator it2 = v2.begin();
//找v1與v2中不同的節點
while(it1 != v1.end() && it2 != v2.end())
{
if (*it1 != *it2)//找到第一個不相同的節點,返回前一個節點
{
return *(--it1);
}
++it1;
++it2;
}
//處理特殊情況,例如:v1:1->2->3 v2:1->2
if (it1 == v1.end() || it2 == v2.end())
return *(--it1);
return NULL;
}
9、判斷一棵二叉樹是否是平衡二叉樹
算法實現
bool IsCompleteBT()//判斷一棵樹是否是完全二叉樹
{
bool flag = true;//判斷一棵子樹是否爲一棵完全二叉樹的標記
queue<Node*> q; //運用隊列進行層序遍歷
q.push(_root);
while(!q.empty())
{
Node* front = q.front();//保存隊首節點
q.pop(); //將隊首元素出隊
/*如果隊首元素的左樹爲空,將標誌置爲false,如果層序遍歷\
\後面的節點還有子樹,說明不是完全二叉樹*/
if (NULL == front->_left)
{
flag = false;
}
else
{
/*如果flag爲假,說明之前已經有節點的孩子爲空,又因當前\
\節點的左孩子不爲空,說明不是完全二叉樹*/
if (flag == false)
{
return false;
}
q.push(front->_left);//繼續向後探索
}
/*如果隊首元素的右樹爲空,將標誌置爲false,如果層序遍歷\
\後面的節點還有子樹,說明不是完全二叉樹*/
if (NULL == front->_right)
{
flag = false;
}
else
{
/*如果flag爲假,說明之前已經有節點的孩子爲空,又因當前\
\節點的右孩子不爲空,說明不是完全二叉樹*/
if (flag == false)
{
return false;
}
q.push(front->_right);//繼續向後探索
}
}
return true;//能走到這裏說明一定是完全二叉樹
}
10、求二叉樹中最遠的兩個節點的距離
外部接口:
int RemoteDistance() //求二叉樹中最遠的兩個節點的距離
{
int tmp = 0;
return _RemoteDistance1(_root,tmp);
}
算法實現:
int _RemoteDistance1(Node* root,int& distance)
{
if (NULL == root)
{
return 0;
}
int tmp = _Depth(root->_left)+_Depth(root->_right);//遞歸去求每棵子樹的最遠距離
if (distance < tmp)//用全局變量保存每棵子樹的最遠距離,並不斷更新
{
distance = tmp;
}
return distance;
}
11、由前序遍歷和中序遍歷重建二叉樹
外部接口:
Node* Rebuild(T* prevOrder,T* inOrder,int n) //根據前序序列和中序序列重建二叉樹
{
assert(prevOrder && inOrder);
int prevIndex = 0; //指向前序序列指針的下標
int inBegin = 0; //中序序列的首位置的下標
int inEnd = n-1; //中序序列的末位置的下標(閉區間)
_root = _Rebuild(prevOrder,prevIndex,inOrder,inBegin,inEnd,n);
return _root;
}
算法實現:
Node* _Rebuild(T* prevOrder,int& prevIndex,T* inOrder,int inBegin,int inEnd,int n)
{
Node* root = NULL;
if (prevIndex < n)//當前序序列的下標<n時才創建新結點
{
root = new Node(prevOrder[prevIndex]);
}
if (NULL == root)//遞歸的返回條件
{
return NULL;
}
if (inBegin == inEnd)//要重建樹中只有一個節點
{
return root;
}
else
{
//在中序序列中找到前序序列中的根
int i = inBegin;
for ( ; i <= inEnd; ++i)
{
if (prevOrder[prevIndex] == inOrder[i])
break;
}
if (i > inEnd)//說明中序序列中沒找到前序序列中的根,所給序列不匹配
{
throw invalid_argument("所給序列不匹配!");//直接拋異常
}
//將根節點作爲分割線,將區間分爲左右兩部分,分別遞歸重建左右子樹
root->_left = _Rebuild(prevOrder,++prevIndex,inOrder,inBegin,i-1,n);
root->_right = _Rebuild(prevOrder,++prevIndex,inOrder,i+1,inEnd,n);
return root;
}
}
12、判斷一棵樹是否是完全二叉樹
算法實現:
bool IsCompleteBT()//判斷一棵樹是否是完全二叉樹
{
bool flag = true;//判斷一棵子樹是否爲一棵完全二叉樹的標記
queue<Node*> q; //運用隊列進行層序遍歷
q.push(_root);
while(!q.empty())
{
Node* front = q.front();//保存隊首節點
q.pop(); //將隊首元素出隊
/*如果隊首元素的左樹爲空,將標誌置爲false,如果層序遍歷\
\後面的節點還有子樹,說明不是完全二叉樹*/
if (NULL == front->_left)
{
flag = false;
}
else
{
/*如果flag爲假,說明之前已經有節點的孩子爲空,又因當前\
\節點的左孩子不爲空,說明不是完全二叉樹*/
if (flag == false)
{
return false;
}
q.push(front->_left);//繼續向後探索
}
/*如果隊首元素的右樹爲空,將標誌置爲false,如果層序遍歷\
\後面的節點還有子樹,說明不是完全二叉樹*/
if (NULL == front->_right)
{
flag = false;
}
else
{
/*如果flag爲假,說明之前已經有節點的孩子爲空,又因當前\
\節點的右孩子不爲空,說明不是完全二叉樹*/
if (flag == false)
{
return false;
}
q.push(front->_right);//繼續向後探索
}
}
return true;//能走到這裏說明一定是完全二叉樹
}
13、求二叉樹的鏡像
外部接口:
void GetMirrorTree()//求二叉樹的鏡像
{
_GetMirrorTree(_root);
}
算法實現:
void _GetMirrorTree(Node* root)//求二叉樹的鏡像
{
if (NULL == root)
{
return;
}
swap(root->_left,root->_right);//交換左右子樹
_GetMirrorTree(root->_left); //遞歸遍歷左子樹
_GetMirrorTree(root->_right);//遞歸遍歷右子樹
}
14、將二叉搜索樹轉換爲一個已排序的雙向鏈表。要求不能創建任何新的節點,只能調整樹中節點指針的指向
外部接口:
void MakeSearchToList()/*將二叉搜索樹轉換成一個排序的雙向鏈表。\
要求不能創建任何新的結點,只能調整樹中結點指針的指向。*/
{
Node* cur = _root;
//尋找最左節點
while (cur->_left)
{
cur = cur->_left;
}
Node* prev = NULL;
_MakeSearchToList1(_root,prev);
//_MakeSearchToList2(_root);
while(cur)//打印鏈表(樹的結構變了之後析構會死循環)
{
cout<<cur->_data<<"<->";
cur = cur->_right;
}
cout<<"null";
}
算法實現:
void _MakeSearchToList1(Node* root,Node*& prev)
{
if (NULL == root)//如果當前樹爲空,則直接返回
return;
_MakeSearchToList1(root->_left,prev);//遞歸左子樹
//進行轉換
root->_left = prev;//root第一次指向最左節點
if (prev)//如果上一個節點不爲空,就將上一個節點的右指針指向當前節點root
{
prev->_right = root;
}
prev = root;//更新保存上一個節點
_MakeSearchToList1(root->_right,prev);//遞歸右子樹
}
void _InOrderList(Node* root,queue<Node*>& q)//將樹的中序遍歷的節點入到隊列中
{
if (NULL == root)
return;
_InOrderList(root->_left,q);//遞歸左子樹
q.push(root); //將當前節點入隊
_InOrderList(root->_right,q);//遞歸右子樹
}
void _MakeSearchToList2(Node* root)
{
if (NULL == root)//如果當前樹爲空則直接返回
return;
queue<Node*> q;
_InOrderList(root,q);//將所給樹的中序序列push到隊列q中
Node* head = q.front();//將隊首節點(即所給樹的最左節點)保存起來
q.pop();
head->_left = NULL;//將隊首節點的左指針置空
Node* prev = head;//用prev保存當前節點
while(!q.empty())//當隊列不空時進入循環
{
Node* front = q.front();//獲取隊首節點
q.pop(); //將隊首節點出隊
prev->_right = front; //將前一個節點的右指針指向隊首節點
front->_left = prev; //將隊首節點的左指針指向上一個節點
front->_right = NULL; //將隊首節點的右指針置空
prev = front; //更新保存上一個節點
}
}