《大话数据结构》笔记——第8章 查找(二)

文章目录

  • 8.6 二叉排序树
    • 8.6.1 二叉排序树查找操作
    • 8.6.2 二叉排序树插入操作
    • 8.6.3 二叉排序树删除操作
    • 8.6.4 二叉排序树总结
  • 8.7 平衡二叉树( AVL树 )
    • 8.7.1 平衡二叉树实现原理
    • 8.7.2 平衡二叉树实现算法

声明:

本博客是本人在学习《大话数据结构》后整理的笔记,旨在方便复习和回顾,并非用作商业用途。

本博客已标明出处,如有侵权请告知,马上删除。

8.6 二叉排序树

假设查找的数据集是普通的顺序存储,那么插入操作就是将记录放在表的末端,给表记录数加一即可,删除操作可以是删除后,后面的记录向前移,也可以是要删除的元素与最后一个元素互换,表记录数减一,反正整个数据集也没有什么顺序,这样的效率也不错。应该说,插入和删除对于顺序存储结构来说,效率是可以接受的,但这样的表由于无序造成查找的效率很低。

如果查找的数据集是有序线性表,并且是顺序存储的,查找可以用折半、插值、 斐波那契等查找算法来实现,可惜,因为有序,在插入和删除操作上,就需要耗费大置的时间。

有没有一种即可以使得插入和删除效率不错,又可以比较高效率地实现查找的算法呢?还真有。

我们在 8.2 节把这种需要在查找时插入或删除的查找表称为动态查找表。我们现在就来看看什么样的结构可以实现动态查找表的高效率。

如果在复杂的问题面前,我们束手无策的话,不妨先从最最简单的情况入手。现在我们的目标是插入和查找同样高效。假设我们的数据集开始只有一个数 {62},然后现在需要将 88 插入数据集,于是数据集成了 {62,88},还保持着从小到大有序。再查找有没有 58 ,没有则插入,可此时要想在线性表的顺序存储中有序,就得移动 62 和 88 的位置,如图 8-6-2 左图,可不可以不移动呢?嗯,当然是可以,那就是二叉树结构。当我们用二叉树的方式时,首先我们将第一个数 62 定为根结点, 88 因为比 62 大,因此让它做 62 的右子树,58 因比 62 小,所以成为它的左子树。此时 58 的插入并没有影响到 62 与 88 的关系,如图 8-6-2 右图所示。

《大话数据结构》笔记——第8章 查找(二)_第1张图片

也就是说,若我们现在需要对集合{ 62, 88, 58, 47, 35, 73, 51, 99, 37, 93 }做查找,在我们打算创建此集合时就考虑用二叉树结构,而且是排好序的二叉树来创建。如图 8-6-3 所示,62 、88 、58 创建好后,下一个数 47 因比 58 小,是它的左子树(见③),35 是 47 的左子树(见④),73 比 62 大,但却比 88 小,是 88 的左子树(见⑤), 51 比 62 小、比 58 小、比 47 大,是 47 的右子树(见⑥), 99 比 62 、 88 都大,是 88 的右子树(见⑦), 37 比 62 、 58 、 47 都小,但却比 35 大,是 35 的右子树(见⑧), 93 则因比 62 、 88 大是 99 的左子树(见⑨)。

《大话数据结构》笔记——第8章 查找(二)_第2张图片

这样我们就得到了一棵二叉树,并且当我们对它进行中序遍历时,就可以得到一个有序的序列{ 35, 37, 47, 51, 58, 62, 73, 88, 93, 99 },所以我们通常称它为二叉排序树

二叉排序树( Binary Sort Tree ),又称为二叉査找树。它或者是一棵空树,或者是具有下列性质的二叉树

  • 若它的左子树不空,则左子树上所有结点的值均小于它的根结构的值;
  • 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
  • 它的左、右子树也分别为二叉排序树。

从二叉排序树的定义也可以知道,它前提是二叉树,然后它采用了递归的定义方法,再者,它的结点间满足一定的次序关系,左子树结点一定比其双亲结点小,右子树结点一定比其双亲结点大。

构造一棵二叉排序树的目的,其实并不是为了排序,而是为了提高查找和插入删除关键字的速度。不管怎么说,在一个有序数据集上的查找,速度总是要快于无序的数据集的,而二叉排序树这种非线性的结构,也有利于插入和删除的实现。

8.6.1 二叉排序树查找操作

首先我们提供一个二叉树的结构:

/* 二叉树的二叉链表结点结构定义 */
typedef struct BiTNode	/* 结点结构 */
{
   
	int data;	/* 结点数据 */
	struct BiTNode *lchild, *rchild; /* 左右孩子指针 */
} BiTNode, *BiTree;

然后我们来看看二叉排序树的查找是如何实现的。

#define TRUE 1
#define FALSE 0
typedef int Status; //创建子函数返回类型 
/* 递归查找二叉排序树 T 中是否存在 key , 指针 f 指向 T 的双亲,其初始调用值为NULL */
/* 若查找成功,则指针 p 指向该数据元素结点,并返回 TRUE */
/* 否则指针 p 指向查找路径上访问的最后一个结点并返回 FALSE */
Status SearchBST(BiTree T, int key, BiTree f, BiTree *p)
{
   
	if (!T)	/* 查找不成功 */
	{
   
		*p = f;
		return FALSE;
	}
	else if (key == T->data)	/* 查找成功 */
	{
   
		*p = T;
		return TRUE;
	}
	else if (key < T->data)
	{
   
		return SearchBST(T->lchild , key, T, p); /*在左子树继续查找 */
	}
	else
	{
   
		return SearchBST(T->rchild, key, T, p); /*在右子树继续查找 */
	}
}
  1. SearchBST 函数是一个可递归运行的函数,函数调用时的语句为 SearchBST(T,93,NULL,p) ,参数 T 是一个二叉链表,其中数据如图 8-6-3 所示,key 代表要查找的关键字,目前我们打算查找 93 ,二叉树 f 指向 T 的双亲,当 T 指向根结点时,f 的初值就为 NULL ,它在递归时有用,最后的参数 P 是为了査找成功后可以得到查找到的结点位置。

    《大话数据结构》笔记——第8章 查找(二)_第3张图片

  2. 第 9〜13 行,是用来判断当前二叉树是否到叶子结点,显然图 8-6-3 告诉我们当前 T 指向根结点 62 的位置,T 不为空,第 11〜12 行不执行。

  3. 第 14〜18 行是查找到相匹配的关键字时执行语句,显然 93≠62 ,第 16〜17 行不执行。

  4. 第 19〜22 行是当要查找关键字小于当前结点值时执行语句,由于 93>62 ,第 21 行不执行。

  5. 第 23〜26 行是当要查找关键字大于当前结点值时执行语句,由于 93>62 ,所以递归调用 SearchBST(T->rchild, key, T, p) 。此时 T 指向了 62 的右孩子 88 ,如图 8-6-4 所示。

    《大话数据结构》笔记——第8章 查找(二)_第4张图片

  6. 此时第二层 SearchBST ,因 93 比 88 大,所以执行第 25 行,再次递归调用 SearchBST(T->rchild, key, T, p) 。此时 T 指向了 88 的右孩子 99 ,如图 8-6-5 所示。

    《大话数据结构》笔记——第8章 查找(二)_第5张图片

  7. 第三层的 SearchBST ,因 93 比 99 小,所以执行第 21 行,递归调用 SearchBST( T->lchild, key, T, p ) 。此时 T 指向了 99 的左孩子 93 ,如图 8-6-6 所示。

    《大话数据结构》笔记——第8章 查找(二)_第6张图片

  8. 第四层 SearchBST ,因 key 等于 T->data ,所以执行第 16〜17 行,此时指针 p 指向 93 所在的结点,并返回 True 到第三层、第二层、第一层,最终函数返回 True 。

8.6.2 二叉排序树插入操作

有了二叉排序树的查找函数,那么所谓的二叉排序树的插入,其实也就是将关键字放到树中的合适位置而已,来看代码。

#define TRUE 1
#define FALSE 0
typedef int Status; //创建子函数返回类型 
/* 当二叉排序树T中不存在关键字等于 key 的数据元素时,*/
/* 插入 key 并返回 TRUE ,否則返回 FALSE */
Status InsertBST(BiTree *T,int key)
{
   
	BiTree p, s;
	if (!SearchBST(*T, key, NULL, &p))	/* 查找不成功 */
	{
   
		s = (BiTree)malloc(sizeof (BiTNode));
		s->data = key;
		s

你可能感兴趣的:(#,《大话数据结构》,数据结构)