数据结构丨数和二叉树

树和二叉树

树型结构是一类重要的非线性数据结构。其中以树和二叉树最为常用,直观看来,树是以分支关系定义的层次结构。

树是n个结点的有限集,在任意一棵非空树中:有且仅有一个特定的称为根的结点。

结点:树的结点包含一个数据元素及若干指向其子树的分支

结点的度:结点拥有的子树数称为结点的度

叶子/终端结点:度为0的结点称为叶子或终端结点,其他不为0的结点称为非终端结点或分支结点

树的度:数内各结点的度的最大值

孩子:结点的子树的根称为该结点的孩子

双亲:相应的,该结点称为孩子的双亲

层次:结点的层次从根开始定义起,根为第一层,根的孩子为第二层。

兄弟:同一个双亲的孩子互称兄弟

堂兄弟:双亲在同一层的结点互为堂兄弟

深度:树中结点的最大层次称为树的深度

森林:森林是m棵互不相交的树的集合

二叉树

二叉树:是另一种树型结构,它的特点是每个结点至多只有两棵子树(即二叉树中不存在度不大于2的结点),并且二叉树的子树有左右之分,其次序不能任意颠倒。

满二叉树:一棵深度为eq?k且有eq?2%5Ek-1个结点的二叉树称为满二叉树

完全二叉树:深度为eq?k的,有eq?n个结点的二叉树,当且仅有其每一个结点都与深度为eq?k的满二叉树中编号从eq?1eq?n的结点一一对应时,称之为完全二叉树。

二叉树的性质:

性质1 在二叉树的第eq?i层上至多有eq?2%5E%7Bi-1%7D个结点(由数学归纳法可以证明)

性质2 深度为eq?k的二叉树至多有eq?2%5E%7Bk%7D-1个结点(由性质1推出)

性质3 对任何一棵二叉树,如果其终端结点数为eq?n_0,度为2的结点数为eq?n_2,则eq?n_0%3Dn_2+1

证明:设该二叉树一共有n个结点,则有:

eq?%5Cleft%5C%7B%5Cbegin%7Bmatrix%7D%20n%3Dn_0+n_1+n_2%5C%5C%20n-1%3Dn_1+2n_2%20%5Cend%7Bmatrix%7D%5Cright.

性质4 具有eq?n个结点的结点的完全二叉树的深度为eq?%5Cleft%20%5Clfloor%20log_2n%20%5Cright%20%5Crfloor+1

性质5 如果对一棵有n个结点的完全二叉树的结点按层序编号,则对任一结点eq?i%281%5Cleqslant%20i%5Cleqslant%20n%29,有:

①如果eq?i%3D1,则结点eq?i是二叉树的根,无双亲;如果eq?i%3E1,则它的双亲是结点2%20%5Cright%20%5Crfloor

②如果eq?2i%3En,则结点eq?i无左孩子,否则其左孩子是结点eq?2i

③如果eq?2i+1%3En,则结点eq?i无右孩子,否则其右孩子是结点eq?2i+1

(考点)二叉树的遍历

代码详解icon-default.png?t=N7T8https://blog.csdn.net/qq_74315738/article/details/139021345递归算法不考,非递归算法可以用堆栈、队列两种

哈夫曼树及其应用

哈夫曼树又称最优树,是一类带权路径长度最短的树。

构造哈夫曼树的方法:选取两个权值最小的结点作为左右子树合并为一棵二叉树,根节点的值赋值为两棵子树的权值之和,重复操作直到合并为一棵树。

哈夫曼编码:可以约定哈夫曼树的左分支为‘0’,右分支为‘1’,从根结点到叶子结点的路径上分支字符组成的字符串作为该叶子结点字符的编码。


上机作业

1、树与二叉树的转换

程序功能:

①传入parent数组,创建一棵树,并将树打印出来;

②将树转换成孩子兄弟法表示的二叉树,并打印二叉树;

③再将二叉树转换回树并打印。

注:这里的打印指将树和二叉树的每个结点的信息打印出来,二叉树的三种遍历方法有空编辑到知识点中。

完整代码: 

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#define MAX_SIZE 20

//定义树结构
typedef struct TreeNode
{
    int data;
    struct TreeNode* parent;
} TreeNode;
typedef struct Tree
{
    TreeNode tns[MAX_SIZE];
    int r, n;
} Tree;
Tree tree;
Tree tree2;

//定义二叉树结构
typedef struct BiTreeNode
{
    int data;
    struct BiTreeNode* leftChild;
    struct BiTreeNode* rightBrother;
} BiTreeNode;
typedef struct BiTree
{
    BiTreeNode btns[MAX_SIZE];
    int r, n;
} BiTree;
BiTree bitree;

Tree createTree(int parent[]);//用parent数组创建树
void printTree(Tree tree);//打印树
void findChild(BiTreeNode* root, Tree tree);//树转二叉树的辅助函数
BiTree treeToBiTree(Tree tree);//将树转换为二叉树
void printBiTree(BiTree bitree);//打印二叉树
void addChild(TreeNode* parent, BiTreeNode* child, Tree* tree);//二叉树转树的辅助函数
Tree biTreeToTree(BiTree bitree);//将二叉树转换为树

int main()
{
    //1、传入parent数组
    int parent[10] = { -1, 0, 0, 0, 1, 1, 3, 6, 6, 6 };

    //2、创建树并打印
    Tree tree = createTree(parent);
    printf("\n树:\n");
    printTree(tree);

    //3、将树转换为二叉树并打印
    BiTree tree1 = treeToBiTree(tree);
    printf("\n转换为二叉树:\n");
    printBiTree(tree1);

    //4、将二叉树转换回树并打印
    Tree tree2 = biTreeToTree(tree1);
    printf("\n转换回树:\n");
    printTree(tree2);

    return 0;
}

Tree createTree(int parent[])
{
    tree.r = 0;
    tree.n = 0;
    while (tree.n < 10)
    {
        tree.tns[tree.n].data = tree.n + 1;
        tree.tns[tree.n].parent = NULL;

        if (parent[tree.n] != -1)
        {
            tree.tns[tree.n].parent = &tree.tns[parent[tree.n]];
        }
        tree.n++;
    }
    return tree;
}

void printTree(Tree tree)
{
    for (int i = 0; i < tree.n; i++)
    {
        if (tree.tns[i].parent != NULL)
        {
            printf("%d ->parent-> %d\n", tree.tns[i].data, tree.tns[i].parent->data);
        }
        else
        {
            printf("%d ->parent-> NULL\n", tree.tns[i].data);
        }
    }
}

void findChild(BiTreeNode* root, Tree tree)
{
    BiTreeNode* currentChild = NULL;
    for (int i = 0; i < tree.n; i++)
    {
        if (tree.tns[i].parent && tree.tns[i].parent->data == root->data)
        {
            BiTreeNode* newChild = &bitree.btns[i];
            if (currentChild == NULL)
            {
                root->leftChild = newChild;
            }
            else
            {
                currentChild->rightBrother = newChild;
            }
            currentChild = newChild;
            findChild(currentChild, tree);
        }
    }
}

BiTree treeToBiTree(Tree tree)
{
    bitree.r = 0;
    bitree.n = tree.n;
    for (int i = 0; i < bitree.n; i++)
    {
        bitree.btns[i].data = tree.tns[i].data;
        bitree.btns[i].leftChild = NULL;
        bitree.btns[i].rightBrother = NULL;
    }
    BiTreeNode* root = &bitree.btns[0];
    findChild(root, tree);
    return bitree;
}

void printBiTree(BiTree bitree)
{
    for (int i = 0; i < bitree.n; i++)
    {
        printf("%2d:  ", bitree.btns[i].data);
        if (bitree.btns[i].leftChild != NULL)
        {
            printf("leftChild->%4d ", bitree.btns[i].leftChild->data);
        }
        else
        {
            printf("leftChild->NULL ");
        }
        if (bitree.btns[i].rightBrother != NULL)
        {
            printf("rightBrother->%4d ", bitree.btns[i].rightBrother->data);
        }
        else
        {
            printf("rightBrother->NULL ");
        }
        printf("\n");
    }
}

void addChild(TreeNode* parent, BiTreeNode* child, Tree* tree2)
{
    tree2->tns[tree2->n].data = child->data;
    tree2->tns[tree2->n].parent = parent;
    tree2->n++;

    if (child->leftChild != NULL)
    {
        addChild(&tree2->tns[tree2->n - 1], child->leftChild, tree2);
    }

    if (child->rightBrother != NULL)
    {
        addChild(parent, child->rightBrother, tree2);
    }
}

Tree biTreeToTree(BiTree bitree)
{
    tree2.r = 0;
    tree2.n = 0;
    addChild(NULL, &bitree.btns[0], &tree2);
    return tree;
}

运行结果如下: 


树:
1 ->parent-> NULL
2 ->parent-> 1
3 ->parent-> 1
4 ->parent-> 1
5 ->parent-> 2
6 ->parent-> 2
7 ->parent-> 4
8 ->parent-> 7
9 ->parent-> 7
10 ->parent-> 7

转换为二叉树:
 1:  leftChild->   2 rightBrother->NULL
 2:  leftChild->   5 rightBrother->   3
 3:  leftChild->NULL rightBrother->   4
 4:  leftChild->   7 rightBrother->NULL
 5:  leftChild->NULL rightBrother->   6
 6:  leftChild->NULL rightBrother->NULL
 7:  leftChild->   8 rightBrother->NULL
 8:  leftChild->NULL rightBrother->   9
 9:  leftChild->NULL rightBrother->  10
10:  leftChild->NULL rightBrother->NULL

转换回树:
1 ->parent-> NULL
2 ->parent-> 1
3 ->parent-> 1
4 ->parent-> 1
5 ->parent-> 2
6 ->parent-> 2
7 ->parent-> 4
8 ->parent-> 7
9 ->parent-> 7
10 ->parent-> 7

D:\Cfiles\DSweek8\Debug\DSweek8.exe (进程 24172)已退出,代码为 0。
按任意键关闭此窗口. . .

逐段解析: 

树与二叉树的转换丨代码详解icon-default.png?t=N7T8https://blog.csdn.net/qq_74315738/article/details/138244642

2、给一棵树,用后序遍历二叉树的操作打印后缀表达式(逆波兰式),基于栈求解表达式的值 

todo 

3、已知先序、中序遍历表达式,确定后序遍历表达 

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#define MAX_SIZE 20

//定义二叉树结构
typedef struct treeNode
{
	char ch;
	struct treeNode* lChild, * rChild;
}treeNode;

//根据前序中序遍历结果创建二叉树
treeNode* createTree(char* pre, char* in, int start, int end);
//打印后序遍历二叉树结果
void printTree(treeNode* root);

int main()
{
	char pre[] = { 'F', 'D', 'X','E', 'A', 'G' };
	char in[] = { 'X', 'D', 'E', 'F', 'A', 'G' };
	treeNode* root = createTree(pre, in, 0, 5);
	printTree(root);
	return 0;
}

treeNode* createTree(char* pre, char* in, int start, int end)
{
	static int preIndex = 0;
	if (start > end)
	{
		return NULL;
	}
	treeNode* root = (treeNode*)malloc(sizeof(treeNode));
	root->ch = pre[preIndex++];
	root->lChild = root->rChild = NULL;
	if (start == end)
	{
		return root;
	}
	int inIndex;
	for (int i = start; i <= end; i++)
	{
		if (in[i] == root->ch)
		{
			inIndex = i;
			break;
		}
	}
	root->lChild = createTree(pre, in, start, inIndex - 1);
	root->rChild = createTree(pre, in, inIndex + 1, end);
	return root;
}

void printTree(treeNode* root)
{
	if (root == NULL)
	{
		return;
	}
	printTree(root->lChild);
	printTree(root->rChild);
	printf("%c ", root->ch);
}
运行结果:

X E D G A F
D:\Cfiles\DSweek8\Debug\DSweek8.exe (进程 14504)已退出,代码为 0。
按任意键关闭此窗口. . .

createTree函数功能解析:

  • 传入前序遍历数组pre和中序遍历数组in,以及数组的起始位置start和终止位置end。在函数内部,使用静态变量preIndex=0来记录当前处理的前序遍历数组的位置。
  • 首先,创建一个新的树节点root,并将pre[preIndex]赋值给root->ch,表示当前树节点的值。然后递增preIndex,指向下一个前序遍历的值。
  • 接着,在中序遍历数组中找到根节点的位置inIndex,根据这个位置,我们可以确定左子树和右子树在中序遍历数组中的范围。
  • 然后,递归调用createTree函数来构建左子树和右子树。对于左子树,我们传入start和inIndex-1来表示左子树在中序遍历数组中的范围;对于右子树,传入inIndex+1和end来表示右子树在中序遍历数组中的范围。
  • 最后,返回根节点root,则根据前序遍历和中序遍历数组构建了一棵二叉树。

4、给定字符和权重文本,生成哈夫曼树,再打印哈夫曼编码 

05bc9d142ffa4e8085533560eab60566.jpeg左图为文本文件的具体内容,第一行是字符个数,之后每行是字符以及对应的权重。


任务分解:
(1)读取文件,创建哈夫曼树;在编写这个功能的函数时,还需要一个用于寻找当前权重最小的结点的函数作为辅助。
(2)打印哈夫曼编码。

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define MIN 65536

//定义哈夫曼树结构
typedef struct hTreeNode
{
	char c;
	int weight;
	int parent, lChild, rChild;
}hTreeNode;

int findMin(hTreeNode* hfTree, int n);//创建哈夫曼树用于找权重最小结点的辅助函数
hTreeNode* createHfTree(char* filename, int* n);//创建哈夫曼树
void printHfCode(hTreeNode* hfTree, int i);//打印哈夫曼编码

int main()
{
	//1、创建哈夫曼树
	char* filename = "D:\\Cfiles\\DSweek8\\source.txt";
	int n = 0;
	hTreeNode* hfTree = createHfTree(filename, &n);

	//2、打印字符和对应的哈夫曼编码
	for (int i = 0; i < n; i++)
	{
		printf("%c 出现频率:%2d ", hfTree[i].c, hfTree[i].weight);
		printf("哈夫曼编码为:");
		printHfCode(hfTree, i);
		printf("\n");
	}
	//3、释放内存空间
	free(hfTree);

	return 0;
}

int findMin(hTreeNode* hfTree, int n)
{
	int target = -1;
	unsigned int fMin = MIN;
	for (int i = 0; i < n; i++)
	{
		if (hfTree[i].weight < fMin && hfTree[i].parent == -1)
		{
			fMin = hfTree[i].weight;
			target = i;
		}
	}
	hfTree[target].parent = 0;
	return target;
}

hTreeNode* createHfTree(char* filename, int* n)
{
	FILE* fp = fopen(filename, "r");
	if (fp == NULL)
	{
		printf("文件打开错误");
		return NULL;
	}
	fscanf(fp, "%d\n", &(*n));

	hTreeNode* hfTree = (hTreeNode*)malloc((2 * (*n) - 1) * sizeof(hTreeNode));
	memset(hfTree, 0, (2 * (*n) - 1) * sizeof(hTreeNode));

	for (int i = 0; i < (*n); i++)
	{
		char ch;
		int weight;
		fscanf(fp, " %c %d\n", &ch, &weight); 

		hfTree[i].c = ch;
		hfTree[i].weight = weight;
		hfTree[i].parent = hfTree[i].lChild = hfTree[i].rChild = -1;
	}
	fclose(fp);

	for (int i = *n; i < (2 * (*n) - 1); i++)
	{
		int m1 = findMin(hfTree, i);
		int m2 = findMin(hfTree, i);

		hfTree[i].parent = -1;
		hfTree[i].lChild = m1;
		hfTree[i].rChild = m2;
		hfTree[i].weight = hfTree[m1].weight + hfTree[m2].weight;
		hfTree[i].c = ' ';

		hfTree[m1].parent = hfTree[m2].parent = i;
	}

	return hfTree;
}

void printHfCode(hTreeNode* hfTree, int i)
{
	if (hfTree[i].parent == -1)
	{
		return;
	}
	int parent = hfTree[i].parent;
	printHfCode(hfTree, parent);
	int child = -1;
	if (hfTree[parent].lChild == i)
	{
		child = 0;
	}
	else if (hfTree[parent].rChild == i)
	{
		child = 1;
	}
	printf("%d", child);
}
运行结果:

a 出现频率: 5 哈夫曼编码为:1111
b 出现频率: 3 哈夫曼编码为:0110
c 出现频率: 4 哈夫曼编码为:1110
d 出现频率: 1 哈夫曼编码为:01110
e 出现频率:11 哈夫曼编码为:00
f 出现频率: 2 哈夫曼编码为:01111
g 出现频率: 9 哈夫曼编码为:110
h 出现频率: 6 哈夫曼编码为:010
i 出现频率: 7 哈夫曼编码为:100
j 出现频率: 8 哈夫曼编码为:101

D:\Cfiles\DSweek8\Debug\DSweek8.exe (进程 6608)已退出,代码为 0。
按任意键关闭此窗口. . .
5、根据文本计算每个字符的权重,生成哈夫曼并计算每个字符的哈夫曼编码,对文本文件进行压缩,最后再根据压缩的哈夫曼编码还原出原来的文本文件。

todo 

//五一假期高铁上写。。。

6、设计一个函数,计算给定二叉树的深度

todo 

7、设计二叉树,查找任意父亲结点所有儿子的算法。 

看不懂。。。 

总结

用malloc开辟内存之后用memset清零,保证内存空间是干净的。虽然我还是忘了memset和free

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值