07-数据结构与算法(树学习笔记整理)
1、树的定义
树是n(n>=0)个结点的有限集。
树的基本术语
树的结点、结点的度、树的度、叶子或终端结点、非终端节点或分支节点、孩子 和双亲、兄弟、祖先和子孙、层数和堂兄弟、树的深度、有序树、无序树、森林空树
N=0时,为空树。
2.树的表示
1.树形表示法
2.括号表示法
3.文氏表示法
4.凹入表示法
3.二叉树
定义
每个结点至多有两颗子树(即二叉树不存在度大于2的结点),并且二叉树的子树有左右之分,其次序不能任意颠倒,即如果将其左右字数颠倒,就成为另一颗不同的二叉树。
二叉树的存储结构
- 顺序存储结构
先转化成满二叉树,
下标(左)=2*(层级-1)+1;
下标(右)=2*(层级-1)+2;
把一个满二叉树自上而下、从左到右顺序编号,依次存放在数组内,可得到图6.8(a)所示的结果。设满二叉树结点在数组中的索引号为i,那么有如下性质。
(1) 如果i = 0,此结点为根结点,无双亲。
(2) 如果i > 0,则其双亲结点为(i -1) / 2 。(注意,这里的除法是整除,结果中的小数部分会被舍弃。)
(3) 结点i的左孩子为2i + 1,右孩子为2i + 2。
(4) 如果i > 0,当i为奇数时,它是双亲结点的左孩子,它的兄弟为i + 1;当i为偶数时,它是双新结点的右孩子,它的兄弟结点为i – 1。
(5) 深度为k的满二叉树需要长度为2 k-1的数组进行存储。
通过以上性质可知,使用数组存放满二叉树的各结点非常方便,可以根据一个结点的索引号很容易地推算出它的双亲、孩子、兄弟等结点的编号,从而对这些结点进行访问,这是一种存储二叉满二叉树或完全二叉树的最简单、最省空间的做法。
为了用结点在数组中的位置反映出结点之间的逻辑关系,存储一般二叉树时,只需要将数组中空结点所对应的位置设为空即可,其效果如图6.8(b)所示。这会造成一定的空间浪费,但如果空结点的数量不是很多,这些浪费可以忽略。
一个深度为k的二叉树需要2 k-1个存储空间,当k值很大并且二叉树的空结点很多时,最坏的情况是每层只有一个结点,再使用顺序存储结构来存储显然会造成极大地浪费,这时就应该使用链式存储结构来存储二叉树中的数据。
2.链式存储结构
二叉树的链式存储结构可分为二叉链表和三叉链表。二叉链表中,每个结点除了存储本身的数据外,还应该设置两个指针域left和right,它们分别指向左孩子和右孩子(如图6.9(a)所示)。
当需要在二叉树中经常寻找某结点的双亲,每个结点还可以加一个指向双亲的指针域parent,如图6.9(b)所示,这就是三叉链表。

图6.10所示的是二叉链表和三叉链表的存储结构,其中虚线箭头表示parent指针所指方向。

二 叉树还有一种叫双亲链表的存储结构,它只存储结点的双亲信息而不存储孩子信息,由于二叉树是一种有序树,一个结点的两个孩子有左右之分,因此结点中除了存 放双新信息外,还必须指明这个结点是左孩子还是右孩子。由于结点不存放孩子信息,无法通过头指针出发遍历所有结点,因此需要借助数组来存放结点信息。图6.10(a)所示的二叉树使用双亲链表进行存储将得到图6.11所示的结果。由于根节点没有双新,所以它的parent指针的值设为-1。

双亲链表中元素存放的顺序是根据结点的添加顺序来决定的,也就是说把各个元素的存放位置进行调换不会影响结点的逻辑结构。由图6.11可知,双亲链表在物理上是一种顺序存储结构。
二叉树存在多种存储结构,选用何种方法进行存储主要依赖于对二叉树进行什么操作来确定。而二叉链表是二叉树最常用的存储结构,下面几节给出的有关二叉树的算法大多基于二叉链表存储结构。
4.二叉树的遍历
4.1深度优先遍历
1.先序遍历
若二叉树为非空,则过程为:
(1) 访问根节点。
(2) 先序遍历左子树。
(3) 先序遍历右子树。
- 中序遍历
若二叉树为非空,则过程为:
(1) 按中序遍历左子树。
(2) 访问根结点。
(3) 按中序遍历右子树。
- 后序遍历
若二叉树为非空,则过程为:
(1) 按后序遍历左子树。
(2) 按后序遍历右子树
(3) 访问根结点。
4.2广度优先遍历
之前所讲述的二叉树的深度优先遍历的搜索路径是首先搜索一个结点的所有子孙结点,再搜索这个结点的兄弟结点。是否可以先搜索所有兄弟和堂兄弟结点再搜索子孙结点呢?
由于二叉树结点分属不同的层次,因此可以从上到下、从左到右依次按层访问每个结点。它的访问顺序正好和之前所述二叉树顺序存储结构中的结点在数组中的存放顺序相吻合。如图6.13中的二叉树使用宽度优先遍历访问的顺序为:ABCDEF。
这个搜索过程不再需要使用递归,但需要借助队列来完成。
(1) 将根结点压入队列之中,开始执行步骤(2)。
(2) 若队列为空,则结束遍历操作,否则取队头结点D。
(3) 若结点D的左孩子结点存在,则将其左孩子结点压入队列。
(4) 若结点D的右孩子结点存在,则将其右孩子结点压入队列,并重复步骤(2)。
线索二叉树
左线索标识ltag的取值:
0:left指向结点的左孩子。
1: left指向结点的前驱结点。
右线索标识rtag的取值:
0:right指向结点的右孩子。
1: right指向结点的后继结点。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication14
{
class BinaryTree
{
private BinaryTreeNode _head;//头指针
private BinaryTreeNode pre;//记录当前结点的前一结点
private string cStr;//用于构造 二叉树
public BinaryTreeNode Head
{
get {return _head ;}
}
public BinaryTree(string Str)
{
cStr = Str;
_head = new BinaryTreeNode(cStr[0]);//添加头结点
ADD(_head,0);
pre = null;
MidOrderThread(_head);//线索化
}
private void ADD(BinaryTreeNode parent, int p)
{
int leftIndex = 2 * p + 1;//左孩子的索引位置
if (leftIndex<cStr.Length)
{
if (cStr[leftIndex]!='#')//为非空结点
{
parent.Left = new BinaryTreeNode(cStr[leftIndex]);
ADD(parent.Left, leftIndex);
}
}
int rightIndex = 2 * p + 2;
if (rightIndex < cStr.Length)
{
if (cStr[rightIndex] != '#')//为非空结点
{
parent.Right = new BinaryTreeNode(cStr[rightIndex]);
ADD(parent.Right, rightIndex);
}
}
}
private void MidOrderThread(BinaryTreeNode node)//中序线索化
{
if (node!=null)//不为空树
{
MidOrderThread(node.Left);//递归遍历左孩子结点
if (node.Left==null)
{//左孩子的指针域为空
node.Ltag = 1;//左线索标识为1
node.Left = pre;//左孩子指针指向前驱结点
}
if (pre!=null&&pre.Right==null)
{
pre.Rtag = 1;
pre.Right = node;
}
pre = node;
MidOrderThread(node.Right);
}
}
public void MidOrder()//线索二叉树的遍历
{
BinaryTreeNode p = _head;
while (p.Left!=null)//获得中序遍历的第一个结点
{
p = p.Left;
}
do
{
Console.WriteLine("{0,-3}{1,3}{2,-3}",p.GetPrev(),p,p.GetNext());
p = p.GetNext();
} while (p!=null);
}
}
}
4.3特殊二叉树
1.满二叉树:
在一棵二叉树中所有分支结点都同时具有左孩子和右孩子,并且所有叶子结点都在同一层上。
2.完全二叉树
完全二叉树只允许树的最后一层出现空结点,且最下层的叶子结点集中在树的左部。
4.4树的存储结构
1.双亲表示法
2.孩子表示法
3.孩子兄弟表示法
5.树和森林
树的存储:
1.双亲表示法
2.孩子表示法
3.孩子兄弟表示法
森林、树、二叉树的相互转换
1.一般树转换为二叉树
2.森林转换为二叉树
3.二叉树还原为一般树
4.二叉树还原为森林
趣味算法:魔术师的秘密
在一次晚会上,一位魔术师掏出一叠扑克牌,取出其中13张黑桃,预先洗好后,把牌面朝下,对观众说:"我不看牌,只数一数就能知道每张牌是什么?"魔术师口中念一,将第一张牌翻过来看正好是A;魔术师将黑桃A放到桌上,继续数手里的余牌,第二次数1,2,将第一张牌放到这叠牌的下面,将第二张牌翻开,正好是黑桃2,也把它放在桌子上。第三次数1,2,3,前面二张牌放到这叠牌的下面,取出第三张牌,正好是黑桃3,这样依次将13张牌翻出,准确无误。现在的问题是,魔术师手中牌的原始顺序是怎样的?
解决这类问题的关键在于利用倒推的方法推出原来牌的顺序。假设桌上摆着13个空盒子,编 号为1至13,将黑桃A放入第一个盒子中,从下一个空盒子开始对空盒子计数,当数到第二个空盒子时,将黑桃2放入空盒子中,然后再从下一个空盒子开始对空盒子计数。顺序放入3,4,5等,直到全部放入13张牌,注意在计数时要跳过非空的盒子,只对空盒子计数,最后得到的牌在盒子中的顺序,就是魔术师手中原来牌的顺序。 计算机就是模拟这种行之有效的倒推方法的。
6.代码实例:
- 二叉树实例
- 二叉树代码实例:
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- namespace ConsoleApplication13
- {
- public class BinaryTreeNode
- {
- private object _data;//数据
- private BinaryTreeNode _left;//左孩子
- private BinaryTreeNode _right;//右孩子
- public object Data
- {
- get {return _data ;}
- }
- public BinaryTreeNode Left
- {
- get { return _left;}
- set { _left=value;}
- }
- public BinaryTreeNode Right
- {
- get { return _right; }
- set { _right = value; }
- }
- public BinaryTreeNode(object data)
- {
- _data = data;
- }
- public override string ToString()
- {
- return _data.ToString();
- }
- }
- }
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Collections;
- namespace ConsoleApplication13
- {
- class BinaryTree
- {
- private BinaryTreeNode _head;//头指针
- private string cStr;//用于构造二叉树的字符串
- public BinaryTreeNode Head//头指针
- {
- get {return _head ;}
- }
- public BinaryTree(string str)
- {
- cStr = str;
- _head = new BinaryTreeNode(cStr[0]);//添加头结点
- ADD(_head,0);//增加孩子结点
- }
- private void ADD(BinaryTreeNode parent,int index)
- {//index 双亲结点的下标
- int leftIndex = 2 * index + 1;//左孩子的下标
- if (leftIndex<cStr.Length)//存在左孩子
- {
- if (cStr[leftIndex] != '#')//左孩子不是空结点
- {//添加左孩子
- parent.Left = new BinaryTreeNode(cStr[leftIndex]);
- //递归方法添加左孩子的左孩子和右孩子
- ADD(parent.Left,leftIndex);
- }
- }
- int rightIndex = 2 * index + 2;//右孩子的下标
- if (rightIndex<cStr.Length)
- {
- if (cStr[rightIndex]!='#')
- {
- parent.Right = new BinaryTreeNode(cStr[rightIndex]);
- ADD(parent.Right, rightIndex);
- }
- }
- }
- public void PreOrder(BinaryTreeNode node)//先序遍历
- {
- if (node!=null)//此二叉树不为空树
- {
- Console.Write(node);
- PreOrder(node.Left);
- PreOrder(node.Right);
- }
- }
- public void MidOrder(BinaryTreeNode node) //中序遍历
- {
- if (node!=null)
- {
- MidOrder(node.Left);
- Console.Write(node);
- MidOrder(node.Right);
- }
- }
- public void AfterOrder(BinaryTreeNode node) //后序遍历
- {
- if (node != null)
- {
- AfterOrder(node.Left);
- AfterOrder(node.Right);
- Console.Write(node);
- }
- }
- //广度优先遍历
- public void LevelOrder()
- {
- Queue queue = new Queue();
- queue.Enqueue(_head);
- while (queue.Count>0)//队列不为空
- {
- BinaryTreeNode node = (BinaryTreeNode)queue.Dequeue();//出队
- Console.Write(node);
- if (node.Left!=null)//结点左孩子不为空
- {//左孩子入队
- queue.Enqueue(node.Left);
- }
- if (node.Right != null)//结点右孩子不为空
- {//右孩子入队
- queue.Enqueue(node.Right);
- }
- }
- }
- }
- }

浙公网安备 33010602011771号