一、树
- n个结点的度数和=n-1
- 高度:
- 深度:
- 树的高度/深度:
二、二叉树
特性
1.包含n (n>0)个节点的二叉树边数为n-1
2.若二叉树的高度为h,h≥0,则它最少有h个节点,最多有2^h-1个节点
(按照节点计算:1、2、4、…2^(h-1)相加)
3.包含n个节点的二叉树的高度最大为n,最小为[log2(n+1)](向上取整)
n<=2^h-1(由2) -> h>=log2(n+1)
满二叉树(哈夫曼树):除叶子节点,其余节点均有2个孩子
完美二叉树:高度为h,节点数2^h-1
完全二叉树:由满二叉树引出来的,若设二叉树的
深度为h
,除第 h 层外
,其它各层 (1~h-1) 的结点数都达到最大个数(即1~h-1层为一个满二叉树)
,第 h 层所有的结点都连续集中在最左边,右侧缺失若干。深度为[log2(n+1)](向上取整)
4.设完全二叉树中,一节点序号为i,1<=i<=n,则:
1)i>1时,该元素父节点编号为[i/2]
2)若2i>n,该元素无左孩子,否则其左孩子编号为2i
3) 若2i+1>n,该元素无右孩子,否则其右孩子编号为2i+1
5.设二叉树中度为2的节点有n2个,度为1的节点有n1个,度为0的节点有n0个,
则n0=n2+1(度之和=n-1;n=n2+n1+n0 两式推导)
二叉树描述
1.数组描述(顺序存储)——利用特征4
数组位置:节点编号(可能造成空间浪费)
2.链表描述
树节点——节点类对象:
数据域、指针:LeftChild、RightChild
n-1条边 -> 2n-(n-1)=n+1个空指针
(合并树和分裂树的操作要好好看,尤其是对指针的操作)
#include<iostream>
using namespace std;
template <class T>
class BinaryTreeNode {
public:
BinaryTreeNode()
{ LeftChild = RightChild = 0; }
BinaryTreeNode(const T& e)
{ data = e; LeftChild = RightChild = 0; }
BinaryTreeNode(const T& e, BinaryTreeNode* l, BinaryTreeNode* r)
{ data = e; LeftChild = l; RightChild = r; }
private:
T data;
BinaryTreeNode<T>* LeftChild, // left subtree
* RightChild; // right subtree
};
template<class T>
class BinaryTree
{
public:
BinaryTree() { root = 0; };
~BinaryTree() {};
bool IsEmpty() const
{
return ((root) ? false : true);
}
bool Root(T& x) const;//取x为根节点,返回成功或失败
void MakeTree(const T& element,
BinaryTree<T>& left, BinaryTree<T>& right);
void BreakTree(T& element, BinaryTree<T>& left,
BinaryTree<T>& right);
void PreOrder(void(*Visit)(BinaryTreeNode<T>* u))//先序
{
PreOrder(Visit, root);
}
void InOrder(void(*Visit)(BinaryTreeNode<T>* u))//中序
{
InOrder(Visit, root);
}
void PostOrder(void(*Visit)(BinaryTreeNode<T>* u))//后序
{
PostOrder(Visit, root);
}
void LevelOrder(void(*Visit)(BinaryTreeNode<T>* u));//逐层
private:
BinaryTreeNode<T>* root; // pointer to root
void PreOrder(void(*Visit)
(BinaryTreeNode<T>* u), BinaryTreeNode<T>* t);
void InOrder(void(*Visit)
(BinaryTreeNode<T>* u), BinaryTreeNode<T>* t);
void PostOrder(void(*Visit)
(BinaryTreeNode<T>* u), BinaryTreeNode<T>* t);
};
//获取根节点数据
template<class T>
bool BinaryTree<T>::Root(T& x) const
{// Return root data in x.
// Return false if no root.
if (root) {
x = root->data;
return true;
}
else return false; // no root
}
//创建树
template<class T>
void BinaryTree<T>::MakeTree(const T& element,
BinaryTree<T>& left, BinaryTree<T>& right)
{// Combine left, right, and element to make new tree.
// left, right, and this must be different trees.
// create combined tree
root = new BinaryTreeNode<T>
(element, left.root, right.root);
// deny access from trees left and right
left.root = right.root = 0;
}
//分裂树
template<class T>
void BinaryTree<T>::BreakTree(T& element,
BinaryTree<T>& left, BinaryTree<T>& right)
{// left, right, and this must be different trees.
// check if empty
if (!root) throw BadInput(); // tree empty
// break the tree
element = root->data;
left.root = root->LeftChild;
right.root = root->RightChild;
delete root;
root = 0;
}
3.另一种链表描述
一个bnnode由元素、左孩子、指向下一相邻节点的指针构成(在同一层);即向左下和向右的指针。
三、二叉树遍历
方法:递归!
1.先序遍历:前缀表达式;中序遍历:中缀表达式;后序遍历:后缀表达式
#include<iostream>
using namespace std;
//先序遍历
template <class T>
void PreOrder(BinaryTreeNode<T>* t)
{// Preorder traversal of *t.
if (t) {
Visit(t); // visit tree root
PreOrder(t->LeftChild); // do left subtree
PreOrder(t->RightChild); // do right subtree
}
}
//中序遍历
template <class T>
void InOrder(BinaryTreeNode<T>* t)
{// Inorder traversal of *t.
if (t) {
InOrder(t->LeftChild); // do left subtree
Visit(t); // visit tree root
InOrder(t->RightChild); // do right subtree
}
}
//后序遍历
template <class T>
void PostOrder(BinaryTreeNode<T>* t)
{// Postorder traversal of *t.
if (t)
{
PostOrder(t->LeftChild); // do left subtree
PostOrder(t->RightChild); // do right subtree
Visit(t); // visit tree root
}
}
2.逐层遍历:采用的是 (二3.) 的那种链表描述
//根->叶逐层,同层由左至右
//逐层遍历
template <class T>
void LevelOrder(BinaryTreeNode<T>* t)
{// Level-order traversal of *t.
LinkedQueue<BinaryTreeNode<T>*> Q;
while (t) {
Visit(t); // visit t
// put t's children on queue
if (t->LeftChild) Q.Add(t->LeftChild);
if (t->RightChild) Q.Add(t->RightChild);
// get next node to visit
try { Q.Delete(t); }
catch (OutOfBounds) { return; }
}
}
3.销毁二叉树:
用后序遍历——删除所有节点
• 辅助函数 —— 删除单个节点static void Free(BinaryTreeNode<T> *t) {delete t;}
• 删除二叉树void Delete() {PostOrder(Free, root); root = 0;}
4.计算高度:
后序遍历:左子树高度、右子树高度较大者+1(根节点)->树的高度
递归公式:h=max{hl, hr} + 1 -> 递归函数
//公共接口
int Height() const {return Height(root);}
template <class T>
int BinaryTree<T>::Height(BinaryTreeNode<T> *t) const
{// Return height of tree *t.
if (!t) return 0; // empty tree
int hl = Height(t->LeftChild); // height of left
int hr = Height(t->RightChild); // height of right
if (hl > hr) return ++hl;
else return ++hr;
}
5.统计节点数目:
方法一:
• 私有辅助函数 Add1static void Add1(BinaryTreeNode<T> *t) {_count++;}
• 节点数统计函数int Size() {_count = 0; PreOrder(Add1, root); return _count;}
方法二:利用递归公式:s=sl+sr+1
template <class T>
int BinaryTree<T>::Size(BinaryTreeNode<T> *t) const
{
if (!t) return 0;
else return Size(t->LeftChild)+Size(t->RightChild)+1;
}
四、优先队列
有限元素集合,每一个元素都有一个优先级,随机输入,按照优先级顺序输出
stl中有默认priority_queue
//当需要用自定义的数据类型时才需要传入这三个参数,
// 使用基本数据类型时,只需要传入数据类型,默认是大顶堆
//升序
priority_queue <int, vector<int>, greater<int> > q;
//降序
priority_queue<int, vector<int>, less<int>>q;
priority_queue<int> a;//默认降序
q.size();//返回q里元素个数
q.empty();//返回q是否为空,空则返回1,否则返回0
q.push(k);//在q的末尾插入k
q.pop();//删掉q的第一个元素
q.top();//返回q的第一个元素
五、堆及堆排序
最大树 :每个节点的值都大于或等于其子节点(若存在)值的树最小树:每个节点的值都小于或等于其子节点(若存在)值的树最大堆: 是一棵最大树,同时是一棵完全二叉树
堆是特殊的完全二叉树——用一维数组描述
//最大堆
template<class T>
class MaxHeap
{
public:
MaxHeap(int MaxHeapSize = 10);
~MaxHeap() { delete[] heap; }
int Size() const { return CurrentSize; }
T Max()
{ //查
if (CurrentSize == 0)
throw OutOfBounds();
return heap[1];
}
MaxHeap<T>& Insert(const T& x); //增
MaxHeap<T>& DeleteMax(T& x); //删
void Initialize(T a[], int size, int ArraySize);
private:
int CurrentSize, MaxSize;
T* heap; // element array
};
//构造函数
template<class T>
MaxHeap<T>::MaxHeap(int MaxHeapSize)
{// Max heap constructor.
MaxSize = MaxHeapSize;
heap = new T[MaxSize + 1];
CurrentSize = 0;
}
//插入函数!!!!
template<class T>
MaxHeap<T>& MaxHeap<T>::Insert(const T& x)
{// Insert x into the max heap.
if (CurrentSize == MaxSize)
throw NoMem(); // no space
// 寻找新元素x的位置
// i——初始为新叶节点的位置,逐层向上,寻找最终位置
int i = ++CurrentSize;
while (i != 1 && x > heap[i / 2])
{// i不是根节点,且其值大于父节点的值,需要继续调整
heap[i] = heap[i / 2]; // 父节点下降
i /= 2; // 继续向上,搜寻正确位置
}
heap[i] = x;
return *this;
}
//删除最大元素操作!!!!!
template<class T>
MaxHeap<T>& MaxHeap<T>::DeleteMax(T& x)
{
// check if heap is empty
if (CurrentSize == 0)
throw OutOfBounds(); // empty
x = heap[1]; // 删除最大元素
// 重整堆
T y = heap[CurrentSize--];
// y取最后一个节点,从根开始重整,同时size-1
// find place for y starting at root
int i = 1, // 要放置的节点的指针位置
ci = 2; // i的孩子,ci刚开始指向左孩子
while (ci <= CurrentSize)
{
// 使ci指向i的两个孩子中较大者
if (ci < CurrentSize && heap[ci] < heap[ci + 1])
ci++;
// y的值大于等于孩子节点吗?
if (y >= heap[ci]) break; // 是,i就是y的正确位置,退出
// 否,需要继续向下,重整堆
heap[i] = heap[ci]; // 大于父节点的孩子节点上升
i = ci; // 向下一层,继续搜索正确位置
ci *= 2;
}
heap[i] = y;
return *this;
}
最大堆的创建
思路一:空堆,n次插入——O(nlogn)
思路二:更好的方法,O(n):n个元素构成完全二叉树,可能不是堆,整理它。
从最后一个内部节点(n/2)开始到根节点,将每个节点i的子树(完全二叉树)整理为堆;整理堆的过程是下降过程,在不断向下寻找当前节点i可以插在哪的过程中,也改变了原有节点的位置。
//建堆函数
template<class T>
void MaxHeap<T>::Initialize(T a[], int size,int ArraySize)
{// Initialize max heap to array a.
delete[] heap;
heap = a;
CurrentSize = size;
MaxSize = ArraySize;
// 从最后一个内部节点开始,一直到根,对每个子树进行堆重整
for (int i = CurrentSize / 2; i >= 1; i--)
{
T y = heap[i]; // 子树根节点元素
// find place to put y
int c = 2 * i; // y的左孩子位置
while (c <= CurrentSize)
{
// 若左孩子小,变为右孩子
if (c < CurrentSize &&heap[c] < heap[c + 1]) c++;
// can we put y in heap[c/2]?
if (y >= heap[c]) break; // yes
// no
heap[c / 2] = heap[c]; // move child up
c *= 2; // move down a level
}
//最后一次跳出时多*2要/掉
heap[c / 2] = y;
}
}
堆排序
n个元素——建最大堆
1.n个元素先建最大堆(用到最大堆的创建方法2, 和删除max)
2.将根和最后一个节点交换
3.将[0:n - 1]的节点重排为最大堆
4.重复,直到完成。这样,在一维数组中就是有序的
//堆排序
template <class T>
void HeapSort(T a[], int n)
{// Sort a[1:n] using the heap sort method.
// create a max heap of the elements
MaxHeap<T> H(1);
H.Initialize(a, n, n); // 建堆,直接使用a作为堆的空间
// extract one by one from the max heap
T x;
for (int i = n - 1; i >= 1; i--)
{
H.DeleteMax(x);//让x的值是根的值
a[i + 1] = x;
}
// 将堆设置为空,但不释放空间——不销毁a
H.Deactivate();
}
复杂度:建最大堆:O(n);每次删除一个元素,共删n次:O(nlogn)
总的时间代价是O(nlogn)
霍夫曼编码
用于文本压缩
常用文本压缩:
关键字编码:用单个字符代替常用的单词
行程长度编码:把一系列重复字符替换为它们重复出现的次数,eg:aaaaaaa=a7
霍夫曼编码:考虑字符的出现频率进行编码——频率高的短码低的长码
关键:利用树——任何一个编码都不是其他编码的前缀;节点的权重——字符频率
构造霍夫曼树:选择两个w值最小的树合并,权重相加作为新的权重;重复,直至只剩一棵树
//huffman类
template<class T>
class Huffman {
friend BinaryTree<int> HuffmanTree(T[], int);
public:
operator T () const { return weight; }
private:
BinaryTree<int> tree;
T weight;//权重
};
//霍夫曼树构造函数
template <class T>
BinaryTree<int> HuffmanTree(T a[], int n)
{// Generate Huffman tree with weights a[1:n].
// create an array of single node trees
Huffman<T>* w = new Huffman<T>[n + 1];
BinaryTree<int> z, zero;
for (int i = 1; i <= n; i++)
{
z.MakeTree(i, zero, zero);
w[i].weight = a[i];
w[i].tree = z;
}
// make array into a min heap
MinHeap<Huffman<T> > H(1);
H.Initialize(w, n, n);
// repeatedly combine trees from heap
Huffman<T> x, y;
for (i = 1; i < n; i++)
{
H.DeleteMin(x);
H.DeleteMin(y);
z.MakeTree(0, x.tree, y.tree);
x.weight += y.weight; x.tree = z;
H.Insert(x);
}
H.DeleteMin(x); // final tree
H.Deactivate();
delete[] w;
return x.tree;
}
树转换为二叉树
规则:1.某节点第一个孩子是左节点;某节点的右兄弟是它的右节点。
森林转换为二叉树
前提:由于根没有兄弟,所以树转换为二叉树后,二叉树的根一定没有右子树。
1.先将森林中的每一棵树转换为二叉树;
2.再将第一棵二叉树的根作为转换后二叉树的根;第一棵二叉树的左子树作为转换后二叉树的左子树;
3.从第二颗树开始,依次把后一棵二叉树的根结点作为前一棵二叉树根结点的右孩子结点,直到所有二叉树都连接到一起
六、Oj
判断是否为堆-堆整理(苏维埃计算机):最大堆的创建
二叉树遍历及二叉树高度:递归,有一定思考
#include<iostream>
using namespace std;
int GetHeight(char* pre, char* in, int n) //求二叉树的高度
{
if (n == 0) //若没有结点,为空树
{
return 0;
}
int i;
for (i = 0; i < n; i++)
{
if (in[i] == pre[0]) //找到根结点在中序的位置
{
break;
}
}
int left = GetHeight(pre + 1, in, i); //左子树的深度
int right = GetHeight(pre + i + 1, in + i + 1, n - i - 1); //右子树的深度
return max(left, right) + 1; //返回左右子树深度的较大值中的较大值+根结点
}
int main()
{
int n;
cin >> n;
char* pre = new char[n + 1];
char*in=new char[n + 1]; //先序和中序
cin >> pre >> in;
cout << GetHeight(pre, in, n) << endl;
return 0;
}
哈夫曼树A
//每次求最小值用堆优先队列来做
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
int main()
{
int n;
cin >> n;
//定义小根堆
priority_queue<int, vector<int>, greater<int>> heap;
while (n--)
{
int x;
cin >> x;
//入堆
heap.push(x);
}
int res = 0;//存结果即最小的体力耗费值
while (heap.size() > 1)//只要堆当中元素个数大于1
{
//取出两个最小的值,进行合并
int a = heap.top(); heap.pop();
int b = heap.top(); heap.pop();
res += a + b;
heap.push(a + b);//把合并结果入堆,注意入堆后又会有顺序
}
cout << res << endl;
return 0;
}