浅学C++(5)数据结构与算法(特殊的二叉树)

本文概述了有序二叉树的结构、线索二叉树的创建与中序遍历优化,介绍了选择树在最大值查找中的应用,对比了堆(大顶堆和小顶堆)的实现以及平衡二叉树(AVL与红黑树)的原理和调整策略。最后涵盖了哈夫曼树的构建和哈夫曼编码在数据压缩中的应用。
摘要由CSDN通过智能技术生成
有序二叉树:
        左子树的数据小于根,右子树的数据大于等于根,这种树称为有序二叉树、二叉搜索树、二叉排序树
        tap:这种树的节点需要频繁的插入、删除,因此不适合顺序存储,插入、删除必须有序
        有序二叉树中的中序遍历就是从小到大,所以有序二叉树也是一种排序算法,查找又天然是二分查找


    线索二叉树:
        规律:在N个节点的链式二叉树中必定有N+1个空指针域
        有序的链式二叉树中有很多的空指针,可以让这些指针指向下一个、前一个节点,这样的话在遍历时就不需要使用递归,
        而可以使用循环遍历,可以提高树的遍历速度。

        中序线索二叉树节点数据项:
            数据
            左子树指针
            右子树指针
            右子树指针标志位(假表示真的右子树,真表示右子树指向下一个节点)

        实现过程:
            1,构建有序二叉树
            2,创建线索
            3,通过线索循环遍历二叉树

    选择树:(胜者树、败者树)
        是一种完全二叉树,待比较的数据都存储在最后一层,根节点是根据左右子树其中一个生成,因此根节点是最大或者最小的
        选择树的功能是快速的找出最大数或者最小值

    堆:
        是一种完全二叉树 不适合链式存储
        大顶堆(大根堆):根节点比左右子树大
        小顶堆(小根堆):根节点比左右子树小
        数据项:
            存储数据的内存首地址
            容量
            数量
        操作:创建、销毁、添加、删除、空堆、满堆
        堆可以实现优先队列


    平衡二叉树:(AVL树)
        前提是有序的二叉树,它的左右子树的高度差不超过1,而且它的所有子树也满足这个条件
        如果一个有序二叉树呈现接近单支状(类似链表),它的查找效率接近链表,因此只有达到平衡查找的效率才会最高
        由于节点的值受限,因此只能通过调整达到有序,而不能进行值的修改
        二叉树不平衡的基础原因:
                 x                                  y
               /   \                              /   \
              y     t1                           z     x
             / \            以y为轴向右旋转      / \   / \
            z   t2                             t3 t4 t2 t1      
           /     \
         t3       t4


                 x                                 y
               /   \                             /   \
              t1    y                           x     z
                   / \      以y为轴向左旋转     / \   / \
                  t2  z                       t1 t2 t3 t4       
                     / \
                   t3   t4

                 x                                  x                                z
               /   \                              /   \                            /   \
              y     t1                           z     t1                         y     x
             / \            以z为轴向左旋转      / \            以z为轴向右旋转    / \   / \
            t2  z                              y   t4                           t2 t3 t4 t1
               / \                            / \
              t3  t4                         t2  t3

                x                                    x                               z
              /   \                                /   \                           /   \
             t1    y        以z为轴向右旋转        t1    z     以z为轴向左旋转      x     y
                  / \                                  / \                       / \   / \ 
                 z   t2                               t3  y                     t1 t3 t4 t2
                / \                                      / \
               t3 t4                                    t4  t2

    红黑树:
        也是一种自平衡的树,他不是根据子树的高度差来调整平衡的,而是给节点设置一种颜色,来达到平衡
        红黑树的特性:
            1,每个节点必须有颜色(红或黑)
            2,根节点必须是黑色
            3,红黑树的叶子节点(NULL)必须为黑色
            4,如果一个节点是红色,则他的子节点必须为黑色,不能有连续的红色节点
            5,从一个节点出发到该节点的子孙节点的所有路径上包含了相同数量的黑色节点
                保证大致上红黑树是平衡的(最长路径不超过最短路径的两倍)

        红黑树插入后的调整:
            插入节点一定为红色
                1,如果父节点是黑色,则直接插入
                2,如果父节点是红色,需要调整
                    叔叔不存在或者叔叔为黑色 进行 左旋或者右旋 祖父节点置红色 父节点置黑色

                    叔叔存在且为红色 祖父节点置红色 父节点和叔叔置黑色 把祖父节点当作当前节点,继续向上讨论调整

        优点:插入、删除的效率比AVL树高
        缺点:没有AVL树平均,查找效率没有AVL树高,但也并不差

    哈夫曼树:
        基本概念:
        路径长度:从一个节点到另一个节点之间的路径条目数
            根节点到第N层节点的路径长度为N-1
        树的路径长度:从根节点出发到每个节点的路径长度之和
        节点的权:若将树中节点赋予一个有某种意义的数值,该数值称为该节点的权
        节点的带权路径长度:从根节点到该节点的路径长度与该节点的权的乘积
        树的带权路径长度:所有的叶子节点的带权路径长度之和 WPL
            WPL是衡量一颗带权二叉树的优劣的关键

        哈夫曼树的目的是为了生成一颗WPL最小的带权二叉树
        构建哈夫曼树:
            1,把n个带权节点存入一个集合F中,把每个节点左右子树置空
            2,从F中选取权值最小的两个节点作为左右子树构建成一颗新的二叉树,且新的根节点的权为左右子树的权值之和
            3,从F中删除刚刚选出来的两个节点,把新得到的根节点放入F中
            4,重复2、3操作,直到F中只剩一棵树,既是哈夫曼树 

    哈夫曼编码:
    目的:解决当年远距离通信(电报)的数据传输的最优解
    待发送的文字:BADCA DFEED
    方法1:转成二进制发送 A 000 B 001   共30个字符
    方法2:
        a、根据文字出现频率,构建哈夫曼树
            假设频率: A27 B8 C15 D15 E30 F5
        b、规定哈夫曼树的左分支为0,右分支为1,则从根节点到叶子节点经过的路径分支所组成的0、1序列为该对应字符的哈弗曼编码
        哈夫曼编码:A01  B1001  C101  D00  E11  F1000
        1001010010101001000111100    共25个字符
    作用:数据压缩、文件压缩的其中一种方式

线索二叉树的实现

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

//线索二叉树
typedef struct TreeNode
{
	int data;
	struct TreeNode* left;
	struct TreeNode* right;
	bool rflag;//为真时右子树是线索
}TreeNode;

TreeNode* create_tree_node(int data)
{
	TreeNode* node = malloc(sizeof(TreeNode));
	node->data = data;
	node->left = NULL;
	node->right = NULL;
	node->rflag = false;
	return node;
}

void _insert_tree(TreeNode** root,TreeNode* node)
{
	if(NULL == *root)
	{
		*root = node;
		return;
	}
	if((*root)->data > node->data)
		_insert_tree(&(*root)->left,node);
	else
		_insert_tree(&(*root)->right,node);
}
void insert_tree(TreeNode** root,int data)
{
	_insert_tree(root,create_tree_node(data));
}

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

TreeNode* prev = NULL;//表示当前root的上一个节点

//中序遍历 创建线索
void make_clue(TreeNode* root)
{
	if(NULL == root) return;
	make_clue(root->left);
	if(NULL != prev && NULL == prev->right)
	{
		prev->right = root;
		prev->rflag = true;
	}
	prev = root;
	make_clue(root->right);
}

void clue_show_tree(TreeNode* root)
{
	while(root)
	{
		while(root->left) root = root->left;
		printf("%d ",root->data);
		while(root->rflag)
		{
			root = root->right;
			printf("%d ",root->data);
		}
		root = root->right;
	}
}

堆的实现

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

//大顶堆

#define TYPE int

#define SWAP(a,b) {typeof(a) t=(a);(a)=(b);(b)=t;}

typedef struct Heap
{
	TYPE* arr;
	size_t cal;
	size_t cnt;
}Heap;

Heap* create_heap(size_t cal)
{
	Heap* heap = malloc(sizeof(Heap));
	heap->arr = malloc(sizeof(TYPE)*cal);
	heap->cal = cal;
	heap->cnt = 0;
	return heap;
}

//满堆
bool full_heap(Heap* heap)
{
	return heap->cnt >= heap->cal;	
}
//空堆
bool empty_heap(Heap* heap)
{
	return 0 == heap->cnt;	
}
//添加
bool add_heap(Heap* heap,TYPE data)
{
	if(full_heap(heap)) return false;
	heap->arr[heap->cnt++] = data;
	
	//添加的位置进行调整 形成大顶堆
	int i = heap->cnt;
	while(i>1)
	{
		if(data > heap->arr[i/2-1])
		{
			SWAP(heap->arr[i-1],heap->arr[i/2-1]);
			i = i/2;
		}
		else return true;
	}
}
//删除 只删除堆顶
bool del_heap(Heap* heap)
{
	if(empty_heap(heap)) return false;
	//交换堆顶和末尾 并删除
	SWAP(heap->arr[0],heap->arr[heap->cnt-1]);
	heap->cnt--;
	int i = 1;//编号
	while(i <= heap->cnt)
	{
		//有右子树
		if(2*i+1 <= heap->cnt)
		{
			if(heap->arr[2*i] > heap->arr[2*i-1] && heap->arr[i-1] < heap->arr[2*i])
			{
				SWAP(heap->arr[i*2],heap->arr[i-1]);
				i = i*2+1;
			}
			else if(heap->arr[i-1] < heap->arr[2*i-1])
			{
				SWAP(heap->arr[i*2-1],heap->arr[i-1]);
				i = i*2;	
			}
			else break;
		}
		//左
		else if(2*i <= heap->cnt)
		{
			if(heap->arr[i-1] < heap->arr[2*i-1])
			{
				SWAP(heap->arr[i*2-1],heap->arr[i-1]); 
				i = i*2;	
			}
			else break;
		}
		else break;
	}
	return true;
}
//遍历
void show_heap(Heap* heap)
{
	for(int i=0;i<heap->cnt;i++)
	{
		printf("%d ",heap->arr[i]);	
	}
	printf("\n");
}
//查看堆顶
TYPE top_heap(Heap* heap)
{
	return heap->arr[0];	
}

//堆排序
void sort_heap(int* arr,int len)
{
	//把数组调整成堆结构
	for(int i=1;i<=len;i++)
	{
		int j=i;
		while(j>1)
		{
			if(arr[j-1]>arr[j/2-1])
			{
				SWAP(arr[j-1],arr[j/2-1]);
				j = j/2;
			}
			else break;
		}
	}
	//删除堆顶 直到堆为空
	while(len >1)
	{
		//交换堆顶 末尾
		SWAP(arr[0],arr[len-1]);
		len--;
		//从上往下
		int i = 1;
		while(i <= len)
		{
			if(i*2+1 <= len)
			{
				if(arr[i*2] > arr[2*i-1] && arr[i*2] > arr[i-1])
				{
					SWAP(arr[i*2],arr[i-1]);
					i = i*2+1;
				}
				else if(arr[2*i-1] > arr[i-1])
				{
					SWAP(arr[i*2-1],arr[i-1]);	
					i = i*2;
				}
				else break;
			}
			else if(i*2 <= len)
			{
				if(arr[2*i-1] > arr[i-1])
				{	
					SWAP(arr[i*2-1],arr[i-1]); 
					i = i*2;	
				}
				else break;
			}
			else break;
		}
	}
}

//递归依赖  从top坐标到end坐标从上往下调整成堆结构
void _sort_heap_recursion(int* arr,int top,int end)
{
	if(top >= end) return;
	int max = top+1; //假设 max 是左右根中最大值的编号
	int l = max*2;
	int r = max*2+1;
	if(l-1 <= end && arr[l-1] > arr[max-1])
	{
		//有左子树 且左子树的值大于max的值 更新max
		max = l;
	}
	if(r-l <= end && arr[r-1] > arr[max-1])
	{
		//有左子树 且左子树的值大于max的值 更新max
		max = r;
	}
	if(max-1 != top)
	{
		//max是最大的 交换根与max
		SWAP(arr[top],arr[max-1]);	
		_sort_heap_recursion(arr,max-1,end);
	}
}

//递归实现堆排序
void sort_heap_recursion(int* arr,int len)
{
	//把数组调整成堆结构
	for(int i=1;i<=len;i++)
	{
		int j=i;
		while(j>1)
		{
			if(arr[j-1]>arr[j/2-1])
			{
				SWAP(arr[j-1],arr[j/2-1]);
				j = j/2;
			}
			else break;
		}
	}
	for(int i=len-1;i>0;i--)
	{
		SWAP(arr[0],arr[i]);
		_sort_heap_recursion(arr,0,i-1);
	}
}

平衡二叉树的实现

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

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

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

//  高度
int high_tree(TreeNode* root)
{
	if(NULL == root) return 0;
	int lh = high_tree(root->left);
	int rh = high_tree(root->right);
	return lh>rh ? lh+1 : rh+1;
}
//  计算左右子树高度差
int diff_high(TreeNode* root)
{
	return high_tree(root->left) - high_tree(root->right);	
}

//  右旋
TreeNode* right_rotate(TreeNode* x)
{
	TreeNode* y = x->left;
	TreeNode* t2 = y->right;

	y->right = x;
	x->left = t2;
	return y;
}

//  左旋
TreeNode* left_rotate(TreeNode* x)
{
	TreeNode* y = x->right;
	TreeNode* t2 = y->left;
		
	y->left = x;
	x->right = t2;
	return y;
}

//  自动调整成平衡 返回调整后的root
TreeNode* auto_balance(TreeNode* x)
{
	if(NULL == x) return NULL;
	int lh = high_tree(x->left);
	int rh = high_tree(x->right);
	//  左比右高
	if(lh-rh > 1)
	{
		if(diff_high(x->left) >= 0)
		{
			//  x->left 为轴 右旋	
			x = right_rotate(x);
		}
		else
		{
			//  左旋
			x->left = left_rotate(x);
			//  右旋
			x = right_rotate(x);
		}
	}
	//  右比左高
	if(rh-lh > 1)
	{
		if(diff_high(x->right) >= 0)
		{
			//  右旋
			x->right = right_rotate(x->right);
			//  左旋
			x = left_rotate(x);
		}
		else
		{
			//  左旋
			x = left_rotate(x);
		}
	}
	return x;
}
//  添加  通过返回值返回添加后的树的root
//  返回值相当于root的指向
TreeNode* insert_tree(TreeNode* root,int data)	
{
	if(NULL == root)
		return create_tree_node(data);
	
	if(data < root->data)
		root->left = insert_tree(root->left,data);
	else
		root->right = insert_tree(root->right,data);

	//  调整成平衡
	root = auto_balance(root);
	return root;
}
	
//  前序
void dlr_show(TreeNode* root)
{
	if(NULL == root) return;
	printf("%d ",root->data);
	dlr_show(root->left);
	dlr_show(root->right);
}

//  中序
void ldr_show(TreeNode* root)
{
	if(NULL == root) return;
	ldr_show(root->left);
	printf("%d ",root->data);
	ldr_show(root->right);
}

/*
	删除节点
	1、待删除的节点是叶子节点,直接删除,重新调整平衡
	2、待删除的节点左或右子树为空,则使用非空节点替换
	3、待删除的节点左右子树非空,根据左右子树高度,选择高的一边
		如果是左高则选择左子树中的最大值替换待删除节点的值
		然后删除左子树的最大值节点
		反之则选择右子树的最小值节点进行同样操作
	最后重新调整平衡
*/

//  找最小值
TreeNode* min_data(TreeNode* root)
{
	TreeNode* min = root;
	while(min->left) min = min->left;
	return min;
}
//  找最大值
TreeNode* max_data(TreeNode* root)
{
	TreeNode* max = root;
	while(max->right) max = max->right;
	return max;
}
TreeNode* del_tree(TreeNode* root,int data)
{
	if(NULL == root) return NULL;
	if(data == root->data)
	{
		//  左右为空 直接删除
		if(NULL == root->left && NULL == root->right)
		{
			free(root);
			return NULL;
		}
		//  左子树非空
		if(NULL == root->right)
		{
			TreeNode* temp = root->left;
			free(root);
			return temp;
		}
		if(NULL == root->left)
		{
			TreeNode* temp = root->right;
			free(root);
			return temp;
		}
		//  左右子树非空
		int lh = high_tree(root->left);
		int rh = high_tree(root->right);
		if(lh >= rh)
		{
			//  左子树找最大值节点	
			TreeNode* node = max_data(root->left);
			//把最大值赋值给待删除节点
			root->data = node->data;
			//把左子树删除最大值节点
			root->left = del_tree(root->left,node->data);
		}
		else
		{
			TreeNode* node = min_data(root->right);
			//  右子树找最小值节点	
			root->data = node->data;
			root->right = del_tree(root->right,node->data);
		}
	}
	if(data < root->data)
		root->left = del_tree(root->left,data);
	else 
		root->right = del_tree(root->right,data);

	root = auto_balance(root);
	return root;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值