数据结构和算法(十 一): 二叉树、线索二叉树、线索二叉树、赫夫曼树

二叉树的定义

二叉树(Binary Tree)是n(n>=0)个结点的有限集合,该集合或者为空集(空二叉树),

或者由一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树的二叉树组成。

二叉树的特点

1.每个结点最多有两棵子树,所以二叉树中不存在度大于2的结点。(注意:不是都需要两棵子树,而是最多可以是两棵,没有子树或者有一棵子树也都是可以的。)

2.左子树和右子树是有顺序的,次序不能颠倒。

3.即使树中某结点只有一棵子树,也要区分它是左子树还是右子树,下面是完全不同的二叉树:

二叉树的五种基本形态

特殊的二叉树

斜二叉树 、

 满二叉树

完全二叉树

二叉树的性质

二叉树的性质一:在二叉树的第i层上至多有2^(i-1)个结点(i>=1)

二叉树的性质二:深度为k的二叉树至多有2^k-1个结点(k>=1)

二叉树的性质三:

 首先我们再假设度为1的结点数为n1,则二叉树T的结点总数n=n0+n1+n2

其次我们发现连接数总是等于总结点数n-1,并且等于n1+2*n2

所以n-1=n1+2*n2 所以n0+n1+n2-1=n1+n2+n2 最后n0=n2+1

二叉树的性质四

 k=⌊log₂n⌋+1

二叉树的性质五:如果对一棵有n个结点的完全二叉树(其深度为⌊log₂n⌋+1)的结点按层序编号,对任一结点i(1<=i<=n)有以下性质: 如果i = 1,则结点 i 是二叉树的根,无双亲;如果i > 1,则其双亲是结点⌊i/2⌋ 如果2i > n,则结点 i 无做左孩子(结点 i 为叶子结点);否则其左孩子是结点2i 如果2i+1 > n,则结点 i 无右孩子;否则其右孩子是结点2i+1

二叉树的存储结构

二叉树的顺序存储结构

二叉链表

typedef struct BiTNode
{
   ElemType data;
   struct BiTNode *lchild, *rchild;
} BiTNode, *BiTree;

二叉树的遍历

二叉树的遍历(traversing binary tree)是指从根结点出发,按照某种次序依次访问二叉树中所有结点,使得每个结点被访问一次且仅被访问一次。

前序遍历:若二叉树为空,则空操作返回,否则先访问根结点,然后前序遍历左子树,再前序遍历右子树

算法:

前序遍历:

//前序遍历算法

void ProOrderTraverse( BiTree T)
{
    if( T == NULL)
        return ;

    //显示节点的数据
    printf("%c" , T->data);
    //再先序遍历左子树
    ProOrderTraverse (T->lchild);
    //再遍历右子树
    ProOrderTraverse (T->rchild); 
}

中序遍历:

//中遍历算法

void InOrderTraverse( BiTree T)
{
    if( T == NULL)
        return ;

    //再先序遍历左子树
    InOrderTraverse (T->lchild);

    //显示节点的数据
    printf("%c" , T->data);

    //再遍历右子树
    InOrderTraverse(T->rchild); 
}

后序遍历:

//后续遍历算法
void PostOrderTraverse( BiTree T)
{
    if( T == NULL)
        return ;

    //再先序遍历左子树
    PostOrderTraverse (T->lchild);


    //再遍历右子树
   PostOrderTraverse(T->rchild); 

    //显示节点的数据
    printf("%c" , T->data);
}

推导遍历结果:

三种遍历都是从根结点开始,前序遍历是先打印再递归左和右。所以前序遍历序列为ABCDEF,第一个字母是A被打印出来,就说明A是根结点的数据。再由中序遍历序列是CBAEDF,可以知道CBA的左子树的结点,E、DFA的右子树的结点

创建一个二叉树:

#include <stdio.h>
#include <stdlib.h>

typedef char ElemType;

typedef struct BiTNode
{
	char data;
	struct BiTNode *lchild, *rchild;
} BiTNode, *BiTree;

// 创建一棵二叉树,约定用户遵照前序遍历的方式输入数据
CreateBiTree(BiTree *T)
{
	char c;

	scanf("%c", &c);
	if( ' ' == c )
	{
		*T = NULL;
	}
	else
	{
		*T = (BiTNode *)malloc(sizeof(BiTNode));
		(*T)->data = c;
		CreateBiTree(&(*T)->lchild);
		CreateBiTree(&(*T)->rchild);
	}
}

// 访问二叉树结点的具体操作,你想干嘛?!
visit(char c, int level)
{
	printf("%c 位于第 %d 层\n", c, level);
}

// 前序遍历二叉树
PreOrderTraverse(BiTree T, int level)
{
	if( T )
	{
		visit(T->data, level);
		PreOrderTraverse(T->lchild, level+1);
		PreOrderTraverse(T->rchild, level+1);
	}
}

int main()
{
	int level = 1;
	BiTree T = NULL;

	CreateBiTree(&T);
	PreOrderTraverse(T, level);

	return 0;
}

        线索二叉树

线索二叉树

其实线索二叉树,等于是把一棵二叉树转变成了一个双向链表,这样对我们的插入删除结点、查找某个结点都带来了方便。所以我们对二叉树以某种次序遍历使其变为线索二叉树的过程称做是线索化。

#include <stdio.h>
#include <stdlib.h>

typedef char ElemType;

// 线索存储标志位
// Link(0):表示指向左右孩子的指针
// Thread(1):表示指向前驱后继的线索
typedef enum {Link, Thread} PointerTag;

typedef struct BiThrNode
{
	char data;
	struct BiThrNode *lchild, *rchild;
	PointerTag ltag;
	PointerTag rtag;
} BiThrNode, *BiThrTree;

// 全局变量,始终指向刚刚访问过的结点
BiThrTree pre;

// 创建一棵二叉树,约定用户遵照前序遍历的方式输入数据
void CreateBiThrTree( BiThrTree *T )
{
	char c;

	scanf("%c", &c);
	if( ' ' == c )
	{
		*T = NULL;
	}
	else
	{
		*T = (BiThrNode *)malloc(sizeof(BiThrNode));
		(*T)->data = c;
		(*T)->ltag = Link;
		(*T)->rtag = Link;

		CreateBiThrTree(&(*T)->lchild);
		CreateBiThrTree(&(*T)->rchild);
	}
}

// 中序遍历线索化
void InThreading(BiThrTree T)
{
	if( T )
	{
		InThreading( T->lchild );		// 递归左孩子线索化

		if( !T->lchild )	// 如果该结点没有左孩子,设置ltag为Thread,并把lchild指向刚刚访问的结点。
		{
			T->ltag = Thread;
			T->lchild = pre;
		}

		if( !pre->rchild )   //如果没有右孩子
		{
			pre->rtag = Thread;
			pre->rchild = T;
		}

		pre = T;       //保持pre指向T的前驱

		InThreading( T->rchild );		// 递归右孩子线索化
	}
}

void InOrderThreading( BiThrTree *p, BiThrTree T )
{
	*p = (BiThrTree)malloc(sizeof(BiThrNode));
	(*p)->ltag = Link;
	(*p)->rtag = Thread;
	(*p)->rchild = *p;
	if( !T )
	{
		(*p)->lchild = *p;
	}
	else
	{
		(*p)->lchild = T;
		pre = *p;
		InThreading(T);
		pre->rchild = *p;
		pre->rtag = Thread;
		(*p)->rchild = pre;
	}
}

void visit( char c )
{
	printf("%c", c);
}

// 中序遍历二叉树,非递归
void InOrderTraverse( BiThrTree T )
{
	BiThrTree p;
	p = T->lchild;

	while( p != T )
	{
		while( p->ltag == Link )
		{
			p = p->lchild;
		}
		visit(p->data);

		while( p->rtag == Thread && p->rchild != T )
		{
			p = p->rchild;
			visit(p->data);
		}
		
		p = p->rchild;
	}
}

int main()
{
	BiThrTree P, T = NULL;
 
	CreateBiThrTree( &T );

	InOrderThreading( &P, T );

	printf("中序遍历输出结果为: ");

	InOrderTraverse( P );

	printf("\n");

	return 0;
}

树、森林及二叉树的相互转换

我们的惊人发现:树、森林的前根(序)遍历和二叉树的前序遍历结果相同,树、森林的后根(序)遍历和二叉树的中序遍历结果相同!

赫夫曼树

这样的二叉树是不是就是最优的赫夫曼树呢

6.12.3 赫夫曼编码

原来

赫夫曼

赫夫曼树,即发送和接收必须要约定好同样的赫夫曼编码规则。

猜你喜欢

转载自blog.csdn.net/qq_41543888/article/details/95760456