《数据结构与算法》| 清华大学 | 第四章 | 二叉树

树介于线性结构和非线性结构,属于半线性结构。树不是简单的线性结构,但确定某种次序后(遍历),具有线性特征。同时,树是 极小连通图极大无环图,属于图状结构的特殊情况。

在这里插入图片描述

1. 树的图形特征

:节点集 V V V 和边集 E E E 组成,记为 G = ( V , E ) G=(V,E) G=(V,E),顶点数 n = ∣ V ∣ n=|V| n=V,边数 e = ∣ E ∣ e=|E| e=E

路径: 节点集 V = { v 0 , v 1 , v 2 , . . . , v k } V=\{v_0, v_1, v_2, ... , v_k\} V={ v0,v1,v2,...,vk},通过 k k k 条路径依次相连,构成一条路径 π = { ( v 0 , v 1 ) , ( v 1 , v 2 ) , . . . , ( v k − 1 , v k ) } \pi = \{(v_0, v_1), (v_1, v_2), ... , (v_{k-1}, v_k) \} π={ (v0,v1),(v1,v2),...,(vk1,vk)}。路径长度 ∣ π ∣ |\pi| π 即所含边数 k k k,环路是 v k = v 0 v_k=v_0 vk=v0
在这里插入图片描述
连通图 是节点之间均有路径,而 无环图 是不含环路,所以树是无环连通图。树缺一条边则有节点成为孤点,所以树成为极小连通图;树多一条边则产生环路,所以树成为极大无环图。

2. 树的线性特征

树具有前驱和后继节点的线性特征。根节点没有前驱节点,叶子节点没有后继节点,分支节点有前驱和后继节点。树的分支节点的前驱有且仅有一个,后继节点则不唯一。

树的任一节点和根存在唯一路径,所以可以根据路径长短对所有节点划分。同一阶层的互为兄弟节点,到根节点的所有前驱节点称为祖先节点,直接前驱称为父节点;所有后继节点称为子孙节点,直接后继称为孩子节点。
在这里插入图片描述
节点深度是从根节点往下数,该节点位于的阶层;节点高度是从叶子节点往上数,该节点位于的阶层。

3. 树的表示

线性结构节点的关系包含前驱和后继,向量可以通过秩访问,链表可以通过双向指针访问。树节点的关系包含父节点、孩子节点、兄弟节点,所以树节点设置便于访问上述节点。
在这里插入图片描述

首先,尝试使用两个向量存储树结构,一个向量存储节点内容 d a t a data data,另一个向量存储父节点的秩,循秩访问节点。此时,访问父节点的时间复杂度是 O ( 1 ) O(1) O(1)
在这里插入图片描述
因为孩子节点个数不唯一,不可以使用向量存储孩子节点,所以访问孩子节点需要遍历整个向量,时间复杂度是 O ( n ) O(n) O(n)。此时可以使用链表存储孩子节点,树节点设置指针 f i r s t C h i l d ( ) firstChild() firstChild() 指向第一个孩子节点,循位置访问。此时,访问孩子节点不需要遍历整棵树。
在这里插入图片描述
同理,可以为树节点再添加一个指针指向兄弟节点。这样树节点就有一个纵向指针指向孩子节点,一个横向指针指向兄弟节点。为统一数据结构,改用指针指向父节点。
在这里插入图片描述

二叉树

树节点的度是节点所含的孩子节点个数,节点度数不超过2的数称为二叉树,从而可区分左右孩子和左右子树。
在这里插入图片描述

扫描二维码关注公众号,回复: 13479189 查看本文章

1. 二叉树的特征

  • 单层节点:根节点为 第 0 0 0 层,个数为 1 ( 2 0 ) 1(2^0) 1(20);第 2 2 2 层至多 2 ( 2 1 ) 2(2^1) 2(21) 个节点,以此类推二叉树第 k k k 层的节点数至多有 2 k 2^k 2k

  • 节点总数 n n n 和树高度 h h h

  • 节点 n n n 和度数 e e e:设度数为0、1、2的节点数分别 n 0 , n 1 , n 2 n_0, n_1, n_2 n0,n1,n2 个,叶子节点数 n 0 = n 2 + 1 n_0=n_2+1 n0=n2+1 节 点 总 数 : n = n 0 + n 1 + n 2 度 数 : e = n − 1 = n 1 + 2 n 2 节点总数:n=n_0+n_1+n_2 \qquad 度数:e=n-1=n_1+2n_2 :n=n0+n1+n2e=n1=n1+2n2

2. 二叉树的原型

在这里插入图片描述

#define BinNodePosi(T) BinNode<T>*  // 节点位置
template <typename T>
struct BinNode {
    
    
	BinNodePosi(T) parent, lc, rc;	// 父亲、孩子
	T data; int height;	// 高度、子树规模
};

2. 先序遍历

  • 递归算法

在这里插入图片描述
先序遍历的操作过程:二叉树为空,则什么也不做;否则访问根节点、遍历左子树、遍历右子树

template <typename T>
void PreOrder( BinNodePosi(T) x ) {
    
    
	if (!x) return;
	visit( x->data );
	PreOrder( x->lc );
	PreOrder( x->rc );
}
  • 迭代算法

在这里插入图片描述
自上而下访问遇到的节点并将右孩子入栈,然后再自下而上地遍历右子树,所以需要设置一个辅助栈保存右子树节点。

template <typename T>
void PreOrder( BinNodePosi(T) x ) {
    
    
	Stack < BinNodePosi(T) > S;	// 辅助栈
	
	while ( x || !S.empty() ) {
    
    	
		/* 自上而下 */
		while ( x ) {
    
    	
			visit( x->data );	// 根:访问当前根节点, 右孩子入栈
			S.push( x->rc );	
			x = x->lc;			// 左:沿藤下行至底部
		}
		
		/* 自上而下 */
		x = S.pop();			// 右:弹出并遍历右子树
	}
}

3. 中序遍历

  • 递归算法

在这里插入图片描述
中序遍历的操作过程:二叉树为空,则什么也不做;否则遍历左子树、访问根节点、遍历右子树

template <typename T>
void InOrder( BinNodePosi(T) x ) {
    
    
	if (!x) return;
	InOrder( x->lc );
	visit( x->data );
	InOrder( x->rc );
}
  • 迭代算法

在这里插入图片描述
先序遍历的迭代需要自上而下探到左子树底部,然后再自下而上访问根节点,所以需要设置一个辅助栈保存遇到的节点。

template <typename T>
void InOrder( BinNodePosi(T) x ) {
    
    
	Stack < BinNodePosi(T) > S;	// 辅助栈
	
	while ( x || !S.empty() ) {
    
    
		/* 自上而下 */	
		while ( x ) {
    
    			// 左:所遇节点入栈, 沿藤下行至底部
			S.push( x );		
			x = x->lc;		
		}
		
		/* 自下而上 */
		x = S.pop();			// 根:根节点出栈并访问
		visit( x->data );		
		x = x->rc;				// 右:转遍历当前根节点的右子树
	}
}

4. 后序遍历

  • 递归算法

在这里插入图片描述
后序遍历的操作过程:二叉树为空,则什么也不做;否则遍历左子树、遍历右子树、访问根节点

template <typename T>
void PreOrder( BinNodePosi(T) x ) {
    
    
	if (!x) return;
	PreOrder( x->lc );
	PreOrder( x->rc );
	visit( x->data );
}
  • 迭代算法

在这里插入图片描述

template <typename T>
void PostOrder( BinNodePosi(T) x ) {
    
    
	Stack < BinNodePosi(T) > S;	// 辅助栈
	BinNodePosi(T) r;
	
	while ( x || !S.empty() ) {
    
    
		/* 自上而下 */	
		while ( x ) {
    
    			// 左:所遇节点入栈, 沿藤下行至底部
			S.push( x );		
			x = x->lc;		
		}
		
		/* 自下而上 */
		x = S.top();		
		if ( x->rc && x->rc != r) {
    
    	// 右:右子树存在,未被访问则转向右子树
			x = x->rc;
		}
		else {
    
    						// 根:右子树不存在或已访问过,则访问当前节点
			x = S.pop();
			visit( x->data );
			r = x;	x = nullptr;	// 记录访问完的节点
		}
		
	}
}

5. 层序遍历

template <typename T>
void LevelOrder( BinNodePosi(T) x ) {
    
    
	Queue< BinNodePosi(T) > Q;	// 辅助队列
	Q.enqueue( x );				// 根节点入队
	while ( ! Q.empty() ) {
    
    
		BinNodePosi(T) x = Q.dequeue();	// 取出队首节点
		visit( x->data );	// 访问队首节点
		
		if ( x->lc ) Q.enqueue( x->lc ); // 左孩子入队
		if ( x->rc ) Q.enqueue( x->rc ); // 右孩子入队 	
	}
}

猜你喜欢

转载自blog.csdn.net/K_Xin/article/details/107476695