数据结构|二叉树


写在前面

  • 本以为这二叉树有什么难的,无非就是几个定义,然后把前中后序遍历搞清楚就行了,然后还有几个满二叉树,完全二叉树的定义。在老师持续输出了几天后发现脑子根本跟不上,二叉树难的是用非递归实现,难的是用学过的栈、队列,与二叉树联合起来,用不同的方法实现算法。老师把我按在地上摩擦,不写点笔记复习一下真的懂不了。

二叉树的定义

二叉树由左子树,右子树以及数据构成,每一层节点数达到最大值称为满二叉树,最后一层从右向左依次缺省节点的树称为完全二叉树。
二叉树的定义:

typedef char ElemType;
typedef struct BTNode{
	struct BTNode* leftchild;
	struct BTNode* rightchild;
	ElemType data;
}BTNode, *BinaryTree;

购买节点:

BTNode* BuyNode(ElemType val){
	BTNode* s = (BTNode*)malloc(sizeof(BTNode));
	if(s == NULL) exit(EXIT_FAILURE);
	s->data = val;
	s->leftchild = NULL;
	s->rightchild = NULL;
	return s;
}

释放函数:

void FreeNode(BTNode* p){
	free(p);
}

二叉树节点个数:
可以用递归找左子树与右子树个数,再加上根节点

int GetSize(BTNode* p){
	if(p == NULL) return 0;
	return GetSize(p->leftchild) + 
			GetSize(p->rightchild) + 1;
}

二叉树高度:
与查询节点个数相似,递归查找左子树右子树的高度最大值
需要自己写一个找最大值的函数

int Max(int a,int b){
	return a>b?a:b;
}
int GetDepth(BTNode* p){
	if(p == NULL) return 0;
	return Max(GetDepth(p->leftchild),GetDepth(p->rightchild)) + 1;
}

创建二叉树:
手动输入字符串创建二叉树,#表示NULL
ch不等于’#'就赋值给s,递归赋值左子树右子树

BTNode* CreateBinatyTree(){
	ElemType ch;
	BTNode* s=NULL;
	scanf("%c",&ch);
	if(ch!='#'){
		s = BuyNode(ch);
		s->leftchild = CreateBinaryTree();
		s->rightchild = CreateBinaryTree();
	}
	return s;
}

测试用例
这里使用了前序遍历测试

创建二叉树2:
不输入,在主函数中定义p(这里是老师第一次摩擦我,指针花活绕死我了,用的应该是c++中的知识,clion不让俩语言一块用)

BTNode* CreateBinaryTree(const char** p){
	BTNode* s=NULL;
	if(p!=NULL && *p!=NULL && **p!='#'){
		s= BuyNode(**p);
		s->leftchild = CreateBinaryTree(&++*p);
		s->rightchild = CreateBinaryTree(&++*p);
	}
	return s;
}

以上两种方法都是告诉了NULL,下面为只有data创建二叉树

利用前序中序排序字符串创建二叉树(PI):
前序的第一个字符为根节点,在中序中,根节点左边的全是左子树,右边的全为右子树。利用这一逻辑创建二叉树。
首先要找到根节点的下标位置

int FindIs(const char* is,char ps,int n){
	assert(is!=NULL);
	for(int i=0;i<n;++i){
		if(ps = is[i]) return i;
	}
	return -1;
}//is为中序排序,ps为根节点字符,n为字符串长度,
//用i返回根节点在is中的下标
BTNode* CreatePI(const char* ps,const char* is,int n){
	BTNode* s = NULL;
	if(n>0){
		s = BuyNode(ps[0]);//前序第一位为根节点
		int pos = FindIs(is,ps[0],n);//找is中根节点的下标
		if(-1 == pos) exit(EXIT_FAILURE);
		//递归写出左子树与右子树
		//左子树前序排序从第二个节点开始pos个,中序从0开始pos个
		s->leftchild = CreatePI(ps+1,is,pos);
		//右子树前序排序从pos+1节点开始n-pos-1个
		//    	中序排序从pos+1节点开始n-pos-1个
		s->rightchild = CreatPI(ps+pos+1,is+pos+1,n-pos-1);
	}
	return s;
}
BTNode* CreateBinaryTreePI(const char* ps,const char* is){
	assert(ps!=NULL && is!=null);
	int n = strlen(ps);
	return CreatePI(ps,is,n);
}

利用后序遍历检验,运行成功。
在这里插入图片描述
利用中序后序排序字符串创建二叉树(IL)
后序排序最后一个字符为根节点,在中序排序中找到根节点的下标pos,左子树的中序排序为0~pos,后序排序为0~pos
右子树的中序排序为pos+1 ~ n-pos-1,后序排序为pos ~n-pos-1

BTNode* CreateIL(const char* is,const char* ls,int n){
	BTNode* s=NULL;
	if(n>0){
		s = BuyNode(ls[n-1]);
		int pos = FindIs(is,ls[n-1],n);
		if(-1 == NULL) exit(EXIT_FAILURE);
		s->leftchild = CreateIL(is,ps,pos);
		s->rightchild = CreateIL(is+pos+1,ls+pos,n-pos-1);
	}
	return s;
}
BTNode* CreateBinaryTreeIL(const char* is,const char* ls){
	assert(is!=NULL&&ls!=NULL);
	int n=strlen(is);
	return CreateIL(is,ls,n);
}

利用前序遍历检验
在这里插入图片描述

前中后序,层次遍历

递归遍历比较简单,根据逻辑写代码就行。

前序遍历:
使用递归实现,逻辑即为“根左右”,访问到根的时候就打印,然后递归遍历左子树,右子树

void PreOrder(BTNode* p){
	if(p != NULL){
		printf("%c",p->data);
		PreOrder(p->leftchild);
		PreOrder(p->rightchild);
	}	
}

中序遍历:
逻辑为“左根右”,到根的时候打印,否则递归

void InOrder(BTNode* p){
	if(p != NULL){
		InOrder(p->leftchild);
		printf("%c",p->data);
		InOrder(p->rightchild);
	}
}

后序遍历:
逻辑为“左右根”,到根的时候打印,否则递归

void PastOrder(BTNode* p){
	if(p!=NULL){
		PastOrder(p->leftchild);
		PastOrder(p->rightchild);
		printf("%c",p->data);
	}
}

层次遍历较为复杂,老师用到了队列
逻辑顺序为:
1.将根节点先入队。
2.队头出队打印,并且访问左右节点,非空便依次入队

void NiceLevelOrder(BTNode* ptr){
	if(ptr == NULL) return;
	std::queue<BTNode*> qu;
    qu.push(ptr);
    while (!qu.empty())//队列不为空就循环
    {
        ptr = qu.front(); qu.pop();//队头出列并打印
        printf("%c ", ptr->data);
        if (ptr->leftchild != nullptr)
        {//左子树不为空就入队
            qu.push(ptr->leftchild);
        }
        if (ptr->rightchild != nullptr)
        {//右子树不为空就入队
            qu.push(ptr->rightchild);
        }
    }
    printf("\n");
}

非递归遍历

这里用到了栈
非递归中序遍历
当树不为空时,向栈中传入数据,树指向左子树。当树空时,即为没有了左子树,出栈并且打印,此时打印的就是最下面的左子树。然后将树ptr赋值为右子树。逻辑便是左根右。

void NiceInOrder(BTNode* ptr){//非递归中序遍历
    if(ptr == NULL) return;
    LinkStack mys;
    InitStack(&mys);
    while(ptr!=NULL || !StackEmpty(&mys)) {
        while (ptr != NULL) {
            Push(&mys, ptr->data);//左子树不为空就入栈
            ptr = ptr->leftchild;//往左子树走
        }
        Pop(&mys, &ptr);//左子树为空时出栈
        printf("%c", ptr->data);//出栈并打印
        ptr = ptr->rightchild;//根有右子树就赋值,没有右子树就下一个循环
    }
    DestroyStack(&mys);
}

非递归后序遍历
与中序遍历不同,中序遍历一直往左走就可以了,但是后序遍历需要考虑右子树,这里用到了tag标记,还不是很明白,再去看看回放。

void StkNicePastOrder(BTNode* ptr)
{
    if (ptr == NULL) return;
    BTNode* tag = NULL;
    LinkStack mys;
    InitStack(&mys);
    SElemType e, x = { ptr,0 };
    Push(&mys, x);
    while (!StackEmpty(&mys))
    {
        Pop(&mys, &e);
        if (++e.popnum == 3)
        {
            printf("%c ", e.pnode->data);
        }
        else
        {
            Push(&mys, e);
            if (e.popnum == 1 && e.pnode->leftchild != nullptr)
            {
                SElemType left = { e.pnode->leftchild,0 };
                Push(&mys, left);
            }
            else if (e.popnum == 2 && e.pnode->rightchild != nullptr)
            {
                SElemType right = { e.pnode->rightchild , 0 };
                Push(&mys, right);
            }
        }
    }
    printf("\n");
    DestroyStack(&mys);
}

查询

二叉树查找某一个值
这个较为简单,利用递归,根节点不对就去找左子树,左子树为空就找右子树

BTNode* FindValue(BTNode* ptr,elemType val){
	if(ptr == NULL || ptr->data == val) return ptr;
	else {
		BTNode* p = FindValue(ptr->leftchild,val);
		if(p == NULL){
			p=FindValue(ptr->rightchild,val);
		}else{
			return p;
		}
	}
}

二叉树找双亲
与查询值相似,递归即可

BTNode* FindParent(BTNode* ptr,elemType child){
	if(ptr == NULL || ptr->leftchild == child || ptr->rightchild == child){
		return ptr;
	}else{
		BTNode* p = FindParent(ptr->leftchild,child);
		if(p == NULL){
			p = FindParent(ptr->rightchild,child);
		}else return p;
	}
}

二叉树查找最近公共祖先


二叉搜索树(BST树)

二叉搜索树的定义:
左子树都小于根节点的值
右子树都大于根节点的值

这一性质使BST树的查询速度要由于链表查询,要么比val大要么比val小
但当数组极端情况下,也就是数组有序时,二叉搜索树就退化成一个仅有右子树或左子树的树,类似于链表。后文将讲述优化方法。
BST树的定义
与二叉树定义相同,这里的数据类型改为int,加入双亲节点parent

typedef int ElemType;
typedef struct BstNode //BinarySortTreeNode
{
    struct BstNode* leftchild;
    struct BstNode* parent;
    struct BstNode* rightchild;
    ElemType data;
}BstNode, * PBstNode;

typedef struct
{
    BstNode* root;
    int cursize;
}BinarySortTree;

BstNode* Buynode(ElemType val, BstNode* Parg = nullptr,
    BstNode* Larg = nullptr, BstNode* Rarg = nullptr)//这里不再赘述
void Freenode(BstNode* p)void InitBSTree(BinarySortTree * ptree)void DestroyBSTree(BinarySortTree* ptree);

BST树查询
利用递归,val的值大于当前节点或小于当前节点,或者就等于当前节点的值。类似于中序遍历。

BstNode* SearchValue(BstNode* ptr, ElemType val)
{
    BstNode* p = nullptr;
    if (nullptr == ptr || val == ptr->data)
    {
        p = ptr;
    }
    else if (val < ptr->data)
    {
        p = SearchValue(ptr->leftchild, val);
    }
    else
    {
        p = SearchValue(ptr->rightchild, val);
    }
    return p;//用指针p返回
}

插入节点
1.如果树为空,就直接插入,cursize+1.
2.找到应该插入的位置,左子树或者右子树。
3.找到应插入的节点后,判断应该插在左边或者右边

bool  Insert(BinarySortTree * proot, ElemType val)
{
    if (nullptr == *proot)
    {
        *proot = Buynode(val);
        return true;
    }//二叉树为空直接插入
    BstNode* pa = nullptr;//定义一个节点标记应该插入的节点
    BstNode* p = *proot;
    while (p != nullptr && p->data != val)
    {
        pa = p;
        p = val < p->data ? p->leftchild : p->rightchild;
    }//val小就往左走,大就往右走。
    //当p为空时,也就是查询到了应插入的位置,并且pa标记了
    if (p != nullptr) return false;//p不为空表示没有地方可以插入。
    p = Buynode(val, pa);//在pa处购买节点,值为val
    if (p->data < pa->data)
    {
        pa->leftchild = p;
    }//val小于pa->data就插在左边
    else
    {
        pa->rightchild = p;
    }//val大于pa->data就插在右边
    return true;
}

删除节点
1.如果树为空或者找不到要删除的值,就返回false
2.

在这里插入代码片

AVL树

前面讲过,如果二叉搜索树遇到了极端情况,也就是数组本身就有序,做成BST树就会退化成一个链表,查询时间大大增加。因此需要优化,让我们制作BST树时,能让数值均匀的分布在二叉树的左右。
数据结构|平衡二叉搜索树(AVL树)
退化

红黑树(RBTree)

未完待续,持续更新

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值