【408数据结构】树(一)

树主要是应用题偏多。(算法题只考过了两次,都挺简单的,就是一个遍历;算法题在下一篇)

思维导图

请添加图片描述

基本概念

养成习惯:区分度和度之和:

结点个数=总度+1

度:指一棵树中,每一个结点度的最大值

链式

链式存储如果考多叉树,只有可能是孩子表示法那一块。
因此,大题只可能是二叉树那块。

遍历

递归遍历很简单。

void preOrder(Node *p)
{
	if(p!=NULL)
	{
		visit(p);
		preOrder(p->lchild);
		preOrder(p->rchild);
	}
}
// 前序、中序、后续遍历,都不难

⭐非递归遍历。

中序和前序是基本一样的,都是1️⃣一路向左入栈左结点,入栈到最后,发现某个点的左孩子空了之后,不能再入栈了之后;2️⃣弹出栈顶,访问:2️⃣.1️⃣如果有左孩子,执行1️⃣;2️⃣.2️⃣如果没有右孩子,执行2️⃣。

void inOrder(BTNode *root)
{
	Stack <BTNode>;
	InitStack(S);
	BTNode p = root;
	while(p || isEmpty(S))
	{
		if(p)
		{
			push(S,p);
			p = p -> lchild;
		}
		else{
			pop(S,p);
			visit(p); // 中序遍历,先 -> 根 -> 右
			p = p -> rchild;
		}
	}
}

void preOrder(BTNode *root)
{
	Stack <BTNode>;
	InitStack(S);
	BTNode p = root;
	while(p || isEmpty(S))
	{
		if(p)
		{
			visit(p); // 先序遍历,根 -> 先 -> 右
			push(S,p);
			p = p -> lchild;
		}
		else{
			pop(S,p);
			p = p -> rchild;
		}
	}
}

后序遍历需要对某一个子树的根进行一个标志处理。因为后序是左右根。某一个根的某一个子树结束搜索之后,会不知道是哪一个子树退出了栈中。因此,需要额外的处理。

void postOrder(BTNode *root)
{
	Stack <BTNode>;
	InitStack(S);
	BTNode p = root;
	while(p || isEmpty(S))
	{
		if(p)
		{
			push(S,p);
			p = p -> lchild;
		}
		else{
			getTop(S,p);
			if(p-> rchild && p->rchild != r)
				p = p -> rchild;
			else{
					pop(S,p)
					visit(p);
					r = p;
					p = NULL;
	
			}
		}
	}
}

⭐几个需要说明的点:

  1. 后序遍历中,遍历到某一个点后,其栈中的元素均是该点的祖先
  2. 后序遍历在非递归遍历时,除了结束的时刻,并不会出现某一时刻的栈空的情况。栈空有且仅有结束时,根结点退栈。

构造

👍🏼给定一个中序序列和一个先序序列,构造该树?

Tree preInCreate(int A[], int B[], int l1, int l2, int h1, int h2)
{// A(l1, h1)先序; B(l2, h2)中序
	
	Node *root =  (Node*)malloc(sizeof(Node));
	root->data = A[l1];
	// 循环,划分
	for(i = l2; B[i]!= root->data; i++);
	llen = i - l2;
	rlen = h2 - i;
	
	// 递归建立左子树
	if(llen) // 有左子树的话
		root->lchild = preInCreate(A,B,l1+1, l1+llen, l2, l2+llen-1);
	else root->lchild = NULL;
	
	// 递归建立右子树
	if(rlen) // 有右子树的话
		root->rchild = preInCreate(A,B,h1-rlen+1, h1, i+1, h2);
	else root->lchild = NULL;
	
	return root;
}

线索二叉树

本质:遍历。

利用空指针域,指向前驱或者是后继。

构造

算法程序:

有pre 有p。先递归左子树;再检查p的左指针是否为空,如果空,则附上;再看pre的右子树是否为空,如果是空,则连到p;再递归右子树。

如果先序、后序就调整一下。【不可能考更深的东西了!】

Node *pre;
void Inthread(Node *p) //中序
{
	Inthread(p->lchild);

	if(!p->lchild)// 没有左孩子,连上
	{
		p -> lchild = pre;
		p -> ltag = 1;
	}
	if(! pre -> rchild){
		pre -> rchild = p;
		pre -> rtag = 1;
	}
	pre = p; 
	Inthread(p->rchild);
}

遍历

事实上,线索二叉树可以从两个方向进行遍历:从前到后、从后到前。

从前到后时,我们需要找到后继。

当前结点p后继的存放位置无非就是两个:

  1. 当前结点p的rtag== 1。此时,(右孩子== 右线索)就是后继。
  2. 当前结点p的rtag==0。此时,以右孩子为根的子树中,我们需要找到中序遍历的第一个结点。(其实就是右子树的最左下角。)

将其封装为两个函数:

Node *find_next(Node *p)
{
	if (p->rtag == 1)
		return p -> rchild;
	else
		return find_first_node(p->rchild);
}
Node *find_first_node(Node p)
{
	while(p->ltag == 0) p = p -> lchild;
	return p;
}

于是,我们可以得到从前到后的遍历:

void Inorder_thread(Node *T)
{
	for(Node *p = find_first_node(T); p!=NULL; p = find_next(p))
		visit(p);
}

手动转换线索

其实,就是写出一遍序列,然后画出图,手动连。关键是知道,箭头的方向永远是指向下一个。

并且,能指向NULL的线索,一定是最前一个或者最后一个。

后序线索树的坑爹情况

后序线索树的遍历可能会出现断了的情况,而中序和先序不会。

例如:

在这里插入图片描述

这样一个后序线索树,d→b→c→a。但是b→c的线索,却是无的。(因为右孩子接后继结点,而有孩子已经连了d了)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值