数据结构----树
数据结构----树
1.基本术语与概念
- 树(tree)是n(n>=0)个有限数据元素的集合T。n=0时称为空树。
- 树有一个特殊的结点其没有前驱结点,此结点称为根结点(root)。
- 当n>1树可被划分为若干个互不相交的集合,T1......TN 称为这个根结点的子树。
2.相关术语
- 结点的度 结点所拥有的子树的个数,称为结点的度。
- 叶子结点 度为0的结点称为叶子结点或终端结点。
- 分支结点 度不为0的结点称为分支结点或非终端结点。一棵树,除了叶子结点外,其他都是分支结点。
- 孩子和双亲 树中结点N的子树的根结点称为N的孩子,而这个结点称为他孩子的结点的双亲。具有同一双亲的孩子结点互为兄弟结点。
- 路径和路径长度 有n1,n2,n3....nk 为一棵树的结点序列,若结点n1 是ni+1 的父结点(1<= i < k),则 n1,n2,n3....nk 称为一条由n1 至 nk 的路径,而该路径的长度为k-1。
- 祖先和子孙 如果有一条路径从结点M到结点N,那么M就称为N的祖先,N称为M的子孙。
- 树的深度 树中所有结点的最大层数,称为树的深度。
- 树的度 树中各结点度的最大值称为该树的度。
- 有序树和无序树 如果一棵树中的结点的各子树从左到右是有序的,即若交换了某结点各子树的相对位置,则构成不同的树,称这棵树为有序树;反之即无序树。
- 森林 零棵或有限棵不相交的树的集合称为森林。任何一棵树删掉了根结点就变成了森林。

3.树的基本操作
- Init 树的初始化
- Root 获取树的根结点
- Parent 获取某结点的双亲
- Child 获取某结点的孩子
- Sibling 获取兄弟结点
- Insert 向某结点插入新结点,即为该结点添加孩子
- Delete 删除某结点
- Traverse 树的遍历
4.二叉树
- 基本概念和定义 二叉树(binary tree)是一个有限元素的集合,该集合或者为空,或者由一个称为根的元素及两个不相交的,分别称为左右子树的二叉树组成。当集合为空,该二叉树称为空二叉树。
- 满二叉树 二叉树中,所有结点都存在左右子树,并且所有叶子结点都在同一层中该树称为满二叉树。
- 完全二叉树 深度为k且有n个结点的二叉树,结点从上到下从左到右编号为i(1<=i<=n)的结点与满二叉树中的编号为i的结点在二叉树中的位置相同,则这棵二叉树称为完全二叉树。

5.二叉树的主要性质
- 一棵非空二叉树的第i层上最多有2i-1 个结点(i>=1)。
- 一个深度为k的二叉树中,最多有2k-1 个结点。
- 对于一棵非空二叉树,如果叶子结点数为n0 ,度数为2的结点数为n2 ,则有n0 = n2 + 1。
- 具有n个结点的完全二叉树的深度k 为log2n + 1。
5.1.二叉树的存储和操作
1.二叉树的顺序存储结构:
用一组连续的存储单元存放二叉树中的结点,一般是按照从上到下,再从左到右的存储方式;


此种方法只能用来表示完全二叉树或满二叉树,因为完全二叉树和满二叉树的结点顺序是满足一定顺序条件的。而普通树则不能用此种方法表达,需要一些特定的方法来确定某结点在逻辑上的前驱结点和后记结点,这样才有意义。
如下图的普通树需要通过额外的方法证明结点的逻辑位置关系:

这里采用多添加两个空结点的方法来表示结点的逻辑顺序位置,显然此方法会造成很大的空间浪费

2.二叉树的链式存储结构
2.1二叉链表存储结构

三个数据位,分别保存左孩子,数据,右孩子,无孩子时存空指针。
2.2三叉链表存储结构

三叉和二叉的前三位一样,多了一个parent的指针,优点是方便通过孩子找双亲,缺点是比二叉浪费更多的存储空间。
3.二叉树的遍历
3.1先序遍历(DLR) 从根结点开始遍历到左子树再遍历到右子树。
1 function preOrder($root){ 2 if(empty($root)){ 3 return; 4 } 5 6 echo $root['data']; 7 8 preOrder($root['lchild']); 9 preOrder($root['rchild']); 10 }

根据线序遍历上例上图的二叉树输顺序为: A , B , D , E , H , C , F , G
3.2中序列遍历(LDR)
1 function inOrder($root){ 2 if(empty($root)){ 3 return; 4 } 5 6 inOrder($root['lchild']); 7 8 echo $root['data']; 9 10 inOrder($root['rchild']); 11 }
3.3后序遍历(LRD)
1 function postOrder($root){ 2 if(empty($root)){ 3 return; 4 } 5 6 postOrder($root['lchild']); 7 8 postOrder($root['rchild']); 9 10 echo $root['data']; 11 }
3.4层次遍历 从root开始逐层遍历,遍历顺序和从上至下从左至右编号的顺序相同
1 function levelOrder($root){ 2 if(empty($root)){ 3 return; 4 } 5 6 $queue = array(); 7 8 array_push($queue,$root); 9 10 while(!empty($queue)){ 11 $node = array_shift($queue); 12 echo $node['data']; 13 array_push($queue,$node['lchild']); 14 array_push($queue,$node['rchild']); 15 } 16 17 }
层次遍历3.1图中的树的输出顺序为 A , B , C , D , E , F , G , H。
6.树的存储
1.双亲表示法


用一个数组存储各个结点,数组元素为一个结构体,存储着结点数据和双亲在该数组中的下标。在双亲表示法中查找某结点的双亲十分简单。
2.孩子链表表示法

用孩子链表表示法表示上图中的树的结构如下图。

孩子链表表示法分为两部分表示
第一部分:用一个与结点个数相同长度的数组记录着结点和指向该结点的孩子链表的指针。
第二部分:由链表所组成的孩子链表,链表记录着两部分信息,分别为该结点在第一部分数组中的下标和指向下一个结点即兄弟结点的指针。链表的顺序为孩子结点的从左到右的顺序。
如上图,根结点A有两个孩子
这二个孩子的下标分别为 1和2 对应数组中结点为B,C,其他结点如此类推。
孩子链表表示法优点点在于能很容易的找到某个结点X的所有孩子,缺点是要找某结点X的双亲结点比较麻烦。和双亲表示法正好相反。
3.孩子兄弟表示法

下图所示即为孩子兄弟表示法所表示的上图的数据结构

由图可以看出孩子兄弟表示法中每个结点数据结构都分为三部分

第一部分:Fson指向该结点第一个孩子的指针
第二部分:该结点的数据
第三部分:指向该结点的下一个兄弟的指针
7.树的遍历
树的先根遍历
双亲表示法:
1 $tree = array( 2 array('A',-1), 3 array('B',0), 4 array('C',0), 5 array('D',1), 6 array('E',1), 7 array('F',2), 8 array('G',2), 9 array('H',4), 10 ); 11 12 function preOrder($t,$order){ 13 echo $t[$order][0]; 14 foreach($t as $o=>$st){ 15 if($st[1] == $order){ 16 preOrder($t, $o); 17 } 18 } 19 20 } 21 preOrder($tree, 0);
孩子链表表示法:
1 $tree = array( 2 array('A',array(1,2)), 3 array('B',array(3,4)), 4 array('C',array(5,6)), 5 array('D',array()), 6 array('E',array(7)), 7 array('F',array()), 8 array('G',array()), 9 array('H',array()), 10 ); 11 12 function preOrder($tree,$order){ 13 echo $tree[$order][0]; 14 15 foreach($tree[$order][1] as $o){ 16 preOrder($tree, $o); 17 } 18 } 19 preOrder($tree, 0);
其他遍历原理和二叉树遍历类似,代码就不再给出了。
8.最优二叉树 哈夫曼树
1.哈夫曼树的基本概念
哈夫曼(Huffman)树也称为最优二叉树,是指对于一组带有确定权值的叶子结点构造的具有最小带权路径长度的二叉树。
二叉树的路径长度是由根结点到所有叶子结点的路径长度之和。而哈夫曼树的结点是带权的,即可得从根结点到各个叶子结点的路径长度与相应结点权值的乘积之和称为二叉树的带权路径长度。


如上二图这两棵书叶子结点相同,但是因为带权路径长度的不同而有所区别,图1为二叉树,图二则为海夫曼树(最优二叉树)。
2.哈夫曼树的构造过程
1.给定n个权值W1 W2 W3 .....Wn 构造n棵只有一个叶子结点的二叉树,这样就得到了二叉树集合T1 T2 ... Tn 。

2.在二叉树集合中选取根结点的权值最小A和次小B的两棵二叉树作为左,右子树构造一颗全新的二叉树C,这棵新的二叉树的根结点的权值为其左右子树根结点权值之和。
3.在这个二叉树集合中删除第二步选取的A和B,将C加入的这个二叉树集合中。


4.重复 2. 3. 当集合中只剩下一棵二叉树的时候这棵树便是要建立的哈夫曼树。

下面给出哈夫曼树的构造代码:
1 class node { 2 public $weight = 0; 3 public $lchild = -1; 4 public $rchild = -1; 5 } 6 7 $nodeNum = array(50,12,3,53,2,1); 8 $nodeArr = array(); 9 10 foreach($nodeNum as $num){ 11 $tmp = new node(); 12 $tmp->weight = $num; 13 $nodeArr[] = $tmp; 14 } 15 $root = null; 16 17 if(count($nodeArr) == 1){ 18 $root = $nodeArr[0]; 19 }else{ 20 $loop = count($nodeArr); 21 for($i = 1;$i < $loop;$i++){ 22 $key1 = $key2 = $m2 = $m1 = PHP_INT_MAX; 23 24 foreach($nodeArr as $k=>$node){26 if($node->weight <= $m1){ 27 $m2 = $m1; 28 $m1 = $node->weight; 29 $k2 = $k1; 30 $k1 = $k;32 }elseif($node->weight <= $m2){ 33 $m2 = $node->weight; 34 $k2 = $k; 35 } 36 } 37 $root = new node(); 38 $root->weight = $nodeArr[$k2]->weight + $nodeArr[$k1]->weight; 39 $root->lchild = $nodeArr[$k2]; 40 $root->rchild = $nodeArr[$k1]; 41 $root->parent = 'c'; 42 unset($nodeArr[$k1],$nodeArr[$k2]); 43 array_push($nodeArr,$root); 47 } 48 } 49 print_r($root);
最后附上二叉树简单生成代码
1 class BtreeNode { 2 public $data = 0; 3 public $lchild = -1; 4 public $rchild = -1; 5 } 6 7 $treeNode = array('A','B',0,'D',0,0,'C','E',0,0,'F',0,0); 8 9 10 function createTree($treeNode,&$pos){ 11 if($treeNode[$pos]){ 12 $node = new BtreeNode(); 13 $node->data = $treeNode[$pos]; 14 $node->lchild = createTree($treeNode, $pos++); 15 $node->rchild = createTree($treeNode, $pos++); 16 return $node; 17 }else{ 18 return -1; 19 } 20 } 21 22 var_dump(createTree($treeNode, 0));
PS 本文部分代码没有经过仔细测试,仅供思路参考。
浙公网安备 33010602011771号