距離上一篇博客已經過去了四天,“每日一題”這個標題給的壓力還是挺大的,這四天一直在學習查找樹,同時更進一步地接觸STL,所以更新慢了。查找樹的刪除操作整整花了一天才學會,比較耗時,另外兩個有點麻煩的地方是迭代器的前進與後退,需要畫圖再根據查找樹的性質推理。這三個操作算得上兩個題吧,整個查找樹操作算得上一個題,這樣想想,嗯,勉強對得起標題啦,O(∩_∩)O。好了,閒話少說,上代碼,吸取了以前的教訓,註釋全部寫在源代碼中,直接複製過來就可以了,關於註釋只寫了比較麻煩的一些操作的,那些簡單的就沒寫了,希望以後拿來看的時候還能看懂:
#ifndef _SEARCH_TREE_H_
#define _SEARCH_TREE_H_
#include "../Utilites/type_traits.h"
#include "Functor.h"
namespace MyDataStructure
{
//將node結構分爲兩層,可以帶來一定的彈性,節點
//基類只負責節點連接的功能,而子類增加了實際的
//數據成員,所謂的彈性在這裏得到了體現,本例的
//子類只需要擁有一個數據成員,而對於紅黑樹而言,
//子類還需要表示顏色的成員,所有使用分層結構可以
//複用
struct BinaryNodeBase
{
typedef BinaryNodeBase* BasePtr;
BasePtr parent; //爲了支持迭代器,增設該指針
BasePtr left;
BasePtr right;
//查找以本節點爲根的樹中的最小值,
//如果有左子樹,則一直左轉到最左邊
//的節點,否則本身爲最小
BasePtr minimum()
{
BinaryNodeBase* res = this;
while (res->left)
{
res = res->left;
}
return res;
}
//查找以本節點爲根的樹中的最大值,
//如果有右子樹,則一直右轉到最右邊
//的節點,否則本身爲最大
BasePtr maximum()
{
BinaryNodeBase* res = this;
while (res->right)
{
res = res->right;
}
return res;
}
};
//具體的節點類
template<typename ValueType>
struct BinaryNode : public BinaryNodeBase
{
typedef BinaryNode<ValueType>* LinkType;
ValueType value;
};
//查找樹迭代器基類
struct SearchTreeIteratorBase
{
typedef BinaryNodeBase::BasePtr BasePtr;
BasePtr node;
//讓迭代器移動到下一個節點
void Increament()
{
//如果迭代器指示的節點有右子節點,
//則以該右子節點爲根的樹的最小值節
//點就是下一個迭代器該指示的節點
if (node->right) node = node->right->minimum();
else
{
//否則,如果當前指示的節點是右子節
//點,就需要往上搜索第一個不是右子節點
//的節點,如果當前指示的節點是左子節點,
//那麼父節點就是了。畫圖根據查找樹的性質容易
//的到算法
BasePtr p = node->parent;
//根節點的父節點爲nullptr,需要注意
while (p && p->right == node)
{
node = p;
p = p->parent;
}
node = p;
}
}
//讓迭代器移動到上一個節點,和Increament
//算法大同小異,對稱地考慮
void Decreament()
{
if (node->left) node = node->left->maximum();
else
{
BasePtr p = node->parent;
while (p && p->left == node)
{
node = p;
p = p->parent;
}
node = p;
}
}
//模擬指針行爲,迭代器是一種智能指針
bool operator ==(const SearchTreeIteratorBase& it)
{
return this->node == it.node;
}
bool operator != (const SearchTreeIteratorBase& it)
{
return this->node != it.node;
}
};
//真正的查找樹迭代器
template<typename Value,typename Ref,typename Ptr>
struct SearchTreeIterator : public SearchTreeIteratorBase
{
typedef Value ValueType;
typedef Ref reference;
typedef Ptr pointer;
typedef SearchTreeIterator<Value, Value&, Value*> iterator;
typedef SearchTreeIterator<Value, const Value&, Value*> const_iterator;
typedef SearchTreeIterator<Value, Value&, Value*> self;
typedef BinaryNodeBase* LinkType;
typedef BinaryNode<Value>* NodePtr;
SearchTreeIterator() {}
SearchTreeIterator(LinkType x) { node = x; }
SearchTreeIterator(const iterator& it) { node = it.node; }
self& operator =(const iterator& it) { node = it.node; return *this; }
//模擬指針行爲,迭代器是一種智能指針
reference operator *(){ return NodePtr(node)->value; }
pointer operator ->() { return &(operator*()); }
self& operator ++()
{
Increament();
return *this;
}
self operator ++(int)
{
self tmp = *this;
Increament();
return tmp;
}
self& operator --()
{
Decreament();
return *this;
}
self operator --(int)
{
self tmp = *this;
Decreament();
return tmp;
}
};
template < typename T, typename Compare = less<T>>
class SearchTree
{
public:
typedef typename ParameterTrait<T>::ParameterType ParameterType;
typedef typename BinaryNode<T> NodeType;
typedef typename BinaryNode<T>* NodePtr;
typedef typename BinaryNode<T>::BasePtr BasePtr;
typedef typename SearchTreeIterator<T, T&, T*>::iterator iterator;
public:
//有兩種方式構造一棵查找樹:
// 1)構造一顆空樹。
// 2)複製一顆已經存在的樹
SearchTree() : size(0),head(nullptr){}
SearchTree(const SearchTree& st)
{
copy(st);
}
SearchTree& operator = (const SearchTree& st)
{
if (&st != this)
{
Clear();
copy(st);
}
return *this;
}
~SearchTree(){ Clear(); }
const int Size(){ return size; }
void Clear()
{
_destroy(head);
head = nullptr;
size = 0;
}
//允許插入重複值的方法(批量版)
void Insert(T values[], int count)
{
for (int i = 0; i < count; ++i)
{
Insert(values[i]);
}
}
//允許插入重複值的方法
void Insert(const ParameterType value)
{
//q表示插入後新節點的父節點
BasePtr q = find_position_for_insert(value);
//q爲nullptr,樹是空的,所以構造頭結點
if (q == nullptr)
{
create_head(value);
}
//否則,插入就好了
else
{
NodePtr node = create_node(value);
insert(&q, node);
}
}
//不允許插入重複值的方法(批量版)
void UniqueInsert(T values[], int count)
{
for (int i = 0; i < count; ++i)
{
UniqueInsert(values[i]);
}
}
//不允許插入重複值的方法
void UniqueInsert(const ParameterType value)
{
//q同Insert中,通過p可以知道樹中是否
//已經插入同樣的值了
BasePtr q = nullptr;
BasePtr p = find_first(head,value, &q);
if (q == nullptr)
{
create_head(value);
}
else
{
//p不爲空,插入會變成重複值,所以什麼也不做了
if (p != nullptr) return;
NodePtr node = create_node(value);
insert(&q, node);
}
}
iterator FindFirst(const ParameterType value)
{
BasePtr temp = nullptr;
return iterator(find_first(head,value,&temp));
}
iterator FindLast(const ParameterType value)
{
BasePtr temp = nullptr;
return iterator(find_last(head, value, &temp));
}
//從樹中刪除一個節點
void Erase(iterator& it)
{
erase(it.node);
}
//從樹中刪除一個節點
void Erase(const ParameterType value)
{
Erase(FindFirst(value));
}
//查找樹中的元素是排序的,獲得樹中最左端的節點
iterator First()
{
BasePtr first = nullptr;
if (head) first = head->minimum();
return iterator(first);
}
//查找樹中的元素是排序的,獲得樹中最右端的節點
iterator Last()
{
BasePtr last = nullptr;
if (head) last = head->maximum();
return iterator(last);
}
//nullptr指示到了盡頭了
iterator End()
{
return iterator(nullptr);
}
private:
NodePtr create_node(const ParameterType value)
{
NodePtr node = new NodeType;
node->parent = node->left = node->right = nullptr;
node->value = value;
return node;
}
void copy(const SearchTree& st)
{
__copy(&head, nullptr, st.head);
}
//複製只能採取先序的方式進行,要讓子
//節點有歸屬,得先構造父節點
void __copy(BasePtr* dst,BasePtr p, BasePtr src)
{
if (src == nullptr)
{
*dst = nullptr;
return;
}
*dst = create_node(((NodePtr)src)->value);
(*dst)->parent = p;
++size;
__copy(&((*dst)->left),*dst, src->left);
__copy(&((*dst)->right),*dst,src->right);
}
//摧毀最好採取後序的方式進行,
//這樣思考起來會自然更多
void _destroy(BasePtr p)
{
if (p == nullptr) return;
_destroy(p->left);
_destroy(p->right);
delete (NodePtr)p;
}
//刪除一個節點是最麻煩的操作
void erase(BasePtr node)
{
if (node == nullptr) return;
//如果待刪除的節點只有一個子節點
//,直接讓節點的父節點繞過它,指向
//它的子節點就好了,代碼之所以複雜,
//是因爲需要考慮父節點是否爲空,以及
//處理節點的連接時要考慮parent指針,
//而維護parent指針則是爲了支持迭代器
//只有右節點
if (node->left == nullptr)
{
BasePtr p = node;
node = node->right;
BasePtr q = parent(p);
if(node != nullptr) node->parent = q;
if (p == head) head = node;
else
{
if (q->left == p) q->left = node;
else q->right = node;
}
delete NodePtr(p);
}
//只有右節點
else if (node->right == nullptr)
{
BasePtr p = node;
node = node->left;
BasePtr q = parent(p);
if (node != nullptr) node->parent = q;
if (p == head) head = node;
else
{
if (q->left == p) q->left = node;
else q->right = node;
}
delete NodePtr(p);
}
//上面兩種情況還包含了沒有子節點的情況
//待刪除節點具有兩個節點,根據查找二叉樹的性質,
//使用其左子樹中的最大值代替待刪除節點,左子樹最大值
//節點要麼某個節點的右子節點,要麼就是待刪除節點的
//左子節點
else
{
BasePtr p = (node->left)->maximum();
BasePtr q = p->parent; //因爲node有兩個子節點,所以p的parent不會爲空
//把待刪除節點的值替換爲其左子樹中的
//最大值,實際上直接將節點進行替換更合理,
//在節點值爲類並擁有許多筆數據時效率更高,
//只是指針操作跟複雜一點
((NodePtr)node)->value = ((NodePtr)p)->value;
//最大值是某個節點的右子節點,
//該最小值節點最多擁有一個左子樹
//否則子樹中就有一個值比它還大,
//於是調整最大值節點的左子樹
if (q != node)
{
q->right = p->left;
}
//最小值是左子節點,此時左子樹最多有一個
//左子樹,於是調整最大值節點的左子樹
else
{
q->left = p->left;
}
//連接時處理parent指針
if (p->left != nullptr) p->left->parent = q;
delete NodePtr(p);
}
--size;
}
void insert(BasePtr* parent, BasePtr node)
{
node->parent = *parent;
if (*parent == nullptr)
head = node;
else if (compare((NodePtr(node))->value, ((NodePtr)(*parent))->value))
(*parent)->left = node;
else
(*parent)->right = node;
++size;
}
void create_head(const ParameterType value)
{
head = create_node(value);
++size;
}
BasePtr left(const BasePtr node){ if (node) return node->left; else return nullptr; }
BasePtr right(const BasePtr node){ if (node) return node->right; else return nullptr; }
BasePtr parent(const BasePtr node){ if (node) return node->parent; else return nullptr; }
BasePtr find_first(BasePtr h,const ParameterType value,BasePtr* last_visited_valid_node)
{
BasePtr res = nullptr;
BasePtr p = h;
*last_visited_valid_node = nullptr;
while (p)
{
*last_visited_valid_node = p;
if (compare(value, ((NodePtr)p)->value)) p = p->left;
else if (value == ((NodePtr)p)->value){ res = p; break; }
else p = p->right;
}
return res;
}
//假設有重複,那麼根據插入算法,後來的重複值都被插到第一個值
//的右子樹中,但未必就是它的右子節點,所以查找到一個節點後,
//還要在該節點的右子樹中繼續查找
BasePtr find_last(BasePtr h,const ParameterType value, BasePtr* last_visited_valid_node)
{
BasePtr res = find_first(h,value, last_visited_valid_node);
if (res != nullptr)
{
BasePtr last_visited_valid_node_1 = nullptr;
BasePtr res_1 = find_first(res->right, value, &last_visited_valid_node_1);
if (res_1 != nullptr)
{
res = res_1;
*last_visited_valid_node = last_visited_valid_node_1;
}
}
return res;
}
//找到位置一定是插入節點的父節點
BasePtr find_position_for_insert(const ParameterType value)
{
BasePtr res = nullptr;
BasePtr p = head;
while (p)
{
res = p;
if (compare(value, ((NodePtr)p)->value)) p = p->left;
else p = p->right;
}
return res;
}
private:
BasePtr head;
int size;
Compare compare;
};
}
#endif
上面連同註釋一共使用了500行代碼,應該還有可以精簡的地方,代碼行數越少越好,想起剛學編程的時候只想着怎樣把代碼寫長,不禁好笑,現在呢覺得對於完成某個功能的代碼,不丟失可讀性的情況下越短越好,個人寫過的總代碼行呢,則是越多越好,這一多一少應該就是作爲一個學習者應該追求的吧。
Functor.h文件的代碼:
#ifndef _FUNCTOR_H_
#define _FUNCTOR_H_
#include "../Utilites/type_traits.h"
namespace MyDataStructure
{
template<typename T>
struct less
{
typedef typename ParameterTrait<T>::ParameterType ParameterType;
bool operator ()(const ParameterType op1, const ParameterType op2)
{
return op1 < op2;
}
};
}
#endif
type_traits.h文件前面的博文已經展示過,不在列出。測試代碼(有重複插入):
// SearchTreeTest.cpp : 定義控制檯應用程序的入口點。
//
#include "stdafx.h"
#include <iostream>
#include "../../include/SearchTree.h"
using namespace MyDataStructure;
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
int v[] = { 1, 3, 3, 9, 7, 9, 4, 2, 8, 10 };
SearchTree<int> st;
st.Insert(v, 10);
for (SearchTree<int>::iterator it = st.Last(); it != st.End(); --it)
cout << *it << " ";
cout << endl;
SearchTree<int> st1(st);
st = st;
SearchTree<int>::iterator it1 = st.FindFirst(9), it2 = st.FindLast(9);
cout << "they are " << (it1 == it2) <<" : "<<*it1<<" "<<*it2<<endl;
for (int i = 0; i < 10; ++i)
{
st.Erase(v[i]);
}
for (SearchTree<int>::iterator it = st.Last(); it != st.End(); --it)
cout << *it << " ";
cout << endl;
for (SearchTree<int>::iterator it = st1.First(); it != st1.End(); ++it)
cout << *it << " ";
cout << endl;
const int n = st.Size();
cout << n << endl;
const int m = st1.Size();
cout << m << endl;
st.Clear();
return 0;
}
運行結果:
把插入操作改爲不允許插入重複值的版本:
st.Insert(v, 10);
改爲:
st.UniqueInsert(v, 10);
在運行,得到結果:
可以看到,程序通過了基本的測試,而對於一些比較複雜的測試則不再構造了,本序列博客與程序只爲學習算法和數據結構,程序的健壯性還暫不考慮。寫這個數據結構最複雜的就是刪除操作,參看了四本書:《STL源碼剖析》、《算法導論》、《數據結構與算法分析(第二版)》以及嚴蔚敏版的《數據結構》,個人感覺最後一本講得最清楚,倒數第二本是用遞歸實現的,容易懂,但是不是太喜歡。寫完了這個結構就可以做許多關於二叉樹的題目了,如果不以查找樹這種方式構建二叉樹,輸入就太麻煩了,現在有了這一個類,後面的題目全部自主產權,還能繼續測試這個類,真是一舉兩得。