整理张铭老师《数据结构与算法》笔记
5.二叉树
5.1 二叉树概念
结点的有限集合 ,空集或一个根结点及两颗互不相交的左右子树的二叉树组成的结合
满二叉树、完全二叉树、扩充二叉树
5.2 二叉树的抽象数据类型
遍历二叉树
5.2.1 深度优先遍历二叉树
1)前序法
2)中序法
3)后序法
深度优先遍历二叉树(递归)
template<class T>
void BinaryTree<T>::DepthOrder(BinaryTreeNode<T>*root)
{
if(root!=NULL){
Visit(root); //前序
Depthorder(root->leftchild()); //递归访问左子树
Visit(root); //中序
Depthorder(root->rightchild()); //递归访问右子树
Visit(root); //后序
}
}
DFS遍历二叉树的非递归算法
1)非递归前序遍历二叉树思想
- 遇到一个结点,访问该结点,并把此结点的非空右结点推入栈中,然后下降去遍历左子树;
- 遍历完左子树后,从栈顶拖出一个结点,并按它的右链接指示再去遍历该结点的右子树结构。
template<class T>
void BinaryTree<T>::PreOrderWithoutRecusion(BinaryTreeNode<T>*root{
using std::stack;//使用STL中的stack
stack<BinaryTreeNode<T>*aStack;
BinaryTreeNode<T>*pointer=root;
aStack.push(NULL)//栈底监视哨
while(pointer){ //或者!aStack.empty()
Visit(point->value()); //访问当前结点
if (pointer->rightchild()!=NULL) //右孩子入栈
aStack.push(pointer->rightchild());
if (pointer->leftchild()!=NULL)
pointer = pointer->leftchild(); //左路下降
else { //左子树访问完毕,转向访问右子树
pointer = aStack.top();
aStack.pop(); //栈顶元素退栈
}
}
2)非递归中序遍历二叉树思想
- 遇到一个结点,把它推入栈中遍历其左子树
- 遍历完左子树,从栈顶托出该结点并访问之,按照其右链地址遍历该结点的右子树
template<class T> void
BinaryTree<T>::PreOrderWithoutRecusion(BinaryTreeNode<T>*root {
using std::stack; // 使用STL中的stack
stack<BinaryTreeNode<T>*>aStack;
BinaryTreeNode<T>*pointer = root;
while (!aStack.empty()||pointer){
if(pointer) {
Visit(pointer->value()); //前序访问点
aStack.push(pointer); //当前结点地址入栈
pointer=pointer->leftchild(); //当前链接结构指向左孩子
}
else { //左子树访问完毕,转向访问右子树
pointer = aStack.top();
aStack.pop(); //栈顶元素退栈
Visit(pointer->value()); //访问当前结点
pointer=pointer->rightchild(); //当前链接结构指向右孩子
}
}
}
3)非递归后序遍历二叉树思想
- 给栈中元素加上特征位
enum Tags{Left,Right}; //定义枚举类型标志位
template<class T>
class StackElement { //栈元素的定义
public:
BinaryTreeNode<T>*pointer; //指向二叉树结点的指针
Tags tag; //标志位
}
template<class T>
void BinaryTree<T>::PreOrderWithoutRecusion
(BinaryTreeNode<T>*root{
using std::stack;//使用STL中的stack
stack<BinaryTreeNode<T>*aStack;
BinaryTreeNode<T>*pointer=root;
aStack.push(NULL)//栈底监视哨
while (!aStack.empty()||pointer) {
while(pointer != NULL){ //沿非空指针压栈,并左路下降
element.pointer = pointer; element.tag = Left;
aStack.push(element); //把标志位为Left的结点压入栈
pointer = pointer->leftchild();
}
element = aStack.top();aStack.pop(); //获得栈顶元素,并退栈
pointer=element.pointer;
if(element.tag==Left){ //如果从左子树回来
element.tag = Right; aStack.push(element);//置标志为Right
pointer=pointer->rightchild();
}
else { //如果从右子树回来
Visit(pointer->value()); //访问当前结点
pointer=NULL; //置point指针为空,以继续弹栈
}
}
}
深搜算法的时间代价分析
各种遍历中,每个结点都被访问且只被访问一次,时间代价为O(n)
深搜算法的空间代价分析
深搜:栈的深度与树的高度有关 ;最好O(logn),最坏O(n)
5.2.2 宽度优先遍历二叉树
从二叉树的的第0层(根结点)开始,自上至下逐层遍历;在同一层中,按从左到右的顺序对结点逐一访问。
template<class T>
void BinaryTree<T>::LevelOrder(BinaryTreeNode<T>*root{
using std::queue;//使用STL中的队列
queue<BinaryTreeNode<T>*aQueue;
BinaryTreeNode<T>*pointer=root; //保存输入参数
if (pointer)aQueue.push(pointer); //根结点入队列
while(!aQueue.empty()) { //队列非空
pointer = aQueue.front();//取队列首结点
aQueue.pop();//当前结点出队列
Visit(pointer->value());
if(pointer->leftchild())
aQueue.push(pointer->leftchild()); //左子树进队列
if(pointer->rightchild())
aQueue.push(pointer->rightchild()); //右子树进队列
宽搜算法的时间代价分析
各种遍历中,每个结点都被访问且只被访问一次,时间代价为O(n)
宽搜算法的空间代价分析
宽搜:与树的最大宽度有关 ;最好O(1),最坏O(n)
5.3 二叉树的存储结构
- 二叉链表
- 三叉链表
5.4 二叉树搜索树(BST)
-
或者是一颗空树;
-
或者是具有下列性质的二叉树:
对于任何一个结点,设其值为K,该结点的左子树(若不空)的任意一个结点的值都小于K;该结点的右子树(若不空)的任意一个结点的值都大于K;且它的左右子树也分别为BST
性质 -
性质:中序遍历是正序的
BST删除(值替换)
void BinarySearchTree<T>:::removehelp(BinaryTreeNode<T>*&rt,const T val){
if (rt==NULL)cout<<val<<"is not in the tree.\n":
else if (val<rt->value())
removehelp(rt->leftchild(),val);
else if (val>rt->value())
removehelp(rt->leftchild(),val);
else {
BinaryTreeNode<T>*temp=rt;
if(rt->leftchild()==NULL) rt=rt->rightchild();
else if(rt->rightchild()==NULL) rt=rt->rightchild();
else {
temp = deletemin(rt->rightchild());
rt->setValue(temp->value());
}
delete temp;
}
}
找rt右子树中最小结点,并删除
template<class T>
BinarySearchTree<T>:::deletemin(BinaryTreeNode<T>*&rt){
if (rt->leftchild()!=NULL)
return deletemin(rt->leftchild());
else { //找到右子树中最小,删除
BinaryTreeNode<T>*temp=rt;
rt=rt->rightchild();
return temp;
}
}
5.5 堆与优先队列
堆:
- 完全二叉树的层次序列,可以用数组表示
- 最小堆是一个关键码序列
- 堆中存储的数是局部有序的,堆不唯一(结点的值与其孩子的之间存在限制,任何一个结点与其兄弟之间没有直接的限制)
- 从逻辑角度看,堆实际上是一种树形结构
5.5.1 堆的类定义
1)对最小堆用筛选法SiftDown调整
template<class T>
void MinHeap<T>::SiftDown(int position){
int i=position; //标识父结点
int j=2*i+1; //标识关键值较小的子结点
Ttemp=heapArray[i]; //保存父结点
while (j<CurrentSize) {
if((j<CurrentSize)&&(heapArray[j]>heapArray[j+1]))
j++;//j指向数值较小的子结点
if(temp>heapArray[j] {
heapArray[i]=heapArray[j];
i=j;
j=2*j+1 ; //向下继续
}
else break;
}
heapArray[i]=temp;
}
2)对最小堆用筛选法SiftUp向上调整
template<class T>
void MinHeap<T>::SiftUp(int position){
// 从position向上调整,使序列成为堆
int tempppos=position;
Ttemp=heapArray[temppos];
while((temppos>0)&&(heapArray[parent(temppos)]>temp)) {
heapArray[temppos]=heapArray[parent(temppos)];
temppos=parent(temppos);
}
heapArray[temppos]=temp;//找到最终位置
}
5.5.2 建最小堆过程
从第一个分支结点heapArray[CurrentSize/2-1] 开始,自底向上逐步把以子树调整成堆
tempplate<class T>
void MinHeap<T>::BuildHeap()
{
//反复调用筛选函数
for (int i=CurrentSize/2-1; i>=0; i--)
SiftDown(i);
}
5.5.3 最小堆插入新元素
template<class T>
bool MinHeap<T>::Insert(const T&newNode)
// 向堆中插入新元素newNode
{
if(CurrentSize==MaxSize) //堆空间已满
return false;
heapArray[CurrentSize]=newNode;
SiftUp(CurrentSize); //向上调整
CurrentSize++;
}
5.5.4最小堆删除元素操作
template<class T>
bool MinHeap<T>::Remove(int pos,T&node) {
if((pos<0)||(pos>=CurrentSize))
return false;
Ttemp=heapArray[pos];
heapArray[pos]=heapArray[--CurrentSize];
if(heapArray[parent(pos)]>heapArray[pos])
SiftUp(pos); //向上筛
else SiftDown(pos); //向下筛
node=temp;
return ture;
}
5.5.5 建堆效率分析
最小堆操作效率 ,建堆算法时间代价O(n)
堆有logn层深,插入删除平均时间代价和最差时间代价都是O(logn)
5.5.6 优先队列
堆可以用于实现优先队列
优先队列:根据需要释放具有最大(小)值的对象
5.6 Huffman树及其应用
- 计算机二进制编码
- 等长编码
- 空间效率
5.6.1 数据压缩和不等长编码
1)Huffman树与前缀编码
Huffman编码将代码与字符相联系
- 不等长编码
- 代码长度取决于对应字符的相对使用频率或“权”
5.6.2 建立Huffman编码树
权越大的叶结点离根越近
- 首先,按照“权”将字符排成一列,接着拿走权值最小的两个字符,再将他们标记为Huffman树的树叶,将这两个树叶标为一个分支结点的两个孩子,而该结点的权为两树叶的权之和。
- 将所得权放回序列,使“权”的顺序保持
- 重复直至序列处理完毕
5.6.3 Huffman树应用
1) 频率越大其编码越短
2)译码:从左至右逐位判别代码串,直至确定一个字符