您的位置:首页 > 理论基础 > 数据结构算法

树及二叉树

2016-08-04 16:59 148 查看

1.基础概念:

树是一种扩展性的数据结构,体现在树额一对多的方面上
一.树的表示方法

1.双亲表示树:结构体包含数据域和指向双亲的指针域,我们也可以添加指向长子的指针域和只想有兄弟的指针域

2.孩子表示树:两种结构体,一种结构体建立出顺序的数组结构,保存数据域和指向孩子的指针域

              第二种结构体,包含有指向对应的孩子的指针域,还包含指向与其有相同的双亲的兄弟的节点

              当然,这样我们确实也不好寻找双亲,所以我们也还可以再第一种结构体中添加有指向双亲的指针域

3.孩子兄弟表示树:结构体包含三种数据,在第一种保存结点的数据,第二种包含长子域,记录指向他的最左孩子的节点的指针域,第三个数据记录本节点的右孩子的节点指针域

二叉树:
1.整个树不存在节点的度大于2的节点

2.做字数和右子树是存在顺序的,不可以随意地调换

3.及时数只存在一个子节点,我们也要区分是左节点还是右节点

4.二叉树的一个节点主要基本形态:空二叉树,只有一个根节点,根节点只有左子树,根节点只有右子树,根节点左右子树均存在

5.特殊二叉树:

     斜树:每个节点只有左子树或者所有的节点值又右子树

     满二叉树:所有的节点都存在左右两个节点

     完全二叉树:叶节点只可以出现在最下面的一层,最下面的节点一定是存在在左部连续区域,倒是第二层的叶子节点一定出现在又不的连续区域内,左孩子一定优先于右孩子,相同节点个数的二叉树,完全二叉树的层数一定是最小的

     (完全二叉树一定要注意连续的问题)

6.二叉树的重要性质:

     1.二叉树的第i层上最多有2^(i-1)个节点

     2.深度为i的二叉树上最多有2^i-1个节点

     3.任意一棵二叉树叶子节点值是a,度是2的节点数是b,存在关系a=b+1;

     4.完全二叉树深度log2n+1;

     5.将层序编号之后我们会发现,i=1为节点,2*i>n(二叉树的结点个数),i无左孩子,2*i+1>n二叉树无右孩子

7.二叉树的存储结构:

     1.顺序存储,二叉树若存在度数不为2的节点,该空间元素赋值为空,(考虑极端情况,若是斜树的话,会造成严重的空间浪费)

       所以二叉树的顺序存储只限定于完全二叉树

     2.二叉链表:数据域,左孩子指针域,右有孩子指针域

8.二叉树遍历顺序:

     1.前序遍历:根节点->左子树->右子树

     2.中序遍历:左子树->根节点->右子树

     3.后序遍历:左子树->右子树->根节点

     4.层序遍历:层级从左到右

2.二叉树的构建:

编者在大量浏览网上讲解的二叉树的博客发现,都是主讲遍历,对于构建则是很少涉及,所以,为了给大家对二叉树一个清晰的认识,在开始讲二叉树之前,我们首先先来学习一下二叉树的构建,实在不行也可以先阅读第3段遍历来熟悉一下遍历顺序的应用

2.1.人为辅助的单一顺序为基础的构建二叉树

该方法通过人为加入界定判断符来帮助通过一种构建顺序来建立二叉树,该方法的好处是只需要一种遍历顺序,但是坏处是人为依赖性非常大,当数据量非常庞大的时候,该方法毫无用处
实例讲解,代码如下:(前序构建)后序和中序可以这样吗(后序貌似可以通过转变遍历顺序来进行,还是说这样会存在错误)
void build(point*& p)
{
char key=0;
cin>>key;
if(key=='#')
{
p=NULL;
return ;
}
else
{
p->key=key;
p->left=p->right=NULL;
build(p->left);
build(p->right);
}
}

2.2.前序+中序/后序+中序(中序必不可少,但是中序单独又不可)

因为无论是前序遍历还是后续遍历,我们无法确定真正的树的节点在哪里,我们只知道在整体的顺序上的遍历顺序分布

这些节点的先后,但是具体的空间的分布是由节点来确定的,所以,我们必须要引入中序遍历

中序遍历的特点是:中序遍历的节点的左右两边的节点组分别是该节点的左子树和右子树,从容帮助我们确定了节点的位置,从而使得

利用中序+前序   中序+后序实现构建二叉树(二叉树的还原)成为可能

代码如下:
测试用例:
8

1 2 3 5 4 6 7 8

3 5 2 6 4 1 8 7

5 3 6 4 2 8 7 1

#include"iostream"
#include"cstdio"
#include"cstdlib"
#include"cstring"
#define N 100

using namespace std;

typedef struct node
{
int key;
struct node* left;
struct node* right;
}point;

class BT
{
public:
BT()
{
memset(pre,0,sizeof(pre));
memset(mid,0,sizeof(mid));
memset(aft,0,sizeof(aft));
root=NULL;
num=0;
}
~BT()
{
clear(root);   //这里牵扯到遍历的一个应用,那就是后续遍历来删除节点
}
friend istream& operator>>(istream&,BT&);
point* premidbuild(int*,int*,int);
point* aftmidbuild(int*,int*,int);
void preorder(point*);
void clear(point*);
void prepremidbuild()
{
root=premidbuild(pre,mid,num);
}
void preaftmidbuild()
{
root=aftmidbuild(aft,mid,num);
}
point* returnroot()
{
return root;
}
private:
point* root;
int num;
int pre
;
int mid
;
int aft
;
};

istream& operator>>(istream& in,BT& k)
{
cout<<"节点总数:";in>>k.num;
cout<<"前序遍历:";for(int i=0;i<k.num;i++) in>>k.pre[i];
cout<<"中序遍历:";for(int i=0;i<k.num;i++) in>>k.mid[i];
cout<<"后序遍历;";for(int i=0;i<k.num;i++) in>>k.aft[i];
return in;
}

void BT::clear(point* p)    //这里采用后序遍历的方式来实现二叉树的逐个删除
{
if(p==NULL) return ;
else
{
clear(p->left);
clear(p->right);
free(p);
}
}

void BT::preorder(point* p)
{
if(p==NULL) return ;
else
{
printf("%d ",p->key);
preorder(p->left);
preorder(p->right);
}
}

point* BT::premidbuild(int* pre,int* mid,int i)
{
if(i==0) return NULL;
point* w=new point;
w->key=pre[0];
w->left=w->right=NULL;
int* p=NULL;
int* q=NULL;
int a=0;    //w的作用是记录当前前序遍历访问到的节点在中序遍历中的序号位置
for(int j=0;j<i;j++)
{
if(pre[1]==mid[j])   //这里就要求二叉树正不可以出现相同的元素
{
a=j;
break;
}
}
p=pre+1;
q=mid;
w->left=premidbuild(p,q,a);
p=pre+a+1;
q=mid+a+1;
w->right=premidbuild(p,q,i-a-1);
return w;
}

point* BT::aftmidbuild(int* aft,int* mid,int i)
{
if(i==0) return NULL;
point* w=new point;
w->left=w->right=NULL;
w->key=aft[i-1];
int* p=NULL;
int* q=NULL;
int a=0;
for(int j=i-1;j>=0;j--)
{
if(aft[i-1]==mid[j])
{
a=j;
break;
}
}
p=aft+a;
q=mid+a;
w->right=aftmidbuild(p,q,i-a-1);
p=aft;
q=mid;
w->left=aftmidbuild(p,q,a);
return w;
}

int main()
{
BT my;
cin>>my;
//my.prepremidbuild();
//my.preorder(my.returnroot());
my.preaftmidbuild();
my.preorder(my.returnroot());
return 0;
}


3.二叉树的四种遍历方式:

1.前序遍历:

前序遍历的顺序在于:1.根访问(可以包含很多操作)
2.左节点扩展
3.右节点扩展
前序遍历的实现由两种:1.递归2.循环
(实际上因为递归的实现较为好理解,所以一般我们都是用递归来实现树的遍历)
数据结构的选择:1.顺序存储(因为二叉树的特殊性,我们用顺序结构(数组)来存储的时候需要人为加入控制结束的条件,因为我们实际上是不清楚一个二叉树上的节点的度是多少)2.链式存储(定义结构体)

代码解析:
1.结构体的定义:
typedef struct node
{
int key;
struct node* left;
struct node* right;
}point:


2.链式访问:
void preorder(point* p)
{
if(p==NULL) return ;  //子树终止
else
{
printf("%d ",p->key);//a sort of work(such as print the key)
preorder(p->left);
preorder(p->right);
}
}


3.顺序访问:
void preorder(int root)  //first used to be the array[1](begin from the array[1])
{
//函数外存在全局的顺序存储内存(数组),并且用num记录数组中元素的大小,low记录二叉树的层数
if(array[root]='#') return ; //#代表人为加入的中断标记
else
{
printf("%d ",array[root]);
preorder(2*root);    //应用二叉树的性质,标号存在规律,n(根节点)-》n*2(左孩子存在的话)-》n*2+1(右孩子存在的话)
preorder(2*root+1);
}
}

2.3中序遍历和后序遍历

实际上中序遍历和后序遍历的原理和前序遍历是差不多的,这里就不再过多赘述,我们接下来详细的讨论层序遍历的方法

  

4.层序遍历:

实际上层序遍历并没有我们想象的那么简单,我们在这里需要引入一种数据结构叫做:队列,实际上我们是一层一层的逐一扩展,将新的节点加入队列,下一次将该节点的孩子节点再加入队列,知道我们的队列访问为空截止,说明说有的点都已经遍历完全
talk is cheap,show me the code
1.链式存储:
void rankorder(point* root)
{
point* queue
;    //在下不喜欢用STL,那就人工模拟队列,希望看客海量谅解一下
int head=1;
int tail=2;
queue[1]=root;
while(head!=tail)
{
if(queue[head]->left!=NULL) queue[tail++]=queue[head]->left;
if(queue[head]->right!=NULL) queue[tail++]=queue[head]->right;
head++;
}
for(int i=1;i<=tail-1;i++) printf("%d ",queue[i]->key);   //当然niyekeyi在加入队列的时候将其输出,复杂度是没有影响的
}
2.顺序存储:
void preorder(int first)
{
int queue
;
int head=1;
int tail=2;
queue[1]=array[first];
while(head!=tail)
{
if(queue[head*2]!='#') queue[tail++]=queue[head*2];
if(queue[head*2+1]!='#') queue[tail++]=queue[head*2+1];
head++;
}
for(int i=1;i<=tail-1;i++) printf("%d ",queue[i]);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息