1.35k likes | 1.44k Views
第 六 章 查找. 6.1 静态查找技术 6.2 二叉排序树 6.3 平衡二叉排序树 (AVL 树 ) *6.4 红 - 黑树 * 6.5 B- 树和 B+ 树 6.6 哈希 (Hash) 方法. i. 静态查找技术. 1 、搜索:. 在数据集合之中,搜索具有特定关键字的结点。. 通常分为 静态搜索表 :集合中的结点总数是固定的或者很少发生变化。可以无序或组 织成有序表。 动态搜索表 :集合中的结点总数是经常在发生变化。组织成树形结构。 在 内存 中进行的搜索:重点减少比较、或查找的次数。评价标准:平均搜索长度。
E N D
第 六 章 查找 6.1 静态查找技术 6.2 二叉排序树 6.3 平衡二叉排序树(AVL树) *6.4 红-黑树 *6.5 B-树和B+树 6.6 哈希(Hash)方法
i 静态查找技术 • 1、搜索: • 在数据集合之中,搜索具有特定关键字的结点。 • 通常分为静态搜索表:集合中的结点总数是固定的或者很少发生变化。可以无序或组 织成有序表。 • 动态搜索表:集合中的结点总数是经常在发生变化。组织成树形结构。 • 在内存中进行的搜索:重点减少比较、或查找的次数。评价标准:平均搜索长度。 • 在外存中进行的搜索:重点在于减少访问外存的次数。评价标准:读盘次数。 • 2、静态搜索结构: 采用静态向量(或数组),0号单元用作哨兵单元,1号单元到n号单元保存结点。 哨兵单元 Vector 8 100 10 ……………… 0 7 1 3 0 1 2 n-3 n-2 n-1 n
静态查找表(ADT) template <class Type> class Vector { public: Vector ( int size); // 构造函数,静态查找表的结点个数为size. ~ Vector ( ) { delete [ ]Array; } const Type & operator [ ] ( int index ) const; //具有越界检查的下标操作。 const Vector & operator = ( const Vector & R); //大小相等的向量的复制。 int Length( ) const { return ArraySize; } void Double( ) { Resize( ArraySize * 2 );} // 在向量的单元用完时,容量加倍。 void Resize( int Newsize); // 修改向量的大小。 void Sentinel( const Type key ){ Array[0] = key; } //置0号单元的内容为待查key。 protected: Type * Array; // 保存结点的数组 intArraySize; // 大小。 void GetArray( ); Vector(const Vector & R ); // 冻结使用构造函数复制另一向量的功能。 };
静态查找表(ADT) • 部分操作的实现: template <class Type> const Type & Vector<Type> :: operator [ ] ( int index ) const{ Exception( index ≤0 || index > ArraySize, “Out of boundary!”); return Array[index]; } template <class Type> const Vector<Type> & Vector<Type> :: operator = ( const Vector<Type> R ){ if ( this != &R ) { Exception( ArraySize != R.ArraySize, “Size is not same!”); for(int k = 1; k <= ArraySize; k++ ) Array[k] = R.Array[k]; } return *this; }
静态查找表(ADT) • 部分操作的实现: template <class Type> void Vector<Type> :: Resize( int NewSize ){ Type * oldArray = Array; const int minsize = Min(ArraySize, NewSize); // 取二者小者。 ArraySize = NewSize; GetArray( ); for(int k = 1; k <= minsize; k++ ) Array[k] = oldArray[k]; delete [ ]oldArray; } template <class Type> void Vector<Type> :: GetArray(){ Array = new Type[ArraySize+1]; } template <class Type> Vector<Type> :: Vector( int Size){ ArraySize = Size; GetArray( ); }
i 顺序查找 • 应用范围:顺序表或线性链表表示的静态查找表。表内元素之间无序或有序。 template <class Type> int SeqSearch( const Vector<Type> & A, const Type key,int N ){ A[0] = key; // 将待查key 置于哨兵单元,可省掉越界检查。 for ( int k = N; A[k] != key; --k ) return k; // 返回0,查找失败。否则,找到关键字为key 的结点的下标 } 设置哨兵的好处: 在顺序表中总可以找到待查结点。否则,必须将 判断条件 i >= 0 加进 for 语句。 e.g: 查找 x = 8 的结点所在的数组元素的下标。 key 8 100 10 ……………… 0 7 1 3 向量 Vector 0 1 2 n-3 n-2 n-1 n
i 顺序查找 • 应用范围:顺序表或线性链表表示的静态查找表。表内元素之间无序或有序。 template <class Type> int SeqSearch( const Vector<Type> & A, const Type key,int N ){ A[0] = key; // 将待查key 置于哨兵单元,可省掉越界检查。 for ( int k = N; A[k] != key; --k ) return k; // 返回0,查找失败。否则,找到关键字为key 的结点的下标 } 设置哨兵的好处: 在顺序表中总可以找到待查结点。否则,必须将 判断条件 i >= 0 加进 for 语句。 e.g: 查找 x = 8 的结点所在的数组元素的下标。 key 8 100 10 ……………… 0 7 1 3 向量 Vector 0 1 2 n-3 n-2 n-1 n
i 顺序查找 • 应用范围:顺序表或线性链表表示的静态查找表。表内元素之间无序或有序。 template <class Type> int SeqSearch( const Vector<Type> & A, const Type key,int N ){ A[0] = key; // 将待查key 置于哨兵单元,可省掉越界检查。 for ( int k = N; A[k] != key; --k ) return k; // 返回0,查找失败。否则,找到关键字为key 的结点的下标 } 设置哨兵的好处: 在顺序表中总可以找到待查结点。否则,必须将 判断条件 i >= 0 加进 for 语句。 e.g: 查找 x = 8 的结点所在的数组元素的下标。 key 8 100 10 ……………… 0 7 1 3 向量 Vector 0 1 2 n-3 n-2 n-1 n
i 顺序查找 • 应用范围:顺序表或线性链表表示的静态查找表。表内元素之间无序或有序。 template <class Type> int SeqSearch( const Vector<Type> & A, const Type key,int N ){ A[0] = key; // 将待查key 置于哨兵单元,可省掉越界检查。 for ( int k = N; A[k] != key; --k ) return k; // 返回0,查找失败。否则,找到关键字为key 的结点的下标 } 设置哨兵的好处: 在顺序表中总可以找到待查结点。否则,必须将 判断条件 i >= 0 加进 for 语句。 e.g: 查找 x = 8 的结点所在的数组元素的下标。 key 8 100 10 ……………… 0 7 1 3 向量 Vector 0 1 2 n-3 n-2 n-1 n
i 顺序查找 • 应用范围:顺序表或线性链表表示的静态查找表。表内元素之间无序或有序。 template <class Type> int SeqSearch( const Vector<Type> & A, const Type key,int N ){ A[0] = key; // 将待查key 置于哨兵单元,可省掉越界检查。 for ( int k = N; A[k] != key; --k ) return k; // 返回0,查找失败。否则,找到关键字为key 的结点的下标 } 设置哨兵的好处: 在顺序表中总可以找到待查结点。否则,必须将 判断条件 i >= 0 加进 for 语句。 e.g: 查找 x = 8 的结点所在的数组元素的下标。 key 8 100 10 ……………… 0 7 1 3 向量 Vector 0 1 2 n-3 n-2 n-1 n
i 顺序查找 • 应用范围:顺序表或线性链表表示的静态查找表。表内元素之间无序或有序。 template <class Type> int SeqSearch( const Vector<Type> & A, const Type key,int N ){ A[0] = key; // 将待查key 置于哨兵单元,可省掉越界检查。 for ( int k = N; A[k] != key; --k ) return k; // 返回0,查找失败。否则,找到关键字为key 的结点的下标 } 设置哨兵的好处: 在顺序表中总可以找到待查结点。否则,必须将 判断条件 i >= 0 加进 for 语句。 e.g: 查找 x = 8 的结点所在的数组元素的下标。 key 8 100 10 ……………… 0 7 1 3 向量 Vector 0 1 2 n-3 n-2 n-1 n
i 顺序查找 • 应用范围:顺序表或线性链表表示的静态查找表。表内元素之间无序或有序。 template <class Type> int SeqSearch( const Vector<Type> & A, const Type key,int N ){ A[0] = key; // 将待查key 置于哨兵单元,可省掉越界检查。 for ( int k = N; A[k] != key; --k ) return k; // 返回0,查找失败。否则,找到关键字为key 的结点的下标 } 设置哨兵的好处: 在顺序表中总可以找到待查结点。否则,必须将 判断条件 i >= 0 加进 for 语句。 e.g: 查找 x = 8 的结点所在的数组元素的下标。 key 8 100 10 ……………… 0 7 1 3 向量 Vector 0 1 2 n-3 n-2 n-1 n
i 顺序查找 • 应用范围:顺序表或线性链表表示的静态查找表。表内元素之间无序或有序。 template <class Type> int SeqSearch( const Vector<Type> & A, const Type key,int N ){ A[0] = key; // 将待查key 置于哨兵单元,可省掉越界检查。 for ( int k = N; A[k] != key; --k ) return k; // 返回0,查找失败。否则,找到关键字为key 的结点的下标 } 设置哨兵的好处: 在顺序表中总可以找到待查结点。否则,必须将 判断条件 i >= 0 加进 for 语句。 e.g: 查找 x = 7 的结点所在的数组元素的下标。 key 7 100 10 ……………… 0 7 1 3 向量 Vector 0 1 2 n-3 n-2 n-1 n
i 顺序查找 • 应用范围:顺序表或线性链表表示的静态查找表。表内元素之间无序或有序。 template <class Type> int SeqSearch( const Vector<Type> & A, const Type key,int N ){ A[0] = key; // 将待查key 置于哨兵单元,可省掉越界检查。 for ( int k = N; A[k] != key; --k ) return k; // 返回0,查找失败。否则,找到关键字为key 的结点的下标 } 设置哨兵的好处: 在顺序表中总可以找到待查结点。否则,必须将 判断条件 i >= 0 加进 for 语句。 e.g: 查找 x = 7 的结点所在的数组元素的下标。 key 7 100 10 ……………… 0 7 1 3 向量 Vector 0 1 2 n-3 n-2 n-1 n
i 顺序查找 • 应用范围:顺序表或线性链表表示的静态查找表。表内元素之间无序或有序。 template <class Type> int SeqSearch( const Vector<Type> & A, const Type key,int N ){ A[0] = key; // 将待查key 置于哨兵单元,可省掉越界检查。 for ( int k = N; A[k] != key; --k ) return k; // 返回0,查找失败。否则,找到关键字为key 的结点的下标 } 设置哨兵的好处: 在顺序表中总可以找到待查结点。否则,必须将 判断条件 i >= 0 加进 for 语句。 e.g: 查找 x = 7 的结点所在的数组元素的下标。 key 7 100 10 ……………… 0 7 1 3 向量 Vector 0 1 2 n-3 n-2 n-1 n
顺序查找的性能 • 设 n 为结点的总数。 • 平均查找长度AVL(Average Search Length ) • 成功查找情况下:设每个结点的查找概率相等 • 1 • ASL=∑(( n-i+1) ) • i=n • = (n+1)/ 2 1 n
顺序查找的性能 • 一般查找情况下(包括成功、不成功两种情况):设成功与不成功两种情况 • 可能性相等,每个结点的查找概率也相等。 • 1 • ASL=∑(( n-i+1) ) +∑((n+1) ) • = 3(n+1)/4 1 1 1 2(n+1) 2n i=n i=n+1 0 1 3 8 10 100 100 10 0 8 1 3 0 1 2 3 4 5 6 共有n+1=7种不成功的查找情况
折半查找(二分查找) • 应用范围:顺序表,表内元素之间有序。不可直接用于线性链表。 e.g: 查找 key = 9 的结点所在的数组元素的下标地址。 • 查找成功的情况:数组 Vector 如下图所示有序 • 数组 Vector :递增序 Vector[i]. Key <= Vector[I+1]. Key; i= 1,2,……n • 查找范围 :low(低下标)= 1; high(高下标)= 7 (初始时为最大下标 n ); • 比较对象:中点元素,其下标地址为 mid = (low+high)/ 2 =4 mid=4 但 key=9 < 10, 向左 key 4 8 9 10 11 13 19 0 1 2 3 4 5 6 7 high=7 low=1
折半查找 • 应用范围:顺序表,表内元素之间有序。不可直接用于线性链表。 e.g: 查找 key = 9 的结点所在的数组元素的下标地址。 • 查找成功的情况:数组 Vector 如下图所示有序 • 数组 Vector :递增序 Vector[i]. Key <= Vector[I+1]. Key; i= 1,2,……n • 查找范围 :low(低下标)= 1; high(高下标)= 3 mid=4 key 4 8 9 10 11 13 19 0 1 2 3 4 5 6 7 high=3 (mid -1) low=1
折半查找 • 应用范围:顺序表,表内元素之间有序。不可直接用于线性链表。 e.g: 查找 key = 9 的结点所在的数组元素的下标地址。 • 查找成功的情况:数组 Vector 如下图所示有序 • 数组 Vector :递增序 Vector[i]. Key <= Vector[I+1]. Key; i= 1,2,……n • 查找范围 :low(低下标)= 1; high(高下标)= 3 • 比较对象:中点元素,其下标地址为 mid = (low+high)/ 2 =2 mid=2 key 4 8 9 10 11 13 19 0 1 2 3 4 5 6 7 high=3 (mid -1) low=1
折半查找 • 应用范围:顺序表,表内元素之间有序。不可直接用于线性链表。 e.g: 查找 key = 9 的结点所在的数组元素的下标地址。 • 查找成功的情况:数组 Vector 如下图所示有序 • 数组 Vector :递增序 Vector[i]. Key <= Vector[I+1]. Key; i= 1,2,……n • 查找范围 :low(低下标)= 1; high(高下标)= 3 • 比较对象:中点元素,其下标地址为 mid = (low+high)/ 2 =2 mid=2;但 key=9 > 8, 向右 key 4 8 9 10 11 13 19 0 1 2 3 4 5 6 7 high=3 (mid -1) low=1
折半查找 • 应用范围:顺序表,表内元素之间有序。不可直接用于线性链表。 e.g: 查找 key = 9 的结点所在的数组元素的下标地址。 • 查找成功的情况:数组 Vector 如下图所示有序 • 数组 Vector :递增序 Vector[i]. Key <= Vector[I+1]. Key; i= 1,2,……n • 查找范围 :low(低下标)= 3; high(高下标)= 3 mid=2 key 4 8 9 10 11 13 19 0 1 2 3 4 5 6 7 high=3 (mid -1) low=3 (mid +1)
折半查找 • 应用范围:顺序表,表内元素之间有序。不可直接用于线性链表。 e.g: 查找 key = 9 的结点所在的数组元素的下标地址。 • 查找成功的情况:数组 Vector 如下图所示有序 • 数组 Vector :递增序 Vector[i]. Key <= Vector[I+1]. Key; i= 1,2,……n • 查找范围 :low(低下标)= 3; high(高下标)= 3 • 比较对象:中点元素,其下标地址为 mid = (low+high)/ 2 =3 mid=3 key 4 8 9 10 11 13 19 0 1 2 3 4 5 6 7 high=3 low=3
折半查找 • 应用范围:顺序表,表内元素之间有序。不可直接用于线性链表。 e.g: 查找 key = 9 的结点所在的数组元素的下标地址。 • 查找成功的情况:数组 Vector 如下图所示有序 • 数组 Vector :递增序 Vector[i]. Key <= Vector[I+1]. Key; i= 1,2,……n • 查找范围 :low(低下标)= 3; high(高下标)= 3 • 比较对象:中点元素,其下标地址为 mid = (low+high)/ 2 =3 mid=3; key=9 且中点值也为 9 ,找到 key 4 8 9 10 11 13 19 0 1 2 3 4 5 6 7 high=3 low=3
折半查找 e.g: 查找 key = 5 的结点所在的数组元素的下标地址。 • 查找不成功的情况:数组 Vector 如下图所示有序 • 数组 Vector :递增序 Vector[i]. Key <= Vector[I+1]. Key; i= 1,2,……n • 查找范围 :low(低下标)= 1; high(高下标)= 7 (初始时为最大下标 n ); • 比较对象:中点元素,其下标地址为 mid = (low+high)/ 2 mid=4 但 key=5 < 10, 向左 key 4 8 9 10 11 13 19 0 1 2 3 4 5 6 7 high=7 low=1
折半查找 e.g: 查找 key = 5 的结点所在的数组元素的下标地址。 • 查找不成功的情况:数组 Vector 如下图所示有序 • 数组 Vector :递增序 Vector[i]. Key <= Vector[I+1]. Key; i= 1,2,……n • 查找范围 :low(低下标)= 1; high(高下标)= 3 ; mid=4 key 4 8 9 10 11 13 19 0 1 2 3 4 5 6 7 low=1 high=3(mid-1)
折半查找 e.g: 查找 key = 5 的结点所在的数组元素的下标地址。 • 查找不成功的情况:数组 Vector 如下图所示有序 • 数组 Vector :递增序 Vector[i]. Key <= Vector[I+1]. Key; i= 1,2,……n • 查找范围 :low(低下标)= 1; high(高下标)= 3 ; • 比较对象:中点元素,其下标地址为 mid = (low+high)/ 2 =2 mid=2 key 4 8 9 10 11 13 19 0 1 2 3 4 5 6 7 low=1 high=3(mid-1)
折半查找 e.g: 查找 key = 5 的结点所在的数组元素的下标地址。 • 查找不成功的情况:数组 Vector 如下图所示有序 • 数组 Vector :递增序 Vector[i]. Key <= Vector[I+1]. Key; i= 1,2,……n • 查找范围 :low(低下标)= 1; high(高下标)= 3 ; • 比较对象:中点元素,其下标地址为 mid = (low+high)/ 2 =2 mid=2 ; 但 key=5 < 8, 向左 key 4 8 9 10 11 13 19 0 1 2 3 4 5 6 7 low=1 high=3(mid-1)
折半查找 e.g: 查找 key = 5 的结点所在的数组元素的下标地址。 • 查找不成功的情况:数组 Vector 如下图所示有序 • 数组 Vector :递增序 Vector[i]. Key <= Vector[I+1]. Key; i= 1,2,……n • 查找范围 :low(低下标)= 1; high(高下标)= 1 mid=2 key 4 8 9 10 11 13 19 0 1 2 3 4 5 6 7 low=1 high=1(mid-1)
折半查找 e.g: 查找 key = 5 的结点所在的数组元素的下标地址。 • 查找不成功的情况:数组 Vector 如下图所示有序 • 数组 Vector :递增序 Vector[i]. Key <= Vector[I+1]. Key; i= 1,2,……n • 查找范围 :low(低下标)= 1; high(高下标)= 1 • 比较对象:中点元素,其下标地址为 mid = (low+high)/ 2 = 1 mid=1 key 4 8 9 10 11 13 19 0 1 2 3 4 5 6 7 low=1 high=1
折半查找 e.g: 查找 key = 5 的结点所在的数组元素的下标地址。 • 查找不成功的情况:数组 Vector 如下图所示有序 • 数组 Vector :递增序 Vector[i]. Key <= Vector[I+1]. Key; i= 1,2,……n • 查找范围 :low(低下标)= 1; high(高下标)= 1 • 比较对象:中点元素,其下标地址为 mid = (low+high)/ 2 = 1 mid=1 ; 但 key=5 > 4, 向右 key 4 8 9 10 11 13 19 0 1 2 3 4 5 6 7 low=1 high=1
折半查找 e.g: 查找 key = 5 的结点所在的数组元素的下标地址。 • 查找不成功的情况:数组 Vector 如下图所示有序 • 数组 Vector :递增序 Vector[i]. Key <= Vector[I+1]. Key; i= 1,2,……n • 查找范围 :low(低下标)= 1; high(高下标)= 1 • 比较对象:中点元素,其下标地址为 mid = (low+high)/ 2 = 1 失败条件:low > high; 处于间隙中的键值导致这种情况! mid=1 ; 但 key=5 > 4, 向右 key 4 8 9 10 11 13 19 0 1 2 3 4 5 6 7 low=2 high=1
折半查找 template <class Type> int BinarySearch( const Vector<Type> & A, const Type key,int N ){ int low = 1, // 所查找的那一段的下界结点的下标。 high = N, // 所查找的那一段的上界结点的下标。 mid; // 所查找的那一段的中间结点的下标。 while(low <= high ) { mid = (low + high ) /2; if ( A[mid] = = key) return mid; else if (key < A[mid]) high = mid –1; else low = mid +1; } return 0; } // 返回0,查找失败.。否则,找到值为key 的结点的索引或下标。
折半查找的性能 注意:(n-1)/2 = n/2 最坏情况分析:设 key 和中点的二次比较的时间代价 1 如果 n = 1, 则 low = high = mid , 则代价为 1,记为 S(1)= 1 如果 n 是奇数,那么中点元素的左、右段各有 (n-1)/2个元素 如果 n 是偶数,中点元素的左段有 n/2-1 个元素;右段有 n/2个元素 因此,算法工作的那一段,最多有 n/2 项 ∴ S(n)= 1 + S( n/2 ) = 1 + 1 + S( n/22) = 1 + 1 + 1 + S( n/23) = 1 + 1 + 1 + ……… + 1 + S( n/2k) 注意:n/2 = n/2 总共 K 个 1 当 1 <= n/2k< 2 时;则 n/2k = 1 此时: 2k <= n < 2k+1 即 k <= log2n < k+1 注意:k 不可为小数,它是正整数。 ∴ k = log2n 故: S(n)= 1 + log2n
mid= 4 4 8 9 10 11 13 19 20 0 1 2 3 4 5 6 7 8 high=8 low=1 mid= 4 4 8 9 10 11 13 19 0 1 2 3 4 5 6 7 high=7 low=1 折半查找的性能
折半查找的性能 最坏情况分析: 定理:在最坏情况下,二分查找法的查找有序表的最大的比较次数为 1+ log2n ,大体上和log2n 成正比。 也可用判定树的方法进行推导。 如: ? key = k4 4 < > 1 2 3 4 5 6 7 8 6 2 4 8 9 10 11 13 19 29 < > < 当寻找 key = 8 及小于、大 于 8 的键值的相应结点时, 查找次数最大。达到了判定 树的深度或高度。 1 3 5 7 < > < > < > < > 0 1 2 3 4 5 6 8 < > 注意:当判定树为 n = 2t -1 ( t=1,2,3 …… )时,为满二叉树。 否则,除最下一层外,余为满二叉树。 7 8
折半查找的性能 平均情况分析(在成功查找的情况下): 设每个 结点的查找概率相同都为 1/n。为了简单起见,设结点个数为 n = 2t -1 (t = 1,2,3 …… )。 ∴ 经过 1 次比较确定的结点个数为 1 = 20 个 ,红色标识的结点。 经过 2 次比较确定的结点个数为 2 = 21 个 ,绿色标识的结点。 经过 3 次比较确定的结点个数为 4 = 22 个 ,蓝色标识的结点。 . . . 经过 t 次比较确定的结点个数为 2t-1 个 ,黑色标识的结点。 注意:∵ 20 + 21 + 22 + … + 2t-1 = 2t -1 ∴ 最多经过 t 次比较可以找到有序表中的任何一个结点 e.g: 当 t = 4 时的例子:最多经过 t=4 次比较找到任何一个结点 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 4 8 9 10 11 13 19 29 32 47 65 77 81 93 99
折半查找的性能 平均情况分析(在成功查找的情况下): ASL = ( 20×1+ 21×2+ 22×3+ … + 2t-1 ×t) / n t = ∑ (i × 2i-1 ) / n i=1 = [(n + 1) ×( log2(n + 1) - 1 ) + 1 ] / n = (n + 1) ×( log2(n + 1) / n - 1 = (n + 1) ×( log2(n + 1) / n - 1 结论:在成功查找的情况下,平均查找的代价约为 ASL = log2(n + 1) - 1 或者简单地记为:ASL = log2n - 1
4 < > 6 2 < > < 1 3 5 7 < > < > < > < > 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 4 8 9 10 11 13 19 折半查找的性能 平均情况分析(考虑成功、非成功查找两种的情况下):为了简单起见,设结点个数为 n = 2t -1 (t = 1,2,3 …… )。 这样,成功查找的情况共有 n 种情况,非成功查找的情况共有 n + 1 情况。 设每种情况出现的概率相同,即都为 1/(2n+1) 。 t ASL = ( ∑ (i × 2i-1 )+ t × ( n + 1 )) / (2n+1) i=1 = log2n + 1/2 e.g: 当 t = 3 时的例子: 成功:最多经过 t=3 次比较 失败:都必须经过 t = 3 次比较
差值查找 1、除中点下标的选择和二分查找不同外,其余类似。用于关键字值 均匀的情况,平均特性更好。 • 2、实现 • 设 mid 为中点的下标。low 为具有最小关键字值结点的下标, high 为具有最大关键字值结点的下标。 • (x-Element[low].key) • mid = low + (high-low-1)× • (Eelement[high].key-Element[low].key)
? = Fibonacci查找 1、Fibonacci 数 定义: F0 = 0 F1 = 1 Fk = Fk-1 + Fk-2 ( K >= 2) 如:0, 1, 1,2,3,5,8,13,21,34,55,89,144,233 • 2、实现 • 设结点的总数为 n = Fu - 1,查找键值为 key 的结点 • 首先比较 key ST[Fu-1].key • 如果 key < ST[Fu-1].key ,则比较 key = ST[Fu-1 -Fu-3].key • 如果 key > ST[Fu-1].key ,则比较 key = ST[Fu-1+Fu-3].key ? ? • 3、注意: • 参照下图,设根结点(或子树的根结点)同它的左、右儿子的下标之差为Fu-3 • 那么,根结点(或子树的根结点)的左儿子的同它的儿子的下标之差为Fu-4 • 根结点(或子树的根结点)的右儿子的同它的儿子的下标之差为Fu-5 • 可以设计类似于二分查找的算法。但先要把 Fu-3 、 Fu-4 、 Fu-5计算出来,它们也构成 • Fibonacci 数
Fibonacci查找 如:0, 1, 1,2,3,5, 8,13,21,34,55,89,144,233 0, 1, 2,3,4, 5,6, 7, 8, 9, 10,11,12, 13, • 4、e.g: n = F7- 1 = 13 - 1 = 12个结点的查找过程 • Fu-1 = 8 • Fu-3 = 3 • Fu-4 = 2 • Fu-5 = 1 注意:Fu-2 = 5 ST[Fu-1].key 8 差为 Fu-3 = 3 > < 11 5 > 差为 Fu-5 = 1 差为 Fu-4 = 2 < > < 3 7 10 12 注意:本示意图从1开始编号,书上是从0开始进行编号。 < > < < 2 4 6 9 共 Fu-2-1 =5-1=4个结点 < 共 Fu-1-1 =8-1=7个结点 1 5、优点:只用 +、-法,不用除法。平均查找速度更快。O(log2n)级。 缺点:最坏情况下比二分查找法差。必须先给出 Fibonacci 数。
二叉排序树 • 特点:用于频繁进行插入、删除、查找的所谓动态查找表。 • 二叉排序树(二叉查找树):空或有一个根,根的左子树若非空,则左子树上的所有结点的关键 • 字值均小于根结点的值。根的右子树若非空,则右子树上的所有结点的关键字值 • 均大于根结点的值。根结点的左右子树同样是二叉排序树。 e、g:二叉排序树(二叉查找树),确定结点的大小,可根据结点类型进行定义。 L 122 N 250 C 99 P 300 M 200 E 110 Y 105 230 216
二叉排序树(ADT) template <class Type> class BST { // 二叉排序树的ADT public: BST ( ) { } // 二叉排序树的构造函数。 ~BST ( ) { } // 二叉排序树的析构函数。 virtual int Insert( const Type & x) = 0; // 插入x。 virtual int Remove( const Type & x ) = 0; // 删除x。 virtual const Type & Find(const Type & x) = 0;// 查找值为x 的结点。 virtual int IsFound( const Type & x )= 0;// 若x 找到,则返回1。 virtual const Type & FindMin( ) = 0; // 返回最小值。 virtual const Type & FindMax( ) = 0; //返回最大值。 virtual int IsEmpty( ) const = 0;// 二叉排序树为空,返回1,否则为0。 virtual int IsFull( )const = 0;// 二叉排序树为满,返回1,否则为0。 virtual void MakeEmpty( ) = 0; //清除二叉排序树中的所有结点。 };
二叉排序树的结点类 • 二叉排序树中的结点类的实现,为了表示简单,没有采用继承的方式,采用结构。 • 二叉排序树BST的结点表示。 template <class Type> struct BSTNode { // 二叉排序树的结点的表示。 Type data; // 结点的数据场。 BSTNode * left; // 给出结点的左儿子的地址。 BSTNode * right; // 给出结点的右儿子的地址。 int BalanceFactor; // 结点的平衡度,用于AVL 树。 int Size; // 以本结点为根的子树的所有结点的个数,用于顺序统计。 BSTNode ( ): left(NULL), right(NULL), Size(1), BalanceFactor(1) { } BSTNode ( const Type x ) : data(x), left(NULL), right(NULL), Size(1), BalanceFactor(1) { } BSTNode (const Type x, BSTNode * L, BSTNode * R ): data(x), left(L), right(R), Size(1), BalanceFactor(1) { } ~BSTNode( ) { } };
二叉排序树类 template <class Type> class BinarySearchTree:public BST<Type> { public: BinarySearchTree ( ) : LastFind(NULL), Root(NULL) { } ~BinarySearchTree ( ) { FreeTree(Root); } int Insert( const Type & x) { return Insert(x,Root);} //插入x 到以Root 为根的二叉排序树。成功则返回1。 const Type & Find( const Type & x ) { return ( LastFind = Find(x,Root)) ? LastFind->data:ItemNotFound } // 若查找x 成功,则返回二叉排序树中的相应数据项。否则,返回ItemNotFound。 const Type & FindMin( )const { const BSTNode * p = FindMin(Root); return p ? p->data:ItemNotFound; } //返回二叉排序树中的最小的数据项。若树为空,返回ItemNotFound。 const Type & FindMax( ) const { const BSTNode * p = FindMax(Root); return p ? p->data:ItemNotFound; } //返回二叉排序树中的最大的数据项。若树为空,返回ItemNotFound。
二叉排序树类 int IsFound (const Type & x) { return Find(x,Root)!= NULL;} //若x找到,则返回True。 int WasFound ( ) const { return LastFind != NULL;}//最近一次查找成功,则返回True。 int Remove( const Type & x ) { return Remove(x, Root);} // 从二叉排序树中删除值为x 的结点,删除成功返回True。 int RemoveMin( ) { return RemoveMin(Root);} // 从二叉排序树中删除最小结点,删除成功返回True。 int IsEmpty( ) const { return Root == NULL; } //二叉排序树为空,返回True,否则为False。 void MakeEmpty ( ){ FreeTree(Root); Root = NULL;} //清除二叉排序树中的所有结点。 protected: BSTNode<Type> * Root; constBSTNode<Type> * LastFind; Type ItemNotFound; // 用于查找失败时返回。 constBSTNode<Type> * Find ( const Type & x,constBSTNode<Type> * T) const; constBSTNode<Type> * FindMin ( constBSTNode<Type> * T) const; constBSTNode<Type> * FindMax ( constBSTNode<Type> * T) const; int Insert( const Type & x,BSTNode<Type> * & T); int RemoveMin(BSTNode<Type> * & T); int Remove( const Type & x,BSTNode<Type> * & T); };
二叉排序树的查找 • 分割式查找法: • 查找步骤:若根结点的关键字值等于查找的关键字,成功。 • 否则,若小于根结点的关键字值,查其左子树。大于根结点的关键 字值,查其右子树。在左右子树上的操作类似。 122 template <class Type> BSTNode<Type > * BinarySearchTree<Type>:: Find(const Type & x, const BSTNode<Type > * T) const { while ( T != NULL ) if ( X < T->data ) T = T->left else if ( X > T->data ) T = T->right; else return T; return NULL; } // 查找失败,返回空。查找成功,返回指向相应结点的指针。 250 99 200 300 110 105 230 216
二叉排序树的查找分析 • 平均情况分析(在成功查找两种的情况下) • e.g: 下述两种情况下的成功的平均查找长度 ASL 15 50 20 20 60 30 15 30 70 ASL=(1+2+2+3+3+3)/6=14/6 50 60 70 AVL=(1+2+3+4+5+6)/6=21/6
二叉排序树的查找分析 • 平均情况分析(在成功查找两种的情况下) • 在一般情况下,设 P(n,i)且它的左子树的结点个数为 i 时的平均查找长 • 度。右图的结点个数为 n = 6 且 i = 3; 则 • P(n,i)= P(6, 3) = [ 1+ ( P(3) + 1) * 3 + ( P(2) + 1) * 2 ] / 6 • = [ 1+ ( 5/3 + 1) * 3 + ( 3/2 + 1) * 2 ] / 6 • 注意:这里 P(3)、P(2) 是具有 3 个结点、2 个结点的二叉排序树的平均查找 • 长度。 在一般情况下,P(i)为具有 i 个结点二叉排序树的平均查找 • 长度。 • P(3) = (1+2+2)/ 3 = 5/3 • P(2) = (1+2)/ 2 = 3/2 • ∴ P(n,i)= [ 1+ ( P(i) + 1) * i + ( P(n-i-1) + 1) * (n-i-1) ] / n • ∴ n-1 • P(n)= ∑P(n,i)/ n • i=0 • <= 2(1+I/n)lnn • 因为: 2(1+I/n)lnn≈1.38logn • 故:P(n)=O(logn) 50 20 60 15 30 70 左子树0到n-1个结点 右子树n-1到0个结点