1.二叉查找树
- 二叉查找树或者为空,或者满足以下性质:
- 每个结点的左子树中所有结点的值都小于该结点的值。
- 每个结点的右子树中所有结点的值都大于该结点的值。
- 每个结点的左、右子树也是二叉查找树。
类定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
| template <class KEY, class OTHER> class BinarySearchTree: public dynamicSearchTable<KEY, OTHER> { private: struct BinaryNode { SET<KEY, OTHER> data; BinaryNode *left; BinaryNode *right; BinaryNode( const SET<KEY, OTHER> & thedata, BinaryNode *lt=NULL, BinaryNode *rt=NULL ) : data( thedata ), left( lt ), right( rt ) { } }; BinaryNode *root;
public: BinarySearchTree( ) ; ~BinarySearchTree( ); SET<KEY, OTHER> *find(const KEY &x) const ; void insert( const SET<KEY, OTHER> & x ); void remove( const KEY & x );
private: void insert( const SET<KEY, OTHER> & x, BinaryNode *& t ); void remove( const KEY & x, BinaryNode *& t ); SET<KEY, OTHER> *find(const KEY &x, BinaryNode *t ) const; void makeEmpty( BinaryNode *t ); };
template <class KEY, class OTHER> SET<KEY, OTHER> *BinarySearchTree<KEY, OTHER>::find(const KEY &x, BinaryNode* t) const { if (t == nullptr || t->data.key == x) return (t); if (x < t->data.key) return find(x, t->left); else return find(x, t->right); }
template <class KEY, class OTHER> void BinarySearchTree<KEY, OTHER>::insert(const SET<KEY, OTHER> & x, BinaryNode *& t) { if (t == nullptr) { t = new BinaryNode(x, nullptr, nullptr); } else if (x.key < t->data.key) { insert(x, t->left); } else if (x.key > t->data.key) { insert(x, t->right); } }
template <class KEY, class OTHER> void BinarySearchTree<KEY, OTHER>::remove( const KEY & x, BinaryNode * & t ) { if( t == NULL ) return; if ( x < t->data.key ) remove( x, t->left ); else if( t->data.key < x ) remove( x, t->right ); else if( t->left != NULL && t->right != NULL ) { BinaryNode *tmp = t->right; while (tmp->left != NULL) tmp = tmp->left; t->data = tmp->data; remove( t->data.key, t->right ); } else { BinaryNode *oldNode = t; t = ( t->left != NULL ) ? t->left : t->right; delete oldNode; } }
|
二叉查找树的平均性能
最优情况(平衡BST)
- 树高度:O(log n)
- 查找/插入/删除时间复杂度:O(log n)
- 条件:树完全平衡,左右子树高度差≤1
最差情况(退化成链表)
- 树高度:O(n)
- 查找/插入/删除时间复杂度:O(n)
- 条件:所有节点只有左子树或右子树
平均情况分析
- n 个结点二叉查找树左子树的节点数有n种可能性,如果概率是相等的。设P(n)为查找n个结点的二叉查找树的平均查找时间,则:
1 2
| P(n) = sum(i = 0 ~ n - 1){1/n * [1 + (P(i) * i + (P(n - i - 1) + 1) * (n - i - 1))]} = 2 * (1 + 1/n) * ln(n) = 1.38log(n)
|
2.平衡二叉查找树
- 平衡因子(平衡度):结点的平衡度是结点的左子树的高度-右子树的高度。
- 空树的高度定义为0(树的高度的定义)。
- 平衡二叉树:每个结点的平衡因子都为 +1、-1、0 的二叉树。或者说每个结点的左右子树的高度最多差1的二叉树。
- 可以证明平衡树的高度至多约为:1.44log(N+2) -1.328
平衡二叉树的性能(记忆)
定理:具有 N 个结点的平衡树,高度 h 满足:
1
| log(2, N+1) <= h <= 1.44log(2, N+1) - 0.328;
|
因此,平衡二叉树的操作都是O(logN)。
类定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| template <class KEY, class OTHER> class AvlTree: public dynamicSearchTable<KEY, OTHER> { struct AvlNode { SET<KEY, OTHER> data; AvlNode *left; AvlNode *right; int height; AvlNode( const SET<KEY, OTHER> &element , AvlNode *lt, AvlNode *rt, int h=1) : data(element), left( lt ), right( rt ), height(h) { } }; AvlNode *root; public: AvlTree() { root = NULL; } ~AvlTree( ) { makeEmpty( root); } SET<KEY, OTHER> *find( const KEY & x ) const; void insert( const SET<KEY, OTHER> & x ) ; void remove( const KEY & x ); private: void insert( const SET<KEY, OTHER> & x, AvlNode * & t ) ; bool remove( const KEY & x, AvlNode * & t ) ; void makeEmpty( AvlNode *t ); int height( AvlNode *t ) const { return t == NULL ? 0 : t->height;} void LL( AvlNode * & t ); void LR( AvlNode * & t ); void RL( AvlNode * & t ); void RR( AvlNode * & t ); int max(int a, int b) {return (a>b)?a:b;} bool adjust(AvlNode *&t, int subTree); };
|
查找
1 2 3 4 5 6 7 8 9 10 11 12
| //查找的非递归实现 template <class KEY, class OTHER> SET<KEY, OTHER> *AvlTree<KEY, OTHER>::find(const KEY &x) const { AvlNode *t = root; while (t!=NULL && t->data.key != x) { if (t->data.key > x) t = t->left; else t = t->right; } if (t==NULL) return NULL; else return t; }
|
插入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| //插入 template <class KEY, class OTHER> void AvlTree<KEY, OTHER>::insert(const SET<KEY, OTHER> &x, AvlNode *&t) { if (t == NULL) // 在空树上插入 t = new AvlNode(x, NULL, NULL); else if (x.key < t->data.key) { // 在左子树上插入 insert(x, t->left); if (height(t->left) - height(t->right) == 2) // t失衡 if (x.key < t->left->data.key) LL(t); // 左左情况 - 右单旋 else LR(t); // 左右情况 - 先左旋后右旋 } else if (t->data.key < x.key) { // 在右子树上插入 insert(x, t->right); if (height(t->right) - height(t->left) == 2) // t失衡 if (t->right->data.key < x.key) RR(t); // 右右情况 - 左单旋 else RL(t); // 右左情况 - 先右旋后左旋 } // 重新计算t的高度 t->height = max(height(t->left), height(t->right)) + 1; // 调用树高的操作求左右子树高度 }
//LL情况(O(1)) template <class KEY, class OTHER> void AvlTree<KEY, OTHER>::LL( AvlNode * & t ) { AvlNode *t1 = t->left; // 未来的树根 t->left = t1->right; t1->right = t; t->height = max( height( t->left ), height( t->right ) ) + 1; t1->height = max( height( t1->left ), height(t)) + 1; t = t1; }
//RR情况(O(1)) template <class KEY, class OTHER> void AvlTree<KEY, OTHER>::RR( AvlNode * & t ) { AvlNode *t1 = t->right; // 未来的树根 t->right = t1->left; t1->left = t; t->height = max( height( t->left ), height( t->right ) ) + 1; t1->height = max( height( t1->right ), height(t)) + 1; t = t1; }
//LR情况(先RR再LL) template <class KEY, class OTHER> void AvlTree<KEY, OTHER>::LR( AvlNode * & t ) { RR( t->left ); LL( t ); }
//RL情况(先LL再RR) template <class KEY, class OTHER> void AvlTree<KEY, OTHER>::RL( AvlNode * & t ) { LL( t->right ); RR( t ); }
|
删除
- 结点删除同二叉查找树。在删除了叶结点或只有一个孩子的结点后,子树变矮,返回false。
- 每次递归调用后,检查返回值。如果是true,直接返回true。否则分5种情况进行处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
|
template <class KEY, class OTHER> bool AvlTree<KEY, OTHER>::remove(const KEY &x, AvlNode *&t) { if (t == NULL) return true; if (x == t->data.key) { if (t->left == NULL || t->right == NULL) { AvlNode *oldNode = t; t = (t->left != NULL) ? t->left : t->right; delete oldNode; return false; } else { AvlNode *tmp = t->right; while (tmp->left != NULL) tmp = tmp->left; t->data = tmp->data; if (remove(tmp->data.key, t->right)) return true; return adjust(t, 1); } } if (x < t->data.key) { if (remove(x, t->left)) return true; return adjust(t, 0); } else { if (remove(x, t->right)) return true; return adjust(t, 1); } }
|
调整
- 进入调整函数,一定是某棵子树变矮了;
- 调整函数检查结点有没有失衡。如果失衡,则做相应的调整;
- 函数的返回值是子树有没有变矮,变矮返回false,否则返回true;
- 函数的第一个参数是所要检查的结点的地址t。第二个参数是t的哪棵子树变矮了。0是左子树变矮,1 是右子树变矮。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| template <class KEY, class OTHER> bool AvlTree<KEY, OTHER>::adjust(AvlNode *&t, int subTree) { if (subTree) { // 在右子树上删除,使右子树变矮 if (height(t->left) - height(t->right) == 1) return true; // 情况a:已平衡 if (height(t->right) == height(t->left)) { // 情况b:高度不变但需更新 --t->height; return false; } if (height(t->left->right) > height(t->left->left)) { // 情况d:LR型失衡 LR(t); return false; } LL(t); // 情况c和e:LL型失衡 if (height(t->right) == height(t->left)) return false; else return true; } else { // 在左子树删除 if (height(t->right) - height(t->left) == 1) return true; // 情况a:已平衡 if (height(t->right) == height(t->left)) { // 情况b:高度不变但需更新 --t->height; return false; } if (height(t->right->left) > height(t->right->right)) { // 情况d:RL型失衡 RL(t); return false; } RR(t); // 情况c和e:RR型失衡 if (height(t->right) == height(t->left)) return false; else return true; } }
|
总结
- 与插入操作一样, 失衡节点存在于被删节点到根节点的路径上;
- 在删除一个结点后,必须沿着到根结点的路径向上回溯,随时调整路径上的结点的平衡度;
- 插入时,最多只需要调整一个结点。而删除时,无法保证子树在平衡调整后的高度不变。只有当某个结点的高度在删除前后保持不变,才无需继续调整;
- 递归的删除函数有一个布尔型的返回值。当返回值为true时,调整停止。当返回值为false时,继续调整。
3.哈希查找
不比较关键字的大小。以关键字值 KEY为自变量,利用哈希函数计算查找位置。理想情况下,时间复杂度为 O(1)。但它不支持有关有序操作。
哈希函数
- 每个结点在表中的存储位置是由一个函数H确定。该函数以结点的关键字值为自变量,计算出该关键字对应的结点的存储位置。该函数称为哈希函数。
- 哈希函数的值域为
0 ~(m - 1)
- 哈希函数的选择标准
- 计算速度快
- 散列地址尽可能均匀,使得冲突机会尽可能的少
常用的哈希(散列)函数:用于查找的哈希
直接地址法
H(key) = key 或 H(key) = a * key + b 如:关键字集合为{100,400,600,200,800,900},取散列函数为H(x)=key,则需要901个单元。取H(x)=x/100,需要10个单元。
除留余数法
H(key)= key MOD p 或 H(key)= key MOD p + c 这里p为小于等于m素数。如:m = 1024, 则 p = 1019 。
- 最常用,余数总在
0 ~ p-1 之间。
- 选取 p 为素数,散列函数值分布会比较均匀。(较好)
数字分析法
- 对关键字集合中的所有关键字,分析每一位上数字分布。取数字分布均匀的位作为地址的组成部分。
1 2 3 4 5 6 7 8
| 3 4 7 0 5 2 4 3 4 9 1 5 8 7 3 4 8 2 5 9 6 3 4 8 5 5 7 0 3 4 8 6 5 0 5 3 4 9 8 5 5 8 3 4 7 9 5 7 1 1-2-3-4-5-6-7----列数
|
第1、2、5列对区分不同的关键字完全没有意义,第3列意义较小。于是可以只选择4、6、7三列的值。
平方取中法
- 将关键字平方后,取其结果的中间各位作为散列函数值。由于中间各位和每一位数字都有关系,因此均匀分布的可能性较大。
- 比如:4731 * 4731 = 22,382,361。中间部分究竟要选取几位,依赖于散列表的单元总数。若散列表总共有100 个单元,我们可以选取最中间的部分,即第4、5位,那么关键字值为4731的结点的散列地址可选为82。
折叠法
- 如果关键字相当长,以至于和散列表的单元总数相比大得多时,可采用此法。
- 具体实现:是选取一个长度后,将关键字按此长度分组相加。
- 例如,关键字值为542242241,按3位折叠,可以得到542+242+241=1025。抛弃进位,得到散列结果为25。
冲突问题
要选择一个一一对应的哈希函数很困难。一般的哈希函数都是多对一。当两个以上的关键字映射到一个存储单元时,称为冲突或碰撞。
闭散列表法:利用本散列表中的空余单元
线性探测法
发生冲突时探测下一个单元,直到找到空闲单元为止。即:H(key) = (H(key) + i) MOD m 其中i为探测次数。
- 初级冲突:不同关键字值的结点得到同一个散列地址。
- 二次聚集:同不同散列地址的结点争夺同一个单元。
- 结果:冲突加剧,最坏时可能达到 O(n)级代价。
二次探测再散列,随机探测再散列
- 二次探测:
H(key) = (H(key) + i^2) MOD m 其中i为探测次数。
- 随机探测:
H(key) = (H(key) + i * rand()) MOD m 其中i为探测次数,rand()为随机数。
再hashing法
- 出现冲突时,采用多个 hashing 函数计算散列地址,直到找到空单元为止。
Hi = RHi(key) i=1,2,…,k
RHi为不同的哈希函数。
- 例如,若两个hashing函数,则探测序列如下:
H1(x),(H1(x)+H2(x))mod M,(H1(x)+2*H2(x))mod M……
不容易“聚集”,但增加了计算时间。
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
| template <class KEY, class OTHER> class closeHashTable : public dynamicSearchTable<KEY, OTHER> { private: struct node { SET<KEY, OTHER> data; int state; node() { state = 0; } }; node *array; int size; int (*key)(const KEY &x); static int defaultKey(const int &x) { return x; } public: closeHashTable(int length = 101, int (*f)(const KEY &x) = defaultKey); ~closeHashTable() { delete[] array; } SET<KEY, OTHER> *find(const KEY &x) const; void insert(const SET<KEY, OTHER> &x); void remove(const KEY &x); };
template <class Type> closeHashTable<Type>::closeHashTable(int length, int (*f)(const Type &x) ) { size = length; array = new node[size]; key = f; }
template <class KEY, class OTHER> void closeHashTable<KEY, OTHER>::insert(const SET<KEY, OTHER> &x) { int initPos, pos; initPos = pos = key(x.key) % size; do { if (array[pos].state != 1) { array[pos].data = x; array[pos].state = 1; return; } pos = (pos+1) % size; } while (pos != initPos); }
template <class KEY, class OTHER> void closeHashTable<KEY, OTHER>::remove(const KEY &x) { int initPos, pos; initPos = pos = key(x) % size; do { if (array[pos].state == 0) return; if (array[pos].state == 1 && array[pos].data.key == x) { array[pos].state = 2; return; } pos = (pos + 1) % size; } while (pos != initPos); }
template <class KEY, class OTHER> SET<KEY, OTHER> *closeHashTable<KEY, OTHER>::find(const KEY &x) const { int initPos, pos; initPos = pos = key(x) % size; do { if (array[pos].state == 0) return NULL; if (array[pos].state == 1 && array[pos].data.key == x) return (SET<KEY, OTHER> *)&array[pos].data; pos = (pos + 1) % size; } while (pos != initPos); return NULL; }
|
开散列表法
- 链地址法 :将具有同一散列地址的结点保存于M 存区的各自的链表之中。
- 公共溢出区法:将发生冲突的结点都存放在一个公共溢出区内。M 存区只存放一个记录。发生冲突的记录都存放在公共溢出区内。M 存区和公共溢出区都可以分配几个磁道或柱面作为存储空间。
类定义(了解)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
| template <class KEY, class OTHER> class openHashTable : public dynamicSearchTable<KEY, OTHER> { private: struct node { SET<KEY, OTHER> data; node *next; node(const SET<KEY, OTHER> &d, node *n = NULL) { data = d; next = n; } node() { next = NULL; } }; node **array; int size; int (*key)(const KEY &x); static int defaultKey(const int &x) { return x; } public: openHashTable(int length = 101, int (*f)(const KEY &x) = defaultKey); ~openHashTable(); SET<KEY, OTHER> *find(const KEY &x) const; void insert(const SET<KEY, OTHER> &x); void remove(const KEY &x); };
template <class KEY, class OTHER> openHashTable<KEY, OTHER>::openHashTable (int length, int (*f)(const KEY &x) ) { size = length; array = new node* [size]; key = f; for (int i = 0; i < size; ++i) array[i] = NULL; }
template <class KEY, class OTHER> openHashTable<KEY, OTHER>::~openHashTable() { node *p, *q; for (int i = 0; i< size; ++i) { p = array[i]; while (p!=NULL) { q= p->next; delete p; p = q; } ; } delete [] array; }
template <class KEY, class OTHER> void openHashTable<KEY, OTHER>::insert (const SET<KEY, OTHER> &x) { int pos ; node *p; pos = key(x.key) % size; array[pos] = new node(x, array[pos]); }
template <class KEY, class OTHER> void openHashTable<KEY, OTHER>::remove(const KEY &x) { int pos ; node *p, *q; pos = key(x) % size; if (array[pos] == NULL) return; p = array[pos]; if (array[pos]->data.key == x) { array[pos] = p->next; delete p; return; } while (p->next != NULL && !(p->next->data.key == x) ) p = p->next; if (p->next != NULL) { q = p->next; p->next = q->next; delete q; } }
template <class KEY, class OTHER> SET<KEY, OTHER> *openHashTable<KEY, OTHER> ::find(const KEY &x) const { int pos ; node *p; pos = key(x) % size; p = array[pos]; while (p != NULL && !(p->data.key == x) ) p = p->next; if (p == NULL) return NULL; else return (SET<KEY, OTHER> *)p; }
|