第九章 数据结构专题 —— 树(上)
ps 往后的部分已代码为主,具体的实现逻辑和知识点在大学上课和考研里都复习了,但是一直没有动手敲,理解的很肤浅
9.1 二叉树的存储结构与基本操作
9.1.2 定义:
struct node{
int data; //数据域
node* lchild;
node* rchild;
};
由于在二叉树建树前根节点不存在,因此其地址一般设为NULL
node* root = NULL ;
而如果需要新建结点
node* newNode(int v){
node* Node = new node;
Node->data = v;
Node->lchild = Node->rchild = NULL;
return Node; //返回新建结点的地址
}
9.1.2 二叉树结点的查找、修改
需要使用递归来完成查找并修改的操作,二叉树递归的两个重要元素:递归式和递归边界。在这里,递归式是指对当前结点的左子树和右子树分别递归,递归边界是当前结点为空或到达死胡同。
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); //递归式
search(root->rchild,x,newdata); //递归式
}
9.1.3 二叉树结点的插入
二叉树结点的插入位置就是数据域在二叉树中查找失败的位置,而由于这个位置是确定的,因此在递归查找的过程中一定是只根据二叉树的性质来选择左子树或右子树的一颗子树进行递归,且最后到达空树(死胡同)的地方就是查找失败的地方,也就是结点需要插入的地方
注意根节点指针root要使用引用,否者插入不成功,前面的search函数中不需要加因为是因为search函数修改的是指针root指向的内容,而不是root本身
void insert(node* &root,int x){
if(root == NULL){
root = newNode(x);
return;
}
if(由二叉树的性质,x应该插入左子树){
insert(root->lchild,x); //往左子树搜索(递归式)
}else{
insert(root->rchild,x); //往右子树搜索(递归式)
}
}
9.1.4 二叉树的创建
其实就是二叉树插入的过程
node* Create(int data[],int n){
node* root = NULL;
for(int i=0;i<n;i++){
insert(root,data[i]);
}
return root; //返回根节点
}
9.1.5 完全二叉树的存储
令1号位存放根节点,对于完全二叉树的任何一个结点x,左孩子编号为2x,右孩子编号为2x+1
9.2 二叉树的遍历
9.2.1 先序遍历
//先序遍历
void preorder(node* root){
if(root == NULL){
return; //到达空树,递归边界
}
printf("%d\n",root->data); //访问根结点,在这里直接输出
preorder(root->lchild);
preorder(root->rchild);
}
9.2.2 中序遍历
先序遍历或者后序遍历加上中序遍历可以唯一确定一棵树
//中序遍历
void inorder(node* root){
if(root == NULL){
return;
}
inorder(root->lchild);
printf("%d",root->data);
inorder(root->rchild);
}
9.2.3 后序遍历
//后续遍历
void postorder(node* root){
if(root == NULL){
return;
}
postorder(root->lchild);
postorder(root->rchild);
printf("%d",root->data);
}
9.2.4 层序遍历
层序遍历相当于对二叉树从根节点开始的广度优先搜索(BFS)
思路:1.将根节点root加入队列q 2.取出队首结点,访问它 3.如果该结点有左孩子,将左孩子入队。
4.如果该结点有右孩子,将右孩子入队 5.返回2,直到队列为空
//层序遍历
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(now->rchild != NULL) q.push(now->rchild);
}
}
很多题目需要记录结点所在层次
struct tNode{
int data;
int layer; //层次
tNode* lchild;
tNode* rchild;
};
//层次遍历,带层次
void layerOrder(tNode* root){
queue<tNode*> q;
root->layer = 1;
q.push(root);
while(!q.empty()){
tNode* now = q.front();
q.pop();
printf("%d",now->data); //访问队首元素
if(now->lchild != NULL){
now->lchild->layer = now->layer+1;
q.push(now->lchild);
}
if(now->rchild != NULL){
now->rchild->layer = now->layer+1;
q.push(now->rchild);
}
}
}
9.2.5 给定一棵树的先序遍历和中序遍历,如何确定此树
//给定一棵树的先序遍历和中序遍历,如何确定此树
//当前先序序列区间为[preL,preR],中序序列区间为[inL,inR],返回根节点地址
node* create(int preL,int preR,int inL,int inR){
if(preL > preR){
return NULL; //先序序列
}
node* root = new node; //新建一个新的结点,用来存放当前二叉树的根节点
root->data = pre [preL]; //新结点的数据域为根节点的值
int k;
for(k = inL;k<=inR;k++){
if(in[k] == pre[preL]){ //在中序遍历中找到in[k] == pre[L] 的结点
break;
}
}
int numLeft = k - inL; //左子树的结点个数
//左子树的先序区间为[preL+1,preL+numLeft],中序区间[inL,k-1]
//返回左子树的根结点地址,赋值给root的左指针
root->lchild = create(preL+1,preL+numLeft,inL,k-1);
//右子树的先序区间为[preL+numLeft+1,preR],中序区间[k+1,inR]
//返回右子树的根结点地址,赋值给root的右指针
root->rchild = create(preL+numLeft+1,preR,k+1,inR);
return root; //返回根结点地址
}
9.2.6 二叉树的静态实现
struct node{
int data;
int lchild;
int rchild;
}Node[maxn];
int index=0;
int newNode(int v){
Node[index].data=v;
Node[index].lchild=-1;//以-1或maxn表示空,因为数组范围是0~maxn-1
Node[index].rchild=-1;
return index++;
}
//查找
void search(int root,int x,int newdata){
if(root==-1){
return;
}
if(Node[root].data==x){
Node[root].data = newdata;
}
search(Node[root].lchild,x,newdata);
search(Node[root].rchild,x,newdata);
}
//插入
void insert(int &root,int x){
if(root==-1){
root = newNode(x);
return;
}
if(root!=-1){
insert(Node[root].lchild,x);
}else{
insert(Node[root].rchild,x);
}
}
//二叉树建立
int create(int data[],int n){
int root=-1;
for(int i=0;i<n;i++){
insert(root,data[i]);
}
return root;
}
//遍历
void preorder(int root){
if(root==-1){
return;
}
cout<<Node[root].data<<endl;
preorder(Node[root].lchild);
preorder(Node[root].rchild);
}
void inorder(int root){
if(root==-1){
return;
}
inorder(Node[root].lchild);
cout<<Node[root].data<<endl;
inorder(Node[root].rchild);
}
void postorder(int root){
if(root==-1){
return;
}
postorder(Node[root].lchild);
postorder(Node[root].rchild);
cout<<Node[root].data<<endl;
}
void layerOrder(int root){
queue<int> q;
q.push(root);
while(!q.empty()){
int now = q.front();
q.pop();
cout<<Node[root].data<<endl;
if(Node[root].lchild!=-1) q.push(Node[now].lchild);
if(Node[root].rchild!=-1) q.push(Node[now].rchild);
}
}
9.3 树的遍历
9.3.1 树的静态写法
由于直接开数组经常会超过题目限制,因此需要使用STL中的vector
struct node{
int data;
vector<int> child; //指针域,存放所有子节点的下标
}Node[maxn];
int index=0;
int newNode(int v){
Node[index].data = v;
Node[index].child.clear();
return index++;
}
9.3.2 树的先根遍历
//先根遍历
void PreOrder(int root){
cout<<Node[root].data<<endl;
for(int i=0;i<Node[root].child.size();i++){
PreOrder(Node[root].child[i]);
}
}
9.3.3 树的层序遍历
//层序遍历
void LayerOrder(int root){
queue<int> Q;
Q.push(root);
while(!Q.empty()){
int front = Q.front();
cout<<Node[front].data<<endl;
Q.pop();
for(int i=0;i<Node[front].child.size();i++){
Q.push(Node[front].child[i]); //将当前结点的所有子结点入队
}
}
}
//如果需要对结点的层号求解
struct tNode{
int layer;
int data;
vector<int> child;
}tNode[maxn];
void layerOrder(int root){
queue<int> Q;
Q.push(root);
tNode[root].layer=0;
while(!Q.empty()){
int front=Q.front();
cout<<tNode[root].data<<endl;
Q.pop();
for(int i=0;i<tNode[front].child.size();i++){
int child = tNode[front].child[i]; //当前结点的第i个子结点的编号
tNode[child].layer = tNode[front].layer+1;
Q.push(child);
}
}
}