今天在博客上看到2014年腾讯实习生招聘笔试有考二叉树的概念题,于是,来补补这方面的知识。这里

树和二叉树

树 ,定义:树是n(n>=0)个结点的有限集。

树的特点:

(1)在非空树中,有且仅有一个特定的称为根(root)的结点;

(2)当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1,T2...,其中每个集合本身又是一棵树,并称为根的子树。

对于非空树而言

  • 每个节点有零个或多个子节点;
  • 没有父节点的节点称为根节点;
  • 每一个非根节点有且只有一个父节点;
  • 除了根节点外,每个子节点可以分为多个不相交的子树;

树的定义是递归


基本概念:

结点 数据元素的内容及其指向其子树根的分支统称为结点。
结点的度 结点的分支数。
终端结点(叶子) 度为0的结点。
非终端结点 度不为0的结点。
结点的层次 树中根结点的层次为1,根结点子树的根为第2层,以此类推。
树的度 树中所有结点度的最大值。
树的深度 树中所有结点层次的最大值。
有序树、无序树 如果树中每棵子树从左向右的排列拥有一定的顺序,不得互换,则称为有序树,否则称为无序树。
森林 是mm≥0)棵互不相交的树的集合。
在树结构中,结点之间的关系又可以用家族关系描述,定义如下:
孩子、双亲 结点子树的根称为这个结点的孩子,而这个结点又被称为孩子的双亲。 
子孙 以某结点为根的子树中的所有结点都被称为是该结点的子孙。

祖先 从根结点到该结点路径上的所有结点。
兄弟 同一个双亲的孩子之间互为兄弟。
堂兄弟 双亲在同一层的结点互为堂兄弟。

树的种类:

无序树:树中任意节点的子节点之间没有顺序关系这种树称为无序树,又称为自由树。

有序树:树中任意节点的子节点之间有顺序关系这种树称为有序树。

比如:

             A                                A

            |    |                            |    |

          B     C                        C     B

如果表示同一棵树,就叫无序树,如果表示两棵不同的树,就叫有序树,也就是说结点的位置是不是影响树的构成。


输的常用操作有:

常用操作:
1) 构造一个树 CreateTree (T)
2)清空以T为根的树 ClearTree(T)
3)判断树是否为空 TreeEmpty(T)
4)获取给定结点的第i个孩子 Child(T,linklist,i) 
5)获取给定结点的双亲 Parent(T,linklist)
6)遍历树
Traverse(T)

等等



树的存储结构常用的有三种:

双亲表示法,孩子表示法,孩子兄弟表示法

双亲表示法的形式如下:

typedef char ElemType;
typedef struct PTNode  //结点结构
{
	ElemType data;
	int parent;

}PTNode;

typedef struct //树结构
{
	PTNode nodes[MAXSIZE];
	int r,n; //根位置和结点的个数
}PTree;

其中MAXSIZE是自己定义的,如100.

当然,调用时最好定义一个树节点,方便使用。比如

//创建一个树节点,里面包含数据和它的父亲的位置

PTNode node(ElemType data,int parent)
{
	PTNode ptNode = {data,parent};
	return ptNode;
}

简单的双亲表示法 树结构C程序如下:

#include <iostream>
using namespace std;

//树的双亲表示存储表示
const int MAXSIZE = 100;
typedef char ElemType;
typedef struct PTNode//结点结构
{
	ElemType data;
	int parent; //双亲位置
}PTNode;

typedef struct //树结构
{
	PTNode nodes[MAXSIZE];
	int r,n;//根的位置和结点数
}PTree;

//创建一个树节点,里面包含数据和它的父亲的位置
PTNode node(ElemType data,int parent)
{
	PTNode ptNode = {data,parent};
	return ptNode;
}

//在某个树中,获取某个节点的父节点
PTNode getParent(PTree tree,PTNode node)
{
	return tree.nodes[node.parent];
}

//在某个树中,获取某个节点的所有孩子
//只需要判断某个节点的父亲是不是当前这个节点,那么必须遍历整棵树
PTNode* getChilds(PTree tree,PTNode node,int* count)
{
	int i = 1;//从根节点开始
	*count = 0;
	PTNode* t = NULL;
	//遍历树
	for (;i < tree.n; i++)
	{
		if (getParent(tree,tree.nodes[i]).data == node.data)
		{
			//创建一个PTNode
			t = (PTNode*)realloc(t,sizeof(PTNode)*(++*count));
			if (!t)
			{
				cout<<"memory error !";
				exit(0);
			}
			t[*count-1] = tree.nodes[i];
		}
	}
	return t;
}

int main()
{
	PTree tree;
	tree.r = 0;//根的位置,根节点的父亲位置为-1
	tree.nodes[0] = node('a',-1);
	tree.nodes[1] = node('b',0);
	tree.nodes[2] = node('c',0);
	tree.nodes[3] = node('d',1);
	tree.nodes[4] = node('e',2);
	tree.nodes[5] = node('f',2);
	tree.nodes[6] = node('g',3);
	tree.nodes[7] = node('h',3);
	tree.nodes[8] = node('i',3);
	tree.nodes[9] = node('j',4);
	tree.n = 10;//表示树的节点数
	//双亲表示法的好处是很好找到某个节点的父亲,时间复杂度是O(1)

	
	//                             a
	//                            /  \
	//                           b    c
	//                          /    / \
	//                         d    e   f
	//                       / | \  |
	//                      g  h  i j

	/

	//遍历整个树
	int i = 1;
	for (; i < tree.n; i++)
	{
		cout<<tree.nodes[i].data<<"的父亲是:"<<getParent(tree,tree.nodes[i]).data<<endl;

	}
	//获取某个节点的子节点
	int count;
	PTNode *t;
	t = getChilds(tree,tree.nodes[2],&count);
	//输出子节点
	cout<<tree.nodes[2].data<<"的子节点有:"<<count<<"个 !"<<endl;
	for (i = 0; i < count; i++)
	{
		cout<<t[i].data<<endl;
	}

	system("pause");
	return 0;
}

运行结果:



还有孩子表示法,孩子兄弟表示法

明天继续~~~


树的双亲表示法 存储

//树的双亲表存储表示
#include <iostream>



typedef char TElemType;
#define MAX_TREE_SIZE 100

typedef struct  //结点结构
{
	TElemType data;	//数据域
	int parent;		// 双亲位置域
}PTNode;	//结点结构

typedef struct  //树结构
{
	PTNode nodes[MAX_TREE_SIZE];	//存储结点的数组
	int n; // 结点数
}PTree;

typedef struct
{
	int num;
	TElemType name;
}QElemType; // 定义队列元素类型


int InitTree(PTree *T)
{ // 操作结果: 构造空树T
	(*T).n=0;
	return 1;
}

// 若T为空树,则返回1,否则返回0
int TreeEmpty(PTree T)
{
	if(T.n)
		return 0;
	else
		return 1;
}

///
//very important
//  返回T的深度
int TreeDepth(PTree T)
{
	int k,m,def,
		max=0;	//存储深度

	for(k=0;k<T.n;++k)
	{
		def=1; // 初始化本际点的深度
		m = T.nodes[k].parent;
		while(m != -1)
		{
			m = T.nodes[m].parent;
			def++;
		}
		if(max < def)
			max = def;
	}
	return max; // 最大深度
}
///
// 返回T的根
TElemType Nil=' '; // 以空格符为空
TElemType Root(PTree T)
{
	int i;
	for(i=0;i<T.n;i++)
		if(T.nodes[i].parent < 0)
			return T.nodes[i].data;
	return Nil;
}
///




//
typedef struct QNode
{
	QElemType data;		//数据域
	struct QNode *next;	//指针域
}QNode,*QueuePtr;

typedef struct
{
	QueuePtr front,//队头指针,指针域指向队头元素
		rear; //队尾指针,指向队尾元素
}LinkQueue;

void DestroyTree()
{
	// 由于PTree是定长类型,无法销毁
}

// 构造一个空队列Q
int InitQueue(LinkQueue *Q)
{
	(*Q).front=(*Q).rear=(QueuePtr)malloc(sizeof(QNode));	//动态分配一个空间
	if(!(*Q).front)
		exit(0);
	(*Q).front->next=NULL;	//队头指针指向空,无数据域,这样构成了一个空队列
	return 1;
}


// 插入元素e为Q的新的队尾元素
int EnQueue(LinkQueue *Q,QElemType e)
{
	QueuePtr p=(QueuePtr)malloc(sizeof(QNode));
	if(!p) // 存储分配失败
		exit(0);
	//生成一个以为e为数据域的队列元素
	p->data=e;
	p->next=NULL;
	//将该新队列元素接在队尾的后面
	(*Q).rear->next=p;
	(*Q).rear=p;
	/*这是结点(元素)入队的操作。(*Q).rear是链队的尾指针,指向
	链队的最后一个元素。(*Q).rear->next指的是链队最后一个元素的
	指针域。p是要入队的新结点。(*Q).rear->next=p是新结点入队的操
	作,即原来的最后一个结点的指针域里的指针指向了p,所以p就入队了,
	成为链队的最后一个结点。(*Q).rear=p,就是链队的尾指针指向刚刚
	入队的尾结点p,为了再将新的结点入队。若再将新的结点入队再重复这
	两句代码即可。
	
	即 Q->rear->next=p    让p接入Q->rear
	Q->rear=p       此时p为最后一个节点,让Q->rear指向p

	队列有个队尾指针rear
	这两句话相当于在队尾加入一个元素p  (*Q).rear->next=p;
	那么队尾就变成p了 (*Q).rear=p;

	*/
	return 1;
}

// 若Q为空队列,则返回1,否则返回0
int QueueEmpty(LinkQueue Q)
{
	if(Q.front==Q.rear)
		return 1;
	else
		return 0;
}


// 若队列不空,删除Q的队头元素,用e返回其值,并返回1,否则返回0
int DeQueue(LinkQueue *Q,QElemType *e)
{
	QueuePtr p;
	if((*Q).front==(*Q).rear)
		return 0;
	p=(*Q).front->next;	//队头元素
	*e=p->data;
	(*Q).front->next=p->next;
	if((*Q).rear==p)
		(*Q).rear=(*Q).front;
	free(p);
	return 1;
}
/
// 层序遍历树T,对每个结点调用函数Visit一次且仅一次
void TraverseTree(PTree T,void(*Visit)(TElemType))
{
	int i = 0,tmp = -1,k = 0;
	std::cout<<T.nodes[i].parent<<"\t";
	Visit(T.nodes[i].data);
	for(i=1;i<T.n;i++)
	{
		if(T.nodes[i].parent != T.nodes[i-1].parent)
		std::cout<<std::endl<<T.nodes[i].parent<<"\t";
		
		Visit(T.nodes[i].data);
		
	}
	
	printf("\n");
}
//
void vi(TElemType c)
{
	printf(" %c ",c);
}

//
// 构造树T
int CreateTree(PTree *T)
{
	LinkQueue q;
	QElemType p,qq;
	int i=1,j,l;
	char c[MAX_TREE_SIZE]; // 临时存放孩子结点数组

	InitQueue(&q); // 初始化队列
	printf("请输入根结点(字符型,空格为空): ");
	scanf("%c%*c",&(*T).nodes[0].data); // 根结点序号为0,%*c吃掉回车符
	if((*T).nodes[0].data != Nil) // 非空树
	{
		(*T).nodes[0].parent = -1; // 根结点无双亲
		qq.name = (*T).nodes[0].data;
		qq.num = 0;
		EnQueue(&q,qq); // 入队此结点
		while(i < MAX_TREE_SIZE && !QueueEmpty(q)) // 数组未满且队不空
		{
			DeQueue(&q,&qq); // 出队一个结点
			printf("请按长幼顺序输入结点%c的所有孩子: ",qq.name);
			gets(c);
			l=strlen(c);
			for(j=0;j<l;j++)
			{
				(*T).nodes[i].data=c[j];
				(*T).nodes[i].parent=qq.num;
				p.name=c[j];
				p.num=i;
				EnQueue(&q,p); // 入队此结点
				i++;
			}
		}
		if(i>MAX_TREE_SIZE)
		{
			printf("结点数超过数组容量\n");
			exit(0);
		}
		(*T).n=i;
	}
	else
		(*T).n=0;
	return 1;
}

//
//  改cur_e为value
int Assign(PTree *T,TElemType cur_e,TElemType value)
{
	int j;
	for(j=0;j<(*T).n;j++)
	{
		if((*T).nodes[j].data == cur_e)
		{
			(*T).nodes[j].data = value;
			return 1;
		}
	}
	return 0;
}
//
// 若cur_e是T的非根结点,则返回它的双亲,否则函数值为"空"
TElemType Parent(PTree T,TElemType cur_e)
{
	int j;
	for(j=1; j < T.n;j++) // 根结点序号为0
		if(T.nodes[j].data == cur_e)
			{std::cout<<T.nodes[j].parent;
	return T.nodes[T.nodes[j].parent].data;}
	return Nil;
}
//
// 若cur_e是T的非叶子结点,则返回它的最左孩子,否则返回"空"
TElemType LeftChild(PTree T,TElemType cur_e)
{
	int i,j;
	for(i=0;i<T.n;i++)
		if(T.nodes[i].data == cur_e) // 找到cur_e,其序号为i
			break;
	for(j=i+1; j < T.n;j++)	// 根据树的构造函数,孩子的序号>其双亲的序号
		// 根据树的构造函数,最左孩子(长子)的序号<其它孩子的序号
			if(T.nodes[j].parent == i)
				return T.nodes[j].data;
	return Nil;
}

// 若cur_e有右(下一个)兄弟,则返回它的右兄弟,否则返回"空"
TElemType RightSibling(PTree T,TElemType cur_e)
{
	int i;
	for(i=0;i<T.n;i++)
		if(T.nodes[i].data==cur_e) // 找到cur_e,其序号为i
			break;
	if(T.nodes[i+1].parent == T.nodes[i].parent)
		// 根据树的构造函数,若cur_e有右兄弟的话则右兄弟紧接其后
			return T.nodes[i+1].data;
	return Nil;
}
//
//  插入c为T中p结点的第i棵子树
int InsertChild(PTree *T,TElemType p,int i,PTree c)
{
	int j,k,l,f=1,n=0; // 设交换标志f的初值为1,p的孩子数n的初值为0
	PTNode t;

	if(!TreeEmpty(*T)) // T不空
	{
		for(j=0;j<(*T).n;j++)	// 在T中找p的序号
			if((*T).nodes[j].data==p)	// p的序号为j
				break;
		l = j+1;	// 如果c是p的第1棵子树,则插在j+1处
		if(i > 1) // c不是p的第1棵子树
		{
			for(k=j+1; k < (*T).n; k++) // 从j+1开始找p的前i-1个孩子
				if((*T).nodes[k].parent == j) // 当前结点是p的孩子
				{
					n++; // 孩子数加1
					if(n == i-1) // 找到p的第i-1个孩子,其序号为k1
						break;
				}
				l = k+1; // c插在k+1处
		} // p的序号为j,c插在l处
		if(l < (*T).n) // 插入点l不在最后
			// 依次将序号l以后的结点向后移c.n个位置
				for(k=(*T).n-1; k >= l; k--)
				{
					(*T).nodes[k+c.n] = (*T).nodes[k];	//向后移c.n个位置
					if((*T).nodes[k].parent >= l)
						(*T).nodes[k+c.n].parent+=c.n;
				}
				for(k=0;k<c.n;k++)
				{
					(*T).nodes[l+k].data=c.nodes[k].data; // 依次将树c的所有结点插于此处
					(*T).nodes[l+k].parent=c.nodes[k].parent+l;
				}
				(*T).nodes[l].parent=j; // 树c的根结点的双亲为p
				(*T).n+=c.n; // 树T的结点数加c.n个
				while(f)
				{ // 从插入点之后,将结点仍按层序排列
					f=0; // 交换标志置0
					for(j=l; j < (*T).n-1; j++)
						if((*T).nodes[j].parent > (*T).nodes[j+1].parent)
						{
							// 如果结点j的双亲排在结点j+1的双亲之后(树没有按层序排
							// 列),交换两结点
							t=(*T).nodes[j];
							(*T).nodes[j]=(*T).nodes[j+1];
							(*T).nodes[j+1]=t;
							f=1; // 交换标志置1
							for(k=j;k<(*T).n;k++) // 改变双亲序号
								if((*T).nodes[k].parent==j)
									(*T).nodes[k].parent++; // 双亲序号改为j+1
								else if((*T).nodes[k].parent==j+1)
									(*T).nodes[k].parent--; // 双亲序号改为j
						}
				}
				return 1;
	}
	else // 树T不存在
		return 0;
}

//
// 返回第i个结点的值
TElemType Value(PTree T,int i)
{
	if(i<T.n)
		return T.nodes[i].data;
	else
		return Nil;
}
//
// 输出树T
int Print(PTree T)
{
	int i;
	printf("结点个数=%d\n",T.n);
	printf(" 结点 双亲\n");
	for(i=0;i<T.n;i++)
	{
		printf("    %c",Value(T,i)); // 结点
		if(T.nodes[i].parent>=0) // 有双亲
			printf("    %c",Value(T,T.nodes[i].parent)); // 双亲
		printf("\n");
	}
	return 1;
}
//


int deleted[MAX_TREE_SIZE+1]; // 删除标志数组(全局量)

// 删除T中结点p的第i棵子树
void DeleteChild(PTree *T,TElemType p,int i)
{
	int j,k,n=0;
	LinkQueue q;
	QElemType pq,qq;
	
	for(j=0;j<=(*T).n;j++)
		deleted[j]=0; // 置初值为0(不删除标记)
	pq.name='a'; // 此成员不用
	InitQueue(&q); // 初始化队列
	for(j=0;j<(*T).n;j++)
		if((*T).nodes[j].data==p)
			break; // j为结点p的序号
	for(k=j+1;k<(*T).n;k++)
	{
		if((*T).nodes[k].parent==j)
			n++;
		if(n==i)
			break; // k为p的第i棵子树结点的序号
	}
	if(k<(*T).n) // p的第i棵子树结点存在
	{
		n=0;
		pq.num=k;
		deleted[k]=1; // 置删除标记
		n++;
		EnQueue(&q,pq);
		while(!QueueEmpty(q))
		{
			DeQueue(&q,&qq);
			for(j=qq.num+1;j<(*T).n;j++)
				if((*T).nodes[j].parent==qq.num)
				{
					pq.num=j;
					deleted[j]=1; // 置删除标记
					n++;
					EnQueue(&q,pq);
				}
		}
		for(j=0;j<(*T).n;j++)
			if(deleted[j]==1)
			{
				for(k=j+1;k<=(*T).n;k++)
				{
					deleted[k-1]=deleted[k];
					(*T).nodes[k-1]=(*T).nodes[k];
					if((*T).nodes[k].parent>j)
						(*T).nodes[k-1].parent--;
				}
				j--;
			}
		(*T).n-=n; // n为待删除结点数
	}
}



int main()
{
	
	
	int i;
	PTree T,p;
	TElemType e,e1;
	
	InitTree(&T);
	
	printf("构造空树后,树空否? %d(1:是 0:否) 树根为%c 树的深度为%d\n",
		TreeEmpty(T),Root(T),TreeDepth(T));
	CreateTree(&T);
	printf("构造树T后,树空否? %d(1:是 0:否) 树根为%c 树的深度为%d\n",
		TreeEmpty(T),Root(T),TreeDepth(T));
	printf("层序遍历树T:\n");
	TraverseTree(T,vi);
	printf("请输入待修改的结点的值 新值: ");
	scanf("%c%*c%c%*c",&e,&e1);
	Assign(&T,e,e1);
	printf("层序遍历修改后的树T:\n");
	TraverseTree(T,vi);


	printf("%c的双亲是%c,长子是%c,下一个兄弟是%c\n", e1,
		Parent(T,e1),LeftChild(T,e1),RightSibling(T,e1));
	printf("建立树p:\n");
	InitTree(&p);
	CreateTree(&p);
	printf("层序遍历树p:\n");
	TraverseTree(p,vi);
	printf("将树p插到树T中,请输入T中p的双亲结点 子树序号: ");
	scanf("%c%d%*c",&e,&i);
	InsertChild(&T,e,i,p);
	Print(T);
	printf("删除树T中结点e的第i棵子树,请输入e i: ");
	scanf("%c%d",&e,&i);
	DeleteChild(&T,e,i);
	Print(T);
	


	/**/
	system("pause");
	return 0;
}

//
/*
输出效果:


构造空树后,树空否? 1(1:是 0:否) 树根为  树的深度为0
请输入根结点(字符型,空格为空): a
请按长幼顺序输入结点a的所有孩子: bc
请按长幼顺序输入结点b的所有孩子: d
请按长幼顺序输入结点c的所有孩子: e
请按长幼顺序输入结点d的所有孩子:
请按长幼顺序输入结点e的所有孩子:
构造树T后,树空否? 0(1:是 0:否) 树根为a 树的深度为3
层序遍历树T:
a b c d e
请输入待修改的结点的值 新值: e f
层序遍历修改后的树T:
a b c d f
f的双亲是c,长子是 ,下一个兄弟是
建立树p:
请输入根结点(字符型,空格为空): A
请按长幼顺序输入结点A的所有孩子: B
请按长幼顺序输入结点B的所有孩子:
层序遍历树p:
A B
将树p插到树T中,请输入T中p的双亲结点 子树序号: b 2
结点个数=7
 结点 双亲
    a
    b    a
    c    a
    d    b
    A    b
    f    c
    B    A
删除树T中结点e的第i棵子树,请输入e i: a 1
结点个数=3
 结点 双亲
    a
    c    a
    f    c
请按任意键继续. . .

*/





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值