常用数据结构 -- 二叉树

转自
[url]http://blog.csdn.net/bjyfb/article/details/7519360[/url]
[url]http://blog.csdn.net/bjyfb/article/details/7522000[/url]

二叉树遍历实例代码

//BinaryTree.h

#ifndef BINARY_TREE_H
#define BINARY_TREE_H

#include <Windows.h>
#include <iostream>
using namespace std;

namespace BinaryTreeSpace
{
struct BinaryTreeNode
{
int nData;
BinaryTreeNode* pLeftChild;
BinaryTreeNode* pRightChild;

BinaryTreeNode()
{
nData = 0;
pLeftChild = NULL;
pRightChild = NULL;
}
};

enum EM_ORDER
{
EM_DLR = 0,
EM_LDR = 1,
EM_LRD = 2
};

class BinaryTree
{
public:
BinaryTree();

BinaryTreeNode* AddNode(int nData);

//前序遍历
void VisitTree_DLR(const BinaryTreeNode* pTree = NULL);

//中序遍历
void VisitTree_LDR(const BinaryTreeNode* pTree = NULL);

//后序遍历
void VisitTree_LRD(const BinaryTreeNode* pTree = NULL);

//打印树的信息
void PrintNode(EM_ORDER emType, const BinaryTreeNode* pNode);

//获取数的深度
int GetDepth(const BinaryTreeNode* pTree = NULL);

//获取到也节点的数量
int GetLeafCout(const BinaryTreeNode* pTree = NULL);

//获取到所有节点的数量
int GetNodeCout(const BinaryTreeNode* pTree = NULL);

const BinaryTreeNode* GetRoot() const
{
return m_pRootNode;
}

private:
BinaryTreeNode* m_pRootNode;
int m_nLeftCount;
int m_nRightCount;
int m_nTotelNode;
};
//
void BinaryTree_Test1();
void BinaryTree_Test2();
}

#endif



#include "BinaryTree.h"

namespace BinaryTreeSpace
{
#define GetTreeAddress(pTree) \
const BinaryTreeNode* pTemp = NULL;\
if (NULL == pTree)\
{\
pTemp = m_pRootNode;\
}\
else\
{\
pTemp = pTree;\
}\

#define PrintNodeInfo(emType) \
m_nLeftCount = 0;\
m_nRightCount = 0;\
m_nTotelNode = 0;\
PrintNode(emType, pTemp);\
cout << "\nm_LeftCount = " << m_nLeftCount << " m_RightCount = " << m_nRightCount <<" m_nTotelNode = " << m_nTotelNode << endl;\


BinaryTree::BinaryTree()
: m_pRootNode(NULL)
, m_nLeftCount(0)
, m_nRightCount(0)
, m_nTotelNode(0)
{ }

BinaryTreeNode* BinaryTree::AddNode(int nData)
{
if (NULL == m_pRootNode)
{
m_pRootNode = new BinaryTreeNode();
m_pRootNode->nData = nData;

return m_pRootNode;
}
else
{
BinaryTreeNode* pTempPtr = m_pRootNode;
BinaryTreeNode* pNewNode = new BinaryTreeNode();
pNewNode->nData = nData;

while (pTempPtr != NULL)
{
if (nData > pTempPtr->nData)
{
if (NULL == pTempPtr->pRightChild)
{
pTempPtr->pRightChild = pNewNode;
break;
}
else
{
pTempPtr = pTempPtr->pRightChild;
}
}
else
{
if (NULL == pTempPtr->pLeftChild)
{
pTempPtr->pLeftChild = pNewNode;
break;
}
else
{
pTempPtr = pTempPtr->pLeftChild;
}
}
};

return pNewNode;
}
}

void BinaryTree::VisitTree_DLR(const BinaryTreeNode* pTree)
{
cout << "前序遍历:" << endl;
GetTreeAddress(pTree);
PrintNodeInfo(EM_DLR);
}

void BinaryTree::VisitTree_LDR(const BinaryTreeNode* pTree)
{
cout << "中序遍历:" << endl;
GetTreeAddress(pTree);
PrintNodeInfo(EM_LDR);
}
void BinaryTree::VisitTree_LRD(const BinaryTreeNode* pTree)
{
cout << "后序遍历:" << endl;
GetTreeAddress(pTree);
PrintNodeInfo(EM_LRD);
}

void BinaryTree::PrintNode(EM_ORDER emType, const BinaryTreeNode* pNode)
{
if (pNode != NULL)
{
if (EM_DLR == emType)
{
m_nTotelNode++;
cout << " " << pNode->nData;
}

//Get Left Leaf
if (pNode->pLeftChild != NULL)
{
m_nLeftCount++;
PrintNode(emType, pNode->pLeftChild);
}

if (EM_LDR == emType)
{
m_nTotelNode++;
cout << " " << pNode->nData ;
}

//Get Right Leaf
if (pNode->pRightChild != NULL)
{
m_nRightCount++;
PrintNode(emType, pNode->pRightChild);
}

if (EM_LRD == emType)
{
m_nTotelNode++;
cout << " " << pNode->nData;
}
}
}

int BinaryTree::GetDepth(const BinaryTreeNode* pTree)
{
if (NULL == pTree)
{
return 0;
}

int nLeftCout = GetDepth(pTree->pLeftChild);
int nRightCout = GetDepth(pTree->pRightChild);
return (nLeftCout>nRightCout ? nLeftCout : nRightCout) + 1;
}

int BinaryTree::GetLeafCout(const BinaryTreeNode* pTree)
{
if (NULL == pTree)
{
return 0;
}

if (pTree->pLeftChild==NULL
&& pTree->pRightChild==NULL)
{
return 1;
}

return GetLeafCout(pTree->pLeftChild) + GetLeafCout(pTree->pRightChild);
}

int BinaryTree::GetNodeCout(const BinaryTreeNode* pTree)
{
if (NULL == pTree)
{
return 0;
}

return 1 + GetNodeCout(pTree->pLeftChild) + GetNodeCout(pTree->pRightChild);
}

void BinaryTree_Test1()
{
cout << "--------------------------------BinaryTree_Test1--------------------------------" << endl;

BinaryTree cBinaryTree;
BinaryTreeNode* pTemp = cBinaryTree.AddNode(1);

//
pTemp->pLeftChild = new BinaryTreeNode();
pTemp->pLeftChild->nData = 2;

pTemp->pLeftChild->pLeftChild = new BinaryTreeNode();
pTemp->pLeftChild->pLeftChild->nData = 4;
pTemp->pLeftChild->pRightChild = new BinaryTreeNode();
pTemp->pLeftChild->pRightChild->nData = 5;

pTemp->pLeftChild->pRightChild->pLeftChild = new BinaryTreeNode();
pTemp->pLeftChild->pRightChild->pLeftChild->nData = 7;

pTemp->pLeftChild->pRightChild->pLeftChild->pRightChild = new BinaryTreeNode();
pTemp->pLeftChild->pRightChild->pLeftChild->pRightChild->nData = 8;

//
pTemp->pRightChild = new BinaryTreeNode();
pTemp->pRightChild->nData = 3;

pTemp->pRightChild->pRightChild = new BinaryTreeNode();
pTemp->pRightChild->pRightChild->nData = 6;

cBinaryTree.VisitTree_DLR(pTemp);
cBinaryTree.VisitTree_LDR(pTemp);
cBinaryTree.VisitTree_LRD(pTemp);

cout << "树的深度为:" << cBinaryTree.GetDepth(pTemp) << endl;
cout << "树的节点数量为:" << cBinaryTree.GetNodeCout(pTemp) << endl;
cout << "树的叶节点数量为:" << cBinaryTree.GetLeafCout(pTemp) << endl;


cBinaryTree.VisitTree_DLR(pTemp->pLeftChild);
cBinaryTree.VisitTree_DLR(pTemp->pRightChild);
cBinaryTree.VisitTree_DLR(pTemp->pLeftChild->pRightChild);
cBinaryTree.VisitTree_DLR(pTemp->pLeftChild->pLeftChild);
}

void BinaryTree_Test2()
{
cout << "--------------------------------BinaryTree_Test2--------------------------------" << endl;
BinaryTree cBinaryTree;
int array[]={7,4,2,3,15,35,6,45,55,20,1,14,56,57,58};
for(int i = 0; i < sizeof(array)/sizeof(array[0]); i++)
{
cBinaryTree.AddNode(array[i]);
}

cBinaryTree.VisitTree_DLR(NULL);
cBinaryTree.VisitTree_LDR(NULL);
cBinaryTree.VisitTree_LRD(NULL);

cout << "树的深度为:" << cBinaryTree.GetDepth(cBinaryTree.GetRoot()) << endl;
cout << "树的节点数量为:" << cBinaryTree.GetNodeCout(cBinaryTree.GetRoot()) << endl;
cout << "树的叶节点数量为:" << cBinaryTree.GetLeafCout(cBinaryTree.GetRoot()) << endl;
}
}



#include "BinaryTree.h"
using namespace BinaryTreeSpace;
void main()
{
BinaryTree_Test1();
BinaryTree_Test2();
}


[img]https://api5.yunpan.360.cn/intf.php?method=Share.getPublicThumbByNid&qid=108635719&nid=14670935459451229&size=800_600&devtype=web&v=1.0.1&rtick=14670935793397&share_qid=108635719&sign=4f1a249393185b6fe505d60eb5b6bb0b&[/img]
[img]https://api5.yunpan.360.cn/intf.php?method=Share.getPublicThumbByNid&qid=108635719&nid=14670935459458636&size=800_600&devtype=web&v=1.0.1&rtick=14670935794571&share_qid=108635719&sign=2db5c6fa6ecee48fb4805cb1c0183b69&[/img]

二叉树并不是一种特殊的树,是一种独立的数据结构。下面是一些关于二叉树入门级的、纯理论的东东,高手请Alt+F4,千万别往下翻,会影响您的心情!
[size=medium][b]二叉树的分类[/b][/size]
[b]满二叉树:[/b]除最后一层无任何子节点外,每一层上的所有结点都有两个子结点。
[b]完全二叉树:[/b]除了最下面一层,其他层结点都是饱满的,并且最下层上的结点都集中在该层最左边的若干位置上。(满二叉树也是完全二叉树)
[b] 非完全二叉树:[/b]既不是满二叉树,也非完全二叉树。
例:
[align=center][img]https://img-my.csdn.net/uploads/201204/28/1335620382_2366.png[/img][/align]

[size=medium][b] 二叉树的遍历[/b][/size]
[color=red][b]
前序遍历(先根遍历):根左右。
后序遍历(后根遍历):左右根。
中序遍历(中根遍历):左跟右。[/b][/color]
层次遍历:一层一层自左向右。
例:
[align=center][img]https://img-my.csdn.net/uploads/201204/28/1335624849_8491.png[/img][/align]

[color=red][b]图中前序遍历结果是:1,2,4,5,7,8,3,6;
图中中序遍历结果是:4,2,7,8,5,1,3,6;
图中后序遍历结果是:4,8,7,5,2,6,3,1;
图中层次遍历结果是:1,2,3,4,5,6,7,8;[/b][/color]
树和二叉树的转换
将树的孩子结点转成二叉树的左子结点,树的兄弟结点转成二叉树的右子结点。
例:
[align=center][img]https://img-my.csdn.net/uploads/201204/28/1335625896_7610.png[/img][/align]

[b] 二叉树的一些重要特性[/b]
1、在二叉树的第i层上最多有2^(n-1)个结点(i>=1);
例:以图1为例:任一图中第2层,最多只能有2个结点。验证正确!
2、深度为k的二叉树最多有2^k - 1个结点(K>=1);
例:以图1为例:图中所有二叉树深度为3,因此,该些二叉树最多有2^3 -1 = 7个结点,验证正确!
3、对任何一颗二叉树,如果其叶子结点数为n0,度为2的结点数为n2,则n0 = n2 + 1;
例:以图1为例:看最后一个非完全二叉树,图中所示,叶子结点n0 = 2;度为2的结点n2 = 1(结点2);则2 = 1 + 1。验证正确!
4、如果对一颗有n个结点的完全二叉树的结点按层序编号(从第1层到⌊log2n⌋ + 1层,每层从左到右),则对任一结点i(1<=i<=n),(⌊⌋向下取整符号) 有:
如果i=1,则结点i无父节点,是二叉树的根;如果i>1,则父节点是⌊i/2⌋ ;
例:
以图1左侧的完全二叉树为例:若i = 3,则i > 1,⌊3/2⌋ = 1,3的根结点为1。验证 正确!
如果2i>n,则结点i为叶子结点,无左子结点;否则,其左子结点是结点2i;
例:
以图1左侧的完全二叉树为例:若i = 3,因 n = 5,则2i>n,由此推出3为叶子结点。若i = 2,因 n = 5,则2i<n,由此推出2的左子结点为4。验证正确!
如果2i+1>n,则结点i无右子结点,否则,其右子结点是结点2i + 1。
例:
以图1左侧的完全二叉树为例:上一条否命题求出了左子树结点,而这条正好求出了右子树结点。结点i=2的右子树结点为5,验证正确!

[color=red][b] 查找二叉树(二叉排序树)[/b][/color]
二叉排序树(Binary Sort Tree)又称二叉查找树(Binary Search Tree)。其定义为:二叉排序树或者是空树,或者是满足如下性质的二叉树:
①若它的左子树非空,则左子树上所有结点的值均小于根结点的值;
②若它的右子树非空,则右子树上所有结点的值均大于根结点的值;
③左、右子树本身又各是一棵二叉排序树。
例:
[align=center][img]https://img-my.csdn.net/uploads/201204/29/1335675441_2398.png[/img][/align]

[b]对排序二叉树的一些操作[/b]
[color=red][b]查找:[/b][/color]若根结点的关键字值等于查找的关键字,成功。
   否则,若小于根结点的关键字值,递归查左子树。
   若大于根结点的关键字值,递归查右子树。
   若子树为空,查找不成功。
[color=red]插入:[/color]分以下几种情况进行相应的处理:
①如果相同键值的结点已在查找二叉树中,则不再插入;
②如果查找二叉树为空树,则以新结点为查找二叉树;
③将要插入结点的键值与插入后的父结点的键值比较,就能确定新结点是父结点的左子结点,还是右子结点,并进行相应插入,新插入的结点一定是一个新添加的叶子结点。
[color=red]删除:[/color]分以下几种情况进行相应的处理:
①若待删除的结点p是叶子结点,则直接删除该结点;
②若待删除的结点p只有一个子节点,则将这个子结点与待删除结点的父结点直接连接,然后删除节点p;
③若待删除的结点p有两个子结点,则在其左子树上,用中序遍历寻找关健值最大的结点s,用结点s的值代替结点p的值,然后删除节点s,结点s必属于上述①、②情况之一。
例:删除图1中的根结点5
首先,中序遍历结点5的左子树,获得最大的结点s=4,将p=5和s=4互换位置,发现要删除的p结点属于第①种情况,则直接删除p=5结点。


[color=red][b]最优二叉树(哈夫曼树)[/b][/color]

最优二叉树是带权路径长度最小的树。
[color=red]基本术语
树的路径长度:是从树根到树中每一个结点的路径长度之和。在结点数目相同的二叉树中,完全二叉树的路径长度最短。
结点的权:根据应用的需要给树的结点赋的权值。
带权路径长度:结点到树根之间的路径长度与该结点上权的乘积,称为结点的带权路径长度。
树的带权路径长度(树的代价):树中所有叶结点的带权路径长度之和,称为树的代价。[/color]

构造哈夫曼树
[align=center][img]https://img-my.csdn.net/uploads/201205/01/1335859681_9732.jpg[/img][/align]

[b]哈弗曼编码[/b]
求出哈夫曼树后,以上图为例,只需人为规定左侧为0,右侧为1,那么结点23的编码是:00;结点11的编码是:010。哈夫曼编码是一种应用广泛且非常有效的数据压缩技术,该技术一般可将数据文件压缩掉20%-90%,其压缩效率取决于被压缩文件的特征。


[b] 线索二叉树[/b]

二叉树的遍历本质上是将一个复杂的非线性结构转换为线性结构,使每个结点都有了唯一前驱和后继(第一个结点无前驱,最后一个结点无后继)。对于二叉树的一个结点,查找其左右子女是方便的,其前驱后继只有在遍历中得到。为了容易找到前驱和后继,有两种方法。一是在结点结构中增加向前和向后的指针fwd和bkd,这种方法增加了存储开销,不可取;二是利用二叉树的空链指针。现将二叉树的结点结构重新定义如下:
[align=center][img]https://img-my.csdn.net/uploads/201205/01/1335863459_4340.png[/img][/align]

对于标志域规定如下:
Lbit=0,Lchild是通常的指正;Lbit=1,Lchild是线索;
Rbit=0,Rchild是通常的指针;Rbit=1,Rchild是线索;
将二叉树转化为线索二叉树
第一步:将将二叉树的前序遍历、中序遍历、后序遍历的顺序写下来;
下图前序遍历顺序:A B D E H C F G I
中序遍历:D B H E A F C G I
后序遍历:D H E B F I G C A
第二步:参照遍历的顺序,找到各个结点对应的前驱和后驱,如下图:
[align=center][img]https://img-my.csdn.net/uploads/201205/01/1335864086_4919.png[/img][/align]

绿色线表示前驱;红色线表示后驱。
上面提到线索二叉树的使用就是为了方便的找到前驱和后驱,对于中序遍历,右指针为空的结点D E H F I,利用线索(红色箭头)可以直接标识出该结点的后驱;但对于右指针非空的普通的结点B、A、C、G,它的后继是右子树最左下的结点,比如B的后继结点为H,A的后继结点为F,因此,中序遍历中找后驱分为两种情况。
后序线索二叉树找后驱可分为三种情况,第一种:根结点后序为null;
第二种,如果一个结点为父结点的右孩子或父结点的左孩子(父结点没有右子树),他的后继就是父结点;比如C的后驱为A结点。
第三种,如果结点是父节点的左孩子,而且父结点有右孩子,那么后继为父结点的右子树按后序遍历列出的第一个结点。比如B结点的后继结点为F结点。


[color=red][b]平衡二叉树[/b][/color]
定义:一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。平衡二叉树的提出大大降低了操作的时间复杂度。
动态调平衡
[color=red]LL型平衡旋转(单向右旋平衡处理)[/color]
[align=center][img]https://img-my.csdn.net/uploads/201205/01/1335883242_1387.png[/img][/align]
[color=red]RR型平衡旋转(单向左旋转平衡处理)[/color]
[align=center][img]https://img-my.csdn.net/uploads/201205/01/1335883252_2695.png[/img][/align]
[color=red]LR型平衡旋转(双向旋转,先左后右)[/color]
[align=center][img]https://img-my.csdn.net/uploads/201205/01/1335883262_9184.png[/img][/align]
[color=red]RL型平衡旋转(双向旋转,先右后左)[/color]
[align=center][img]https://img-my.csdn.net/uploads/201205/01/1335883308_5431.png[/img][/align]

[b]堆[/b]
再补充一个堆,其实一般所说的堆就是一棵完全二叉树。
定义:n个元素的序列{k1,k2,...,kn}当满足下列关系关系时,称为堆:
Ki <= K2i 且 Ki <= K2i+1或者Ki>=K2i且Ki>=K2i+1;
定义比较抽象,举例说明:
[align=center][img]https://img-my.csdn.net/uploads/201205/02/1335949974_2251.png[/img][/align]

Ki <= K2i 且 Ki <= K2i+1:以上图为例,1结点 < 2结点且1结点 < 3结点,下面子树同样成立(孩子结点大于父结点),因此这类完全二叉树就被称为小根堆;
如果有一棵完全二叉树符合Ki>=K2i且Ki>=K2i+1(和上面类似,不演示例子),孩子结点小于父结点,这类二叉树被称为大根堆。
特性
对于结点i,i>=n/2时,表示结点i为叶子结点。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值