(一)教材
晴神宝典(算法笔记) 注:本文中未注明出处的代码段均出自晴神宝典
谭浩强C++程序设计
算法笔记pdf版本资源: https://pan.baidu.com/s/1WtrX-uvKQ-UwmK7dViP3vg
提 取 码: u3gk
含上机,需要的朋友请自提
注:
本文持续更新。
(二)论述题
1.什么是树的宽度,什么是树的高度?
宽度:树中结点最大的度成为树的度(也叫树的宽度);
深度:从根节点开始自顶向下逐层累加至该结点时的深度值;
高度:从最底层叶子结点开始自底向上逐层累加至该结点时的高度值;
深度=高度。
2.二叉树与度为二的树的区别
度二位的树不区分左右子树,二叉树有严格区分
3.树的结点定义
树的存储结构一般使用链表定义,所以需要对树的结点进行规定。
4.函数参数中什么时候使用引用,什么时候不使用?
如果函数中需要新建结点,即对二叉树的结构做出修改,就需要加引用;如果只是修改当前已有结点的内容,或仅仅是遍历树,就不需要加引用。
(可以参考C 语言里对链表中Head指针的修改——有可能改变head的情况和只是遍历的情况)
(三)编码
一、基础操作
总述:
树的定义是递归的,这导致树的大多数操作也是递归的
struct node{//树结点定义
typename data; //数据域
node* lchild; //左儿子
node* rchild; //右儿子
};
4.新建结点函数
//用于新建一个结点的函数
笔记:
①C++中申请一个新的空间的方式:node* Node =new node; //申请一个新空间
②C++中结点数据的展现方式:Node->data=v; //结点权值为v
(与C区别开来——
【C】申请一个结点:struct Node * p=(struct Node*)malloc(sizeof(struct Node));
【C】结点数据:(*p).data;
)
node* newNode(int v)
{
node* Node =new node; //申请一个新空间
Node->data=v;//结点权值为v
Node->lchild=Node->rchild=NULL;
return Node;
}
5.查找一个结点:
void search(node *root,int x,int newdata)
{
if(root==NULL)
{
return;//递归边界,空树
}
if(root->data==x)//找到数据域为x的结点,把它修改成newdata
{
root->data=newdata;
}
search(root->lchild,x,newdata);//往左子树搜索x
search(root->rchild,x,newdata);//往右子树搜索x
}
6.插入一个结点
//向二叉树插入一个数据域为x的新结点
void insert(node *&root,int x) //必须使用引用
{
if(root==NULL)//空树,查找失败,
{
root =newNode(x);
return;
}
if(左子树){
insert(root->lchild,x);
}
else
{
insert(root->rchild,x);
}
}
7.创建一个二叉树
//data数组保存要存入树的信息
node* Create(int data[],int n)
{
node* root =NULL;//建立根节点
for(int i=0;i<n;i++)
{
insert (root,data[i]);
}
return root;
}
二、二叉树的遍历
二叉树遍历方式有四种:
①先序遍历;DFS
②中序遍历;DFS
③后序遍历;DFS
④层次遍历。BFS
①先序遍历
根节点-》左子树-》右子树
递归访问
void preorder(node* root)
{
if(root==NULL)//递归边界
{
return;
}
printf("%d\n",root->data);//输出当前结点
preorder(root->lchild);//递归访问左子树
preorder(root->rchild);//递归访问右子树
}
②中序遍历
左子树 -》 根节点 -》右子树
只要知道根节点,就可以通过根节点在中序遍历序列中的位置区分出左右子树
所以先序+中序/后序+中序可以确定整棵树的结构。
void inorder(node *root)
{
if(root==NULL)
{
return ;
}
inorder(root->lchild);
printf("%d\n",root->data);
inorder(root->rchild);
}
③后序遍历
void postorder(node *root)
{
if(root==NULL)
{
return ;
}
postorder(root->lchild);
postorder(root->rchild);
printf("%d\n",root->data);
}
④层次遍历
(对二叉树进行广度优先搜索)
思路:
使用一个辅助队列Q,先把根节点放进去,同时将它的左右儿子入队,继续访问;出队一个结点,继续访问它的左右儿子结点,入队,直到整个队列没有结点。
void LayerOrder(node *root)
{
queue <node*> q;
q.push(root); //根节点入队
while(!q.empty())
{
node *now =q.front();//取出队首元素
q.pop();
printf("%d",now->data) ;
if(now->lchild!=NULL)
q.push(now->lchild);
if(now0>rchild!=NULL)
q.push(now->rchild);
}
}
三、 二叉树的结构确立
1.给定一棵二叉树的先序遍历序列和中序遍历序列,重建这棵二叉树。
算法思想:
依然是个递归函数
参数信息:先序序列为【preL,preR】,中序序列区间为[inL,inR],返回的是根节点的地址
node *create (int preL,int preR,int int inL,int inR)
{
if(preL>preR) //先序的长度为0时,整棵树没有根,返回
{
return NULL;
}
node* root=new node;//建立一个结点,存放当前二叉树的根节点
root->data=pre[preL];//存放根节点的数据域
for(int k=inL;k<inR;k++)//在中序序列中找根节点的位置
{
if(in[k]==pre[preL])
{
break;
}
}
int numLeft = k-inL;//左子树结点个数
root->lchild = create(preL +1,preL +numLeft,inL,k-1);//递归建立左子树
root->rchild = create(preL +numLeft+1,preR,k+1,inR);//递归建立右子树
return root;
}
四、二叉树的搜索:BFS与DFS
1.深度优先搜索DFS
【PAT A1053】给定一颗树和每个结点的权值,求从所有根节点到叶子结点的路径,是的每条路径上的权值之和等于给定的常数S。如果有多条这样的路径,则按路径非递增的顺序输出。
思路:
步骤一:这是一棵普通性质的树,因此令结构体node存放结点的数据域和指针域,其中指针域使用vector存放所有孩子结点的编号。又考虑到最后的输出需要按权值从大到小排序,因此不妨在读入的时候就事先对每个结点的子节点vector进行排序,这样在遍历时就会优先遍历到权值大的子结点。
步骤二:令Int型数path[]存放递归过程中产生路径上的结点编号。接下来进行DFS,参数又三个:当前访问的结点标号index,当前路径path上的结点个数numNode(也是递归层数,因为每深入一层,path上就会多一个结点)以及当前路径上的权值和sum。
递归过程的伪代码如下:
①若sum>S,直接return ;
②若sum==S ,说明当前访问结点index为止,输入中需要达到的S已经得到,这时如果结点index为叶子结点,则输出path数组中所有数据,否则return ;
③若sum<S,说明要求还未满足,此时枚举当前访问结点index的所有子节点,对每一个子节点child,先将其存入path,然后在子基础上往下一层递归,下一层的递归参数为child,numNode+1,sum+node[child].weight
void DFS(int index,int numNode ,int sum)
{
if(sum>S) return ;
if(sum==S){
if(Node[index].child.size()!=0)return;
for(int i=0;<numNode;i++)
{
printf("%d",Node[path[i]].weight);
if(i<numNode-1)printf(" ");
else printf("\n");
}return;
for(int i=0;i<Node[index].child.size();i++)
{
int child = Node[index].child[i];
path[numNode]=child;
DFS(child,numNode+1,sum+NOde[child].weight);
}
}
五、二叉查找树 BST
二叉查找树(Binary Search Tree,BST)是一种特殊的二叉树,又称为排序二叉树。
中序遍历二叉查找树即可得到有序序列。
它的递归定义如下:
①要么二叉查找树是一棵空树;
②要么二叉查找树是由根节点、左子树、右子树组成,其中左子树和右子树都是二叉查找树,且左子树上所有节点的数据域均小于等于根节点的数据域,右子树上的所有结点的数据域都大于根节点的数据域。
二叉查找树基本操作——查找:
①若当前根节点为空,说明查找失败,返回;
②如果需要查找的x等于当前根节点的数据域root->data,说明查找成功,访问;
③如果需要查找的x小于当前根节点的数据域,说明应该往左子树拆招,因此向root->lchild递归;
④反之,向root->rchild递归;
void search(node* root,int x)
{
if(root==NULL){
printf("searc failed\n");
return;
}
if(x==root->data)
{
printf("%d\n",root->data);
}
else if(x<root->data)
{
search(root->lchild,x);
}else
{
search(root->rchild,x);
}
}
二叉树基本操作——插入操作
查找某个数据域的结点一定是沿着确定的路径惊醒的,因此,党对某个需要查找的值在二叉查找树中查找成功,说明结点已经存在;反之,如果这个需要查找的值在二叉查找树中查找失败,那么说明查找失败的地方一定是结点需要插入的地方。
因此可以在查找操作的基础上,在root==NULL时新建需要插入的结点。
void search(node* root,int x)
{
if(root==NULL){
root=newNode(x);//新建结点,值为x
return;
}
if(x==root->data)
{
printf("%d\n",root->data);
}
else if(x<root->data)
{
search(root->lchild,x);
}else
{
search(root->rchild,x);
}
}
二叉查找树的基本操作——建立
建立一棵二叉查找树,就是先后插入n个结点的过程,这和一般二叉树的建立是完全一致的,因此代码也基本相同。
node *Create(int data[],int n0
{
node*root = NULL;
for(int i=0;i<n;i++)
{
insert(root,data[i]);
}
return root;
}
注意:二叉查找树不唯一,即便是同一组数据,当插入顺序不同时得到的二叉查找树也可能不同。
二叉查找树的基本操作——删除
为了在删除结点之后得到的仍然是一棵二叉查找树,有两种方法可以对某个特定结点进行删除:
第一种是选择它左侧比它小的结点对他进行覆盖,然后把那个结点删除掉;
第二种是选择它右侧紧邻着它且比它大的结点对他进行覆盖,然后把那个结点删除掉。
代码:晴神P313
六、平衡二叉树 AVL
AVL也是二叉查找树,但是在BST的基础上增添了平衡的条件,
二叉平衡树具有一个特殊值,叫做平衡因子,每个结点都必须具有一个平衡因子,所谓平衡,就是说每个节点的左右两个子树的结点数差值不超过1;
晴神P320