第五章 树与二叉树

一、二叉树

链式存储结构

	typedef struct BiTNode{
		ElemType data;
		struct BiTNode *lchild,*rchild;
	}BiTNode,*BiTree;

遍历

先序遍历

递归版

	void PreOrder(BiTree T)
	{
		if(T != NULL)
		{
			visit(T);//访问根结点
			PreOrder(T->lchild);//递归遍历左子树
			PreOrder(T->rchild);//递归遍历右子树
		}
	}

非递归版

	void PreOrder2(BiTree T)
	{
		Stack S;
		BiTree p = T;
		InitStack(S);
		while(p || !IsEmpty(S)) //栈不空或p不空时循环
		{
			if(p) //一路向左遍历
			{
				visit(p);//先序遍历,先访问根结点
				Push(S,p);//并入栈
				p = p->lchild; //左孩子不空,一直向左走
			}
			else //p空,出栈回溯,并转向出栈结点的右子树
			{
				Pop(S,p); 
				p = p->rchild; //转向根结点的右子树进行先序遍历
			}
		}
	}

中序遍历

递归版

void InOrder(BiTree T)
{
	if(T != NULL)
	{
		InOrder(T->lchild); //递归遍历左子树
		visit(T); //访问根结点
		InOrder(T->rchild); //递归遍历右子树
	}
}

非递归版

void InOrder2(BiTree T)
{
	Stack S;
	BiTree p = T;
	while(p || !IsEmpty(S)) //栈不空或p不空时循环
	{
		if(p) //一路向左遍历
		{
			Push(S,p);
			p = p->lchild;
		}
		else //p空,出栈回溯,并转向出栈结点的右子树
		{
			Pop(S,p);
			visit(p); //访问根结点
			p = p->rchild;//转向根结点的右子树进行中序遍历
		}
	}
}

后序遍历

递归版

void PostOrder(BiTree T)
{
	if(T != NULL)
	{
		PostOrder(T->lchild);
		PostOrder(T->rchild);
		visit(T);
	}
}

非递归版

思想:按照“根右左”的顺序遍历,将结果保存到栈中,最后依次出栈访问,恰好得到“左右根”的访问顺序。

void PostOrder(BiTree T)
{
	Stack s1,s2;
	BiTree p = T;
	while(p || !IsEmpty(s1)) //栈不空或p不空时循环
	{
		if(p) //一路向右遍历
		{
			Push(s2,p);
			Push(s1,p);
			p = p->rchild;
		}
		else //p空,出栈回溯,并转向出栈结点的左子树
		{
			Pop(s1,p);
			p = p->lchild;//转向根结点的左子树进行遍历
		}
	}
	while(!IsEmpty(s2))
	{
		Pop(s2,p);
		visit(p);//访问每个结点
	}
}

层序遍历

void LevelOrder(BiTree T)
{
	Queue Q;
	BiTree p;
	InitQueue(Q);
	EnQueue(Q,T);
	
	while(!IsEmpty(Q))
	{
		DeQueue(Q,p);
		visit(p);
		if(p->lchild != NULL) EnQueue(Q,p->lchild);
		if(p->rchild != NULL) EnQueue(Q,p->rchild);
	}
}

线索化

存储结构

typedef struct ThreadNode
{
	ElemType data;
	struct ThreadNode *lchild,*rchild;
	int ltag,rtag;//左右线索标志,0表示左右孩子,1表示线索
}ThreadNode,*ThreadTree;

构造(以中序为例)

	void InThread(ThreadTree &p,ThreadTree &pre)
	{
		if(p != NULL)
		{
			InThread(p->lchild,pre); //递归,线索化左子树
			if(p->lchild == NULL) //若左子树为空,建立前驱结点
			{
				p->ltag = 1;
				p->lchild = pre;
			}
			if(p->rchild == NULL && pre != NULL) //建立上一个结点的后驱结点
			{
				pre->rtag = 1;
				pre->rchild = p;
			}
			InThread(p->rchild,p);//递归,线索化右子树,更新当前结点为刚刚访问的结点
		}
		
	}

主过程算法

void CreateInThread(ThreadTree T)
{
	ThreadTree pre = NULL;
	if(T != NULL)
	{
		InThread(T,pre);
		pre->rchild = NULL;//处理遍历的最后一个结点
		pre->rtag = 1;
	}
}

遍历(以中序为例)

求中序线索二叉树中中序序列下的第一个结点

TreeNode *Firstnode(ThreadNode *p)
{
	while(p->ltag == 0) p = p->lchild; //最左下结点(不一定叶结点)
	return p;
}

求中序线索二叉树中结点p的后继

TreeNode *nextnode(ThreadNode *p)
{
	if(p->rtag == 0) return Firstnode(p->rchild);
	else return p->rchild;
}

不含头结点的中序线索二叉树的中序遍历的算法

	void Inorder(ThreadNode *T)
	{
		for(ThreadNode *p = Firstnode(T);p != NULL;p = nextnode(p))
		{
			visit(p);
		}
	}

二、树和森林

存储结构

双亲表示法(顺序存储)

#define MAX_TREE_SIZE 100
typedef struct{
	ElemType data;
	int parent;
}PTNode;

typedef struct{
	PTNode nodes[MAX_TREE_SIZE];
	int n;
}PTree;

孩子兄弟表示法(链式存储) 树转换成森林

typdef struct CSNode{
	ElemType data;
	struct CSNode *firstchild,*nextsibling;//第一个孩子和右兄弟指针
}CSNode,*CSTree;

相互转换方法

树转换成二叉树:大家变小家,双亲管老大,大孩管小孩

  1. 在兄弟之间加一条线
  2. 对每个结点,只保留它与第一个孩子的连线,与其他孩子的连线全部删去
  3. 以树根为轴心,顺时针旋转45°
    image
    image

森林转换成二叉树:先把每棵树转换成二叉树,再连接每棵二叉树的根结点

二叉树转换成树:小家变大家,连右变团圆,删除原右链

  1. 若某结点是其双亲的左孩子,则把该结点的右孩子、右孩子的右孩子等都与该结点的双亲结点用连线连起来。
  2. 删除原二叉树中所有双亲结点与右孩子结点之间的连线。
  3. 以根节点为轴心,逆时针转动45°,使结构层次分明。
    image
    image

二叉树转换成森林:断右变多个二叉树,逐一转换为树

  1. 断开右子树,直到无右结点为止
  2. 连接每个结点左孩子的右孩子,直到无右子树为止
  3. 删去每个结点最初存在的右链

树和森林的遍历与二叉树遍历的对应关系

森林 二叉树
先序遍历 先序遍历 先序遍历
后根遍历 中序遍历 中序遍历

三、二叉搜索树

查找

BSTNode* BST_Search(BiTree T,ElemType key)
{
	while(T != NULL && T->data != key)
	{
		if(key < T->data) T = T->lchild;
		else T = T->rchild;
	}
	return T;
}

插入

int BST_Insert(BiTree T,KeyType k)
{
	if(T == NULL)
	{
		T = (BiTree)malloc(sizeof(BSTNode));
		T->data = k;
		T->lchild = T->rchild = NULL;
		return 1;//插入成功
	}
	if(k == T->data) return 0;//存在相同关键字的结点,插入失败
	else if(k < T->data) return BST_Insert(T->lchild,k);//插入到左子树
	else return BST_Insert(T->rchild,k);//插入到右子树
}

删除

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    TreeNode* FindMax(TreeNode* root) //一直向右走,找到树上最大值
    {
        if(!root) return NULL;
        TreeNode* t = root;
        while(t->right)
        {
            t = t->right;
        }
        return t;
    }
    TreeNode* deleteNode(TreeNode* root, int key) 
    {
        TreeNode* tmp;
        if(root)
        {
            if(key == root->val) //找到了要删除的结点
            {
                if(root->left && root->right) //左右孩子存在,则用左子树最大值的结点值替换当前结点(西电答案默认前驱)
                {
                    tmp = FindMax(root->left);
                    root->val = tmp->val;
                    root->left = deleteNode(root->left,tmp->val);//删除左子树的最大值结点
                }
                else //有1个孩子或叶结点
                {
                    tmp = root;//备份当前结点用于删除
                    if(!root->left) root = root->right;//右孩子代替当前根结点
                    else if(!root->right) root = root->left;//左孩子代替当前根结点
                    delete(tmp);//删除先前根结点 
                }
                
            }
            else if(key < root->val) //递归去左子树查找并删除
            {
                root->left = deleteNode(root->left,key);
                
            }
            else root->right = deleteNode(root->right,key);//递归去右子树查找并删除
            return root;
        }
        else return root;
    }
};

四、平衡二叉树

插入后发生不平衡时的调整策略

基本思想

先找到离插入结点最近的平衡因子的绝对值大于1的结点A,再对以A为根的子树,在保持二叉排序树特性的前提下,调整各结点的位置关系,使之重新达到平衡。

1.LL平衡旋转

执行条件

结点A的左孩子的左子树上插入结点导致A的不平衡

执行方法:右旋

  1. 将A的左孩子B向右上旋转代替A成为根结点
  2. 将A结点向右下旋转成为B的右子树的根结点
  3. B的原右子树则作为A结点的左子树

1.RR平衡旋转

执行条件

结点A的右孩子的右子树上插入结点导致A的不平衡

执行方法:左旋

  1. 将A的右孩子B向左上旋转代替A成为根结点
  2. 将A结点向左下旋转成为B的左子树的根结点
  3. B的原左子树则作为A结点的右子树

1.LR平衡旋转

执行条件

结点A的左孩子的右子树上插入结点导致A的不平衡

执行方法:先将A结点的左子树左旋,后将A结点右旋

1.RL平衡旋转

执行条件

结点A的右孩子的左子树上插入结点导致A的不平衡

执行方法:先将A结点的右子树右旋,后将A结点左旋

posted @   安河桥北i  阅读(19)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示