算法笔记第九章——数据结构专题二

树的定义:n个节点组成的有限集合。n=0,空树;n>0,1个根节点,m个互不相交的有限集,每个子集为根的子树。
基本术语:
节点的度:树中某个节点的子树的个数。
在树中一般说节点的度就指的是节点孩子个数,不算入度,入度是图中的概念,虽然说树也属于图吧,但是如果他明确指出这个数据结构是树的话,问你度就仅仅是出度,树是图,但是图不一定是树

树的度:树中各节点的度的最大值。
分支节点:度不为零的节点。
叶子节点:度为零的节点。
路径:i->j;路径长度:路径经过节点数目减1。
孩子节点:某节点的后继节点;
双亲节点:该节点为其孩子节点的双亲节点(父母节点);
兄弟节点:同一双亲的孩子节点;
子孙节点:某节点所有子树中的节点;
祖先节点:从树节点到该节点的路径上的节点。

节点的层次:根节点为第一层(以此类推);
树的高度:树中节点的最大层次。

有序树:树中节点子树按次序从左向右安排,次序不能改变;无序树:与之相反。
森林:互不相交的树的集合。

二叉树:有限节点集合,或为空,或为一个根节点和两棵互不相交的左子树和右子树组成。

满二叉树:每个分支节点都有左孩子节点和右孩子节点。

  1. 叶子节点都在最下一层
  2. 只有度为0和2的节点
  3. 深度为k,那么节点数有2k-1个

完全二叉树:如果对满二叉树的结点进行编号, 约定编号从根结点起, 自上而下, 自左而右。则深度为k的, 有n个结点的二叉树, 当且仅当其每一个结点都与深度为k的满二叉树中编号从1至n的结点一一对应时, 称之为完全二叉树

PAT里的树的输入

PAT里树题的输入有一种是下面这样的。使用’-'表示空结点。
这个时候要用字符串去读取。如果用字符:那么数字不能读入(比如15.)如果用int,那么-不能读入。

string child1,chidl2;
cin>>child1>>child2;
if(child1[0]!='-'){
    
    
	do something;
}
if(child2[0]!='-'){
    
    
	do something;
}

二叉树的存储结构与基本操作

引用
在这里插入图片描述

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 = NULL;
    Node->rchild = NULL;
    return Node;
}
//把二叉树中值为x的多个结点的值改为newdata
void search(node *root, int x, int newdata)
{
    
    
    if(root == NULL) return;//递归边界,死胡同
    if(root->data==x) root->data = newdata;
    search(root->lchild,x,newdata);
    search(root->rchild,x,newdata);
}

//在二叉树中插入一个数据为x的新的节点
//注意根节点指针root需要使用引用,否则不会插入成功
void insert(node* &root,int x)
{
    
    
    if(root==NULL){
    
    
        root = newNode(x);
        return;
    }
    if(由二叉树的性质,应该插入到左子树){
    
    
        insert(root->lchild,x);
    }
    else{
    
    
        insert(root->rchild,x);
    }
}

//二叉树的建立
node *Creat(int data[],int n)
{
    
    
    node* root = NULL;
    for(int i=0;i<n;i++){
    
    
        insert(root,data[i]);
    }
    return root;
}

在这里插入图片描述

二叉树的遍历

int *(p1[5]);  //指针数组,可以去掉括号直接写作 int *p1[5];
int (*p2)[5];  //二维数组指针,不能去掉括号

在这里插入图片描述

先序遍历

根节点 -> 左子树 -> 右子树

递归实现
void preorder(node *root)
{
    
    
    if(root==NULL){
    
    
        return;  //到达空树,递归边界
    }
    //访问根节点root
    printf("%d\n",root->data);
    preorder(root->lchild);
    preorder(root->rchild);
}
非递归实现
void preorder_non_recursive(node *root)
{
    
    
    node *stack[100];
    unsigned int top = 0;
    stack[0] = root;
    do{
    
    
        while(stack[top]!=NULL)
        {
    
    
            printf("%d\n",stack[top]->data);
            stack[++top] = stack[top]->lchild;  //压栈
        }
        if(top>0){
    
    
            stack[top] = stack[--top]->rchild;  //弹栈
        }
    }while(top>0||stack[top]!=NULL);
}

中序遍历

左子树 -> 根节点 -> 右子树
通过中序遍历,只要知道根节点就可以根据根节点在序列中的位置判断出左子树和右子树中的元素

递归实现
void inorder(node *root)
{
    
    
    if(root==NULL) return;
    inorder(root->lchild);
    printf("%d\n",root->data);
    inorder(root->rchild);
}
非递归实现
void inorder_non_recursive(node *root)
{
    
    
    node* stack[100];
    int top=0;
    stack[top] = root;
    do{
    
    
        while(stack[top]!=NULL)
        {
    
    
            stack[++top] = stack[top]->lchild;
        }
        if(top>0)
        {
    
    
            printf("%d\n",stack[--top]->data);
            stack[top] = stack[top]->rchild;
        }
    }while(top>0||stack[top]!=NULL);
}

后序遍历

左子树 -> 右子树 -> 根节点

递归实现
void postorder(node *root)
{
    
    
    if(root==NULL) return;
    postorder(root->lchild);
    postorder(root->rchild);
    printf("%d\n",root->data);
}
非递归实现

可以参考一下

void postorder_non_recursive(node *root)
{
    
    
    node* p = root;
    int top = -1;
    node* stack[100];
    int tag[100];  //用于标识从左(0)或者右(1)返回
    while(p!=NULL||stack[top]!=NULL)
    {
    
    
        while(p!=NULL)
        {
    
    
            top++;
            stack[top] = p;
            tag[top] = 0;  //右子树还没有访问,仅仅是访问这个节点的左子树
            p = p->lchild;
        }
        while(top!=-1&&tag[top]==1)//tag[top]为1说明这个点没有左子树了,这个点就该输出了
        {
    
    
            printf("%d\n",stack[top]);
            top--;
        }
        if(top!=-1)
        {
    
    
            tag[top] = 1;//设置标记这个节点没有左子树了,准备输出这个节点
            p = stack[top];
            p = p->rchild;
        }
        else break;
    }
}

层序遍历

struct node{
    
    
    int data;
    int layer;
    node* lchild;
    node* rchild;
};

void LayerOreder(node* root)
{
    
    
    queue<node*> q; //注意队列里面是存地址
    root->layer = 1;
    q.push(root);
    while(!q.empty())
    {
    
    
        node* now = q.front();
        printf("%d\n",now->data);
        q.pop();
        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);
        }
    }
}

给定一个二叉树的先序遍历遍历和中序遍历序列重建这个树

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

//当前先序区间是[preL,preR]中序区间是[inL,inR]
node* creat(int preL,int preR,int inL,int inR)
{
    
    
    if(preL>preR) return; //先序序列长度小于等于0,当前的树为空直接返回
    node* root = new node; //新建立一个节点,用来存放当前二叉树的根节点
    //注意不要忘记用new申请空间
    root->data = pre[preL];
    int k;
    for(k = inL;k<=inR;k++)
    {
    
    
        if(in[k]==pre[preL]) break;
    }
    int numLeft = k - inL;
    //左子树的先序区间为[preL+1,preL+numLeft]
    //中序区间为[inL,k-1]
    //返回左子树根节点地址,赋值给root的左指针
    root->lchild = creat(preL+1,preL+numLeft,inL,k-1);
    //右子树的先序区间是[preL+numLeft+1,preR]
    //中序区间是[k+1,inR]
    root->rchild = creat(preL+numLeft+1,preL,k+1,inR);
    return root;

}

中序遍历序列与 先序遍历or后序遍历or层序遍历序列中的任意一个结合起来可以构建唯一一个二叉树,但是后面三个两两结合,甚至三个一起使用都无法构建出唯一的二叉树,这是因为先序,后序,层序,都是提供了根节点,作用是相同的,都必须由中序序列,区分出左右子树才行

一般的树

树的静态写法

const int maxn = 100;
struct node{
    
    
    int data;
    int child[maxn];//存放所有孩子的下标
}Node[maxn];

然而上述这种写法,由于child个数是不确定的,所以只能给他开到maxn
但是对于一些结点个数非常多的题目来说,这种写法容易出问题
所以,可以使用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++;
}

树的先根遍历(和二叉树的先序遍历实质是一样的但是叫法不一样)
void PreOrder(int root)
{
    
    
    printf("%d",Node[root].data);
    for(int i=0;i<Node[root].child.size();i++)
    {
    
    
        PreOrder(Node[Node[root].child[i]]);
    }
}


那么接下来,思考一个问题:上面的代码为什么没写递归边界?
因为vector是根据需要而个数动态的,当没有孩子的时候,vector的
size为0,自然也就不会执行那个节点的for循环了,这里相当于是递归边界了

//树的层序遍历
struct node{
    
    
    int layer;
    int data;
    vector<int> child;
}Node[maxn];

void LayerOrder(int root)
{
    
    
    queue<int> Q;
    Q.push(root);
    Node[root].layer = 0;
    while(!Q.empty())
    {
    
    
        int front = Q.front();
        Q.pop();
        printf("%d",Node[front].data);
        for(int i=0;i<Node[front].child.size();i++)
        {
    
    
            int child = Node[front].child[i];
            Node[child].layer = Node[front].layer + 1;
            Q.push(Node[child]);
        }
    }
}

BST(二叉排序树)

在这里插入图片描述
BST根节点的值一定大于其左子树的所有节点的值,并且不会出现右子树中的有的节点的值小于等于左子树中某节点的值
因为如果右子树中有某个节点小于等于左子树中某一节点的值,那么插入这个节点时,一定会经过这个根节点,那么经过这个根节点时,由于他的值肯定比根节点小,所以就不会插入右子树的。

struct node{
    
    
    int data;
    node *lchild;
    node *rchild;
};

//生成一个新的节点
node *newNode(int v)
{
    
    
    node *Node = new node;
    Node->data = v;
    Node->lchild = NULL;
    Node->rchild = NULL;
    return Node;
}

//搜索数据为x的节点
void search(node *root,int x)
{
    
    
	if(root==NULL)
	{
    
    
		printff("search 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);
	}
}

//插入一个值为x的节点
void insert(node* &root,int x)
{
    
    
    if(root==NULL)
	{
    
    
		root = newNode(x);
		return;
	}
	if(x==root->data) return;
	else if(x<root->data)
	{
    
    
		insert(root->lchild,x);
	}
	else{
    
    
		insert(root->rchild,x);
	}
}

//BST的建立
node *Creat(int data[],int n)
{
    
    
	node *root = NULL;
	for(int i=0;i<n;i++)
	{
    
    
		insert(root,data[i]);
	}
	return root;
}

//寻找以root为根节点的树种的最大权值节点
node *findMax(node* root)
{
    
    
	while(root->rchild != NULL)
	{
    
    
		root = root->rchild;
	}
	return root;
}
//寻找以root为根节点的树种的最小权值节点
node *findMax(node* root)
{
    
    
	while(root->lchild != NULL)
	{
    
    
		root = root-lchild;
	}
	return root;
}

//删除节点
void deleteNode(node* &root,int x)
{
    
    
	if(root == NULL) return;
	if(root->data==x){
    
    
		if(root->lchild==NULL&&root->rchild==NULL)
		{
    
    
			root = NULL;
		}
		else if(root->lchild!=NULL)  //这里也就是说如果左右子树都有,其实按照左子非空和右子树非空的方法来删除都是正确的
		{
    
                                //虽然会产生不同的BST
			node *pre = findMax(root->lchild);
			root->data = pre->data;
			deleteNode(root->lchild,pre->data);
		}
		else{
    
    
			node *next = findMin(root->rchild);
			root->data = next->data;
			deleteNode(root->rchild,next->data);
		}
	}
	else if(root->data>x)
	{
    
    
		deleteNode(root->lchild,x);
	}
	else{
    
    
		deleteNode(root->rchild,x);
	}
}

猜你喜欢

转载自blog.csdn.net/weixin_44972129/article/details/110431412