轉載自:
http://blog.csdn.net/LLZK_/article/details/52829525
本文主要講了如何使用c++來構建一個二叉樹類,以及一些功能算法的實現。文中大部分函數的思想都是遞歸,其中賦值運算符重載有傳統寫法和現代寫法兩個版本,層序遍歷是非遞歸,前、中、后序遍歷有遞歸和非遞歸兩個版本。
1、構造函數(遞歸)
2、拷貝構造函數(遞歸)
3、析構函數(遞歸)
4、賦值運算符重載(傳統/現代)
5、前中后序遍歷(遞歸/非遞歸)
6、層序遍歷(非遞歸)
7、查找第k層結點個數(遞歸)
8、精確查找值為x的結點,並返回當前結點的指針(遞歸)
9、查找葉子結點個數(遞歸)
10、查找結點總個數(遞歸)
11、計算樹的深度(遞歸)
樹的結點類型,每個結點都需要有一個指向右孩子的指針,一個指向左孩子的指針,以及一個數據。(當然,如果你想構造三叉樹的話,也可以增加一個指向父節點的指針。)
這里我寫出了結點的構造函數,方便我們在創建樹的時候使用。
注意:這棵樹我使用了模板
- template<typename T>
- struct BinaryTreeNode
- {
- BinaryTreeNode<T>* _left;//左孩子
- BinaryTreeNode<T>* _right;//右孩子
- T _data;//數據
- BinaryTreeNode(T data = T())//結點自己的構造函數,T()為一個匿名對象。
- :_left(NULL)//初始化為空
- , _right(NULL)
- , _data(data)
- {}
- };
下面代碼中會經常用到BinaryTreeNode<T> 所以將其重命名為Node
- typedef BinaryTreeNode<T> Node;
當我們向寫二叉樹類的時候,直接給類設定一個根結點,以這個根結點為基礎,構建二叉樹。
- class BinaryTree
- {
- public:
- private:
- BinaryTreeNode<T>* _root;//根節點
- };
1、構造函數
BinaryTree(const T* a, size_t size,int index, const T& invalid)
構造函數有4個參數,T類型的指針a,傳參時傳一個數組,負責傳入數據。size保存數組a 的大小,index記錄下標,invalid表示非法值。
因為我們需要用到遞歸,所以在這個函數內部我們需要再封裝一個遞歸函數_MakeTree(),並讓它的返回值為一個Node*類型的指針。
- BinaryTree(const T* a, size_t size,int index, const T& invalid)
- {
- _root = _MakeTree(a,size,index,invalid);
- }
我們先來觀察一個樹:
你會看到上面有許多NULL,這些NULL我們就可以理解為非法值invalid。這棵樹的前序遍歷為:
1 2 3 NULL NULL 4 NULL NULL 5 6
最后一個結點不需要非法值,到時候直接創建即可。
與上對應,我們傳數組時,應該傳的值即為 int a[10] = {1,2,3,'#','#',4,'#','#',5,6}。非法值的值可以隨意設,這里我設為‘#’,注意,你向以什么的樣的順序建樹,就以什么樣的順序傳參,事先要約定好。(這里我用的是前序)
_MakeTree函數實現:
- Node* _MakeTree(const T* a, size_t size, int& index, const T& invalid)
- {
- Node *root = NULL;
- if (index < size && a[index] != invalid)
- {
- root = new Node(invalid);
- root->_data = a[index];
- root->_left = _MakeTree(a, size, ++index, invalid);
- root->_right = _MakeTree(a, size, ++index, invalid);
- }
- return root;
- }
2、拷貝構造函數
同上,同樣實現一個遞歸函數,返回值仍為Node*
- BinaryTree(const BinaryTree<T>& t)
- {
- _root = CopyTree(t._root);
- }
注意:上文提到的當前結點,在每一層的遞歸中值都是不一樣的。每遞歸一層,當前結點就會變成傳入的參數root。包括下文
CopyTree實現代碼:
- Node* CopyTree(const BinaryTreeNode<T>* _root)3、
- {
- if (_root == NULL)
- {
- return NULL;
- }
- Node* root = new Node(_root->_data);
- root->_left = CopyTree(_root->_left);
- root->_right = CopyTree(_root->_right);
- return root;
- }
3、析構函數
同上,但是析構函數不需要返回值。
- ~BinaryTree()
- {
- Destroy(_root);
- }
- void Destroy( Node* _root)
- {
- Node* tmp = _root;
- if (tmp == NULL)//如果根結點為空,則不需要delete,直接return。
- return;
- Destroy(tmp->_left);
- Destroy(tmp->_right);
- delete tmp;
- tmp = NULL;
- }
當我們寫任何賦值運算符重載時,都會有兩種寫法
①傳統寫法,一個元素一個元素的拷貝,傳參時傳的是const的引用。
這樣賦值有個麻煩的地方,我們知道,當給一個結點賦值的時候,這個結點原本的內容,空間就要被銷毀掉。就是說我們既要new又要delete。當這個樹有1個億結點時,我們豈不是要重復1億次?有沒有一種更簡單的方法呢?
②現代寫法(建議使用,很巧妙),調用swap函數,交換兩個樹的root。傳參時用的值傳遞。
- BinaryTree<T>& operator=(BinaryTree<T> t)
- {
- if (this != &t)//自賦值的優化
- {
- std::swap(_root, t._root);
- }
- return *this;
- }
5、前中后序遍歷(遞歸)
①前序
這個我就不再多說了,構造,拷貝構造,析構,都是利用前序遍歷的道理來做的。當前結點-->左子樹-->右子樹。
代碼:
- void PrevOrder()
- {
- _PrevOrder(_root);
- cout << endl;
- }
_PrevOrder()
- void _PrevOrder(Node* _root)
- {
- Node* tmp = _root;
- if (tmp == NULL)
- {
- return;
- }
- cout << tmp->_data << " ";
- _PrevOrder(tmp->_left);
- _PrevOrder(tmp->_right);
- }
判斷當前結點是否為空,為空的話,不處理,直接返回。先遞歸訪問當前結點左子樹,當左子樹處理完畢,再依次返回處理當前結點,再遞歸訪問當前結點右子樹。
- void InOrder()
- {
- _InOrder(_root);
- cout << endl;
- }
_InOrder()
- void _InOrder(Node* _root)
- {
- Node* tmp = _root;
- if (tmp == NULL)
- {
- return;
- }
- _InOrder(tmp->_left);
- cout << tmp->_data << " ";
- _InOrder(tmp->_right);
- }
③后序
判斷當前結點是否為空,為空的話,不處理,直接返回。不為空的話,先遞歸訪問當前結點節點的左子樹,再遞歸訪問當前結點根節點的右子樹,最后訪問當前結點。
- void PostOrder()
- {
- _PostOrder(_root);
- cout << endl;
- }
_PostOrder()
- void _PostOrder(Node* _root)
- {
- Node* tmp = _root;
- if (tmp == NULL)
- {
- return;
- }
- _PostOrder(tmp->_left);
- _PostOrder(tmp->_right);
- cout << tmp->_data << " ";
- }
6、前中后序非遞歸。
這里我告訴大家一個真理,任何的遞歸都可以用棧來替換實現。這里我就用一個輔助棧來實現前中后序的非遞歸。
①前序
代碼:
- void PrevOrder_NonR()
- {
- Node* cur = _root;
- stack<Node*> s;
- if (cur == NULL)
- {
- return;
- }
- while (cur || !s.empty())
- {
- while (cur)
- {
- s.push(cur);
- cout << cur->_data << " ";
- cur = cur->_left;
- }
- Node* top = s.top();
- s.pop();
- cur = top->_right;
- }
- cout << endl;
- }
中序和后序的道理與上相同,只是當前節點的輸出做了小小的改動。下面直接貼出代碼
②中序(非遞歸)
- void InOrder_NonR()
- {
- Node* cur = _root;
- stack<Node*> s;
- if (cur == NULL)
- {
- return;
- }
- while (cur || !s.empty())
- {
- while (cur)
- {
- s.push(cur);
- cur = cur->_left;
- }
- Node* top = s.top();
- cout << top->_data << " ";
- s.pop();
- cur = top->_right;
- }
- cout << endl;
- }
后序的非遞歸較上面兩個多了一個變量,prev,它記錄了上一次訪問的節點。因為后序是最后訪問當前節點的,當我們訪問一個節點,我們不知道這個節點的右樹是否被訪問過。所以我們需要記錄一下上一個訪問的節點。以便訪問右樹時做判斷。
- void PostOrder_NonR()
- {
- Node* cur = _root;
- Node* prev = NULL;
- stack<Node*> s;
- while (cur || s.empty())
- {
- while (cur)
- {
- s.push(cur);
- cur = cur->_left;
- }
- Node* top = s.top();
- //如果右樹為空或者右樹已經訪問過,則訪問當前結點,並出棧
- //如果右樹不為空並且沒有訪問過,則訪問右樹
- if (top->_right == NULL || prev == top->_right)
- {
- cout << top->_data << " ";
- prev = top;
- s.pop();//返回父節點
- }
- else
- {
- cur = top->_right;
- }
- }
- cout << endl;
- }
7、層序遍歷
顧名思義。層序遍歷就是按層來訪問一顆樹,一次訪問一層。這里我們用到了一個隊列。
代碼:
- void _LevelOrder(Node* _root)
- {
- Node *tmp = _root;
- queue<Node*> q;
- q.push(tmp);
- while (!q.empty())
- {
- Node* top = q.front();
- q.pop();
- cout << top->_data << " ";
- if (top->_left)
- {
- q.push(top->_left);
- }
- if (top->_right)
- {
- q.push(top->_right);
- }
- }
- }
8、查找第k層節點個數
這個問題,我們只需要查找第k-1層有多少個孩子就行了。
同樣為遞歸,前序。
- size_t FindKlevel(size_t k)
- {
- return _FindKlevel(_root,k);
- }
_FindKlevel()
- size_t _FindKlevel(Node* _root,size_t k)
- {
- Node *cur = _root;
- if (cur == NULL || k < 0)
- {
- return 0;
- }
- if (k == 1)
- {
- return 1;
- }
- size_t left = _FindKlevel(cur->_left, k-1);
- size_t right = _FindKlevel(cur->_right, k-1);
- return left + right;
- }
前序遞歸查找,如果根節點為空,返回NULL,如果當前節點等於x,返回當前節點的指針。如果當前節點不等於x,則遞歸進入左子樹查找,若左子樹沒有,則遞歸進入右子樹查找,若這棵樹中沒有x,返回NULL。
- Node* Find(const T& x)
- {
- return _Find(_root,x);
- }
- Node* _Find(Node* _root,const T& x)
- {
- Node* ret = NULL;
- Node* cur = _root;
- if (cur == NULL)
- {
- return NULL;
- }
- if (cur->_data == x)
- {
- ret = cur;
- }
- else
- {
- ret = _Find(cur->_left,x);
- if (ret == NULL)
- {
- ret = _Find(cur->_right,x);
- }
- }
- return ret;
- }
10、查找結點總個數。
結點總個數 = 當前節點個數+左子樹節點個數+右子樹節點個數。
前序遞歸查找,若當前節點為空,返回,不為空則加1,--->遞歸左子樹----->遞歸右子樹。
- size_t Size()
- {
- return _Size(_root);
- }
_Size()
- size_t _Size( BinaryTreeNode<T>* _root )
- {
- size_t ret = 0;
- if (_root == NULL)
- {
- return ret;
- }
- ret++;
- ret += _Size(_root->_left);
- ret += _Size(_root->_right);
- }
樹的深度取左子樹深度+1和右子樹深度+1的最大值。(+1為根節點的深度)
- size_t Depth()
- {
- return _Depth(_root);
- }
- size_t _Depth(Node* _root)
- {
- if (_root == NULL)
- {
- return 0;
- }
- int left = _Depth(_root->_left) + 1;
- int right = _Depth(_root->_right) + 1;
- return left > right ? left : right;
- }
