Download presentation
Presentation is loading. Please wait.
1
第七章 集合与搜索 集合及其表示 并查集 静态搜索表 二叉搜索树 AVL树
2
集合及其表示 集合基本概念 集合是成员(对象或元素)的一个群集。集合中的成员可以是原子(单元素),也可以是集合。 集合的成员必须互不相同。
在算法与数据结构中所遇到的集合,其单元素通常是整数、字符、字符串或指针,且同一集合中所有成员具有相同的数据类型。
3
colour = { red, orange, yellow, green, black, blue, purple, white }
name = { “An”, “Cao”, “Liu”, “Ma”, “Peng”, “Wang”, “zhang” } 集合中的成员一般是无序的,但在表示它时,常写在一个序列里。 常设定集合中的单元素具有线性有序关系,此关系可记作“<”,表示“优先于”。 整数、字符和字符串都有一个自然线性顺序。指针也可依据其在序列中安排的位置给予一个线性顺序。
4
集合(Set)的抽象数据类型 template <class Type> class Set {
B A B A B AB 或 A+B AB 或 AB A-B 集合(Set)的抽象数据类型 template <class Type> class Set { Set (int MaxSetSize) : MaxSize(MaxSetSize); void MakeEmpty (Set& s); int AddMember (Set& s, const Type x); int DelMember (Set& s, const Type& x);
5
void Assign (Set& s1, Set& s2);
void Union (Set& s1, Set& s2); void Intersection (Set& s1, Set& s2); void Difference (Set& s1, Set& s2); int Contains (Set& s, const Type& x); int Equal (Set& s1, Set& s2); int SubSet (Set& s1, Set& s2); }
6
当集合是全集合{ 0, 1, 2, …, n }的一个子集,且 n是不大的整数时,可用位(0, 1)向量来实现集合。
用位向量实现集合抽象数据类型 当集合是全集合{ 0, 1, 2, …, n }的一个子集,且 n是不大的整数时,可用位(0, 1)向量来实现集合。 当全集合是由有限的可枚举的成员组成的集合时,可建立全集合成员与整数 0, 1, 2, …的一一对应关系,用位向量来表示该集合的子集。
7
集合的位向量(bit Vector)类的定义
#include <assert.h> const int DefaultSize = 100; class Set { private: int * bitVector; int MaxSize; public: Set ( int MaxSetSize = DefaultSize ); ~Set ( ) { delete [ ] bitVector; }
8
void MakeEmpty ( ) { for ( int i = 0; i < MaxSize; i++ ) bitVector[i] = 0; } int GetMember ( const int x ) { return x >= 0 && x < MaxSize ? bitVector[x] : -1; } int AddMember ( const int x ); int DelMember ( const int x ); Set& operator = ( Set& right ); Set& operator + ( Set& right );
9
Set& operator * ( Set& right );
int Contains ( const int x ); int SubSet ( Set& right ); int operator == ( Set& right ); }; 使用示例 Set s1, s2, s3, s4, s5; int index, equal; for ( int k = 0; k < 10; k++ ) //集合赋值 { s1.AddMember( k ); s2.AddMember( k +7 ); } // s1 : { 0, 1, 2, …, 9 }, s2 : { 7, 8, 9, …, 16 }
10
s3 = s1+s2; //求s1与s2的并 { 0, 1, …, 16 } s4 = s1*s2; //求s1与s2的交 { 7, 8, 9 } s5 = s1-s2; //求s1与s2的差 { 0, 1, …, 6 } // s1 : { 0, 1, 2, …, 9 } index = s1.SubSet ( s4 ); //s4在s1中首次匹配 cout << index << endl; //位置,index = 7 // s1 : { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 } // s4 : { 7, 8, 9 } equal = s1 == s2; //集合s1与s2比较相等 cout << equal << endl; //为0, 两集合不等
11
用位向量实现集合时部分操作的实现 Set :: Set (int MaxSetSize) : MaxSize (MaxSetSize) { assert ( MaxSize > 0 ); bitVector = new int [MaxSize]; assert ( bitVector != 0 ); for ( int i = 0; i < MaxSize; i++ ) bitVector[i] = 0; }
12
int Set :: Addmember ( const int x ) {
assert ( x >= 0 && x < MaxSize ); if ( ! bitVector[x] ) { bitVector[x] = 1; return 1; } return 0; } int Set :: DelMember ( const int x ) { if ( bitVector[x] ) { bitVector[x] = 0; return 1; }
13
Set& Set :: operator = ( Set& right ) {
assert ( MaxSize == right.MaxSize ); for ( int i = 0; i < MaxSize; i++ ) bitVector[i] = right.bitVector[i]; return *this; } int Set :: Contains ( const int x ) { assert ( x >= 0 && x < MaxSize ); return bitVector[x];
14
Set& Set :: operator + ( Set& right ) {
assert ( MaxSize == right.MaxSize ); Set temp ( MaxSize ); for ( int i = 0; i < MaxSize; i++ ) temp.bitVector[i] = bitVector[i] || right.bitVector[i]; return temp; } this right temp
15
Set& Set :: operator * ( Set & right ) {
assert ( MaxSize == right.MaxSize ); Set temp ( MaxSize ); for ( int i = 0; i < MaxSize; i++) temp.bitVector[i] = bitVector[i] && right.bitVector[i]; return temp; } this right temp
16
Set& Set :: operator - ( Set & right ) {
assert ( MaxSize == right.MaxSize ); Set temp ( MaxSize ); for ( int i = 0; i < MaxSize; i++ ) temp. bitVector[i] = bitVector[i] && !right.bitVector[i]; return temp; } this right temp
17
int Set :: operator == ( Set& right ) {
assert ( MaxSize == right.MaxSize ); for ( int i = 0; i < MaxSize; i++) if ( bitVector[i] != right.bitVector[i] ) return 0; return 1; } this right i
18
int Set :: SubSet (Set& right ) { assert ( MaxSize == right.MaxSize );
for ( int i = 0; i < MaxSize; i++) if (bitVector[i] && ! right.bitVector[i]) return 0; return 1; } this right i
19
用有序链表实现集合的抽象数据类型 用带表头结点的有序链表表示集合 用有序链表来表示集合时,链表中的每个结点表示集合的一个成员。
first first 08 17 23 35 49 72 用带表头结点的有序链表表示集合 用有序链表来表示集合时,链表中的每个结点表示集合的一个成员。 各结点所表示的成员 e0, e1, …, en 在链表中按升序排列,即 e0 < e1 < … < en。 集合成员可以无限增加。因此,用有序链表可以表示无穷全集合的子集。
20
集合的有序链表类的定义 template <class Type> class SetList;
template <class Type> class SetNode { friend class SetList<Type>; public: SetNode (const Type& item ) : data (item), link (NULL); private: Type data; SetNode<Type> * link; };
21
template <class Type> class SetList {
private: SetNode<Type> *first, *last; //有序链表的表头指针, 表尾指针 public: SetList ( ) //构造函数 { first = last = new SetNode<Type>(0); } ~SetList ( ) { MakeEmpty( ); delete first; } void MakeEmpty ( ); //置空集合 int AddMember ( const Type& x ); int DelMember ( const Type& x );
22
SetList<Type> & operator = ( SetList<Type>&
right ); //将集合right赋给集合this SetList<Type> & operator + ( SetList<Type>& right ); //求集合this与集合right的并 SetList<Type> & operator * ( SetList<Type>& right ); //求集合this与集合right的交 SetList<Type> & operator - ( SetList<Type>& right ); //求集合this与集合right的差 int Contains ( const Type& x ); //判x是否集合的成员 int operator == ( SetList<Type>& right ); //判集合this与集合right相等
23
集合的搜索、加入和删除操作 Type& Min ( ); //返回集合中最小元素值 Type& Max ( ); //返回集合中最大元素值
} 集合的搜索、加入和删除操作 template <class Type> int SetList<Type> :: Contains ( const Type& x ) { //若x是集合成员, 则函数返回1, 否则返回0 SetNode<Type> * temp = first->link; while ( temp != NULL && temp->data < x ) temp = temp->link; //循链搜索
24
if ( temp != NULL && temp->data == x )
return 1; //找到, 返回1 else return 0; //未找到, 返回0 } template <class Type> int SetList<Type> :: AddMember ( const Type& x ) { SetNode<Type> *p = first->link, *q = first; while ( p != NULL && p->data < x ) { q = p; p = p->link; } //循链扫描 if ( p != NULL && p->data == x ) return 0; //集合中已有此元素
25
SetNode<Type> * s = new SetNode (x);
s->link = p; q->link = s; //链入 if ( p == NULL ) last = s; //链到链尾时改链尾指针 return 1; } template <class Type> int SetList<Type> :: DelMember ( const Type& x ) { SetNode<Type> * p = first->link, * q = first; while ( p != NULL && p->data < x ) { q = p; p = p->link; } //循链扫描
26
用有序链表表示集合时的几个重载函数 if ( p != NULL && p->data == x ) { //找到
q->link = p->link; //重新链接 if ( p == last ) last = q; //删去链尾结点时改链尾指针 delete p; return 1; //删除含x结点 } else return 0; //集合中无此元素 用有序链表表示集合时的几个重载函数 template <class Type> SetList<Type>& SetList<Type> ::
27
operator = ( SetList<Type>& right ) {
//复制集合right到this SetNode<Type> * pb = right.first->link; //源 SetNode<Type> * pa = first = //目标 new SetNode<Type>; while ( pb != NULL ) { //逐个结点复制 pa->link = new SetNode<Type>(pb->data); pa = pa->link; pb = pb->link; } pa->link = NULL; last = pa; return *this; //目标链表收尾
28
//求集合this与集合right的并, 结果通过临时 //集合temp返回,this集合与right集合不变。
template <class Type> SetList<Type>& SetList<Type> :: operator + ( SetList<Type>& right ) { pa first 17 35 right.first 08 17 23 49 pb pc temp.first 08 17 23 23 35 49
29
SetNode<Type> *pb = right.first->link;
SetNode<Type> *pa = first->link; SetList<Type> temp; SetNode<Type> *pc = temp.first; while ( pa != NULL && pb != NULL ) { if ( pa->data == pb->data ) { //共有元素 pc->link=new SetNode<Type>(pa->data); pa = pa->link; pb = pb->link; } else if ( pa->data < pb->data ) { pa = pa->link; } else { //pa->data > pb->data
30
pc->link=new SetNode<Type>(pb->data);
pb = pb->link; } pc = pc->link; if ( pa != NULL ) pb = pa; //pb指未扫完集合 while ( pb != NULL ) { //向结果链逐个复制 pc->link = new SetNode<Type>(pb->data); pc = pc->link; pb = pb->link; pc->link = NULL; temp.last = pc; //链表收尾 return temp;
31
template <class Type> int SetList<Type> ::
operator == ( SetList<Type> & right ) { SetNode<Type> * pb = right.first->link; SetNode<Type> * pa = first->link; while ( pa != NULL && pb != NULL ) if ( pa->data == pb->data ) //相等, 继续 { pa = pa->link; pb = pb->link; } else return 0; //不等时中途退出, 返回0 if ( pa != NULL || pb != NULL ) return 0; //链不等长时, 返回0 return 1; }
32
并查集 (Union-Find Sets) 并查集支持以下三种操作: Union (Root1, Root2) //并操作
Find (x) //搜索操作 UFSets (s) //构造函数 对于并查集来说,每个集合用一棵树表示。 为此,采用树的双亲表示作为集合存储表示。集合元素的编号从0到 n-1。其中 n 是最大元素个数。
33
在双亲表示中,第 i 个数组元素代表包含集合元素 i 的树结点。根结点的双亲为-1,表示集合中的元素个数。
在同一棵树上所有结点所代表的集合元素在同一个子集合中。 为此,需要有两个映射: 集合元素到存放该元素名的树结点间的对应; 集合名到表示该集合的树的根结点间的对应。
34
为简化讨论,忽略实际的集合名,仅用表示集合的树的根来标识集合。
设 S1= {0, 6, 7, 8 }, S2= { 1, 4, 9 }, S3= { 2, 3, 5 } 集合名 指针 0 S1 1 S2 2 S3 4 2 6 7 8 1 9 3 5 为简化讨论,忽略实际的集合名,仅用表示集合的树的根来标识集合。
35
初始时, 用构造函数 UFSets(s) 构造一个森林, 每棵树只有一个结点, 表示集合中各元素自成一个子集合。
用 Find(i) 寻找集合元素 i 的根。如果有两个集合元素 i 和 j, Find(i) == Find(j), 表明这两个元素在同一个集合中。 如果两个集合元素 i 和 j 不在同一个集合中,可用 Union(i, j) 将它们合并到一个集合中。
36
下标 parent 集合 S1, S2 和 S3 的双亲表示 4 6 7 8 4 1 9 1 9 6 7 8 S1 S2的可能的表示方法
37
const int DefaultSize = 10;
class UFSets { //并查集类定义 private: int *parent; //集合元素数组 int size; //集合元素的数目 public: UFSets ( int s = DefaultSize ); //构造函数 ~UFSets ( ) { delete [ ] parent; } //析构函数 const UFSets& operator = (UFSets& right); //重载函数:集合赋值 void Union ( int Root1, int Root2 ); //基本例程 : 两个子集合合并
38
int Find ( int x ); //基本例程 : 搜寻集合x的根 void UnionByHeight ( int Root1, int Root2 ); //改进例程 : 压缩高度的合并算法 }; UFSets :: UFSets ( int s ) { //构造函数 size = s; //集合元素个数 parent = new int [size]; //双亲指针数组 for ( int i = 0; i < size; i++ ) parent[i] = -1; //每一个自成一个单元素集合 }
39
并查集操作的算法 查找 = -5 < 0 结束 Find (4) Find (3) = 3 Find (2) =2
1 2 3 4 -5 1 2 3 Find (4) Find (3) = 3 Find (2) =2 Find (1) = 1 Find (0) = 0 = -5 < 0 结束
40
int UFSets :: Find ( int x ) { if ( parent [x] < 0 ) return x;
parent Parent[0] =-5 Parent[4] =3 Parent[1] =0 Parent[3] =2 Parent[2] =1 int UFSets :: Find ( int x ) { if ( parent [x] < 0 ) return x; else return Find ( parent [x] ); }
41
void UFSets :: Union ( int Root1, int Root2 ) {
//求两个不相交集合Root1与Root2的并 parent[Root2] = Root1; //将Root2连接到Root1下面 } Find和Union操作性能不好。假设最初 n 个元素构成 n 棵树组成的森林,parent[i] = -1。做处理Union(n-2, n-1), …, Union(1, 2), Union(0, 1)后,将产生退化的树。
42
合并 Union(3,4) Union(2,3) Union(1,2) Union(0,1) 1 2 3 4 -1 -1 -1 -1 -1
1 2 3 4 -1 -1 -1 -1 -1 -5 3 2 1 1 -2 -3 -4 2 2 4 3 3 2 1 1 Union(3,4) 4 3 3 3 2 2 Union(2,3) 4 4 3 3 Union(1,2) Union(0,1)
43
执行一次Union操作所需时间是 O(1),n-1次Union操作所需时间是O(n)。
若再执行Find(0), Find(1), …, Find(n-1), 若被搜索的元素为 i,完成 Find(i) 操作需要时间为O(i),完成 n 次搜索需要的总时间将达到 改进的方法 按树的结点个数合并 按树的高度合并 压缩元素的路径长度
44
按树结点个数合并 结点个数多的树的根结点作根 Union(2, 0) 1 2 3 4 5 6 -1 -1 -1 -1 -1 -1 -1 2
1 2 3 4 5 6 -1 -1 -1 -1 -1 -1 -1 Union(2, 0) 2 2 -5 -2 -7 1 3 5 1 3 2 2 2 2 2 6 5 4 6 4 3 3 3 3
45
WeightedUnion ( int Root1, int Root2 ) { //按Union的加权规则改进的算法
void UFSets :: WeightedUnion ( int Root1, int Root2 ) { //按Union的加权规则改进的算法 int temp = parent[Root1] + parent[Root2]; if ( parent[Root2] < parent[Root1] ) { parent[Root1] = Root2; //Root2中结点数多 parent[Root2] = temp; //Root1指向Root2 } else { parent[Root2] = Root1; //Root1中结点数多 parent[Root1] = temp; //Root2指向Root1
46
按树高度合并 高度高的树的根结点作根 Union(2, 0) 1 2 3 4 5 6 -0 -0 -0 -0 -0 -0 -0 2 2 -2
1 2 3 4 5 6 -0 -0 -0 -0 -0 -0 -0 Union(2, 0) 2 2 -2 -1 -2 1 3 5 1 3 2 2 2 2 2 6 5 4 6 4 3 3 3 3
47
Union操作的折叠规则 为进一步改进树的性能,可以使用如下折叠规则来“压缩路径”。即:如果 j 是从 i 到根的路径上的一个结点,且 parent[j] root[j], 则把 parent[j] 置为 root[i]。 6 7 8 6 7 9 5 8 1 9 1 3 从 i = 5 压缩路径 3 5
48
int UFSets :: CollapsingFind ( int i ) { //使用折叠规则的搜索算法 //让 j 循双亲指针走到根
for ( int j = i; parent[j] >= 0; j = parent[j] ); //让 j 循双亲指针走到根 while ( i != j ) { //换 parent[i] 到 j int temp = parent[i]; parent[i] = j; i = temp; } return j;
49
静态搜索表 搜索(Search)的概念 所谓搜索,就是在数据集合中寻找满足某 种条件的数据对象。 搜索的结果通常有两种可能:
搜索成功,即找到满足条件的数据对象。 这时,作为结果, 可报告该对象在结构中 的位置, 还可给出该对象中的具体信息。 搜索不成功,或搜索失败。作为结果, 应报告一些信息, 如失败标志、位置等。
50
通常称用于搜索的数据集合为搜索结构,它是由同一数据类型的对象(或记录)组成。
在每个对象中有若干属性,其中有一个属性,其值可唯一地标识这个对象。称为关键码。使用基于关键码的搜索,搜索结果应是唯一的。但在实际应用时,搜索条件是多方面的,可以使用基于属性的搜索方法,但搜索结果可能不唯一。 实施搜索时有两种不同的环境。 静态环境,搜索结构在插入和删除等操作的前后不发生改变。 静态搜索表
51
静态搜索表 动态环境,为保持较高的搜索效率, 搜索结构在执行插入和删除等操作的前后将自动进行调整,结构可能发生变化。 动态搜索表
template <class Type> class dataList; template <class Type> class Node { friend class dataList<Type>; private: Type key;
52
other; public: Node ( const Type& value ) : key (value) { } Type getKey ( ) const { return key; } void setKey ( Type k ) { key = k; } }; template <class Type> class dataList { protected: Node <Type> *Element; //数据存储数组 int ArraySize, CurrentSize; //数组最大长度和当前长度
53
public: dataList ( int sz = 10 ) : ArraySize (sz) { Element = new Node <Type> [sz]; } virtual ~dataList ( ) { delete [ ] Element; } friend ostream& operator << (ostream& OutStream, const dataList<Type>& OutList ); friend istream & operator >> ( istream& InStream, dataList<Type>& InList ); };
54
template <class Type> class searchList :
public dataList<Type> { //作为派生类的静态搜索表的类定义 public: searchList ( int sz = 10 ) : dataList<Type> (sz) { } virtual ~searchList ( ) { } virtual int Search ( const Type & x ) const; }; template <class Type> istream& operator >> ( istream& InStream, dataList<Type>& InList )
55
{ //从输入流对象InStream输入数据到表InList中 cout << “输入数组当前大小 : ”; Instream >> InList.CurrentSize; cout << “输入数组元素值 : \n”; for ( int i = 0; i < InList.CurrentSize; i++ ) { cout << “元素 ” << i << “ : ”; InStream >> InList.Element[i]; } return InStream;
56
template <class Type> ostream& operator <<
( ostream& OutStream, const dataList<Type> & OutList ) { //将表OutList内容输出到输出流对象OutStream OutStream << “数组内容 : \n”; for ( int i = 0; i < OutList.CurrentSize; i++ ) OutStream << OutList.Element[i] << ‘ ’; OutStream << endl; OutStream << “数组当前大小 : ” << OutList.CurrentSize << endl; return OutStream; }
57
衡量一个搜索算法的时间效率的标准是:在搜索过程中关键码的平均比较次数或平均读写磁盘次数(只适合于外部搜索),也称为平均搜索长度ASL(Average Search Length),通常它是搜索结构中对象总数 n 或文件结构中物理块总数 n 的函数。 在静态搜索表中, 利用数组元素的下标作为数据对象的存放地址。搜索算法根据给定值x, 在数组中进行搜索。直到找到x在数组中的位置或可确定在数组中找不到x为止。 另外衡量一个搜索算法还要考虑算法所需要的存储量和算法的复杂性等问题。
58
顺序搜索 (Sequential Search)
所谓顺序搜索, 又称线性搜索, 主要用于在线性结构中进行搜索。 设若表中有 CurrentSize 个对象,则顺序搜索从表的先端开始, 顺序用各对象的关键码与给定值x进行比较, 直到找到与其值相等的对象, 则搜索成功; 给出该对象在表中的位置。 若整个表都已检测完仍未找到关键码与x相等的对象, 则搜索失败。给出失败信息。
59
template <class Type>
int searchList<Type> :: Search ( const Type& x ) const { //顺序搜索关键码为x的数据对象, 第0号位置 //作为控制搜索自动结束的“监视哨”使用 Element[0].key = x; int i = CurrentSize; //将x送0号位置设置监视哨 while ( Element[i].key != x ) i-- ; //从后向前顺序搜索 return i; }
60
const int Size = 10; main ( ) { //顺序搜索的主过程 searchList<float> List1 (Size); //定义搜索表 float Target; int Location; cin >> List1; cout << List1; //输入List1 cout << “搜索浮点数 : ”; cin >> Target; if ( ( Location = List1.search ( Target ) ) != 0 ) cout << “找到位置在 ” << Location << endl; //搜索成功, 输出找到的位置 else cout << “ 没有找到.\n”; //搜索失败 }
61
顺序搜索的平均搜索长度 设搜索第 i 个元素的概率为 pi, 搜索到第 i 个元素所需比较次数为 ci , 则搜索成功的平均搜索长度:
在顺序搜索并设置“监视哨”情形: ci = n- i +1, i = n, n-1, , 1,因此
62
在等概率情形,pi = 1/n, i = 1, 2, , n。 在搜索不成功情形,ASLunsucc = n+1. template <class Type> int SearchList<Type> :: Search ( const Type& x, int& loc ) const { //顺序搜索的递归算法 if ( loc >= CurrentSize ) return -1; else if ( Element[loc].getKey( ) == x ) return loc; else return Search ( x, loc+1 ); }
63
基于有序顺序表的顺序搜索算法 template <class Type> int searchList<Type> :: Search ( const Type& x ) const { //顺序搜索关键码为x的数据对象 for ( int i = 0; i < CurrentSize; i++ ) if ( Element[i].key == x ) return i; //成功 else if ( Element[I].key > x ) break; return -1; //顺序搜索失败, 返回失败信息 }
64
( 10, 20, 30, 40, 50, 60 ) 有序顺序表的顺序搜索 10 < > = 20 < > = 30
65
基于有序顺序表的折半搜索 设n个对象存放在一个有序顺序表中,并按其关键码从小到大排好顺序。
折半搜索时, 先求位于搜索区间正中的对象的下标mid,用其关键码与给定值x比较: Element[mid].key == x,搜索成功; Element[mid].key > x,把搜索区间缩小 到表的前半部分,继续折半搜索; Element[mid].key < x,把搜索区间缩小 到表的后半部分,继续折半搜索。 如果搜索区间已缩小到一个对象,仍未找到想要搜索的对象,则搜索失败。
66
6 搜索 low mid high 6 low mid high 6 5 6 6 low mid high 搜索成功的例子
67
5 搜索 low mid high 5 low mid high 5 5 5 6 low mid high 搜索失败的例子
68
template <class Type> class orderedList :
public dataList<Type> { //有序表的类定义,继承了数据表 public: orderedList (int sz = 10) : dataList<Type> (sz) { } virtual ~orderedList ( ) { } virtual int Search ( const Type& x ) const; }; template <class Type> int orderedList<Type> :: //折半搜索算法
69
BinarySearch ( const Type & x, const int low, const int high ) const {
//折半搜索的递归算法 int mid = -1; if ( low <= high ) { mid = ( low + high ) / 2; if ( Element[mid].key < x ) mid = BinarySearch ( x, mid +1, high ); else if ( Element[mid].key > x ) mid = BinarySearch ( x, low, mid -1 ); } return mid;
70
template<class Type> int orderedList <Type>::
BinarySearch ( const Type & x ) const { //折半搜索的迭代算法 int high = CurrentSize-1, low = 0, mid; while ( low <= high ) { mid = ( low + high ) / 2; if ( Element[mid].key < x ) low = mid + 1; //右缩搜索区间 else if ( Element[mid].key ) > x ) high = mid - 1; //左缩搜索区间 else return mid; //搜索成功 } return -1; //搜索失败
71
搜索不成功时检测指针停留在某个外结点(失败结点)。
搜索成功时检测指针停留在树中某个结点。 搜索不成功时检测指针停留在某个外结点(失败结点)。 35 搜索45 15 45 10 25 50 20 30 搜索22
72
( 10, 20, 30, 40, 50, 60 ) 有序顺序表的折半搜索的判定树 30 < > = 10 50 <
(2*1+3*6)/7 30 < > = 10 50 < > < > = = 20 40 60 < = > < = > < = >
73
折半搜索性能分析 若设 n = 2h-1,则描述折半搜索的判定树是高度为 h-1 的满二叉树。
2h = n+1, h = log2(n+1)。 第0层结点有1个, 搜索第0层结点要比较1次; 第1层结点有2个, 搜索第1层结点要比较2次; …, 第 i (0 i h) 层结点有 2i 个, 搜索第 i 层结点要比较 i+1次,…。 假定每个结点的搜索概率相等,即 pi = 1/n,则搜索成功的平均搜索长度为
74
可以用归纳法证明 这样
75
二叉搜索树 ( Binary Search Tree )
定义 二叉搜索树或者是一棵空树,或者是具有下列性质的二叉树: 每个结点都有一个作为搜索依据的关键码(key),所有结点的关键码互不相同。 左子树(如果存在)上所有结点的关键码都小于根结点的关键码。 右子树(如果存在)上所有结点的关键码都大于根结点的关键码。 左子树和右子树也是二叉搜索树。
76
二叉搜索树例 结点左子树上所有关键码小于结点关键码 右子树上所有关键码大于结点关键码
35 结点左子树上所有关键码小于结点关键码 右子树上所有关键码大于结点关键码 15 45 10 25 40 50 20 30 注意:若从根结点到某个叶子结点有一条路径,路径左边的结点的关键码不一定小于路径上的结点的关键码。
77
n 个结点的二叉搜索树的数目 {123} {132} {213} {231} {312} {321} 【例】3 个结点的二叉搜索树 1 1
{123} {132} {213} {231} {312} {321} 1 1 2 3 3 2 3 1 3 1 2 3 2 2 1
78
如果对一棵二叉搜索树进行中序遍历,可以按从小到大的顺序,将各结点关键码排列起来,所以也称二叉搜索树为二叉排序树。
二叉搜索树的类定义 #include <iostream.h> template <class Type> class BST; template <class Type> Class BstNode <Type>: public BinTreeNode { friend class BST <Type>;
79
protected: Type data; BstNode<Type> *leftChild, *rightChild; }; public: BstNode ( ) : leftChild (NULL), rightChild (NULL) { } //构造函数 BstNode ( const Type d ) : data (d), leftChild (NULL), rightChild (NULL) { } BstNode ( const Type d = 0, BstNode<Type> * L = NULL, BstNode<Type> *R = NULL) : data (d), leftChild (L), rightChild (R) { }
80
Type GetData ( ) { return data; }
~BstNode ( ) { } //析构函数 }; template <class Type> class BST : public BinaryTree<Type> { private: BstNode<Type> *root; //根指针 Type RefValue; //数据输入停止的标志 void MakeEmpty ( BstNode<Type> *& ptr ); void Insert ( Type& x, BstNode<Type> *& ptr ); //插入
81
void Remove ( Type& x, BstNode<Type> *& ptr ); //删除 void PrintTree ( BstNode<Type> * ptr ) const; BstNode<Type> *Copy ( const BstNode<Type> * ptr ); //复制 BstNode<Type> *Find ( Type& x, BstNode<Type> * ptr ); //搜索 BstNode<Type> *Min ( BstNode<Type> * ptr ) const; //求最小 BstNode<Type> *Max ( BstNode<Type> * ptr ) const; //求最大 public:
82
BST ( ) : root (NULL) { } //构造函数
BST ( Type value ) : RefValue (value), root (NULL) { } //构造函数 ~BST ( ); //析构函数 const BST& operator = ( const BST& Value ); void MakeEmpty ( ) { MakeEmpty ( root ); root = NULL; } void PrintTree ( ) const { PrintTree ( root ); } int Find ( const Type& x ) //搜索元素 { return Find ( x, root ) != NULL; } Type Min ( return Min ( root )->data ); Type Max ( return Max ( root )->data );
83
二叉搜索树上的搜索 void Insert ( const Type& x )
{ Insert ( x, root ); } //插入新元素 void Remove (const Type& x ) { Remove ( x, root ); } //删除含x的结点 } 二叉搜索树上的搜索 在二叉搜索树上进行搜索,是一个从根结点开始,沿某一个分支逐层向下进行比较判等的过程。它可以是一个递归的过程。
84
假设想要在二叉搜索树中搜索关键码为x的元素,搜索过程从根结点开始。
如果根指针为NULL,则搜索不成功;否则用给定值x与根结点的关键码进行比较: 如果给定值等于根结点的关键码,则搜索成功。 如果给定值小于根结点的关键码,则继续递归搜索根结点的左子树; 否则,递归搜索根结点的右子树。
85
搜索28 搜索失败 搜索45 搜索成功 35 15 45 10 25 40 50 20 30
86
template <class Type>
BstNode<Type> * BST<Type> :: Find ( const Type& x, BstNode<Type> * ptr ) const { //二叉搜索树的递归的搜索算法 if ( ptr == NULL ) return NULL; //搜索失败 else if ( x < ptr->data ) //在左子树搜索 return Find ( x, ptr->leftChild ); else if ( x > ptr->data ) //在右子树搜索 return Find ( x, ptr->rightChild ); else return ptr; //相等,搜索成功 }
87
template <class Type>
BstNode <Type> * BST <Type> :: Find (const Type& x, BstNode<Type> * ptr ) const { //二叉搜索树的迭代的搜索算法 if ( ptr != NULL ) { BstNode<Type> * temp = ptr; //从根搜索 while ( temp != NULL ) { if ( temp->data == x ) return temp; if ( temp->data < x ) temp = temp->rightChild; //右子树 else temp = temp->leftChild; //左子树 }
88
二叉搜索树的插入 } return NULL; //搜索失败 每次结点的插入,都要从根结点出发搜索插入位置,然后把新结点作为叶结点插入。
35 二叉搜索树的插入 15 45 每次结点的插入,都要从根结点出发搜索插入位置,然后把新结点作为叶结点插入。 10 25 40 50 20 30 插入新结点28 28
89
递归的二叉搜索树插入算法 为了向二叉搜索树中插入一个新元素,必须先检查这个元素是否在树中已经存在。
在插入之前,先使用搜索算法在树中检查要插入元素有还是没有。 搜索成功: 树中已有这个元素,不再插入。 搜索不成功: 树中原来没有关键码等于给定值的结点,把新元素加到搜索操作停止的地方。 递归的二叉搜索树插入算法 template <class Type> void BST<Type>::
90
BstNode<Type> * & ptr) {
Insert ( const Type& x, BstNode<Type> * & ptr) { if ( ptr == NULL ) { //空二叉树 ptr = new BstNode<Type> (x); //创建结点 if ( ptr == NULL ) { cout << “存储不足" << endl; exit (1); } } else if ( x < ptr->data ) //在左子树插入 Insert ( x, ptr->leftChild ); else if ( x > ptr->data ) //在右子树插入 Insert ( x, ptr->rightChild );
91
输入数据,建立二叉搜索树的过程 输入数据 { 53, 78, 65, 17, 87, 09, 81, 15 } 53 53 53 53 53 78 78 17 78 17 78 65 65 65 87 53 53 53 17 78 17 78 17 78 09 65 87 09 65 87 09 65 87 81 15 81
92
template <class Type>
BST<Type> :: BST ( Type value ) { //输入数据, 建立二叉搜索树。RefValue 是输入 //结束标志。这个值应取不可能在输入序列中 //出现的值, 例如输入序列的值都是正整数时, //取RefValue为0或负数。 Type &x; root = NULL; RefValue = value; cin >> x; while ( x != RefValue ) { Insert ( x, root ); cin >> x; } }
93
同样 3 个数据{ 1, 2, 3 },输入顺序不同,建立起来的二叉搜索树的形态也不同。这直接影响到二叉搜索树的搜索性能。
如果输入序列选得不好,会建立起一棵单支树,使得二叉搜索树的高度达到最大。 {2, 1, 3} {1, 2, 3} {1, 3, 2} {2, 3, 1} {3, 1, 2} {3, 2, 1} 1 1 2 3 3 2 3 1 3 1 2 3 2 2 1
94
二叉搜索树的删除 在二叉搜索树中删除一个结点时,必须将因删除结点而断开的二叉链表重新链接起来,同时确保二叉搜索树的性质不会失去。
为保证在删除后树的搜索性能不至于降低,还需要防止重新链接后树的高度增加。 删除叶结点,只需将其双亲结点指向它的指针清零,再释放它即可。 被删结点缺右子树,可以拿它的左子女结点顶替它的位置,再释放它。
95
被删结点缺左子树,可以拿它的右子女结点顶替它的位置,再释放它。
被删结点左、右子树都存在,可以在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删结点中,再来处理这个结点的删除问题。 53 53 缺右子树, 用左子女顶替 17 78 17 78 09 45 65 87 09 23 65 87 删除45 23
96
53 删除78 53 17 78 17 94 缺左子树, 用右子女顶替 09 23 94 09 23 88 88 53 删除78 53 17 78 17 81 09 45 65 94 在右子树上找中序下第一个结点填补 09 45 65 94 23 81 23 88 88
97
二叉搜索树的删除算法 template <class Type> void BST <Type> ::
Remove (const Type& x, BstNode<Type> * & ptr) { BstNode<Type> * temp; if ( ptr != NULL ) if ( x < ptr->data ) //在左子树中删除 Remove ( x, ptr->leftChild ); else if ( x > ptr->data ) //在右子树中删除 Remove ( x, ptr->rightChild ); else if ( ptr->leftChild != NULL && ptr->rightChild != NULL ) {
98
temp = ptr->rightChild;
while ( temp->leftChild != NULL ) temp = temp->leftChild; //找ptr右子树中序下第一个结点 ptr->data = temp->data; //填补上 Remove ( ptr->data, temp ); //在temp为根的子树中删除该结点 } else { // ptr结点只有一个或零个子女 temp = ptr; if ( ptr->leftChild == NULL ) ptr = ptr->rightChild;
99
二叉搜索树性能分析 else if ( ptr->rightChild == NULL )
ptr = ptr->leftChild; //只有左子女 delete temp; } 二叉搜索树性能分析 对于有 n 个关键码的集合,其关键码有 n! 种不同排列,可构成不同二叉搜索树有 (棵) 用树的搜索效率来评价这些二叉搜索树。
100
例,已知关键码集合 { a1, a2, a3 } = { do, if, to },对应搜索概率 p1, p2, p3, 在各搜索不成功间隔内搜索概率分别为 q0, q1, q2, q3。可能的二叉搜索树如下所示。 to if p3 p2 if do to p2 q3 p1 p3 do p1 q2 q0 q1 q2 q0 q1 q3 (a) (b)
101
(c) (d) 扩充二叉搜索树 (e) do to p1 p3 if do q0 p2 p1 q3 to if p2 q1 p3 q0 q2
102
在扩充二叉搜索树中 ○表示内部结点,包含了关键码集合中的某一个关键码; □表示外部结点,代表各关键码间隔中的不在关键码集合中的关键码。 在每两个外部结点间必存在一个内部结点。 设二叉树的内部结点有 n 个,外部结点有 n+1 个。如果定义每个结点的路径长度为该结点的层次,并用 I 表示所有 n 个内部结点的路径长度之和,用 E 表示 n+1 个外部结点的路径长度之和,可以用归纳法证明,E = I+2n。
103
一棵扩充二叉搜索树的搜索成功的平均搜索长度ASLsucc可以定义为该树所有内部结点上的权值p[i]与搜索该结点时所需的关键码比较次数c[i] (= l[i] + 1)乘积之和:
搜索不成功的平均搜索长度ASLunsucc为树中所有外部结点上权值q[j]与到达外部结点所需关键码比较次数c'[j](= l'[j])乘积之和:
104
设树中所有内、外部结点的搜索概率都相等:
扩充二叉搜索树总的平均搜索长度为: ASL = ASLsucc + ASLunsucc 所有内、外部结点上的权值关系为 (1) 相等搜索概率的情形 设树中所有内、外部结点的搜索概率都相等: p[i] = q[j] = 1/7,1 i 3, 0 j 3 图(a):ASLsucc = 1/7*3+1/7*2+1/7*1 = 6/7, ASLunsucc = 1/7*3*2+1/7*2+1/7*1 = 9/7。 总平均搜索长度 ASL = 6/7 + 9/7 = 15/7。
105
图(a): ASL = 15/7 图(d):ASL = 15/7
图(b):ASL = 13/ 图(e):ASL = 15/7 图(c):ASL = 15/7 图(b)的情形所得的平均搜索长度最小。 一般把平均搜索长度达到最小的扩充二叉搜索树称作最优二叉搜索树。 在相等搜索概率的情形下,所有内部、外部结点的搜索概率都相等,视它们的权值都为 1。同时,第 k 层有 2k 个结点,k = 0, 1, 。则有 n 个内部结点的扩充二叉搜索树的内部路径长度 I 至少等于序列
106
0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, … 的前 n 项的和。 因此,最优二叉搜索树的搜索成功的平均搜索长度和搜索不成功的平均搜索长度分别为: (2) 不相等搜索概率的情形 设树中所有内、外部结点的搜索概率互不相等。
107
图(a):ASLsucc = 0.5*3+0.1*2+0.05*1 = 1.75,
p[1] = 0.5, p[2] = 0.1, p[3] = 0.05 q[0] = 0.15, q[1] = 0.1, q[2] = 0.05, q[3] = 0.05 to p2=0.1 p3=0.05 if q3=0.05 if p3=0.05 p1=0.5 p2=0.1 do to do q3= 0.05 p1=0.5 q2=0.05 q0=0.15 q1=0.1 q2=0.05 q1=0.1 q0=0.15 (a) (b) 图(a):ASLsucc = 0.5*3+0.1*2+0.05*1 = 1.75, ASLunsucc = 0.15*3+0.1*3+0.05*2+0.05*1= 0.9, ASL = ASLsucc + ASLunsucc = 2.65。
108
图(b) : ASL = 1.9; 图(d) : ASL = 2.15;
do to p1=0.5 p3=0.05 q3=0.05 q0= 0.15 if do p2=0.1 p1=0.5 to if p2=0.1 q1=0.1 p3=0.05 q0=0.15 q2=0.05 q3=0.05 q1=0.1 q2=0.05 (c) (d) 图(c) : ASLsucc = 0.5*1+0.1*2+0.05*3 = 0.85, ASLunsucc = 0.15*1+0.1*2+0.05*3+0.05*3 = 0.75, ASL = = 1.6。 图(b) : ASL = 1.9; 图(d) : ASL = 2.15;
109
图(e) : ASLsucc = 0.5*1 +0.05*2+0.1*3 = 0.9;
do 图(e) : ASLsucc = 0.5* *2+0.1*3 = 0.9; ASLunsucc = 0.15*1+ 0.1*3+0.05*3+0.05*2 = 0.55; ASL = = 1.6. p1=0.5 to q0=0.15 p3=0.05 if p2=0.1 q3=0.05 q1=0.1 q2=0.05 (e) 由此可知,图(c)和图(e)的情形下树的平均搜索长度达到最小,因此,图(c)和图(e)的情形是最优二叉搜索树。
110
AVL树 高度平衡的二叉搜索树 AVL树的定义
高度不平衡 高度平衡 A C B A D C B E D E
111
结点的平衡因子balance (balance factor)
AVL树任一结点平衡因子只能取 -1, 0, 1 如果一个结点的平衡因子的绝对值大于1,则这棵二叉搜索树就失去了平衡, 不再是AVL树。 如果一棵二叉搜索树是高度平衡的, 且有 n 个结点,其高度可保持在O(log2n),平均搜索长度也可保持在O(log2n)。
112
AVL树的类定义 template <class Type> class AVLTree { public:
struct AVLNode { //AVL树结点 Type data; int balance; AVLNode<Type> * left, * right; AVLNode ( ) : left (NULL), right (NULL), balance (0) { } AVLNode ( Type d, AVLNode<Type> * l = NULL, AVLNode<Type> *r = NULL) : data (d), left (l), right (r), balance (0) { } };
113
protected: Type RefValue; AVLNode *root; int Insert (AVLNode<Type>*& Tree, Type x); void RotateLeft (AVLNode<Type> * Tree, AVLNode<Type> *& NewTree); void RotateRight (AVLNode<Type> * Tree, void LeftBalance (AVLNode<Type> *& Tree); void RightBalance (AVLNode<Type> *&Tree); int Height (AVLNode<Type> * t) const; public:
114
AVLTree ( ) : root (NULL) { }
AVLNode (Type Ref ) : RefValue (Ref), root (NULL) { } int Insert (Type x) { return Insert (root, x); } friend istream& operator >> (istream& in, AVLTree<Type>& Tree); friend ostream& operator << (ostream& out, const AVLTree<Type>& Tree); int Height ( ) const; }
115
平衡化旋转 如果在一棵平衡的二叉搜索树中插入一个新结点,造成了不平衡,此时必须调整树的结构,使之平衡化。 平衡化旋转有两类:
单旋转 (左旋和右旋) 双旋转 (左平衡和右平衡) 每插入一个新结点时, AVL 树中相关结点的平衡状态会发生改变。因此, 在插入一 个新结点后,需要从插入位置沿通向根的路径回溯,检查各结点的平衡因子。
116
如果在某一结点发现高度不平衡,停止回溯。从发生不平衡的结点起,沿刚才回溯的路径取直接下两层的结点。
如果这三个结点处于一条直线上,则采用单旋转进行平衡化。单旋转可按其方向分为左单旋转和右单旋转, 其中一个是另一 个的镜像,其方向与不平衡的形状相关。 如果这三个结点处于一条折线上,则采用双旋转进行平衡化。双旋转分为先左后右和先右后左两类。
117
右单旋转 左单旋转 左右双旋转 右左双旋转 左单旋转 (RotateLeft )
118
在子树E中插入新结点,该子树高度增1导致结点A的平衡因子变成+2,出现不平衡。
+1 A A +2 C B C B +1 C A E h D h D E E B D h + 1 h h h h + 1 h h 在子树E中插入新结点,该子树高度增1导致结点A的平衡因子变成+2,出现不平衡。 沿插入路径检查三个结点A、C和E。它们处于方向为“\”的直线上,需做左单旋转。 以结点C为旋转轴,让结点A反时针旋转。
119
右单旋转 (RotateRight ) template <class Type>
void AVLTree<Type> :: RotateLeft ( AVLNode<Type> * Tree, AVLNode<Type> *& NewTree ) { //左单旋转的算法 NewTree = Tree->right; Tree->right = NewTree->left; NewTree->left = Tree; } 右单旋转 (RotateRight )
120
在左子树D上插入新结点使其高度增1,导致结点A的平衡因子增到 -2,造成不平衡。
-1 A A -2 B C B -1 C B D A D E h E h E C D h + 1 h h h h + 1 h h 在左子树D上插入新结点使其高度增1,导致结点A的平衡因子增到 -2,造成不平衡。 为使树恢复平衡,从A沿插入路径连续取3 个结点A、B和D,它们处于一条方向为“/”的直线上,需要做右单旋转。 以结点B为旋转轴,将结点A顺时针旋转。
121
先左后右双旋转 (RotationLeftRight)
template <class Type> void AVLTree <Type>:: RotateRight ( AVLNode<Type> * Tree, AVLNode<Type> *& NewTree) { //右单旋转的算法 NewTree = Tree->left; Tree->left = NewTree->right; NewTree->right = Tree; } 先左后右双旋转 (RotationLeftRight)
122
在子树F或G中插入新结点,该子树的高度增1。结点A的平衡因子变为 -2,发生了不平衡。
从结点A起沿插入路径选取3个结点A、B和E,它们位于一条形如“”的折线上,因此需要进行先左后右的双旋转。 首先以结点E为旋转轴,将结点B反时针旋转,以E代替原来B的位置,做左单旋转。 再以结点E为旋转轴,将结点A顺时针旋转,做右单旋转。使之平衡化。
123
A A -2 -1 +1 B C B C -1 E D E h D h F G h F G h h-1 h-1 h h-1 左单 旋转 插入
124
-2 右单 旋转 A E -2 +1 C B A E G B h D F G C h-1 D F h h h-1 h h h
125
先右后左双旋转 (RotationRightLeft)
右左双旋转是左右双旋转的镜像。 在子树F或G中插入新结点,该子树高度增1。结点A的平衡因子变为2,发生了不平衡。 从结点A起沿插入路径选取3个结点A、C和D,它们位于一条形如“”的折线上,需要进行先右后左的双旋转。 首先做右单旋转:以结点D为旋转轴,将结点C顺时针旋转,以D代替原来C的位置。 再做左单旋转:以结点D为旋转轴,将结点A反时针旋转,恢复树的平衡。
126
+1 +2 A A 右单旋转 +1 B C B C D h D h E E F G F G h h h-1 h-1 h-1 h -1 插入
127
+2 A D 左单 旋转 +2 -1 B D A C h B C F G E F G E h h h-1 h-1 h h h
128
AVL树的插入 在向一棵本来是高度平衡的AVL树中插入一个新结点时,如果树中某个结点的平衡因子的绝对值 |balance| > 1,则出现了不平衡,需要做平衡化处理。 在AVL树上定义了重载操作“>>”和“<<”,以及中序遍历的算法。利用这些操作可以执行AVL树的建立和结点数据的输出。 算法从一棵空树开始,通过输入一系列对象关键码,逐步建立AVL树。在插入新结点时使用平衡旋转方法进行平衡化处理。
129
例,输入关键码序列为 { 16, 3, 7, 11, 9, 26, 18, 14, 15 },插入和调整过程如下。
-1 -2 1 16 16 16 7 7 左右双旋 1 -1 3 3 3 16 3 16 7 11 2 2 1 7 7 7 1 -2 3 11 3 16 3 11 右单旋 1 -1 9 16 11 9 16 9 26
130
1 11 11 左单旋 右左双旋 1 2 7 16 7 16 -1 3 9 26 3 9 26 18 1 11 11 -1 7 18 7 18 -1 3 9 16 26 3 9 16 26 14
131
1 2 左右双旋 11 11 -1 -2 7 18 7 18 -2 3 9 16 26 3 9 15 26 1 14 14 16 15 从空树开始的建树过程
132
AVL树的删除 (1) 如果被删结点x最多只有一个子女,那么问题比较简单: 将结点x从树中删去。
因为结点x最多有一个子女,可以简单地把x的双亲结点中原来指向x的指针改指到这个子女结点; 如果结点x没有子女,x双亲结点的相应指针置为NULL。 将原来以结点x为根的子树的高度减1。
133
(2) 如果被删结点x有两个子女: 搜索 x 在中序次序下的直接前驱 y (同样可以找直接后继)。 把 结点y 的内容传送给结点x,现在问题转移到删除结点y。把结点y当作被删结点x。 因为结点y最多有一个子女,我们可以简单地用(1)给出的方法进行删除。
134
必须沿 x 通向根的路径反向追踪高度的变化对路径上各个结点的影响。
用一个布尔变量 shorter 来指明子树的高度是否被缩短。在每个结点上要做的操作取决于 shorter 的值和结点的 balance,有时还要依赖子女的 balance 。 布尔变量 shorter 的值初始化为True。然后对于从 x 的双亲到根的路径上的各个结点 p,在 shorter 保持为 True 时执行下面操作。如果 shorter 变成False,算法终止。
135
case 1 : 当前结点 p 的balance为0。如果它的左子树或右子树被缩短,则它的 balance改为 1 或-1,同时 shorter 置为False。
1 不旋转 h-1 h-1 h h h 删除一个结点,高度降低一层
136
case 2 : 结点 p 的balance不为0,且较高的子树被缩短,则 p 的balance改为0,同时 shorter 置为True。
-1 不旋转 h h+1 h h h 高度减1 删除一个结点,高度降低一层
137
case 3 : 结点 p 的balance不为0,且较矮的子树又被缩短,则在结点 p 发生不平衡。需要进行平衡化旋转来恢复平衡。
令 p 的较高的子树的根为 q (该子树未被缩短), 根据 q 的balance,有如下 3 种平衡化操作。 在case 3a, 3b和3c的情形中,旋转的方向取决于结点 p 的哪一棵子树被缩短。
138
case 3a : 如果 q (较高的子树) 的balance为 0,执行一个单旋转来恢复结点 p 的平衡,置shorter为False。
1 -1 q p 左单旋 1 h-1 h h h-1 h h h 删除结点
139
case 3b : 如果 q 的balance与 p 的balance相同,则执行一个单旋转来恢复平衡, 结点 p 和 q 的balance均改为0, 同时置shorter 为 True。
1 q p 左单旋 1 h-1 h h h-1 h h-1 h-1 删除结点
140
case 3c : 如果 p 与 q 的balance相反, 则执行一个双旋转来恢复平衡, 先围绕 q 转再围绕 p 转。新根结点的balance置为0,其它结点的balance相应处理, 同时置shorter为True。 p r 1 q -1 q 右左双旋 p r h-1 h 高度减1 h-1 h-1 h-1 h-1 h-1 h-1 或 h-2 h-1 或 h-2 删除 结点
141
举例 M E P C J N R B D H K O Q T A G I L S F 树的初始状态 -1 1 1 1 1 -1 -1 -1
-1 -1 B D H K O Q T -1 A G I L S F 树的初始状态
142
寻找结点P在中序下的直接前驱O, 用O顶替P, 删除O。
-1 用O取代P M 1 1 E P 1 1 -1 -1 C J N R -1 1 -1 -1 B D H K O Q T -1 A G I L S F 寻找结点P在中序下的直接前驱O, 用O顶替P, 删除O。
143
O与R的平衡因子同号, 以R为旋转轴做左单旋转, M的子树高度减 1。
删除结点P -1 左单旋转 M 1 1 E O 1 1 -1 -1 C J N R -1 1 -1 -1 B D H K Q T -1 A G I L S F O与R的平衡因子同号, 以R为旋转轴做左单旋转, M的子树高度减 1。
144
M的子树高度减 1, M发生不平衡。M与E的平衡因子反号, 做左右双旋转。
删除结点P -1 向上继续调整 M 1 E R -1 -1 -1 C J O T -1 1 -1 B D H K N Q S -1 A G I L F M的子树高度减 1, M发生不平衡。M与E的平衡因子反号, 做左右双旋转。
145
删除结点P J E M -1 1 -1 C H K R -1 -1 -1 B D G I L O T A F N Q S
146
AVL树的高度 设在新结点插入前AVL树的高度为 h,结点个数为 n,则插入一个新结点的时间是O(h)。对于AVL树来说,h 多大?
设 Nh 是高度为 h 的AVL树的最小结点数。根的一棵子树的高度为 h-1,另一棵子树的高度为 h-2,这两棵子树也是高度平衡的。因此有 N-1 = 0 (空树) N0 = 1 (仅有根结点) Nh = Nh-1 + Nh-2 +1 , h > 0
147
可以证明, 对于 h 0, 有 Nh = Fh+3 -1 成立。 有 n 个结点的AVL树的高度不超过 1.44*log2(n+1)-1 在AVL树删除一个结点并做平衡化旋转所需时间为 O(log2n)。 二叉搜索树适合于组织在内存中的较小的索引(或目录)。对于存放在外存中的较大的文件系统,用二叉搜索树来组织索引不太合适。 在文件检索系统中大量使用的是用B树或B+树做文件索引。
Similar presentations