数据结构——初学者学习笔记 【五】树和二叉树


本章是对树的学习,树结构各结点的之间的关系,和树结构的性质是我学习的重点,理解了树结构的性质即他能干什么,才能更好的运用起来。

一、树的介绍

​ 树是一种非线性的数据结构,是以分支关系定义的层次结构,比如人类社会中的族谱、及各种机制、组织的关系都可以用树形象的表示。重点学习二叉树的存储和相关操作,还要讨论树、森林、二叉树的转换关系。

二、树的定义和基本术语

:是n个结点的有限集,当0==n时称为空树,我们不讨论空树。

根结点:树的最顶层的结点,一棵树有且仅有一个。

子树:一棵树除根结点外,剩余的是若干个互不相交的有限集,每一个集合本身又是棵树,称称为根的子树。

结点的度:树的结点包含一个数据元素及若干个指向其子树的分支,结点拥有的子树称为结点的度。

叶子结点:结构的度为0,被称为叶子结点或终端结点。

分支结点:结构的度不为0,被称为分支结点或非终端结点,也被称为内部结点。

树的度:是指树内各结点度的最大值。

密度:指的是一棵树中,所有结点的总数。

孩子、双亲、兄弟、祖先、子孙:结点的子树称为该结点的孩子,而该结点是孩子结点的双亲,拥有共同双亲的结点互为兄弟,从双亲结点往上,直到根结点都称为孩子结点的祖先结点,以某结点为根的子树中的任一结点都被称为该结点的子孙。

层数、深度、高度:从根结点开始定义,根为第一层、根的孩子为第二层依次类推,树中结点的最大层数被称为树的深度或高度,双亲在同一层的结点互为堂兄弟。

有序树和无序树:将树中结点的各子树看成从左到右是有序次,即不能交换(顺序有意义,表达一些含义),则称该树为有序树,否则称为无序树。

森林:若干个棵互不相交的树的集合称为森林,对树中每个结点而言,其子树集合就是森林。

就逻辑结构而言,任何一棵树都是一个二元组 Tree = (root,F),其中root是数据元素,称做树的根结点,F是若干棵子树构成的森林。

三、二叉树的定义和性质

二叉树:

​ 是一种特殊的树型结构,也就是每个结点最多有两棵子树(二叉树中不存在度大于2的结点),并且二叉树的子树有左右之分,顺序不能颠倒。

满二叉树:

​ 若一棵树的层数为k,它总结点数是2^k-1,则这棵树就是满二叉树。

完全二叉树:

​ 若一棵树的层数为k,它前k-1层的总结点数是2^(k-1)-1,第k层的结点按照从左往右的顺序排列,则这棵树就是完全二叉树。

二叉树的性质:
性质1:

​ 在二叉树的第i层上,最多有2^(i-1)个结点。

性质2:

​ 深夜为k的二叉树,最多有2^k-1个节点。

性质3:

​ 对于任何一棵二叉树,如果叶子结点的数量为n0,度为2结点的数量为n2,则n0=n2+1;

性质4:

​ 具有n个结点的完全二叉树的高度为(log2n)+1。

性质5:

​ 有一个n个结点的完全二叉树,结点按照从上到下从左到右的顺序排序为1~n。

​ 1、i > 1时,i/2就是它的双亲结点。

​ 2、i*2是i的左子树,当i*2>n时,则i没有左子树。

​ 3、2*i+1是i的右子树,2*i+1>n时,则i没有右子树。

三、二叉树的遍历

前序遍历

​ 1、判断二叉树是否为空,若二叉树为空,则不操作。

​ 2、访问根结点

​ 3、前序遍历左子树

​ 4、前序遍历右子树

中序遍历

​ 1、判断二叉树是否为空,若二叉树为空,则不操作。

​ 2、中序遍历左子树

​ 3、访问根结点

​ 4、中序遍历右子树

后序遍历

​ 1、判断二叉树是否为空,若二叉树为空,则不操作。

​ 2、后序遍历左子树

​ 3、后序遍历右子树

​ 4、访问根结点

层序遍历

​ 按照从上到下、从左到右的顺序遍历二叉树,需要与队列结构配合,普通的顺序队列即可,不需要链式队列或循环队列。

四、二叉树的顺序表示与实现

​ 前提:由于顺序存储需要根据元素的相对位置确定关系,所有先把二叉树补成完全二叉树,之前空的点可以使用特殊的值表示,并把完全二叉树的层序遍历结果存储到数组中,只有补成了完全二叉树,才能根据性质5的公式对二叉树进行相关操作。

​ 注意:性质5的公式是按照结点的序号设计的,在使用时要注意数组下标与序号的转换。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <math.h>

#define PARENT_TREE(i) 	((i + 1) / 2 - 1)
#define LEFT_TREE(i) 	((i + 1) * 2 - 1)
#define RIGHT_TREE(i) 	((i + 1) * 2)

typedef struct TreeArray
{
	char *arr;
	size_t cnt;
} TreeArray;

// 创建树
TreeArray *create_tree(const char *str)
{
	TreeArray *tree = malloc(sizeof(TreeArray));
	tree->cnt = strlen(str);
	tree->arr = malloc(tree->cnt);
	strncpy(tree->arr, str, tree->cnt);
	return tree;
}

// 初始化树
void init_tree(TreeArray *tree, const char *str)
{
	free(tree->arr);
	tree->cnt = strlen(str);
	tree->arr = malloc(tree->cnt);
	strncpy(tree->arr, str, tree->cnt);
}

// 销毁树
void destroy_tree(TreeArray *tree)
{
	free(tree->arr);
	free(tree);
}

// 清空树
void clear_tree(TreeArray *tree)
{
	free(tree->arr);
	tree->cnt = 0;
}

// 判断是否是空树
bool empty_tree(TreeArray *tree)
{
	return 0 == tree->cnt || '#' == tree->arr[0];
}

// 计算树的深度
size_t hight_tree(TreeArray *tree)
{
	size_t hight = 1;
	while (pow(2, hight) < tree->cnt)
		hight++;
	return hight;
}

// 访问根结点
char root_tree(TreeArray *tree)
{
	if (empty_tree(tree))
		return '#';

	return tree->arr[0];
}

// 访问指定的结点
char value_tree(TreeArray *tree, size_t index)
{
	if (index >= tree->cnt)
		return '#';

	return tree->arr[index];
}

// 给指定的位置赋值
bool assign_tree(TreeArray *tree, size_t index, char value)
{
	if (index >= tree->cnt)
		return false;

	tree->arr[index] = value;
	return true;
}

// 访问双亲结点
char parent_tree(TreeArray *tree, size_t index)
{
	if (0 == index || PARENT_TREE(index) >= tree->cnt)
		return '#';

	return tree->arr[PARENT_TREE(index)];
}

// 访问左子树结点
char left_child_tree(TreeArray *tree, size_t index)
{
	if (LEFT_TREE(index) >= tree->cnt)
		return '#';

	return tree->arr[LEFT_TREE(index)];
}

// 访问右子树结点
char right_child_tree(TreeArray *tree, size_t index)
{
	if (RIGHT_TREE(index) >= tree->cnt)
		return '#';
	return tree->arr[RIGHT_TREE(index)];
}

// 访问左兄弟结点
char left_sigling_tree(TreeArray *tree, size_t index)
{
	int left = LEFT_TREE(PARENT_TREE(index));
	if (0 == index || left >= tree->cnt || left == index)
		return '#';

	return tree->arr[left];
}

// 访问右兄弟结点
char right_sigling_tree(TreeArray *tree, size_t index)
{
	int right = RIGHT_TREE(PARENT_TREE(index));
	if (0 == index || right >= tree->cnt || right == index)
		return '#';

	return tree->arr[right];
}

// 插入子树 lr(left true,right false)
bool insert_tree(TreeArray *tree, size_t index, bool lr, char value)
{
	size_t pos = lr ? LEFT_TREE(index) : RIGHT_TREE(index);
	if (pos >= tree->cnt || '#' != tree->arr[pos])
		return false;

	tree->arr[pos] = value;
	return true;
}

bool delete_tree(TreeArray *tree, size_t index, bool lr)
{
	// 计算出要删除的子树的下标
	size_t pos = lr ? LEFT_TREE(index) : RIGHT_TREE(index);
	// 判断要删除的子树下标是否合法
	if (pos >= tree->cnt)
		return false;

	// 计算出要删除结点的左右子树下标
	size_t pos_left = LEFT_TREE(pos), pos_right = RIGHT_TREE(pos);
	// 判断删除的结点是否有左子树,如果有则删除失败
	if (pos_left < tree->cnt && '#' != tree->arr[pos_left])
		return false;
	// 判断删除的结点是否有右子树,如果有则删除失败
	if (pos_right < tree->cnt && '#' != tree->arr[pos_right])
		return false;

	tree->arr[pos] = '#';
	return true;
}

// 前序遍历二叉树
void _pre_order_tree(TreeArray *tree, int index)
{
	// 根
	if (index >= tree->cnt || '#' == tree->arr[index])
		return;
	printf("%c ", tree->arr[index]);
	// 左
	_pre_order_tree(tree, LEFT_TREE(index));
	// 右
	_pre_order_tree(tree, RIGHT_TREE(index));
}

// 前序遍历
void pre_order_tree(TreeArray *tree)
{
	_pre_order_tree(tree, 0);
	printf("\n");
}

void _in_order_tree(TreeArray *tree, int index)
{
	if (index >= tree->cnt || '#' == tree->arr[index])
		return;
	// 左
	_in_order_tree(tree, LEFT_TREE(index));
	// 根
	printf("%c ", tree->arr[index]);
	// 右
	_in_order_tree(tree, RIGHT_TREE(index));
}

// 中序遍历
void in_order_tree(TreeArray *tree)
{
	_in_order_tree(tree, 0);
	printf("\n");
}

void _post_order_tree(TreeArray *tree, int index)
{
	if (index >= tree->cnt || '#' == tree->arr[index])
		return;
	// 左
	_post_order_tree(tree, LEFT_TREE(index));
	// 右
	_post_order_tree(tree, RIGHT_TREE(index));
	// 根
	printf("%c ", tree->arr[index]);
}

// 后序遍历
void post_order_tree(TreeArray *tree)
{
	_post_order_tree(tree, 0);
	printf("\n");
}

// 层序遍历
void level_order_tree(TreeArray *tree)
{
	for (int i = 0; i < tree->cnt; i++)
	{
		if ('#' != tree->arr[i])
			printf("%c ", tree->arr[i]);
	}
	printf("\n");
}

int main(int argc, const char *argv[])
{
	TreeArray *tree = create_tree("FCEADHG##B###M");
	level_order_tree(tree);
	printf("---------------------\n");
	printf("%d\n", hight_tree(tree));
	printf("%c\n", right_sigling_tree(tree, 4));
	insert_tree(tree, 0, true, 'X');
	printf("%d\n", delete_tree(tree, 1, false));
	return 0;
}

五、二叉树的链式表示与实现

有两种创建链式二叉树的方式:

方式一:需要给每个度不为2的结点补充一些空白子结点,使用树中除了空白结点其它结点的度全为2,然后以前序的方式遍历二叉树,然后以遍历的结果创建二叉树。

方式二:不需要对二叉做任何改变,以前序+中序或中序+后序的遍历结果创建二叉树。

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

typedef struct TreeNode
{
	char data;
	struct TreeNode *left;
	struct TreeNode *right;
} TreeNode;

TreeNode *create_node(char data)
{
	TreeNode *node = malloc(sizeof(TreeNode));
	node->data = data;
	node->left = NULL;
	node->right = NULL;
	return node;
}

// 方式1
TreeNode *create_tree(const char **str)
{
	if ('#' == **str)
		return NULL;
	TreeNode *root = create_node(**str);
	*str += 1;
	root->left = create_tree(str);
	*str += 1;
	root->right = create_tree(str);
	return root;
}

// 方式二
TreeNode *pre_in_create_tree(char *pre, char *in, int len)
{
	if (len <= 0)
		return NULL;

	TreeNode *root = create_node(*pre);

	int pos = 0;
	while (*pre != in[pos])
		pos++;

	root->left = pre_in_create_tree(pre + 1, in, pos);
	root->right = pre_in_create_tree(pre + 1 + pos, in + 1 + pos, len - 1 - pos);
	return root;
}

// 方式二
TreeNode *post_in_create_tree(char *post, char *in, int len)
{
	if (len <= 0)
		return NULL;

	TreeNode *root = create_node(post[len - 1]);

	int pos = 0;
	while (post[len - 1] != in[pos])
		pos++;

	root->left = post_in_create_tree(post, in, pos);
	root->right = post_in_create_tree(post + pos, in + pos + 1, len - 1 - pos);
}

size_t height_tree(TreeNode *root)
{
	if (NULL == root)
		return 0;

	size_t lh = height_tree(root->left);
	size_t rh = height_tree(root->right);

	return lh > rh ? lh + 1 : rh + 1;
}

size_t density_tree(TreeNode *root)
{
	if (NULL == root)
		return 0;

	return 1 + density_tree(root->left) + density_tree(root->right);
}

void pre_order_tree(TreeNode *root)
{
	if (NULL == root)
		return;
	printf("%c ", root->data);
	pre_order_tree(root->left);
	pre_order_tree(root->right);
}

void in_order_tree(TreeNode *root)
{
	if (NULL == root)
		return;

	in_order_tree(root->left);
	printf("%c ", root->data);
	in_order_tree(root->right);
}

void post_order_tree(TreeNode *root)
{
	if (NULL == root)
		return;

	post_order_tree(root->left);
	post_order_tree(root->right);
	printf("%c ", root->data);
}

void level_tree(TreeNode *root)
{
	size_t density = density_tree(root), front = 0, rear = 0;
	TreeNode *queue[density];
	queue[rear++] = root;

	while (front != rear)
	{
		TreeNode *node = queue[front++];
		printf("%c ", node->data);
		if (NULL != node->left)
			queue[rear++] = node->left;
		if (NULL != node->right)
			queue[rear++] = node->right;
	}
}

int main(int argc, const char *argv[])
{
	const char *pre_str = "FCA##DB###EH##GM###";
	TreeNode *pre_root = create_tree(&pre_str);
	pre_order_tree(pre_root);
	printf("\n");

	/*
	char post[] = "ADCHGEF";
	char in[] = "ACDFHEG";
	TreeNode* root = post_in_create_tree(post,in,7);
	pre_order_tree(root);
	printf("%d\n",height_tree(root));
	printf("%d\n",density_tree(root));
	*/
	level_tree(pre_root);

	return 0;
}

六、二叉搜索树的表示与实现

二叉搜索树介绍:

​ 二叉搜索树又叫作有序二叉树、二叉查找树,具有以下特点:若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉搜索树。

​ 二叉搜索树作为一种经典的数据结构,它既有链表的快速插入与删除操作的特点,又有数组快速查找的优势(O(log2n);所以应用十分广泛,例如在文件系统和数据库系统一般会采用这种数据结构进行高效率的排序与检索操作。

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

typedef struct TreeNode
{
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	size_t height;	// 结点的高度
	size_t density; // 结点的密度
} TreeNode;

// 创建结点
TreeNode *create_node(int val)
{
	TreeNode *root = malloc(sizeof(TreeNode));
	root->val = val;
	root->left = NULL;
	root->right = NULL;
	root->height = 1;
	root->density = 1;
	return root;
}

// 自动调整树的高度和密度
void auto_tree(TreeNode *root)
{
	if (NULL == root)
		return;

	int lh = 0, rh = 0, ld = 0, rd = 0;
	if (NULL != root->left)
	{
		// 当左子树不为空时,调整左子树的高度和密度
		auto_tree(root->left);
		// 重新获取左子树的高度和密度
		lh = root->left->height;
		ld = root->left->density;
	}

	if (NULL != root->right)
	{

		// 当右子树不为空时,调整右子树的高度和密度
		auto_tree(root->right);
		// 重新获取右子树的高度和密度
		rh = root->right->height;
		rd = root->right->density;
	}

	// 更新当前结点的高度和密度
	root->height = lh > rh ? lh + 1 : rh + 1;
	root->density = ld + rd + 1;
}

void _add_tree(TreeNode **root, TreeNode *node)
{
	if (NULL == *root)
	{
		*root = node;
		return;
	}

	if ((*root)->val > node->val)
		_add_tree(&(*root)->left, node);
	else
		_add_tree(&(*root)->right, node);
}

void add_tree(TreeNode **root, int val)
{
	// root是main函数中root指针的地址
	_add_tree(root, create_node(val));
	auto_tree(*root);
}

// 之所这样设计是为了方便删除并兼顾查询,查询到的是指向值为val结点的指针的地址
TreeNode **_query_tree(TreeNode **root, int val)
{
	if (NULL == *root)
		return root;

	if (val == (*root)->val)
		return root;
	if (val <= (*root)->val)
		return _query_tree(&(*root)->left, val);
	else
		return _query_tree(&(*root)->right, val);
}

bool query_tree(TreeNode *root, int val)
{
	TreeNode **node = _query_tree(&root, val);
	return NULL != *node;
}

void _del_tree(TreeNode **root)
{
	TreeNode *temp = *root;
	if (NULL == temp->left)
		*root = temp->right;
	else if (NULL == temp->right)
		*root = temp->left;
	else
	{
		*root = temp->left;
		_add_tree(root, temp->right);
	}
	free(temp);
}

bool del_tree(TreeNode **root, int val)
{
	TreeNode **node = _query_tree(root, val);
	if (NULL == *node)
		return false;

	_del_tree(node);
	auto_tree(*root);
	return true;
}

bool modify_tree(TreeNode **root, int old, int val)
{
	TreeNode **node = _query_tree(root, old);
	if (NULL == *node)
		return false;

	_del_tree(node);
	_add_tree(root, create_node(val));
	auto_tree(*root);
	return true;
}

void _in_order_tree(TreeNode *root)
{
	if (NULL == root)
		return;

	_in_order_tree(root->left);
	printf("%d ", root->val);
	_in_order_tree(root->right);
}

void in_order_tree(TreeNode *root)
{
	_in_order_tree(root);
	printf("\n");
}

void level_order_tree(TreeNode *root)
{
	if (NULL == root)
		return;

	TreeNode *queue[root->density];
	int front = 0, rear = 0;
	queue[rear++] = root;

	while (front != rear)
	{
		int cnt = rear - front;
		while (cnt--)
		{
			TreeNode *node = queue[front++];
			printf("[v:%d h:%d d:%d] ", node->val, node->height, node->density);
			if (NULL != node->left)
				queue[rear++] = node->left;
			if (NULL != node->right)
				queue[rear++] = node->right;
		}
		printf("\n");
	}
}

// 访问第k个小的数,按大小值的顺序访问
void _assign_tree(TreeNode *root, int k, int *pos, int *ptr)
{
	if (NULL == root)
		return;

	_assign_tree(root->left, k, pos, ptr);
	if (++*pos == k)
		*ptr = root->val;
	_assign_tree(root->right, k, pos, ptr);
}

int assign_tree(TreeNode *root, int k)
{
	if (k < 1)
		return -1;

	int pos = 0, val = -1;
	_assign_tree(root, k, &pos, &val);
	return val;
}

int main(int argc, const char *argv[])
{
	TreeNode *root = NULL;
	for (int i = 0; i < 10; i++)
	{
		add_tree(&root, rand() % 100);
	}
	in_order_tree(root);
	modify_tree(&root, 77, 99);
	in_order_tree(root);
	level_order_tree(root);

	return 0;
}

二叉搜索树的缺点:

​ 二叉搜索树的元素添加顺序会影响二叉搜索树的形状,二叉搜索树的形状会影响它的操作效率,在极端情况下,二叉搜索树可能会呈单枝状分布,使用速度接近单向链表,这种极端情况出现在的原因就添加的元素基本有序。

七、平衡二叉树

平衡二叉树介绍:

​ 平衡二叉树的全称叫平衡二叉搜索树,它首是一棵二叉搜索树,且具有以下性质:它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

​ 对于一般的二叉搜索树,它的理想高度是为log2n,其各操作的时间复杂度(O(log2n))。但是,在某些极端的情况下(如:在插入的序列是有序的时),二叉搜索树将退化成近似单向链表,此时它的操作时间复杂度将退化成线性的,即O(n)。

​ 我们可以通过随机化建立二叉搜索树来尽量的避免这种情况,但是在进行了多次的操作之后,由于在删除时,我们总是选择将待删除节点的后继代替它本身,这样就会造成总是右边的节点数目减少,以至于树向左偏沉。这同时也会造成树的平衡性受到破坏,提高它的操作的时间复杂度,而平衡二叉搜索树就可以弥补这个缺点。

如何把不平衡的二叉树调整为平衡二叉树:

​ 我在检查二叉树是否平衡时,肯定是从叶子结点往根结点遍历,因此我们遇到的不平衡的二叉树有以下四种情况,分别是:

//1 解决方法:以B轴向右旋转
      A						   B
     / \                     /   \
    B   t1                  C     A
   / \                     / \   /  \
  C   t2                  t4 t3 t2  t1
 / \
t4   t3


//2 解决方法:先以C轴向左旋转,再以C为轴向右旋转
    A                A                 C
   / \              / \              /   \
  B   t1           C   t1          B      A  
 / \              / \             /  \   /  \
t2  C            B   t3          t2  t4 t3   t1
   / \          / \
  t4   t3      t2  t4
 
//3 解决方法:以B轴向左旋转
  A				 B		
 / \           /   \
t1  B         A     C
   / \       / \   / \
  t2  C     t1 t2 t3  t4
     / \
	t3   t4
	
//4 解决方法:以C轴向右旋转,再以C轴向左旋转
  A           A              C
 / \         / \           /   \
t1  B       t1  C         A     B
   / \         / \       / \   / \
  C   t4      t2  B     t1 t2 t3  t4
 / \              / \
t2   t3          T3  T4
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

#define TreeHight(node) (NULL == (node) ? 0 : (node)->height)

typedef struct TreeNode
{
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	size_t height;
} TreeNode;

// 创建结点
TreeNode *create_node(int val)
{
	TreeNode *root = malloc(sizeof(TreeNode));
	root->val = val;
	root->left = NULL;
	root->right = NULL;
	root->height = 1;
	return root;
}

// 调整树的高度
void auto_height_tree(TreeNode *root)
{
	if (NULL == root)
		return;

	auto_height_tree(root->left);
	auto_height_tree(root->right);
	int lh = TreeHight(root->left);
	int rh = TreeHight(root->right);
	root->height = lh > rh ? lh + 1 : rh + 1;
}

void right_rotate_tree(TreeNode **rpp)
{
	TreeNode *A = *rpp;
	TreeNode *B = A->left;
	TreeNode *t = B->right;

	*rpp = B;
	B->right = A;
	A->left = t;
    auto_height_tree(*rpp);
}

void left_rotate_tree(TreeNode **rpp)
{
	TreeNode *A = *rpp;
	TreeNode *B = A->right;
	TreeNode *t = B->left;

	*rpp = B;
	B->left = A;
	A->right = t;
    auto_height_tree(*rpp);
}

void auto_balance_tree(TreeNode **rpp)
{
	if (NULL == *rpp)
		return;

	auto_balance_tree(&(*rpp)->left);
	auto_balance_tree(&(*rpp)->right);

	int lh = TreeHight((*rpp)->left);
	int rh = TreeHight((*rpp)->right);

	if (lh - rh > 1)
	{
		if (TreeHight((*rpp)->left->left) >= TreeHight((*rpp)->left->right))
			right_rotate_tree(rpp);
		else
		{
			left_rotate_tree(&(*rpp)->left);
			right_rotate_tree(rpp);
		}
		auto_balance_tree(rpp);
	}
	else if (lh - rh < -1)
	{
		if (TreeHight((*rpp)->right->right) >= TreeHight((*rpp)->right->left))
			left_rotate_tree(rpp);
		else
		{
			right_rotate_tree(&(*rpp)->right);
			left_rotate_tree(rpp);
		}
		auto_balance_tree(rpp);
	}
}

bool is_balance_tree(TreeNode *root)
{
	if (NULL == root)
		return true;

	bool flag1 = is_balance_tree(root->left);
	bool flag2 = is_balance_tree(root->right);

	return flag1 && flag2 && 1 >= abs(TreeHight(root->left) - TreeHight(root->right));
}

void _in_order_tree(TreeNode *root)
{
	if (NULL == root)
		return;

	_in_order_tree(root->left);
	printf("%d ", root->val);
	_in_order_tree(root->right);
}

void in_order_tree(TreeNode *root)
{
	_in_order_tree(root);
	printf("\n");
}

void level_order_tree(TreeNode *root)
{
	if (NULL == root)
		return;

	TreeNode *queue[100];
	int front = 0, rear = 0;
	queue[rear++] = root;

	while (front != rear)
	{
		int cnt = rear - front;
		while (cnt--)
		{
			TreeNode *node = queue[front++];
			printf("[v:%d h:%d] ", node->val, node->height);
			if (NULL != node->left)
				queue[rear++] = node->left;
			if (NULL != node->right)
				queue[rear++] = node->right;
		}
		printf("\n");
	}
}

// 添加结点
void _add_tree(TreeNode **rpp, TreeNode *node)
{
	if (NULL == *rpp)
	{
		*rpp = node;
		return;
	}

	if (node->val < (*rpp)->val)
		_add_tree(&(*rpp)->left, node);
	else
		_add_tree(&(*rpp)->right, node);
}

// 添加元素
void add_tree(TreeNode **rpp, int val)
{
	// 创建结点并添加
	_add_tree(rpp, create_node(val));
	// 调整树的高度
	auto_height_tree(*rpp);
	// 让树自动平衡
	auto_balance_tree(rpp);
}

// 初始化二权树
TreeNode *init_tree(int *arr, int size)
{
	TreeNode *root = NULL;
	// 循环把所有的元素创建结点并添加到树中
	for (int i = 0; i < size; i++)
	{
		_add_tree(&root, create_node(arr[i]));
	}

	// 调整树的高度
	auto_height_tree(root);
	// 让树自动平衡
	auto_balance_tree(&root);
	return root;
}

int main(int argc, const char *argv[])
{
	int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
	TreeNode *root = NULL;
	for (int i = 0; i < 10; i++)
	{
		add_tree(&root, arr[i]);
	}
	printf("%d\n", is_balance_tree(root));
	return 0;
}
平衡二叉树的优点:

​ 避免二叉搜索树的单支状分布的情况,能让二叉搜索树以最佳的状态进行操作,时间复杂接近O(log2n)。

平衡二叉树的缺点:

​ 平衡二叉树在创建、添加、删除时,为了让二叉树保持平衡需要进行大量的左旋、右旋、高度计算,所以平衡二叉树在创建时,添加、删除结点时速度比较慢。

​ 因此平衡二叉树适合使用在数据量大且数据量稳定的情况,没有大量的添加、删除动作,大多数情况下都是进行查询操作。

红黑树与AVL树的区别:

​ 1、AVL树在物理上就是平衡的,所以在创建、添加、删除时速度比较慢,但它的查询速度接近平衡二叉树的极限。

​ 2、红黑树是一种特殊的AVL树,它的物理结构不是严格的平衡,而是接近平衡,从根结点所有的叶子结点的速度大致相同,它的非绝对平衡使用节约很多左旋、右旋的次数,因此它创建、添加、删除时速度比AVL树要快,查询速度接近AVL树。

八、线索二叉树

线索二叉树介绍:

​ 我们在遍历二叉树时会得到一个线性的序列结果,但遍历的过程是非线性的(函数递归),该过程比较耗时,我们可以借助结点中的空的指针,增加一些线索,使用二叉树能够使用循环语句进行遍历,提高二叉树遍历的速度。

添加线索的方法:

​ 在二叉树的结点中增加两个成员,使它的结点成为以下结构:

// 使用这种结点可以构建二叉双向链表,作为二叉树的存储结点,也叫作线索链表
typedef struct TreeNode
{
	int val;				// 数据域
	bool ltag;				// false == ltag left指针域指向是当前结点的左子树,否则指向的是它的前驱结点
	struct TreeNode* left;
	bool rtag;				// false == rtag right指针域指向是当前结点的右子树,否则指向的是它的后继结点
	struct TreeNode* right;
}
线索二叉树的表示与实现:
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

#define Link false
#define Thread true

typedef struct TreeNode
{
	int val;
	bool ltag;
	struct TreeNode *left;
	bool rtag;
	struct TreeNode *right;
} TreeNode;

TreeNode *create_node(int val)
{
	TreeNode *root = malloc(sizeof(TreeNode));
	root->val = val;
	root->ltag = false;
	root->left = NULL;
	root->rtag = false;
	root->right = NULL;
	return root;
}

void _add_tree(TreeNode **rpp, TreeNode *node)
{
	if (NULL == *rpp)
	{
		*rpp = node;
		return;
	}

	if (node->val < (*rpp)->val)
		_add_tree(&(*rpp)->left, node);
	else
		_add_tree(&(*rpp)->right, node);
}

void add_tree(TreeNode **rpp, int val)
{
	_add_tree(rpp, create_node(val));
}

TreeNode *init_tree(int *arr, size_t len)
{
	TreeNode *root = NULL;
	for (int i = 0; i < len; i++)
	{
		_add_tree(&root, create_node(arr[i]));
	}
	return root;
}

void _in_order_tree(TreeNode *root)
{
	if (NULL == root)
		return;

	_in_order_tree(root->left);
	printf("%d ", root->val);
	_in_order_tree(root->right);
}

void in_order_tree(TreeNode *root)
{
	_in_order_tree(root);
	printf("\n");
}

static TreeNode *pre;
void in_threading(TreeNode *root)
{
	if (NULL == root)
		return;

	in_threading(root->left);
	if (NULL == root->left)
	{
		root->ltag = Thread;
		root->left = pre;
	}
	if (NULL == pre->right)
	{
		pre->rtag = Thread;
		pre->right = root;
	}
	pre = root;
	in_threading(root->right);
}

TreeNode *in_order_threading(TreeNode *root)
{
	TreeNode *head = create_node(-1);
	head->ltag = Link;
	head->rtag = Thread;
	head->right = head;

	if (NULL == root)
		head->left = head;
	else
	{
		head->left = root;
		pre = head;
		in_threading(root);
		pre->right = head;
		pre->rtag = Thread;
	}
	return head;
}

void in_order_thread_tree(TreeNode *head)
{
	TreeNode *node = head->left;
	while (node != head)
	{
		while (Link == node->ltag)
			node = node->left;
		printf("%d ", node->val);
		while (node->rtag == Thread && node->right != head)
		{
			node = node->right;
			printf("%d ", node->val);
		}
		node = node->right;
	}
}

int main(int argc, const char *argv[])
{
	int arr[10];
	for (int i = 0; i < 10; i++)
	{
		arr[i] = rand() % 100;
	}

	TreeNode *root = init_tree(arr, 10);
	in_order_tree(root);

	// 添加线索
	TreeNode *inhead = in_order_threading(root);
	// 遍历线索二叉树
	in_order_thread_tree(inhead);
	return 0;
}

九、堆树

堆结构介绍:

​ 大根堆:根结点的值比它的左右子树都要大,同时它的左右子树也遵循这项规则,也就是说一棵树的根结点中存储的是这棵树中的最大值,这种完全二叉树叫大根堆。

​ 小根堆:根结点的值比它的左右子树都要小,同时它的左右子树也遵循这项规则,也就是说一棵树的根结点中存储的是这棵树中的最小值,这种完全二叉树叫小根堆。

​ 堆结构是是一种特殊的完全二叉树,它与堆栈完全不同且没有任何关系,我们可以从堆中顺序获取堆顶的数据,也可以使用大根堆、小根堆的规则可以对顺序表进行排序。

堆结构的存储:

​ 堆结构是完全二叉树,所以特别适合顺序表存储,因此要熟练掌握二叉树性质5的分式:

​ 有一个n个结点的完全二叉树,结点按照从上到下从左到右的顺序排序为1~n。

​ 1、i > 1时,i/2就是它的双亲结点。

​ 2、i*2是i的左子树,当i*2>n时,则i没有左子树。

​ 3、2*i+1是i的右子树,2*i+1>n时,则i没有右子树。

堆结构的表示与实现:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

#define PARENT(index) ((index + 1) / 2 - 1)
#define LEFT_CHILD(index) ((index + 1) * 2 - 1)
#define RIGHT_CHILD(index) ((index + 1) * 2)

#define swap_mem(p1, p2, size)  \
	do                          \
	{                           \
		char temp[size];        \
		memcpy(temp, p1, size); \
		memcpy(p1, p2, size);   \
		memcpy(p2, temp, size); \
	} while (0)

#define swap(a, b) swap_mem(&a, &b, sizeof(a))

typedef struct MaxHeap
{
	int *base;
	size_t cnt;
	size_t cal;
} MaxHeap;

// 把base中的数据调整为堆结构
void adjust_max_heap(MaxHeap *heap, int index)
{
	// 从index下标处,向上调整堆结构
	while (index > 0)
	{
		// 计算出双亲下标
		int parent = PARENT(index);
		// 计算出双亲结点的左子树
		int left = LEFT_CHILD(parent);
		// 如果左子树下标合法并且值大于双亲,则与双亲交换
		if (left < heap->cnt && heap->base[left] > heap->base[parent])
			swap(heap->base[left], heap->base[parent]);

		int right = RIGHT_CHILD(parent);
		// 如果右子树下标合法并且值大于双亲,则与双亲交换
		if (right < heap->cnt && heap->base[right] > heap->base[parent])
			swap(heap->base[right], heap->base[parent]);

		// 向上一层
		index = parent;
	}
}

// 创建一个空堆
MaxHeap *create_max_heap(size_t cal)
{
	MaxHeap *heap = malloc(sizeof(MaxHeap));
	heap->base = malloc(sizeof(int) * cal);
	heap->cal = cal;
	heap->cnt = 0;
	return heap;
}

// 初始化堆
MaxHeap *init_max_heap(int *arr, size_t len)
{
	MaxHeap *heap = malloc(sizeof(MaxHeap));
	heap->base = malloc(sizeof(int) * len);
	memcpy(heap->base, arr, sizeof(int) * len);
	heap->cal = len;
	heap->cnt = len;

	// 从叶子结点出发向上调整,最后一个叶子结点的双亲结点就是最后一个非叶子结点
	for (int i = len - 1; i > len / 2; i--)
		adjust_max_heap(heap, i);

	return heap;
}

// 销毁堆
void destroy_max_heap(MaxHeap *heap)
{
	free(heap->base);
	free(heap);
}

// 堆是否为空
bool empty_max_heap(MaxHeap *heap)
{
	return 0 == heap->cnt;
}

// 堆是否为满
bool full_max_heap(MaxHeap *heap)
{
	return heap->cal == heap->cnt;
}

// 入堆
bool push_max_heap(MaxHeap *heap, int val)
{
	if (full_max_heap(heap))
		return false;

	// 在末尾添加一个新结点
	heap->base[heap->cnt++] = val;
	// 从最后一个结点出发向上调整
	adjust_max_heap(heap, heap->cnt - 1);
}

// 出堆
bool pop_max_heap(MaxHeap *heap)
{
	if (empty_max_heap(heap))
		return false;

	// 使用最后一个结点覆盖根结点,并且结点的数量减1
	heap->base[0] = heap->base[--heap->cnt];

	// 从根结点出发向下调整
	int parent = 0;
	while (parent < heap->cnt)
	{
		// 假定根结点是根、左右中的最大结点,并计算出它的左右子树的下标
		int max = parent, left = LEFT_CHILD(parent), right = RIGHT_CHILD(parent);

		// 如果左子树下标合法,且左子树的值大于最大结点,则把左子记作最大结点
		if (left < heap->cnt && heap->base[max] < heap->base[left])
			max = left;
		// 如果右子树下标合法,且右子树的值大于最大结点,则把右子记作最大结点
		if (right < heap->cnt && heap->base[max] < heap->base[right])
			max = right;

		// 如果最大结点就是根结点,则不需要再向下调整,
		if (max == parent)
			break;

		// 把最大结点与根结点交换,从从大结点处继续向下调整
		swap(heap->base[max], heap->base[parent]);
		parent = max;
	}
	return true;
}

// 堆顶
int top_max_heap(MaxHeap *heap)
{
	if (empty_max_heap(heap))
		return -1;
	return heap->base[0];
}

// 元素数量
size_t size_max_heap(MaxHeap *heap)
{
	return heap->cnt;
}

int main(int argc, const char *argv[])
{
	MaxHeap *heap = create_max_heap(10);
	int arr[10];
	for (int i = 0; i < 10; i++)
	{
		arr[i] = rand() % 100;
		printf("%d ", arr[i]);
		push_max_heap(heap, arr[i]);
	}
	printf("\n");
	while (!empty_max_heap(heap))
	{
		printf("%d ", top_max_heap(heap));
		pop_max_heap(heap);
	}
	return 0;
}

十、赫夫曼树

赫夫曼树介绍:

​ 赫夫曼树是一种带权重的树,它的结点中有一项数据是权重信息,根据结点中的权重生成一棵二叉树,结点的权生越大,离根结点就越近。

赫夫曼树的创建过程:

​ 1、获取值和权重信息、。

​ 2、根据值和权重信息生成若干棵度为0的树,存储到小根堆中形成森林。

​ 3、从小根堆获取到两棵权重最轻的树,把这两棵树的权重相加生成一个新的只有权重的空白树,它的左子树指向权重较小的树,右子树指向权较大的树,然后再把新的树添加到小根堆。

​ 4、重复步骤3,直到小根堆中只有一棵树,它就是创建出的赫夫曼树的根结点。

赫夫曼树的性质:

​ 1、赫夫曼树只有前序遍历才有意义。

​ 2、赫夫曼树的非叶子结点都是只有权重信息,而没有值的空白结点,反之,所有叶子结点都是既有权重又有值的结点。

​ 3、结点的权重越大,结点的高度编号就是越小,离根结点就越近,前遍历访问的速度就越快,反之结点的权重越小,结点的高度编号就越大,离根结点就是越远,前序遍历访问的速度就越慢。

赫夫曼编码:

​ 从赫夫曼树的根结点出发进行前序遍历,遍历过程中把往左子树遍历记作0,把往右子树遍历记作1,到达叶子结点时,走过的路径所形成的01信息被称为赫夫曼编码,使用这种编码就访问到赫夫曼树根结点出发访问到叶子结点中的数据。

​ 这种编码的特点就是权重越大的数据,生成的编码就越短,权重越小的数据,生成的编码就越长。

​ 这种编码可以用于提高数据传输效率,还有文件的压缩和解压。

赫夫曼树与赫夫曼编码的应用:
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
#include <limits.h>
#include <unistd.h>
#include <math.h>
#include "heap.h"

typedef struct CharCode
{
	uint32_t code; // 赫夫曼编码
	uint8_t cnt;   // 编码位数
} CharCode;

// 赫夫曼编码数组
CharCode code_arr[128];

// 赫夫曼树结点
typedef struct TreeNode
{
	uint8_t data;	 // 数据
	uint32_t weight; // 权重
	struct TreeNode *left;
	struct TreeNode *right;
} TreeNode;

// 用于比较结点权重的回调函数,给小堆根使用的
int node_cmp(const void *p1, const void *p2)
{
	if (((TreeNode *)p1)->weight > ((TreeNode *)p2)->weight)
		return 1;
	if (((TreeNode *)p1)->weight < ((TreeNode *)p2)->weight)
		return -1;
	return 0;
}

TreeNode *create_node(char data, size_t weight)
{
	TreeNode *node = malloc(sizeof(TreeNode));
	node->data = data;
	node->weight = weight;
	node->left = NULL;
	node->right = NULL;
	return node;
}

void _create_code(TreeNode *root, uint8_t path, uint32_t code, uint8_t cnt)
{
	if (NULL == root)
		return;

	code = (code << 1) + path;

	// 到达叶子结点时,记录赫夫曼编码
	if (0 != root->data)
	{
		code_arr[root->data].code = code;
		code_arr[root->data].cnt = cnt;
		return;
	}

	_create_code(root->left, 0, code, cnt + 1);
	_create_code(root->right, 1, code, cnt + 1);
}

// 生成赫夫曼编码
void create_code(TreeNode *root)
{
	_create_code(root->left, 0, 0, 1);
	_create_code(root->right, 1, 0, 1);
}

// 生成赫夫曼树
TreeNode *init_tree(char *val_arr, size_t *weight_arr, size_t len)
{
	// 创建存储森林的小根堆
	Heap *heap = create_heap(len, node_cmp);
	for (int i = 0; i < len; i++)
	{
		push_heap(heap, create_node(val_arr[i], weight_arr[i]));
	}

	// 直到堆中只有一个结点,这就是赫夫曼树的根结点
	while (1 < size_heap(heap))
	{
		// 从堆中获取一个权重最小的树,当作左子树
		TreeNode *left = top_heap(heap);
		pop_heap(heap);

		// 从堆中获取一个权重最小的树,当作右子树
		TreeNode *right = top_heap(heap);
		pop_heap(heap);

		// 左右子树的权重相加生成一个新的空白结点
		TreeNode *root = create_node(0, left->weight + right->weight);
		root->left = left;
		root->right = right;

		// 新的结点加入小根堆
		push_heap(heap, root);
	}

	TreeNode *root = top_heap(heap);
	destroy_heap(heap);

	return root;
}

// 压缩文件
int compress_file(const char *oldfile, const char *newfile)
{
	FILE *rfp = fopen(oldfile, "r");
	if (NULL == rfp)
	{
		printf("%s文件打开失败!\n", oldfile);
		return -1;
	}

	FILE *wfp = fopen(newfile, "w");
	if (NULL == wfp)
	{
		printf("压缩文件失败!\n");
		return -1;
	}

	fseek(rfp, 0, SEEK_END);
	fprintf(wfp, "%ld\n", ftell(rfp));
	rewind(rfp);

	uint8_t buf[4096], cnt = 0;
	uint32_t data = 0, offset = 0xffffffff;

	int ret;
	while (ret = fread(buf, 1, sizeof(buf), rfp))
	{
		for (int i = 0; i < ret; i++)
		{
			if (32 > cnt + code_arr[buf[i]].cnt)
			{
				data = (data << code_arr[buf[i]].cnt) + code_arr[buf[i]].code;
				cnt += code_arr[buf[i]].cnt;
			}
			else
			{
				// 32-cnt 计算出data中还余多少位
				data = (data << (32 - cnt)) + (code_arr[buf[i]].code >> (code_arr[buf[i]].cnt - (32 - cnt)));
				fwrite(&data, 1, sizeof(data), wfp);

				data = (~(offset << (code_arr[buf[i]].cnt - (32 - cnt)))) & code_arr[buf[i]].code;
				cnt = code_arr[buf[i]].cnt - (32 - cnt);
			}
		}
	}

	data = data << (32 - cnt);
	fwrite(&data, 1, sizeof(data), wfp);

	fclose(rfp);
	fclose(wfp);
	return 0;
}

int dp_file(TreeNode *root, const char *filename, const char *oldfile)
{
	FILE *rfp = fopen(filename, "r");
	if (NULL == rfp)
	{
		printf("解压失败!\n");
		return -1;
	}

	long filesize;
	fscanf(rfp, "%ld\n", &filesize);

	FILE *wfp = fopen(oldfile, "w");
	if (NULL == wfp)
	{
		printf("解压失败!\n");
		return -1;
	}

	unsigned int data;
	TreeNode *node = root;
	while (fread(&data, 1, sizeof(data), rfp))
	{
		for (int i = 0; i < 32; i++)
		{
			if ((data << i) & 0x80000000)
				node = node->right;
			else
				node = node->left;
			if (0 != node->data)
			{
				fwrite(&node->data, 1, 1, wfp);
				node = root;
			}
		}
	}
	fclose(rfp);
	fclose(wfp);

	return truncate(oldfile, filesize);
}

int main(int argc, const char *argv[])
{
	char val_arr[] = {9, 10, 12, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126};

	size_t weight_arr[] = {16468, 29797, 15, 173045, 243, 916, 8025, 11, 24, 571, 1177, 8000, 8016, 16626, 517, 7384, 1855, 8775, 13269, 5276, 4848, 3603, 2032, 2593, 886, 3292, 805, 1219, 1360, 563, 4162, 943, 1248, 1032, 104, 241, 8184, 2145, 6653, 4971, 12331, 3990, 4196, 4170, 9197, 92, 856, 8929, 5168, 8000, 8562, 7509, 116, 11576, 10356, 12187, 4616, 1346, 2660, 3251, 1510, 859, 213, 808, 195, 16, 48314, 739, 35359, 8675, 21905, 27939, 79328, 25790, 8803, 17961, 48009, 361, 2336, 22424, 11352, 47506, 34893, 13171, 470, 40014, 35301, 57634, 16370, 4914, 6093, 4605, 6933, 2144, 440, 577, 435, 11};

	// 数据+权重构建出森林存储到小根堆里面
	TreeNode *root = init_tree(val_arr, weight_arr, sizeof(val_arr));
	create_code(root);
	printf("%d\n", compress_file("/usr/include/elf.h", "test.dat"));
	printf("%d\n", dp_file(root, "test.dat", "test.h"));
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值