《数据结构与算法描述:c++实现》学习笔记

第四章 树

二叉查找树(binary search tree)。在很多应用程序中都有使用的两个库集合类set和map的实现基础。
本章目的:

  • 了解树是如何用于实现几个流行的操作系统中的文件系统的。
  • 了解树如何用来计算算术表达式的值。
  • 指出如何利用树支持O(logN)平均时间进行的各种搜索操作,以及如何细化得到最坏情况时间界 O(logN)。
  • 讨论并使用set和map类。

4.1 预备知识

定义的一种自然的方式是递归的方法。一课树是一些结点的集合。这个集合可以是空集,若不是空集,则树由称作根的结点r以及零个或多个非空的子树T1,T2…Tk组成,这些子树中每一颗的根都被来自根r的一条有向的边所连接。
儿子:每一颗子树的根叫做根r的儿子。
父亲:r是每一颗子树的根的父亲。
兄弟:具有相同父亲的结点。
路径:结点n1到nk的路径定义为结点n1,n2,。。,nk的一个序列。
:路径上的边的条数,为k-1.
深度:从根到ni的唯一路径的长。
:ni到一片树叶的最长路径的长。(深度计算根节点到自己,高度计算自己到最下面)
一棵树是N个结点和N-1条边的集合。

4.1.1 树的实现

实现方法:将每个结点的所有儿子都放在树结点的链表中。

struct TreeNode
{
	Object element;
	TreeNode *firstChild;
	TreeNode *nextChild;
};

4.1.2 树的遍历和应用

前序遍历:对结点的处理工作是在他的所有儿子结点被处理之前进行的。
后序遍历:对结点的处理工作是在他的所有儿子结点被处理之后进行的。

4.2 二叉树

二叉树(binary tree)一颗每个结点都不能有多于两个儿子的树。(儿子可能为空)
性质:平均二叉树的深度要比结点个数N小得多。为O(√N),对于特殊类型的二叉树,其深度的平均值是O(logN)。

4.2.1 实现

类似于双向链表的声明。

struct BinaryNode
{
	Object element;
	BinaryNode *left;
	BinaryNode *right;
}

4.3 查找树ADT——二叉查找树

对于树中的每个结点X,它的左子树中的所有项的值小于X中的项,它的右子树中所有项的值大于x中的项。
平均深度为O(logN).
类模板中的contains、insert和remove的调用方法

4.3.1 contains

树T中有项为X的结点,contains操作返回true,否则返回false

bool contains(const Comparable & x, BinaryNode *t) const
{
	if(t == NULL)
		return false;
	else if(x < t->element)
		return contains(x,t->left);
	else if(x > t->element)
		return contains(x,t->right);
	else
		return true;
}

在这里使用尾递归,算法的表达式的简洁性是以速度的降低为代价的,使用的栈空间的量是O(logN)。

4.3.2 findMax和findMin

分别返回指向树中包含最大元和最小元的结点的指针。。findMin从根开始并且只要有左儿子就想左进行,终止点就是最小的元素。findMax相右进行。

//递归实现
BinaryNode * findMin(BinaryNode *t )const
{
	if(t == NULL)
		return NULL;
	if(t->left == NULL)
		return t;
	return findMin(t->left);
}
//非递归实现
BinaryNode *findMax(BinaryNode  *t) const
{
	if(t!=NULL)
		while(t->right != NULL)
			t= t->right;
	return t;
}

4.3.3 insert

先使用contains沿着树查找,如果找到X,什么都不用做。否则,将X插入到遍历的路径上的最后一点。

void insert(const Comparable & x,BinaryNode * &t)
{
	if(t == NULL)
		t = nex BinaryNode(x, NULL, NULL);
	else if(x < t->element)
		insert(x, t->left);
	else if(x > t->element)
		insert(x, t->right);
	else;//do nothing
}

4.3.4 remove

一旦发现需要删除的结点,考虑几种情况

  1. 结点是一片树叶,可以立即被删除。
  2. 结点有一个儿子,该结点可以在其父结点调整它的链以绕过该节点然后被删除。
  3. 结点有两个儿子,用其右子树的最小的数据代替该结点的数据并递归地删除那个结点。

4.4 AVL树

AVL(Adelson-Velskii and Landis)树是带有平衡条件的二叉查找树。这个平衡条件必须要容易保持,而且必须保证树的深度是O(logN)。
一棵AVL树是其每个结点的左子树和右子树的高度最多差1的二叉查找树(空树的高度定义为-1)

在高度为h的AVL树中,最少节点数S(h) = S(h-1) + S(h-2) + 1给出,对于h=0,S(h) = 1;h=1, S(h) = 2.
注意:在AVL树中,比较难的是插入操作,因为存在着破坏平衡性的危险,此时把必须重新平衡的结点叫做α。出现四种不平衡的情况。

  1. 对α的左儿子的左子树进行一次插入
  2. 对α的左儿子的右子树进行一次插入
  3. 对α的右儿子的左子树进行一次插入(2的镜像)
  4. 对α的右儿子的右子树进行一次插入(1的镜像)
    第一种情况是插入发生在外边的情况(左-左和右-右),可以通过对树的一次单旋转完成调整。
    第二种情况是插入发生在内部的情况(左-右和右-左),通过稍微复杂得双旋转完成调整。

4.4.1 单旋转

下图显示了单旋转如何调整情形1和情形4。
情形1:结点k2不满足AVL平衡性质(X是两层深度),左子树比右子树深两层。Y不可能与新X在同一层上,那样可在插入之前就失去了平衡,Y也不能和Z在同一层,那样k1就是在通向根的路径上破坏AVL平衡条件的第一个结点。所以,把X上移一层,Z下移一层。重新安排结点形成一颗等价的树。
在这里插入图片描述
在这里插入图片描述

4.4.2 双旋转

对于情形2和3,树Y太深,单旋转没有降低深度,所以通过双旋转完成。
在这里插入图片描述
在这里插入图片描述

4.5 伸展树

伸展树(splay tree),它保证从空树开始任意连续M次对树的操作最多花费O(MlogN)的时间。一般来说,当M次操作的序列总的最坏情形运行时间为O(Mf(N))时,我们就说它的摊还时间为O(f(N))。
伸展树的基本思想为,当一个节点被访问后,它就要经过一系列AVL树的旋转被推倒根上。另外,伸展树不要求保留高度或平衡信息,因此可以在某种程度上节省空间并简化代码。

4.5.2 伸展

伸展的方法类似于旋转。仍然从底向上沿着访问路径旋转。令X是在访问路径上的一个非根结点,在这个路径上实施旋转操作。如果X的父结点是树根,只要旋转X和树根,这就是沿着访问路径上的最后的旋转。否则,X就有父亲(P)和祖父(G),存在两种情况以及对称的情形要考虑。
第一种之字形,此时X是右儿子,P是左儿子。执行一次就像AVL双旋转那样的双旋转。
第二种一字型,X和P都是左儿子或者右儿子。在这种情况下,左边的树换成右边的。

4.6 树的遍历

递归方法进行树的遍历

void printTree(ostream & out = cout)const
{
	if( isEmpty())
		out<<"Empty tree"<<endl;
	else
		printTree(root,out);
}
/* Internal method to print a subtree rooted at t in sorted order*/
void printTree(BinaryNode *t, ostream & out )const
{
	if(t != NULL)
	{
		printTree(t->left, out);
		out<< t->element <<endl;
		printTree(t->right, out);
	}
}

中序遍历(inorder traversal)一般是先处理左子树,然后是当前的结点,最后处理右子树。其总的运行时间是O(N)。
后序遍历(postorder traversal)先处理两个子树然后才能处理当前结点,总的运行时间也是O(N)

/* Internal method to compute the height of a subtree rooted at t */
int height(Binary *t)
{
	if(t== NULL)
		return -1;
	else
		return 1 + max( height (t->left ), height(t->right));
}

前序遍历(preorder traversal)当前结点在其儿子结点之前处理。
层序遍历(level-order traversal)所有深度为d的结点要在深度为d+1的结点之前进行处理,用的较少。不是用递归实施,而是用到队列。
共同的思想:首先处理NULL的情形,

4.7 B树

阶为M的B树是一颗具有下列结构特性的树
(1)数据项存储在树叶上
(2)非叶结点存储直到M-1个键,以指示搜索的方向;键i代表子树i+1中最小的键。
(3)树的根或者是一片树叶,或者其儿子树在2和M之间
(4)除根外,所有非树叶结点的儿子树在M/2(向上取整)和M之间
(5)所有的树叶都在相同深度上并有L/2(向上取整)和L之间个数据项,稍后描述L的确定。

4.8 标准库中的set和map

4.8.1 set

set是一个排序后的容器,该容器不允许重复。包括begin, end, size, empty等方法。特有的操作是高效的插入、删除和执行基本查找。

插入insert
STL定义了一个名为pair的类模板,该类模板比struct多两个用来访问pair的两项的成员first和second。例如下面两个

pair<iterator, bool> insert (const Object & x);
pair<iterator, bool> insert (iterator hint, const Object & x);

单参数的insert执行如上,双参数insert允许对x将要插入的位置的线索的说明,此时插入很快,可以看为O(1).

删除erase
有几个版本的

int erase( const Object & x);
iterator erase( iterator itr);
iterator erase( iterator start, iterator end);

第一个单参数删除x,返回删除的元素的个数(0或者1)。第二个单参数的执行与在vector和list中一样,删除由iterator指定的位置的对象,返回指向下一个位置的元素。第三个双参数的执行与vector和list一样,从start开始从end结束的所有的项(不包括end)

查找操作

iterator find(const Object &x ) const;

4.8.2 map

map用来存储排序后的由键和值组成的项的集合。键必须唯一,但是多个键可以对应同一个值。因此,值不需要唯一。在map中的键保持逻辑排序后的顺序。支持begin, end, size, empty操作。也支持insert, find, erase

4.8.3 set和map的实现

底层实现是平衡二叉查找树。常常使用自顶向下红黑树。

内含资源如下: 1.基本数据结构 1.1.Array ........... 动态数组 1.2.LinkedList ... 链表 1.3.BST .............. 二分搜索树 1.4.MapBST ..... 二分搜索树(用于实现映射) 1.5.AVLTree ...... AVL树 2.接口 2.1.Queue ........... 队列接口 2.2.Stack .............. 栈接口 2.3.Set .................. 集合接口 2.4.Map ............... 映射接口 2.5.Merger .......... 自定义函数接口 2.6.UnionFind ..... 并查集接口 3.高级数据结构 3.1.ArrayQueue .......................... 队列_基于动态数组实现 3.2.LinkedListQueue .................. 队列__基于链表实现 3.3.LoopQueue ........................... 循环队列_基于动态数组实现 3.4.PriorityQueue ....................... 优先队列_基于最大二叉堆实现 3.5.ArrayPriorityQueue ............. 优先队列_基于动态数组实现 3.6.LinkedListPriorityQueue ..... 优先队列_基于链表实现 3.7.ArrayStack ............................. 栈_基于动态数组实现 3.8.LinkedListStack ..................... 栈_基于链表实现 3.9.BSTSet ..................................... 集合_基于二分搜索树实现 3.10.LinkedListSet ....................... 集合_基于链表实现 3.11.BSTMap ................................ 映射_基于二分搜索树实现 3.12.AVLTreeMap ....................... 映射_ 基于AVL树实现 3.13.LinkedListMap .................... 映射_基于链表实现 3.14.MaxHeap ............................. 最大二叉堆 3.15.SegmentTree ...................... 线段树 3.16.Trie ......................................... 字典树 3.17.QuickFind ............................ 并查集_基于数组实现 3.18.QuickUnion ......................... 并查集_基于树思想实现
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值