1.树的抽象类
1 2 3 4 5 6 7 8 9 10 11 template <class T >class tree {public : virtual void clear () = 0 ; virtual bool isEmpty () const = 0 ; virtual T root (T flag) const = 0 ; virtual T parent (T x, T flag) const = 0 ; virtual T child(T x, int i, T flag) const = 0 ; virtual void remove (T x, int i) = 0 ; virtual void traverse () const = 0 ; };
2.二叉树
基本定义
二叉树(Binary Tree)是结点的有限集合,它或者为空,或者由一个根结点及两棵互不相交的左、右子树构成,而其左、右子树又都是二叉树。
注意:二叉树必须严格区分左右子树。即使只有一棵子树,也要说明它是左子树还是右子树。交换一棵二叉树的左右子树后得到的是另一棵二叉树。
二叉树和有序树是不同的概念。
树和二叉树都是树型结构。二叉树不是一种特殊的树。二叉树可以为空。树不能为空 。
满二叉树
一棵高度为k并具有2k-1个结点的二叉树称为满二叉树。
一棵二叉树中任意一层的结点个数都达到了最大值。
完全二叉树
在满二叉树的基础上,最后一层的结点可以不满,但必须从左到右依次排列。
所有叶结点都出现在最低的两层上。
对任意结点,如果其右子树高度为k,则其左子树高度为k或k-1。
二叉树性质
一棵非空二叉树的第 i 层上最多有2^(i - 1)个结点(i≥1)。
一颗高度为k的二叉树最多有2^k - 1个结点。
对于一棵非空二叉树,如果叶子结点数为n0,度数(子结点的个数)为2的结点数为n2,则有:n0 = n2 + 1 成立。
结论推广:对于一棵非空K叉树,如果叶子结点数为 n0,度数为2的结点数为 n2,依次类推,度数为k的结点数为 nk,则有:
n0 = n2 + 2 * n3 + … + (k - 1) * nk + 1成立。
具有n个结点的完全二叉树的高度k = int(log(2, n)) + 1
二叉树抽象类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 template<class T> class bTree { public: virtual void clear() = 0; virtual bool isEmpty() const = 0; virtual T Root(T flag) const = 0; virtual T parent(T x, T flag) const = 0; virtual T lchild(T x, T flag) const = 0; virtual T rchild(T x, T flag) const = 0; virtual void delLeft(T x) = 0; virtual void delRight(T x) = 0; virtual void preOrder() const = 0; virtual void midOrder() const = 0; virtual void postOrder() const= 0; virtual void levelOrder() const = 0; };
二叉树的遍历
前序遍历
如果二叉树为空,则操作为空,否则:
中序遍历
如果二叉树为空,则操作为空,否则:
后序遍历
如果二叉树为空,则操作为空,否则:
Tip:根据前序序列+中序序列可以唯一确定一棵二叉树。根据后序序列+中序序列也可以唯一确定一棵二叉树。
二叉树的类定义
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 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 template <class T >class binaryTree : public bTree<T> { friend void printTree (const binaryTree &t, T flag) ; private :struct Node { Node *left , *right ; T data; Node () : left (NULL ), right (NULL ) { } Node (T item, Node *L = NULL , Node * R =NULL ) : data (item), left (L), right (R) {} ~Node () {} }; Node *root; public : binaryTree () : root (NULL ) {} binaryTree (T x) { root = new Node (x); } ~binaryTree (); void clear () ; bool isEmpty () const ; T Root (T flag) const ; T lchild (T x, T flag) const ; T rchild (T x, T flag) const ; void delLeft (T x) ; void delRight (T x) ; void preOrder () const ; void midOrder () const ; void postOrder () const ; void levelOrder () const ; void createTree (T flag) ; private : Node *find (T x, Node *t ) const ; void clear (Node *&t) ; void preOrder (Node *t) const ; void midOrder (Node *t) const ; void postOrder (Node *t) const ; }; template <class T >bool binaryTree<T>::isEmpty () const { return root == NULL ; } template <class T >T binaryTree<T>::Root (T flag) const { if (root == nullptr ) return flag; else return root->data; } template <class T >int binaryTree<T>::count (Node *t) const { if (t == nullptr ) return 0 ; return 1 + count (t->left) + count (t->right); } template <class T >int binaryTree<T>::height (Node *t) const { if (t == nullptr ) return 0 ; int leftHeight = height (t->left); int rightHeight = height (t->right); return (leftHeight > rightHeight ? leftHeight : rightHeight) + 1 ; } template <class T >void binaryTree<T>::clear () { clear (root); } template <class T >void binaryTree<T>::clear (Node *&t) { if (t != nullptr ) { clear (t->left); clear (t->right); delete t; t = nullptr ; } } template <class T >void binaryTree<T>::preOrder (binaryTree<T>::Node *t) const { if (t == NULL ) return ; cout << t->data << ' ' ; preOrder (t->left); preOrder (t->right); } template <class T >void binaryTree<T>::midOrder (binaryTree<T>::Node *t) const { if (t == NULL ) return ; midOrder (t->left); cout << t->data << ' ' ; midOrder (t->right); } template <class T >void binaryTree<T>::postOrder (binaryTree<T>::Node *t) const { if (t == NULL ) return ; postOrder (t->left); postOrder (t->right); cout << t->data << ' ' ; } template <class T >void binaryTree<T>::levelOrder () const { linkQueue< Node * > que; Node *tmp; que.enQueue (root); while (!que.isEmpty ()) { tmp = que.deQueue (); cout << tmp->data << ' ' ; if (tmp->left) que.enQueue (tmp->left); if (tmp->right) que.enQueue (tmp->right); } }
层次构建二叉树
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 template <class Type >void BinaryTree<Type>::createTree (Type flag){ linkQueue<Node *> que; Node *tmp; Type x, ldata, rdata; cout << "\n输入根结点:" ; cin >> x; root = new Node (x); que.enQueue (root); while (!que.isEmpty ()) { tmp = que.deQueue (); cout << "\n输入" << tmp->data << "的两个儿子(" << flag << "表示空结点):" ; cin >> ldata >> rdata; if (ldata != flag)que.enQueue (tmp->left = new Node (ldata)); if (rdata != flag) que.enQueue (tmp->right = new Node (rdata)); } cout << "create completed!\n" ; }
二叉树遍历的非递归实现
递归是程序设计中强有力的工具。
递归程序结构清晰、明了、美观。
递归程序的弱点:它的时间、空间的效率比较低。
所以在实际使用中,我们常常希望使用它的非递归版本。二叉树的遍历也是如此。尽管二叉树遍历的递归函数非常简洁,但有时我们还是希望使用速度更快的非递归函数。
非递归前序遍历
前序遍历第一个被访问的结点是根结点,然后访问左子树,最后访问右子树。
可以设置一个栈,保存将要访问的树的树根。
开始时,把二叉树的根结点存入栈中。然后重复以下过程,直到栈为空:
从栈中取出一个结点,输出根结点的值;
然后把右子树,左子树放入栈中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 template <class Type >void BinaryTree<Type>::preOrder () const { linkStack<Node *> s; Node *current; cout << "前序遍历: " ; s.push (root); while (!s.isEmpty ()) { current = s.pop (); cout << current->data; if ( current->right != NULL ) s.push ( current->right ); if ( current ->left != NULL ) s.push ( current->left ); } }
非递归中序遍历
采用一个栈存放要遍历的树的树根。
中序遍历中,先要遍历左子树,接下去才能访问根结点,因此,当根结点出栈时,我们不能访问它,而要访问它的左子树,此时要把树根结点暂存一下。
由于左子树访问完后还要访问根结点,因此仍可以把它存在栈中,接着左子树也进栈。此时执行出栈操作,出栈的是左子树。左子树问结束后,再次出栈的是根结点,此时根结点可被访问。根结点访问后,访问右子树,则将右子树进栈。
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 struct StNode { Node *node; int TimesPop; StNode ( Node *N = NULL ):node (N), TimesPop (0 ) {} }; template <class Type >void BinaryTree<Type>::midOrder () const { linkStack<StNode> s; StNode current (root) ; cout << "中序遍历: " ; s.push (current); while (!s.isEmpty ()) { current = s.pop (); if ( ++current.TimesPop == 2 ) { cout << current.node->data; if ( current.node->right != NULL ) s.push (StNode (current.node->right )); } else { s.push ( current ); if ( current.node->left != NULL ) s.push (StNode (current.node->left)); } } }
非递归后序遍历
将中序遍历的非递归实现的思想进一步延伸,可以得到后序遍历的非递归实现。
当以后序遍历一棵二叉树时,先将树根进栈,表示要遍历这棵树。
根结点第一次出栈时,根结点不能访问,应该访问左子树。于是,根结点重新入栈,并将左子树也入栈。
根结点第二次出栈时,根结点还是不能访问,要先访问右子树。于是,根结点再次入栈,右子树也入栈。
当根结点第三次出栈时,表示右子树遍历结束,此时,根结点才能被访问。
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 template <class Type >void BinaryTree<Type>::postOrder () const { linkStack< StNode > s; StNode current (root) ; cout << "后序遍历: " ; s.push (current); while (!s.isEmpty ()) { current = s.pop (); if (++current.TimesPop == 3 ) { cout << current.node->data; continue ; } s.push (current); if (current.TimesPop == 1 ) { if (current.node->left != NULL ) s.push (StNode (current.node->left)); } else { if (current.node->right != NULL ) s.push (StNode (current.node->right)); } } }
3.哈夫曼树与哈夫曼编码
前缀编码与哈夫曼树
字符只放在叶结点中。
字符编码可以有不同长度。
每个字符的编码都不可能是其他字符编码的前缀
前缀编码可被惟一解码
哈夫曼树是一棵最小代价的二叉树,所有的字符都包含在叶结点上。
哈夫曼算法
给定一个具有n个权值的结点的集合A。
执行n - 1次循环,在每次循环时执行以下操作:从当前集合中选取权值最小、次最小的两个结点,以这两个结点作为内部结点bi的左右儿子,bi的权值为其左右儿子权值之和。
在集合中去除这两个权值最小、次最小的结点,并将内部结点bI加入其中。这样,在集合A中,结点个数便减少了一个。这样,在经过了n-1次循环之后,集合A中只剩下了一个结点,这个结点就是根结点。
哈夫曼树的存储
在哈夫曼树中,每个要编码的元素是一个叶结点,其它结点都是度数为2的节点。
一旦给定了要编码的元素个数,由n0 = n2 + 1可知哈夫曼树的大小为2n-1。
哈夫曼树可以用一个大小为2n的数组来存储。0位置不用 ,根存放在下标1位置。叶结点依次放在n+1到2n的位置。
每个数组元素保存的信息:结点的数据、权值和父结点和左右孩子的位置。
数组下标的意义:
1表示根结点。
2i表示结点i的左孩子。
2i + 1表示结点i的右孩子。
i / 2表示结点i的父结点。
哈夫曼树的实现
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 template <class Type >class hfTree {private : struct Node { Type data; int weight; int parent, left, right; } Node* elem; int length; public : struct hfCode { Type data; string code; }; hfTree (const Type* x, const int * w, int size); ~hfTree () { delete [] elem; } void getCode (hfCode result[]) ; } template <class Type >hfTree<Type>::hfTree (const Type* v, const int * w, int size) { const int MAX_INT = 32767 ; int min1, min2; int x, y; length = 2 * size; elem = new Node[length]; for (int i = size; i < length; ++i) { elem[i].weight = w[i - size]; elem[i].data = v[i - size]; elem[i].parent = elem[i].left = elem[i].right = 0 ; } for (int i = size - 1 ; i > 0 ; --i) { min1 = min2 = MAX_INT; x = y = 0 ; for (int j = i + 1 ; j < length; ++j) { if (elem[j].parent == 0 ) { if (elem[j].weight < min1) { min2 = min1; min1 = elem[j].weight; x = y; y = j; } else if (elem[j].weight < min2) { min2 = elem[j].weight; x = j; } } } elem[i].weight = min1 + min2; elem[i].left = x; elem[i].right = y; elem[i].parent = 0 ; elem[x].parent = i; elem[y].parent = i; } }
getCode()函数的实现
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 template <class Type >void hfTree<Type>::getCode (hfCode result[]) { int size = length / 2 ; int p, s; for (int i = size; i < length; ++i) { result[i - size].data = elem[i].data; result[i - size].code = "" ; p = elem[i].parent; s = i; while (p != 0 ) { if (elem[p].left == s) { result[i - size].code = '0' + result[i - size].code; } else { result[i - size].code = '1' + result[i - size].code; } s = p; p = elem[p].parent; } } }
示例
1 2 3 4 5 [1 ] (26 ) / \ [2 ](14 ) [5 ](12 ) / \ [3 ](5 ) [4 ](9 )
叶子节点:
elem[3]:A(权重 5)
elem[4]:B(权重 9)
elem[5]:C(权重 12)
生成的编码:
A(elem[3]):
父节点是[2],且是左孩子 → 添加 0。
父节点是[1],且是左孩子 → 添加 0。
最终编码:00
B(elem[4]):
父节点是[2],且是右孩子 → 添加 1。
父节点是[1],且是左孩子 → 添加 0。
最终编码:01
C(elem[5]):
父节点是[1],且是右孩子 → 添加 1。
最终编码:1