数据结构-----树

3 篇文章 0 订阅
1 篇文章 0 订阅

目录

1.树的术语

2.2叉树/树的创建

3.2叉树的遍历

4.2叉树的构造

5.哈夫曼树

6.bst树

7.avl树

8.红黑树与avl树


1.树的术语

 

Root根

顶端结点 The top node in a tree.

 

 Child子节点

一个结点所连接的结点 A node directly connected to another node when moving away from the Root.

 

Parent父节点

连接子节点的节点 The converse notion of a child.

 

Siblings同胞

一群有相同父节点的节点A group of nodes with the same parent.

 

Descendant后裔

一个父节点的子孙们A node reachable by repeated proceeding from parent to child.

 

Ancestor祖先

一个结点的爹,爷,祖宗们A node reachable by repeated proceeding from child to parent.

 

Leaf叶

一个没孩子的爹没有子节点的节点(less commonly called External node)

A node with no children.

 

Branch分支

有至少一个子节点的连接Internal node

A node with at least one child.

 

Degree度

维基上那么翻译的,degree本身的意思有度

度数 = 结点的分支数,2叉树为 0, 1或2

一个节点的子树的节点的数量The number of sub trees of a node.

 

Edge连接

节点之间的联系The connection between one node and another.

 

Path路径

一个节点和祖先间的连接和节点A sequence of nodes and edges connecting a node with a descendant.(节点和边的序列,将节点和子节点连接起来。)

 

Level层次

结点的层次从根开始定义起,根为第一层,根的孩子为第二层,依次累计

根到该节点的连接数量+1(根节点自己也算)即为层次数The level of a node is defined by 1 + (the number of connections between the node and the root).(节点的级别由1 +(节点和根之间的连接数)定义。)

 

Height of node节点高度

从该节点到该节点往下连接的最长路径的连接的数量The height of a node is the number of edges on the longest path between that node and a leaf.

 

Height of tree树高

树高即为所连接的最长路径下的节点的层次The height of a tree is the height of its root node.

 

Depth深度

深度就是一个节点的层次The depth of a node is the number of edges from the tree’s root node to the node.(节点的深度是从树的根节点到节点的边的数量。)

 

Forest森林

由不相交的树组成的集合叫森林A forest is a set of n ≥ 0 disjoint trees.

 

 

2.2叉树/树的创建

// 2叉树链式结构
typedef struct twotree
{
	int data;  //节点的值(很多使用模板,或者vector)
	struct twotree * lc; //指向左孩子节点
	struct twotree * rc;//指向右孩子节点
}tree;
//最简单的2叉树 创建
void createttree (tree * T)
{
	int word;
	std::cin >> word;
	if (word == -1)
		T = nullptr;//结束分支
	else
	{
		T = new tree();
		T->data = word;
		createttree(T->lc);
		createttree(T->rc);
	}
}

3.2叉树的遍历

2叉树遍历的顺序:

递归遍历:递归遍历非常的简单粗暴。

/2叉树的遍历(先,中,后遍历),递归版本就是什么时候打印的差异
//先序遍历(打开左子树就打印)
void PreOrder(tree* T)
{
	if (T != 0)
	{
		std::cout << T->data;
		PreOrder(T->lc);
		PreOrder(T->rc);
	}
}
//中序遍历(左子树收回打印)
void IneOrder(tree* T)
{
	if (T != 0)
	{
		IneOrder(T->lc);
		std::cout << T->data;
		IneOrder(T->rc);
	}
}
//后序遍历(右子树收回打印)
void PosOrder(tree* T)
{
	if (T != 0)
	{
		PosOrder(T->lc);
		PosOrder(T->rc);
		std::cout << T->data;
	}
}

 非递归遍历:

 

层次遍历(广度优先遍历)

//层次遍历和非递归先序遍历很像,一个用栈,一个用队列
//层次遍历用一个环形队列
typedef struct qe
{
	tree *data[100];
	int rear = -1;
	int front = -1;
}qe;
//层次遍历
void levelOrder(tree *T)
{
	tree *p;
	qe st;
	//队列最大值
	int Maxsize = 100;
	//进队(根节点)
	st.rear++;
	st.data[st.rear] = T;
	//队不空时循环
	while (st.front != st.rear)
	{
		//每次进2个出一个(出一个节点,进2个节点的子节点,因为队列的关系,能够顺序进出。)
		st.front = (st.front + 1) % Maxsize; // 环形队列的下标
		//出队,每次循环重置p,每次出对的节点的子节点进队,实现层次
		p = st.data[st.front];
		std::cout << p->data;
		//进队
		if (p->lc != nullptr)
		{
			st.rear = (st.rear + 1) % Maxsize;
			st.data[st.rear] = p->lc;
		}
		if (p->rc != nullptr)
		{
			st.rear = (st.rear + 1) % Maxsize;
			st.data[st.rear] = p->rc;
		}
	}
}

 

注意:下面的先序,中序,后续是深度优先遍历(或者说先序遍历是深度优先遍历) 

 

先序遍历(先访问根节点,再依次访问左子树和右子树

//开个顺序栈存树节点
typedef struct sk
{
	tree *data[100];
	int  top = -1;
}sk;
//非递归先序
void PreOrder1(tree* T)
{
	//栈
	sk st;
	//临时树节点
	tree *p;
	if (T != nullptr)
	{
		//压栈,把树节点压进去,这里是根节点
		++(st.top);
		st.data[st.top] = T;
		while (st.top > -1)
		{
			//把节点出栈,打印
			p = st.data[st.top];
			--(st.top);
			std::cout << p->data << " ";
			//这里一次压一个左右孩子节点,下次循环就可以出左节点(右节点先进栈),在吧左节点的2个孩子节点压进去,直到没有孩子节点为空
			if (p->rc != nullptr)
			{
				++(st.top);
				st.data[st.top] = p->rc;
			}
			if (p->lc != nullptr)
			{
				++(st.top);
				st.data[st.top] = p->lc;
			}
		}
	}
}

中序遍历(先访问左子树,再访问根节点吗,最后访问右子树) 

//非递归中序

void InOrder1(tree* T)
{
	//栈
	sk st;
	//临时树节点
	tree *p;
	if (T != nullptr)
	{
		p = T;
		while (st.top > -1 || p!= nullptr)
		{
			while (p != nullptr)
			{
				//把节点的左子树压进去
				++st.top;
				st.data[st.top] = p;
				p = p->lc;
			}
			// 叶节点以没有左子树判断出栈,之后会以叶节点的右子树出他的父节点
			if (st.top > -1)
			{
				//出栈
				p = st.data[st.top];
				--st.top;
				std::cout << p->data << " ";
				p = p->rc; //转回来处理右子树
			}
		}
	}
}

 后序遍历(先访问左子树,再访问右子树,最后访问根节点

//非递归后序

void PosOrder1(tree* T)
{
	//栈
	sk st;
	//临时树节点
	tree *p;
	p = nullptr; // 指向前一个已经访问过的节点
	int flag = 0;
	if (T != nullptr)
	{
		do
		{
			while (T != nullptr) //压左节点进栈
			{
				++st.top;
				st.data[st.top] = T;
				T = T->lc;
			}
			flag = 1; // 表示T已经访问或者为空
			while (st.top > -1 && flag == 1)
			{
				T = st.data[st.top];
				if (T->rc == p)
				{
					//出栈
					std::cout << T->data;
					--st.top;
					p = T;
				}
				else
				{
					T = T->rc;
					flag = 0;
				}
			}
		} while (st.top != -1);
	}
}

 

4.2叉树的构造

想要确定一个2叉树的形状单一一个序列是不行的,必须要2个序列才行,一个序列确定根节点,一个序列确定左右子树。例如中序和先序,中序和后序可以确定唯一的2叉数

 

5.哈夫曼树

哈夫曼树构造原理

哈夫曼树代码实现

6.bst树

bst树代码实现

//二叉查找树节点信息
template<class T>
class TreeNode
{
    public:
		//二叉查找树的节点和二叉树的节点大部分是一样的,不同的是,二叉查找树多了一个值出现的次数
        TreeNode():lson(NULL),rson(NULL),freq(1){}
        T data;//值
        unsigned int freq;//频率
        TreeNode* lson;//指向左儿子的坐标
        TreeNode* rson;//指向右儿子的坐标
};
//二叉查找树类的属性和方法声明
template<class T>
class BST
{
    private:
        TreeNode<T>* root;//根节点
        void insertpri(TreeNode<T>* &node,T x);//插入
        TreeNode<T>* findpri(TreeNode<T>* node,T x);//查找
        void insubtree(TreeNode<T>* node);//中序遍历
        void Deletepri(TreeNode<T>* &node,T x);//删除
    public:
        BST():root(NULL){}
        void insert(T x);//插入接口
        TreeNode<T>* find(T x);//查找接口
        void Delete(T x);//删除接口
        void traversal();//遍历接口

};
//插入
template<class T>
void BST<T>::insertpri(TreeNode<T>* &node,T x)
{
    if(node==NULL)//如果节点为空,就在此节点处加入x信息
    {
        node=new TreeNode<T>();
        node->data=x;
        return;
    }
    if(node->data>x)//如果x小于节点的值,就继续在节点的左子树中插入x
    {
        insertpri(node->lson,x);
    }
    else if(node->data<x)//如果x大于节点的值,就继续在节点的右子树中插入x
    {
        insertpri(node->rson,x);
    }
    else ++(node->freq);//如果相等,就把频率加1
}
//插入接口
template<class T>
void BST<T>::insert(T x)
{
    insertpri(root,x);
}
//查找
template<class T>
TreeNode<T>* BST<T>::findpri(TreeNode<T>* node,T x)
{
    if(node==NULL)//如果节点为空说明没找到,返回NULL
    {
        return NULL;
    }
    if(node->data>x)//如果x小于节点的值,就继续在节点的左子树中查找x
    {
        return findpri(node->lson,x);
    }
    else if(node->data<x)//如果x大于节点的值,就继续在节点的左子树中查找x
    {
        return findpri(node->rson,x);
    }
    else return node;//如果相等,就找到了此节点
}
//查找接口
template<class T>
TreeNode<T>* BST<T>::find(T x)
{
    return findpri(root,x);
}
//删除
template<class T>
void BST<T>::Deletepri(TreeNode<T>* &node,T x)
{
    if(node==NULL) return ;//没有找到值是x的节点
    if(x < node->data)
    Deletepri(node->lson,x);//如果x小于节点的值,就继续在节点的左子树中删除x
    else if(x > node->data)
    Deletepri(node->rson,x);//如果x大于节点的值,就继续在节点的右子树中删除x
    else//如果相等,此节点就是要删除的节点
    {
        if(node->lson&&node->rson)//此节点有两个儿子
        {
            TreeNode<T>* temp=node->rson;//temp指向节点的右儿子
            while(temp->lson!=NULL) temp=temp->lson;//找到右子树中值最小的节点
            //把右子树中最小节点的值赋值给本节点
            node->data=temp->data;
            node->freq=temp->freq;
            Deletepri(node->rson,temp->data);//删除右子树中最小值的节点
        }
        else//此节点有1个或0个儿子
        {
            TreeNode<T>* temp=node;
            if(node->lson==NULL)//有右儿子或者没有儿子
            node=node->rson;
            else if(node->rson==NULL)//有左儿子
            node=node->lson;
            delete(temp);
        }
    }
    return;
}
//删除接口
template<class T>
void BST<T>::Delete(T x)
{
    Deletepri(root,x);
}
//中序遍历函数
template<class T>
void BST<T>::insubtree(TreeNode<T>* node)
{
    if(node==NULL) return;
    insubtree(node->lson);//先遍历左子树
    cout<<node->data<<" ";//输出根节点
    insubtree(node->rson);//再遍历右子树
}
//中序遍历接口
template<class T>
void BST<T>::traversal()
{
    insubtree(root);
}

7.avl树

avl树可以认为是bst树加上一个平衡算法达到动态平衡的状态

avl树插入旋转图:

实现代码:

#include "stdafx.h"

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

#define LH +1   //左高
#define EH 0    //等高
#define RH -1   //右高

//二叉树类型定义
struct tree
{
	int data;             //数据域
	int bf;               //平衡因子
	struct tree *left;    //左孩子
	struct tree *right;   //右孩子
};

typedef struct tree treenode;
typedef treenode *btree;

//右旋
//对以*ptr为根的二叉排序树做右旋处理,处理之后p指向新的树根结点,即旋转
//处理之前左子树的根结点
void R_Rotate(btree *ptr)
{
	btree lc = (*ptr)->left;   //lc指向的*ptr的左孩子的根结点
	(*ptr)->left = lc->right;  //lc的右子树挂接为*ptr的左子树
	lc->right = *ptr;
	*ptr = lc;                 //ptr指向新的结点"
}

//左旋
//对以*ptr为根的二叉排序树做左旋处理,处理之后p指向新的树根结点,即旋转
//处理之前右子树的根结点
void L_Rotate(btree *ptr)
{
	btree rc = (*ptr)->right;   //rc指向的*ptr的由孩子的根结点
	(*ptr)->right = rc->left;   //rc的左子树挂接为*ptr的右子树
	rc->left = *ptr;
	*ptr = rc;                  //ptr指向新的结点
}

/*
以指针root所指结点为根结点的二叉树作左平衡旋转处理,本算法结束时,指针root指向新的结点
*/
void LeftBalance(btree *root)
{

	btree lc;
	btree rd;

	lc = (*root)->left; //ls指向*root的左根结点

						//检测*root的左子树的平衡度,并作相应处理
	switch (lc->bf)
	{
	case LH:
	{
		//新结点插入在*root的左孩子的左子树上,要做单右旋处理
		(*root)->bf = lc->bf = EH;
		R_Rotate(root);
		break;
	}
	case RH:
	{
		//新结点插入在*root左孩子的右子树上要做双旋处理
		//rd指向*t的左孩子的右子树根上
		rd = lc->right;
		switch (rd->bf)
		{
			//修改*root及其左孩子的平衡因子
		case LH:
		{
			(*root)->bf = RH;
			lc->bf = EH;
			break;
		}
		case EH:
		{
			(*root)->bf = lc->bf = EH;
			break;
		}
		case RH:
		{
			(*root)->bf = EH;
			lc->bf = LH;
			break;
		}
		}
		rd->bf = EH;
		//对*root的左子树左左旋平衡处理
		L_Rotate(&(*root)->left);
		//对*root做右旋平衡处理
		R_Rotate(root);
		break;
	}
	}
}

void RightBalance(btree *root)
{
	btree lc;
	btree rd;

	lc = (*root)->right;

	switch (lc->bf)
	{
	case RH:
	{
		(*root)->bf = lc->bf = EH;
		L_Rotate(root);
		break;
	}
	case LH:
	{
		rd = lc->left;
		switch (rd->bf)
		{
		case LH:
		{
			(*root)->bf = EH;
			lc->bf = RH;
			break;
		}
		case EH:
		{
			(*root)->bf = lc->bf = EH;
			break;
		}
		case RH:
		{
			(*root)->bf = LH;
			lc->bf = EH;
			break;
		}
		}
		rd->bf = EH;
		R_Rotate(&(*root)->right);
		L_Rotate(root);
		break;
	}
	}
}

/*
平衡二叉排序树的插入
若在平衡的二叉树root中不存在和e相同关键字的结点,则插入一个数据元素为e的新结点,并返回1,否则返回0,若因插入而使二叉树失去平衡,则作平衡处理,taller变量反应T长高与否
若树长高,则置taller为ture
*/
int InsertAVL(btree *root, int e, bool *taller)
{
	if ((*root) == NULL)
	{
		//该树为一棵空树,创建一个新节点作为根节点
		(*root) = (btree)malloc(sizeof(treenode));
		(*root)->bf = EH;
		(*root)->data = e;
		(*root)->left = NULL;
		(*root)->right = NULL;
		*taller = true;
	}
	else if (e == (*root)->data)
	{
		//关键字相同,则不再继续插入
		*taller = false;
		return 0;
	}
	else if (e < (*root)->data)
	{
		//应该继续在*root的左子树进行搜索
		if (!InsertAVL(&(*root)->left, e, taller)) //当key已经存在时终结递归。
		{
			//未插入
			return 0;
		}
		//已插入到*root的左子树中并且左子树长高
		if (*taller)
		{
			//检查*root的平衡度
			switch ((*root)->bf)
			{
				//原本左子树比右子树高
			case LH:
			{
				//平衡因子为-1
				//左旋
				LeftBalance(root);
				*taller = false;
				break;
			}
			//原本左右树一样高,现在因为左子树长高树长高
			case EH:
			{
				//平衡因子为0
				(*root)->bf = LH;
				*taller = true;
				break;
			}
			//原本右子树比左子树高,现在等高
			case RH:
			{
				//平衡因子为1
				(*root)->bf = EH;
				*taller = false;
				break;
			}
			}
		}
	}
	else
	{
		//应继续在*root的右子树中进行搜索    
		if (!InsertAVL(&(*root)->right, e, taller))
		{
			//未插入
			return 0;
		}
		//已插入到*root的右子树且右子树长高
		if (*taller)
		{
			//检查*root的平衡度
			switch ((*root)->bf)
			{
			case LH:
			{
				//原本左子树比右子树高,现在相等
				(*root)->bf = EH;
				*taller = false;
				break;
			}
			case EH:
			{
				//原来左右子树登高,现在因为右子树长高树长高
				(*root)->bf = RH;
				*taller = true;
				break;
			}
			case RH:
			{
				//原本右子树比左子树高,需要做右旋平衡处理 
				RightBalance(root);
				*taller = false;
				break;
			}
			}
		}
	}
	return 1;
}

/*
二叉树的中序遍历,先序和后续都打印不出从小到大的顺序
*/
void inorder(btree root)
{
	if (root != nullptr)
	{
		inorder(root->left);
		printf("%d    ", root->data);
		inorder(root->right);
	}
}
//先序
void PreOrder(btree root)
{
	if (root != nullptr)
	{
		printf("%d    ", root->data);
		PreOrder(root->left);
		PreOrder(root->right);
	}
}
//后序
void PosOrder(btree root)
{
	if (root != nullptr)
	{
		PosOrder(root->left);
		PosOrder(root->right);
		printf("%d    ", root->data);
	}
}
/*
平衡二叉树的查找
*/

bool FindNode(btree root, int value, btree *pos)
{
	btree ptr = root;

	(*pos) = NULL;

	while (ptr)
	{
		if (ptr->data == value)
		{
			//找到了
			(*pos) = ptr;
			return true;
		}
		else if (ptr->data > value)
		{
			ptr = ptr->left;
		}
		else
		{
			ptr = ptr->right;
		}
	}
	//没有找到
	return false;
}

int main()
{
	int i;
	int j;
	int value;
	int nArr[] = { 1,23,45,34,98,9,4,35,23,56,87,35,96,22,67 };
	std::cout << "数组原本顺序" << std::endl;
	for (j = 0; j < 15; ++j)
	{
		std::cout << nArr[j] << "    ";
	}
	std::cout << std::endl;
	bool taller;

	btree root = NULL;
	btree pos = NULL;

	for (i = 0; i < 15; i++)
	{
		//插入数据
		InsertAVL(&root, nArr[i], &taller);
	}
	printf("---------中序遍历结果(递增序列)---------\n");
	inorder(root);
	printf("\n");
	printf("---------先序遍历结果---------\n");
	PreOrder(root);
	printf("\n");
	printf("---------后序遍历结果---------\n");
	PosOrder(root);

	printf("\n请输入查找的数:");
	scanf("%d", &value);

	if (FindNode(root, value, &pos))
	{
		printf("\n找到了:%d\n", pos->data);
	}
	else
	{
		printf("\nNot find this node\n");
	}
	system("pause");
	return 0;
}


 

8.红黑树与avl树

红黑树与AVL的比较:

AVL是严格平衡树,因此在增加或者删除节点的时候,根据不同情况,旋转的次数比红黑树要多;

红黑是用非严格的平衡来换取增删节点时候旋转次数的降低;

所以简单说,如果你的应用中,搜索的次数远远大于插入和删除,那么选择AVL,如果搜索,插入删除次数几乎差不多,应该选择RB。

有时仅为了排序(建立-遍历-删除),不查找或查找次数很少,rb合算一些。

查找多的话用 AVL ,
添加删除多的话用 RB。

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值