数据结构 树&二叉树

2021年四月三十日 于海棠咖啡馆

常见的树结构:计算机储存

树的特征:对于每个节点,只能由一个前驱(child node),可以有多个后继(parent node),且后继结点没有交集。没有前驱的成为根结点,没有后继的成为叶子节点

树的递归定义:任何一颗非空的树中,有一个特殊的结点t∈D,称为根节点。其余节点可分为m个不相交的子集,成为子树。

基本名词术语:

    1.结点的度:子树数目

    2.树的度:max{结点的度}

    3.分支节点:度非0的结点(非叶子节点)

    4.树的深度/高度:最大层次

    5.路径:路径长度=经过结点数-1

    6.有序性:子树的顺序不能改变(线性表的有序性也是这样:顺序不能改变,而不是数学上的有序单调)

储存结构:

    1.顺序存储:对于完全二叉树比较合适,对于非完全二叉树有浪费。对于退化二叉树有极大的空间浪费。

    2.链式存储:

        2.1.多重链表:k=树的度,每个结点k个指针域 方便但是有空间浪费

        2.2.不定长结点的多重链表:k=该结点的度 不浪费空间,但是动态操作麻烦

二叉树:最多有两个子树,且严格区分左右子树(即使只有一颗子树,也要确定这个是左还是右,这一点比普通的度为2的有序树要严格)

    满二叉树:叶节点集中在最下面一层

    完全二叉树:只有最下面两层结点度可以小于2,且最下面一层的结点依次排列(也就是说除了最后一层都是满的,然后最后一层的全在左边,如果是数组存储的话就不会留空)

二叉树的性质:

    1.具有n个结点的树右n-1个分支:除了根节点,每个节点有两个分支,所以有2*(n-1)/2=n-1个分支

    2.非空二叉树第i层最多有2^(i-1)个结点:等比数列求和即可

    3.深度为h的树最多有2^h - 1个结点:同上,等比求和 且在改树为完全二叉树时取最大值

    4.若非空二叉树有n0个叶子结点,n2个度为2的结点 则n0=n2 + 1

    5.具有n个结点的完全二叉树的深度为 h = [log(n)]-1 这也是所有树二叉树深度最小的情况

    6.按照从上到下,从左往右的按层编号方法:

        若从1开始编号

        对于编号为i的结点,其双亲结点编号为[i/2],左孩子为2i,右孩子为2i+1(前提是这个值不超过n,若这个值超过n则不存在左/右子树)

        若从0开始编号

        对于编号为i的结点,其双亲结点编号为[(i-1)/2],左孩子为2i+1,右孩子为2i+2(前提是这个值不超过n,若这个值超过n则不存在左/右子树)

二叉树的基本操作:

1.创建

创建一个二叉查找树BST:

typedef struct node{
	ElemType data;
	struct node* left;
	struct node* right;
}Node;

typedef struct tree{//指向一棵树 
	Node *root;
}Tree;


void insert(Tree *tree,int item){//创建一棵树 和插入结点都在同一个函数 
	Node *node=(Node*)malloc(sizeof(Node));
	node->data=item;
	node->left=NULL;
	node->right=NULL;
	if(tree->root==NULL){//创建树的情况 
		tree->root=node;
	}
	else{//插入结点的情况 
		Node *temp = tree->root;//从树根开始遍历
		while(temp!=NULL){
			if(item<temp->data){//小于的情况:左树 
				if(temp->left==NULL){//找到左叶子 
					temp->left=node;
					return;
				}
				else{
					temp = temp->left;
				}
			}
			else{//大于等于:右孩子
			    if(temp->right==NULL){
			    	temp->right=node;
					return; 
				}
				else{
					temp = temp->right;
				}
			}
		} 
	}
	return ;
}

2.求根结点 略

3.求双亲结点

方法1

ode *search_parent(Tree *BSTree,ElemType item){//先层次遍历储存到queue中,然后遍历queue,时间复杂度高 
	if(BSTree->root->data==item) return NULL;
	for(int i=0;i<max_node_num;i++) queue[i]=NULL;
	layerorder(BSTree);
	for(int i=0;queue[i]!=NULL;i++){
		if(queue[i]->left->data==item) return queue[i];
		if(queue[i]->right->data==item) return queue[i];
	}
}

方法2

这个方法也可以用来查找某个结点

void search_parent(Node *node,Node *address,ElemType item){//递归 时间复杂度看情况在log n到n之间 ,虽然递归不好定位,但是可以指针传参 
	if(node->left->data==item||node->right->data==item){
		address->data = node->data;
		return ;
	}
	else if(item > node->data){//在右子树 
		search_parent(node->right,address,item);
		return ;
	}
	else if(item < node->data){//在左子树 
		search_parent(node->left,address,item);
		return ;
	}
}
/*注意使用的时候:
    int e;
	scanf("%d",&e);
	Node parent;
	search_parent(BSTree->root,&parent,e);
	printf("%d",parent.data);
*/ 
 

4.求孩子节点 略

5.删除左 /右子树 略

6.遍历二叉树

三种DFS使用递归即可

BFS需要使用队列

Node *queue[max_node_num];
void layerorder(Tree *tree){//层次遍历二叉树,并且储存到queue 
	int front=0,rear=0;
	queue[0]=tree->root;
	Node *p;
	while(front<=rear){
		p=queue[front];
		front++;
		if(p->left!=NULL){
			rear++;
			queue[rear]=p->left;
		}
		if(p->right!=NULL){
			rear++;
			queue[rear]=p->right;
		}
	}
}

由两个序列生成二叉树

1.前序遍历和中序遍历:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct binarytree//定义节点
{
    char val;
    struct binarytree *left, *right;
} bitree;

char pre[100];
char in[100];//存储序列

void BAK(bitree *r)//后序遍历(蹩脚英语写的脑瘫名字)
{
    if (r->left != NULL)
        BAK(r->left);
    if (r->right != NULL)
        BAK(r->right);
    printf("%c", r->val);
}

bitree *constructcore(int prel, int prer, int inl, int inr)//创建树,变量分别为先序的起点终点,中序的起点终点
{
    bitree *root = (bitree *)malloc(sizeof(bitree));
    root->val = pre[prel];//先序遍历的第一个为根
    root->left = NULL;
    root->right = NULL;//一定要记得初始化 防止出现野指针

    if (inl == inr)
        return root;//如果长度为一 没有子树 直接返回

    int rootin;
    for (rootin = inl; rootin <= inr; rootin++)//寻找根在中序遍历中的位置
    {
        if (in[rootin] == root->val)
        {
            break;
        }
    }

    int l_len = rootin - inl;
    int r_len = inr - rootin;//分别求左右子树长度:一是判断是否存在,二是给子树遍历的起止定位

    if (l_len > 0)//注意上方只判断了有没有子树,并没有分别判断左右子树是否存在,还要用长度判断一下
        root->left = constructcore(prel + 1, prel + l_len, inl, rootin - 1);//递归左子树
    if (r_len > 0)
        root->right = constructcore(prel + l_len + 1, prer, rootin + 1, inr);//递归右子树

    return root;
}

int main()
{
    int n;
    scanf("%s", in);
    //getchar();
    scanf("%s", pre);
    //puts(in);puts(pre);
    n = strlen(in);
    //printf("%d",n);
    bitree *tree = constructcore(0, n - 1, 0, n - 1);
    BAK(tree);
}

后序遍历和中序遍历求子树:

仿照前面了,只是要把先序遍历改成后序遍历——即根节点到了最后一个,左右子树在序列中的位置对换一下即

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct binarytree
{
    char val;
    struct binarytree *left, *right;
} bitree;

char post[100];
char in[100];

bitree *constructcore(int postl, int postr, int inl, int inr)
{
    bitree *root = (bitree *)malloc(sizeof(bitree));
    root->val = post[postr];
    root->left = NULL;
    root->right = NULL;

    if (inl == inr)
        return root;

    int rootin;
    for (rootin = inl; rootin <= inr; rootin++)
    {
        if (in[rootin] == root->val)
        {
            break;
        }
    }

    int l_len = rootin - inl;
    int r_len = inr - rootin;

    if (l_len > 0)
        root->left = constructcore(postl, postl + l_len - 1, inl, rootin - 1);
    if (r_len > 0)
        root->right = constructcore(postl + l_len, postr - 1, rootin + 1, inr);

    return root;
}

void pre_traverse(bitree *p)
{
    printf("%c", p->val);
    if (p->left)
        pre_traverse(p->left);
    if (p->right)
        pre_traverse(p->right);
}

int main()
{
    int n;
    scanf("%s", in);
    //getchar();
    scanf("%s", post);
    //puts(in);puts(pre);
    n = strlen(in);
    //printf("%d",n);
    bitree *tree = constructcore(0, n - 1, 0, n - 1);
    pre_traverse(tree);
}

7.求某个结点的层

8.求深度

使用递归 

int get_height(Node *node){//求树高 
	if(node==NULL) return 0;
	return 1 + max(get_height(node->left),get_height(node->right));
}

9.销毁树

10.删除某一个结点 若任然要保持其排序的特性 则需要

    10.1 删除叶节点 直接删除

    10.2 若无左(右)子树 则用右(左)子树的根代替被删除的节点

    10.3 若左右子树都存在 则用右子树中值最小的结点(或者左子树中值最大的结点)代替被删除的结点

  10# lazy deletion:任然保留结点 只是做一个记号表明其被删除 通常用于删除数量不多的情况

在图中也可以使用此来进行逻辑删除 删除点只需将其所有边的权值变为0即可

线索二叉树

1.结点结构

struct node{
    ElemType data;
    struct node *left,*right;
    char lb,rb;//0表示指向直接前驱/后继 1表示指向左/右孩子
};

2.DFS中序遍历(不使用递归)

TBTNodeptr insucc(TBTNodeptr x){//确定直接后继
    TBTNodeptr s;
    s = x->right;
    if(x->rb==1){
        while(s->lb==1) s=s->left;
    }
    return s;
}

void t_order(TBTNodeptr head){
    TBTNodeptr p=head;
    while(1){
        p=insucc(p);
        if(p==head) break;
        VISIT(p);
    }
}

3.建立一颗线索二叉树(中序)

中序遍历二叉树时

prior为前一次访问的结点 p为当前访问的结点

若左指针域空 则让其指向prior

若prior的右指针域为空 则指向p

这里的prior使用全局变量 方便操作


//prior是一个全局变量,初始时指向根节点
void inThreading(TBTNodeptr p){
    if(p==NULL) return;//递归结束的条件
    inThreading(p->left);//由于是中序 先对左子树线索化
    //然后对p线索化
    if(p->left==NULL){
        p->lb=0;
        p->left=prior;
    }
    else p->lb = 1;
    if(prior->right==NULL){
    prior->rb=0;
    prior->right=p;
    }
    else prior->rb=1;
    prior=p;
    inThreading(p->right);
}

平衡二叉树

二叉树的形态可能比较随意 如退化二叉树 也就是说左右树深度差很大

而平衡二叉树的任意一个节点的左右子树 深度差距不超过1

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值