数据结构 树&图

目录

二叉树

二叉树的代码实现

哈夫曼树

基本概念

哈夫曼树的代码实现

 一.哈夫曼树的构成

 二、创建哈夫曼树

 三.为哈夫曼树编码

孩子兄弟树


二叉树

 二叉树:每个结点最多有2个子结点

二叉树的代码实现

结构体

typedef int BTElemType;

typedef struct BiTreeNode {
	BTElemType data;
	BiTreeNode* lChild;
	BiTreeNode* rChild;
}*BiTree;

 初始化

由于很多语句都需要判断结点存不存在,因此二叉树的初始化就是T=NULL

相似的,之后在创建树时,每个结点的左右孩子一开始也需要=NULL

保证结点被声明时=NULL,左右孩子被赋值之前=NULL,直到有值赋给他们,否则指针会变成野指针

//初始化
void InitBT(BiTree& T) {
	T = NULL;

}

//判树空(无任何结点)
bool BTIsEmpty(BiTree T) {
	if (!T)
		return true;
	else
		return false;
}

 前序创建树

 理解

1.将二叉树T传入函数,首先输入一个元素e

2.若e非0,T->data=e;   T->lChild=NULL;  T->rChild=NULL;

3. T的左右孩子先后调用此函数,不断创建

//前序创建树(输入非0值)
void CreatBT(BiTree& T) {
	BTElemType e;
	cin >> e;
	InitBT(T);
	if (e!=0) {
		T = (BiTree)malloc(sizeof(BiTreeNode));
		if (!T)
			exit(OVERFLOW);
		T->data = e;
		T->lChild = NULL;
		T->rChild = NULL;
		CreatBT(T->lChild);
		CreatBT(T->rChild);
	}
}

 二叉树的遍历(前中后、层序遍历)

前中后序遍历的区别即是   访问左子树、右子树、根节点的顺序不同


前序遍历:传进结点->输出data->左孩子递归->右孩子递归

中序遍历:相当于一直找到左孩子不存在的结点,输出本结点data,再右孩子递归

后序遍历:从左子树开始找,找到底层(左右孩子都为NULL了),输出那个结点,再向上回溯

//前序遍历
void PreOrderTraverse(BiTree T) {
	if (T) {
		cout << T->data << " ";
		PreOrderTraverse(T->lChild);
		PreOrderTraverse(T->rChild);
	}
}

//中序遍历
void InOrderTraverse(BiTree T) {
	if (T) {
		InOrderTraverse(T->lChild);
		cout << T->data << " ";
		InOrderTraverse(T->rChild);
	}
}

//后序遍历
void NextOrderTraverse(BiTree T) {
	if (T) {
		NextOrderTraverse(T->lChild);
		NextOrderTraverse(T->rChild);
		cout << T->data << " ";
	}
}

 层序遍历:用队列,存储每一层的结点

从根节点开始入队,只要队不空,出队,输出data,再将当前节点的左右孩子入队

//层序遍历
void LevelOrderTraverse(BiTree T) {
	LinkQueue Q;
	BiTree p;			//存储出队的
	if (T) {
		InitLQ(Q);
		EnLQ(Q, (LQElemType)T);
		while (!LQIsEmpty(Q)) {
			DeLQ(Q, (LQElemType&)p);
			cout << p->data << " ";
			if (p->lChild)
				EnLQ(Q, (LQElemType)p->lChild);
			if (p->rChild)
				EnLQ(Q, (LQElemType)p->rChild);
		}
	}

}

 求树的深度

//求树的深度
int BTDepth(BiTree T) {
	int lDepth, rDepth;
	if (!T)
		return 0;
	lDepth = BTDepth(T->lChild);
	rDepth = BTDepth(T->rChild);
	if (lDepth >= rDepth)
		return lDepth + 1;
	else
		return rDepth + 1;
}

 理解!!!

(1)如果一颗树只有一个结点,它的深度是 1;
(2)如果根结点只有左子树而没有右子树,那么二叉树的深度应该是其左子树的深度加 1;
(3)如果根结点只有右子树而没有左子树,那么二叉树的深度应该是其右树的深度加 1;
(4)如果根结点既有左子树又有右子树,那么二叉树的深度应该是其左右子树的深度较大值加 1

——>

如果这个结点为空,它对深度的贡献就是0,返回给上一层;

向上回溯,每层贡献的深度是下一层返回的深度+1(这个1是本结点贡献的深度)

向上回溯过程中,返回的是lDepth和rDepth中的较大值,因为——根节点到叶结点的最长路径为二叉树的深度

哈夫曼树

基本概念

1、权
2、路径:从树中的一个结点到另一个结点

1)路径长度:路径上的分支数                
2)树的路径长度:从树根到每个结点的路径长度之和

 3)结点的带权路径长度:从该结点到树根之间的路径长度与结点上权的乘积
 4)树的带权路径长度:树中所有带权结点的路径长度之和

3、哈夫曼树:带权路径长度最小的树

哈夫曼树的代码实现

 一.哈夫曼树的构成

一棵有n个叶子结点的哈夫曼树中,度为2的结点有n-1个,则共有2n-1个结点,可以用大小为2n-1的一维数组来存储

每个数组元素是这样的结构体:

 结构体

typedef struct HuffmanTreeNode {
	int lChild, rChlid, parent;  
	int weight;		
}*HuffmanTree;

 二、创建哈夫曼树

 1.初始化 

1)为HT开辟2n-1(total)个存储单元 

 2)初始化每个单元,0~n个单元,左右孩子、双亲初始化为-1(由于-1不存在,所以实际上的意思是初始化的时候先都标记为无),第 i 个的权值为传进来的数组元素weight[i]       

 n~total个单元,左右孩子、双亲初始化为-1,第 i 个的权值为0(也是初始化思想)

2.创建树

n~total是新添加的数,在n~total循环,把其前边最小的数和第二小的数变成它的左右孩子下标,左右孩子的parent下标变成它的下标,它的权值为左右孩子之和


//返回前k个最小的权值
int Min(HuffmanTree HT, int k) {
	int i = 0;
	int min;
	while (HT[i].parent != -1)		//从下标0找到第一个没有父结点的,从它开始找
		i++;
	min = i;			//将其暂时赋值给min
	//i到k找到最小权值的下标
	for (; i < k; i++)
		//如果一样,则返回前面的
		if (HT[i].weight < HT[min].weight && HT[i].parent == -1)
			min = i;
	HT[min].parent = 1;		//改变parent的值,下次就不找这个点了
	return min;
}

void CreatHuffmanTree(HuffmanTree& HT, int* weight, int n) {
	int total = 2 * n - 1;    //remember
	HT = new HuffmanTreeNode[total];  //给传进来的赫夫曼树分配空间,一共有total个结构体
	if (!HT)
		exit(OVERFLOW);
	//给每个结构体初始化
	for (int i = 0; i < n; i++) {
		HT[i].lChild = HT[i].rChlid = HT[i].parent = -1;
		HT[i].weight = weight[i];
	}
	for (int i = n; i < total; i++) {
		HT[i].lChild = HT[i].rChlid = HT[i].parent = -1;
		HT[i].weight = 0;
	}
	for (int i = n; i < total; i++) {
		//先找的min1,所以min1一定是小于min2的,它在前边
		int min1 = Min(HT, i);
		int min2 = Min(HT, i);

		HT[min1].parent = HT[min2].parent = i;
		//给HT[i]的各种成员赋值
		HT[i].weight = HT[min1].weight + HT[min2].weight;
		HT[i].lChild = min1;
		HT[i].rChlid = min2;
	}
}

 三.为哈夫曼树编码

1、基本思想:概率大的字母用短码,小的用长码,构造哈夫曼树,概率越大,路径越短。
2、哈夫曼编码:在哈夫曼的每个分支上标上0或1
——结点的左分支标0,右分支标1
——把从根到每个叶子的路径上的标号连接起来,构成一个二进制串,该二进制串就成为该叶子代表的哈夫曼编码

编码

1)二维数组code存放n个叶结点的编码 

2)tempCoding是一个在结尾放了空字符的临时字符串指针

3)从0~n,cur为当前数据的下标,parent为其双亲,start用于指向编码在tempCoding中的位置,初始时指向最后一个(n-1)

4)当不是根结点时,左0右1进行赋值,再把当前数据改为其双亲数据,继续向上编码

5)tempCoding+start是当前开始有编码的位置,把编码copy给code[i]

void HuffmanCoding(HuffmanTree T, char**& code, int n) {
	code = new char* [n];
	if (!code)
		exit(OVERFLOW);
	char* tempCoding = (char*)malloc(n * sizeof(char));
	if (!tempCoding)
		exit(OVERFLOW);
	tempCoding[n - 1] = '\0';

	for (int i = 0; i < n; i++) {
		int cur = i;
		int parent = T[i].parent;
		int start = n - 1;
		//当未到根结点时
		while (parent != -1) {
			//左0右1
			if (T[parent].lChild == cur)
				tempCoding[--start] = '0';
			else if (T[parent].rChlid == cur)
				tempCoding[--start] = '1';
			cur = parent;
			parent = T[cur].parent;
		}
		code[i] = (char*)malloc((n - start) * sizeof(char));
		if (!code[i])
			exit(OVERFLOW);
		strcpy(code[i], tempCoding + start);
	}
	for (int i = 0; i < n; i++)
		cout << code[i] << endl;
}

孩子兄弟树

结构体

typedef struct CSTreeNode {
	CSTElemType data;
	CSTreeNode* firstChild, * nextSibling;
}*CSTree;

  

和二叉树相比较,把左孩子右孩子换成了首孩子和兄弟

初始化和创建树

 创建树时:

输入非零值创建树的根结点

和二叉树一开始的赋值类似,data存储输入的值e,让两个指针指空

结点入队

当队伍不为空时:

将输入的第一个结点存为当前结点的首孩子,将剩下的输入存为它孩子的兄弟

//初始化树
void InitTree(CSTree& T) {
	T = NULL;
}

//创建树
void CreatTree(CSTree& T) {
	LinkQueue Q;
	CSTElemType e;
	cout << "请输入根节点" << endl;
	cin >> e;
	CSTree p;

	//有树
	if (e != 0) {
		InitLQ(Q);
		T = (CSTree)malloc(sizeof(CSTreeNode));
		if (!T)
			exit(OVERFLOW);
		T->data = e;
		T->firstChild = NULL;
		T->nextSibling = NULL;

		EnLQ(Q, (LQElemType)T);
		while (!LQIsEmpty(Q)) {
			DeLQ(Q, (LQElemType&)p);
			cout << "请输入" << p->data << "的孩子个数" << endl;

			int childNum;
			cin >> childNum;

			//有树
			if (childNum > 0) {
				cout << "请输入" << p->data << "的" << childNum << "个孩子值" << endl;
				cin >> e;
				CSTree q;
				q = (CSTree)malloc(sizeof(CSTreeNode));
				if (!q)
					exit(OVERFLOW);
				q->data = e;
				q->firstChild = NULL;
				q->nextSibling = NULL;
				p->firstChild = q;
				p = q;
				EnLQ(Q, (LQElemType)q);

				for (int i = 1; i < childNum; i++) {
					cin >> e;
					CSTree q2 = (CSTree)malloc(sizeof(CSTreeNode));
					if (!q2)
						exit(OVERFLOW);
					q2->data = e;
					q2->firstChild = NULL;
					q2->nextSibling = NULL;

					p->nextSibling = q2;
					p = q2;
					EnLQ(Q, (LQElemType)q2);
				}
			}
		}
		DestroyLQ(Q);
	}
}

遍历

 前序遍历:和二叉树的前序遍历思路类似

层序遍历:输出根结点的值,入队->当队不空:输出当前结点的首孩子,按次输出该孩子的右兄弟

//前序遍历
void PreOrderTraverse(CSTree T) {
	if (T) {
		cout << T->data << " ";
		PreOrderTraverse(T->firstChild);
		PreOrderTraverse(T->nextSibling);
	}
}

//层序遍历
void LevelOrderTraverse(CSTree T) {
	LinkQueue Q;
	CSTree p, q;
	if (T) {
		cout << T->data << " ";
		InitLQ(Q);
		EnLQ(Q, (LQElemType)T);
		while (!LQIsEmpty(Q)) {
			DeLQ(Q, (LQElemType&)p);
			if (p->firstChild) {
				q = p->firstChild;
				while (q) {
					EnLQ(Q, (LQElemType)q);
					cout << q->data << " ";
					q = q->nextSibling;
				}
			}
		}
	}
}

销毁

 先深入到最后一个首孩子,再找到它的最后一个右兄弟,从右到左,从下到上(这个方向仅用于自己理解。。),向上回溯,用free()释放其空间

//销毁树
void DestroyTree(CSTree& T) {
	if (T) {
		DestroyTree(T->firstChild);
		DestroyTree(T->nextSibling);
		free(T);
		T = NULL;
	}
}

 求树深度

//求树深度
int TreeDepth(CSTree T) {
	CSTree p;
	int depth, max = 0;
	if (!T)
		return 0;
	for (p = T->firstChild; p; p = p->nextSibling) {
		depth = TreeDepth(p);
		if (depth > max)
			max = depth;
	}
	return max + 1;
}

 SomeFunctions


//返回p结点值
CSTElemType Value(CSTree p) {
	return p->data;
}

//返回根节点值
CSTElemType Root(CSTree T) {
	if (T)
		return Value(T);
	else
		return 0;
}

//返回值为e的结点
CSTree Point(CSTree T, CSTElemType e) {
	LinkQueue Q;
	CSTree p;
	if (T) {
		InitLQ(Q);
		EnLQ(Q, (LQElemType)T);
		while (!LQIsEmpty(Q)){
			DeLQ(Q,(LQElemType&)p);
			if (p->data == e)
				return p;
			if (p->firstChild)
				EnLQ(Q, (LQElemType)p->firstChild);
			if (p->nextSibling)
				EnLQ(Q, (LQElemType)p->nextSibling);
		}
	}
	return NULL;
}

//求值为e的双亲结点值
CSTElemType Parent(CSTree T, CSTElemType e) {
	LinkQueue Q;
	CSTree p, q;
	if (T) {
		InitLQ(Q);
		EnLQ(Q, (LQElemType)T);
		while (!LQIsEmpty(Q)) {
			DeLQ(Q, (LQElemType&)p);
			if (p->firstChild) {
				q = p->firstChild;
				while (q && q->data != e) {
					EnLQ(Q, (LQElemType)q);
					q = q->nextSibling;
				}
				if (q)
					return p->data;
			}
		}
	}

	return NULL;
}

//求值为cur_e的最左孩子
CSTElemType LeftChild(CSTree T, CSTElemType cur_e) {
	CSTree p = Point(T, cur_e);
	if (p && p->firstChild)
		return p->firstChild->data;
	return 0;
}

//求值为cur_e的右兄弟
CSTElemType rightSibling(CSTree T, CSTElemType cur_e) {
	CSTree p = Point(T, cur_e);
	if (p && p->nextSibling)
		return p->nextSibling->data;
	else
		return 0;
}

//插入孩子:将c插入到p结点的i个位置
status InsertChild(CSTree& T, CSTree p, int i, CSTree c) {
	int j;
	CSTree q;
	if (T) {
		//插入到长子位置
		if (i == 1) {
			c->nextSibling = p->firstChild;
			p->firstChild = c;
		}
		else {
			int j = 1;
			q = p->firstChild;
			while (q && j < i - 1) {
				q = q->nextSibling;
				j++;
			}
			if (!q || j > i - 1)			//q是插入的点前一个
				return ERROR;		//位置错误
			c->nextSibling = q->nextSibling;
			q->nextSibling = c;

		}
		return OK;
	}
	else {
		return ERROR;
	}
}

//删除p结点的第i棵子树
status DeleteChild(CSTree& T, CSTree p, int i) {
	CSTree q, t;

	if (p) {
		if (i == 1) {
			q = p->firstChild;
			p->firstChild = q->nextSibling;
			q->nextSibling = NULL;
			DestroyTree(q);
		}
		else {
			int j = 1;
			q = p->firstChild;
			while (q && j < i - 1) {
				q = q->nextSibling;
				j++;
			}
			if (!q || j > i - 1)
				return ERROR;
			t = q->nextSibling;		//t是要删除的点
			q->nextSibling = t->nextSibling;
			t->nextSibling = NULL;
			DestroyTree(t);
		}
		return OK;
	}
	else
		return ERROR;
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值