数据结构 树

基本导入
#include<stdio.h>
#include<stdlib.h>
#define MAX_TREE_SIZE 100
#define TelemType int
#define Status int
#define OK 1
#define ERROR 0
#define ElemType int

二叉树的存储结构

顺序存储

typedef TelemType SqBiTree[MAX_TREE_SIZE];	//二叉树的顺序存储结构
SqBiTree bt;

非线性存储

//二叉树的非线性存储结构
typedef struct BiTNode
{
	TelemType data;	//存储数据
	struct BiTNode* Lchild, * Rchild;	//左孩子和右孩子
}BiTNode,*BiTree;

遍历

先序遍历

//先序遍历
void preordef(BiTNode* root)	
{
	if (root)
	{
		printf("%d",root->data);
		preordef(root->Lchild);
		preordef(root->Rchild);
	}
}

先序遍历的另一种实现形式

//先序遍历
Status PreOrderTraverse(BiTree T, Status(*visit)(TelemType e))
{
	if (T)
	{
		if (visit(T->data))
		{
			if (PreOrderTraverse(T->Lchild,visit));
			if (PreOrderTraverse(T->Rchild, visit));
			return OK;
		}
		else
			return ERROR;
	}
	else
		return OK;
}

中序遍历

//中序遍历
void inorder(BiTNode* root)
{
	if (root)
	{
		inorder(root->Lchild);
		printf("%d", root->data);
		inorder(root->Rchild);
	}
}

中序遍历的非递归实现

//中序遍历的非递归实现
void InOrder(BiTNode* root)
{
	initStack(S);	//创建一个栈
	push(S, root);	//压树根入栈
	while (!StackEmpty(S))	//如果栈不空
	{
		while (GetTop(S, p) && p)	//获得栈顶元素,并且栈顶元素不是空
			push(S, p->Lchild);	//压左孩子入栈
		pop(S, p);	//把最后一个左孩子的孩子,即NULL排掉
		if (!StackEmpty(S))	//如果栈不空
		{
			pop(S, p);	//出栈,把左孩子出栈以输出
			printf("%d", p->data);	//输出左孩子
			push(S,p->Rchild);	//在左孩子的父节点,看有没有右孩子
			//为什么这里不需要判断右孩子是否存在,这样pop的时候,不就会产生NULL了么
		}
	}
}
---------------------------------------------------------------------------------------
//中序遍历的非递归实现
void InOrder(BiTNode* root)
{
	InitStack(S);	//创建一个栈
	BiTNode *p == root;	
	while (p || !StackEmpty(S))	 ///如果p不是空,且栈不空
	{
		if (p)	//如果该结点不空,那么就把该压栈,并且移到左孩子。最终把左孩子都压入栈
		{
			push(S, p);
			p = p->Lchild;
		}
		//如果该结点是空的了,那么就要往上走了。
		//栈出左孩子,然后输出左孩子,看有没有右孩子,如果有,那么继续重复上一个的动作。
		//如果没有,再出,出的是父节点,输出父节点。
		else 
		{
			pop(S, p);
			printf("%d", p->data);
			p = p->Rchild;
		}
	}
}

后序遍历

//后序遍历
void postorder(BiTNode* root)
{
	if (root)
	{
		postorder(root->Lchild);
		postorder(root->Rchild);
		printf("%d", root->data);
	}
}
层序遍历

只有一个思想,根据思想实现算法

利用队列
构造一个空队列
树根结点入队列
while (队列不空)
{
	令队头结点X出队列
	访问X结点
	若X的左孩子/右孩子存在,则依次加入队列
}
依据遍历执行的衍生操作

创建二叉树

//创建二叉树(先序遍历的顺序建立二叉树)
void CreateBiTree(BiTree& T)
{
	cin >> ch;
	if (ch == '#')
		T = NULL;
	else
	{
		T = new BiTNode;
		T->data = ch;
		CreateBiTree(T->lchild);
		CreateBiTree(T->rchild);
	}
}

复制二叉树

//复制二叉树
void copy(BiTree T, BiTree& NewT)
{
	if (T == NULL)	//如果是空树,递归结束
	{
		NewT = NULL;
		return;
	}
	else
	{
		NewT = new BiTNode;
		NewT->data = T->data;
		copy(T->lchild, NewT->lchild);	//递归复制左子树
		copy(T->rchild, NewT->rchild);	//递归复制右子树
	}
}

计算二叉树深度

//计算二叉树深度
int depth(BiTree T)	
{
	if (T == NULL)	//是空节点
		return 0;
	else	//比较左右子树的深度,并返回大的加一
	{
		m = depth(T->lchild);
		n = depth(T->rchild);
		return m > n ? m + 1 : n + 1;
	}
}

统计二叉树结点数

//统计二叉树的结点个数
int NodeCount(BiTree T)
{
	if (T == NULL)	//如果该节点为空,直接返回
		return 0;
	else	//如果该节点不空,则返回其左子树和右子树结点数再加上自身
	{
		return NodeCount(T->lchild) + NodeCount(T->rchild) + 1;
	}
}

线索二叉树

为了便于得到结点在任一序列中的前驱和后继信息,为此引入了线索二叉树来保存这些在动态过程中得到的有关前驱和后继的信息。
虽然可以再加两个指针域来存储有关前驱和后继信息,但这样做会将存储密度大大降低。
由于n个结点的二叉链表必定存在n+1个空链域,因此可以利用这些空链域来存放前驱和后继信息。

线索二叉树:
如果结点有左子树,则lchild结点存放左孩子,否则存放其前驱。
如果结点有右子树,则rchild结点存放右孩子,否则存放其后继。

线索化:
对二叉树以某种次序遍历使其变为线索二叉树的过程。
在遍历的过程中修改空指针的过程。

线索二叉树的结构:

typedef enum PointerTag
{
	//Link代表有后继结点,不是线索
	//Thread代表没有后继结点,是线索
	Link = 0,Thread = 1
};

typedef struct BiThrNode
{
	ElemType data;
	struct BiThrNode* Lchild, * Rchild;
	PointerTag Ltag, Rtag;
}*BiThrTree;

中序线索二叉树

以结点p为根的子树中序线索化

  1. 如果p非空,左子树递归线索化
  2. 如果p的左孩子为空,则给p加上左线索,将其LTag置为1,让p的左孩子指针指向pre(前驱),否则加个p的LTag置为0
  3. 如果pre的右孩子为空,则给pre加上右线索,将其RTag置为1,让pre的右孩子指针指向p(后继),否则将pre的RTag置为0
  4. 将pre指向刚刚访问的结点p,即pre=p
  5. 右子树递归线索化
//对树中任意一个结点p为根的子树中序线索化
void InThreading(BiThrTree p)
{//pre是全局变量,初始化时其右孩子指针为空,便于在树的最左点开始建线索
	if (p)
	{
		InThreading(p->lchild);
		if (!p->lchild)
		{
			p->LTag = 1;
			p->lchild = pre;
		}
		else
			p->LTag = 0;
		if (!pre->rchild)
		{
			pre->RTag = 1;
			pre->rchild = p;
		}
		else
			pre->RTag = 0;
		pre = p;
		InThreading(p->rchild);
	}
}

有头结点的中序线索化的构造思想
在二叉树的线索链表上也加一个头结点,并令其lchild的指针指向二叉树的根结点,其rchild的指针指向中序遍历时访问的最后一个结点;
同时,令二叉树中序序列中第一个结点的lchild域指针和最后一个结点rchild域的指针均指向头结点。
这就好比为二叉树建立了一个双向线索链表,既可从第一个结点起顺后继进行遍历,也可从最后一个结点起顺前驱进行遍历。

带头结点的二叉树中序线索化

//完成整个二叉树的中序线索化
void InOrderThreading(BiThrTree& Thrt, BiThrTree T)
{
	Thre = new BiThrNode;	//建立头结点
	Thrt->LTag = 0;		//头结点有左孩子,若树非空,则其左孩子为树根
	Thrt->RTag = 1;		//头结点的右孩子指针为右线索
	Thrt->rchild = Thrt;	//初始化时右指针指向自己
	if (!T)		//如果树为空,则左指针也指向自己
		Thrt->lchild = Thrt;
	else
	{
		Thrt->lchild = T;	//头结点的左孩子指向根,pre初值指向头结点
		pre = Thrt;		
		InThreading(T);		//调用InThreading,对T为根的二叉树进行中序线索化
		pre->rchild = Thrt;	//算法InThreading结束后,pre为最右结点,pre的右线索指向头结点
		pre->RTag = 1;
		Thrt->rchild = pre;	//头结点的右线索指向pre
	}
}

遍历中序线索二叉树
算法步骤:

  1. 指针p指向根结点

  2. p为非空树或遍历未结束,循环执行:

    • 沿左孩子向下,到达最左下结点p,它是中序的第一个结点
    • 访问p
    • 沿右线索反复查找当前结点p的后继结点,并访问后继结点,直至右线索为0或遍历结束
    • 转向p的右子树
void InOrderTraverse_Thr(BiThrTree T)
{//T指向头结点,头结点的左链lchlid指向根结点
//中序遍历二叉线索树T的非递归算法,对每个数据元素直接输出
	p = T->lchild;	//p指向根结点
	while (p != T)	/*空树或遍历结束时,p==T*/
	{
		while (p->LTag == 0)	/*沿左孩子向下*/
			p = p->lchild;	/*访问其左子树为空的结点*/
		cout << p->data;
		while (p->RTag == 1 && p->rchild != T)
		{	//沿右线索访问后继结点
			p = p->rchild;
			cout << p->data;
		}
		p = p->rchild;	//转向右子树
	}
}

中序线索二叉树找指定结点的后继的算法
当p->Rtag=1时,则p->Rchlid指向其后继结点
当p->Rtag=0时,则p所指结点的中序后继必为对右子树中序遍历得到的第一个结点
即从p所指结点的右子树出发,沿左指针链向下找,直到找到一个没有左孩子的结点为止
实现算法:

BiThrTree inordernext(BiThrTree p)
{
	if (p->Rtag == 1)
		return p->Rchild;
	else
	{
		BiThrTree s = p->Rchild;
		while (s->Ltag == 0)
			s = s->Lchild;
		return s;
	}
}

后序线索二叉树
在后序线索二叉树上,查找p所指结点的后继分为两种情况:

  1. 若p所指结点是整棵二叉树的根结点,则无后继结点
  2. 若p->Rtag=1,则p->Rchild指向其后继节点
    若p->Rtag=0,则
    1. 若p所指结点是其父节点f的右孩子,则其父节点f是其后继
    2. 若p所指结点是其父节点的左孩子,则
      1. 若p所指结点没有右兄弟,则其父节点f是其后继
      2. 若p有右兄弟,则其后继结点是其父的右子树上后序遍历得到的第一个结点
BiThrTree postorder_next(BiThrTree p, BiThrTree root)
{
	if (p->Rtag == 1)
			return p->Rchild;
	else
	{
		if (p == f->Rchild)	
			return f;
		if (p == f->Lchild && f->Rtag == 1)
			return f;
		BiThrTree q = f->Rchild;
		while (q->Ltag == 0 || q->Rtag == 0)
		{
			if (q->Ltag == 0)
				q = q->Lchild;
			else
				q = q->Rchild;
		}
		return q;
	}
}
线索二叉树的建立

线索化的实质是将二叉链表中的空指针改为指向前驱或后继的指针(线索)
而前驱或后继的信息只有在遍历时才能得到
因此线索化的过程就是遍历过程中修改空指针的过程

//Thrt指向中序线索化链表的头结点
void InThreading(BiThrTree p)
{
	if (p)
	{
		InThreading(p->Lchild);	//左子树线索化
		if (!p->Lchild)
		{
			p->Ltag = Thread;
			p->Lchild = pre;
		}
		if (!pre->Rchild)	//后继线索
		{
			pre->Rtag = Thread;
			pre->Rchild = p;
		}
		pre = p;
		InThreading(p->Rchild);	//右子树线索化
	}
}

void InOrderThreading(BiThrTree& Thrt, BiThrTree root)	
{
	if (!Thrt == (BiThrTriTree)malloc(sizeof(BiThrNode)))
		exit(OVERFLOW);
	Thrt->Ltag = Link;
	Thrt->Rtag = Thread;
	Thrt->Rchild = Thrt;
	if (root == NULL)
		Thrt->Lchild = Thrt;
	else
	{
		Thrt->Lchild = root;
		pre = Thrt;
		InThreading(root);	//需遍历进行中序线索化
		pre->Rchild = Thrt;
		pre->Rtag = Thread;
		Thrt->Rchild = pre;
	}
}

森林转换成树

//双亲表示法:
#define MAX 100
struct node
{
	char data;
	int parent;
};
typedef struct node NODE
NODE tree[MAX];
--------------------------------------------------------
//孩子兄弟表示法:
struct node
{
	char data;
	struct node* son, * brother;
};
森林的遍历

森林的先序遍历
访问森林中的第一个棵树的根结点
先序遍历第一棵树的根结点的子树森林
先序遍历除第一棵树后剩余的树构成的森林

森林的中序遍历
中序遍历第一棵树的根结点的子树森林
访问第一棵树的根结点
中序遍历除第一棵树外剩余的树构成的森林

森林的先序遍历和中序遍历等同于转换所得的二叉树进行先序遍历和中序遍历

哈夫曼树(最优二叉树)

带权值的路径长度最短的树

哈夫曼树基本结构

typedef struct
{
	char ch;
	unsigned int weight;
	unsigned int parent, lchild, rchild;
}HTNode, * Huffmantree;

Huffmantree HT;
HT = (Huffmantree)malloc(2 * n * sizeof(HTNode));

构造哈夫曼树

//w[]存放n个字符的权值,构造哈夫曼树HT,并求出n个字符的编码存入HC
void HuffmanCoding(HuffmanTree& HT, char** & HC, int* w, int n)
{
	if (n <= 1)
		return;
	m = 2 * n - 1;
	HT = (Huffmantree)malloc((m + 1) * sizeof(HTNode));
	for (i = 1; i <= n; i++)
	{
		HT[i].weight = w[i - 1];
		HT[i].ch = setChar(i);
		HT[i].parent = 0;
		HT[i].lchild = HT[i].rchild = 0;
	}
	for (; i <= m; ++i)
	{
		HT[i].parent = 0;
		HT[i].lchild = HT[i].rchild = 0;
	}
	for (i = n + 1; i <= m; i++)	
	{	//从HT[1...i-1]中选择parent为0且weight最小的两个节点
		select(HT, i - 1, s1, s2);		//其序号为s1,s2
		HT[s1].parent = i;
		HT[s2].parent = i;
		HT[i].lchild = s1;
		HT[i].rchild = s2;
		HT[i].weight = HT[s1].weight + HT[s2].weight;
	}

	//从叶子到根逆向求每个字符的哈夫曼编码
	HC = (char**)malloc((n + 1) * sizeof(char*));
	cd = new char[n];
	cd[n - 1] = '\0';
	for(i=1;i<=n;i++)
	{
		start=n-1;
		for(c=i;f=HT[i].parent;f!=0;c=f,f=HT[f].parent)
			if(HT[f].lchild==c)
				cd[--start]='0';
			else 
				cd[--start]='1';
			HC[i]=new char[n-start];
			strcpy(HC[i],&cd[start]);
	}
	delete cd;
}

哈夫曼树的译码算法

//译码算法
void Decoding(HuffmanTree HT, int m, char* buff)
{	//将二进制编码翻译会信息原文,m是树根的编号
	int p = m;
	while (*buff != '\0' && p != 0)
	{
		if ((*buff) == '0')	
			p = HT[p].lchild;	//进入左分支
		else
			p = HT[p].rchild;	//进入右分支
		buff++;
		if (!HT[p].lchild && !HT[p].rchild)
		{
			cout << HT[p].ch;
			p = m;	//重新从树根出发,进行译码
		}
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值