查找的概念和术语:
查找表、关键字、查找、动态查找表&静态查找表、
【成功】平均查找长度(ASL):需和给定值进行比较的关键字个数的期望值;衡量查找算法的性能。
线性表:最简单的查找表组织方式,更适用于静态查找表
基于线性表的顺序查找、折半查找的分块查找
顺序表的定义:
typedef struct{ ElemType *R; //存储空间基地址 int length; //当前长度 }SSTable;
数据元素类型定义如下:
typedef struct{ KeyType key; //关键字域 InfoType otherinfo; //其他域 }ElemType;
顺序查找:当n很大时,不宜采用
优点:算法简单,对表结构无任何要求
缺点:平均查找长度较大,查找效率较低。
查找时从表的最后开始比较
int Search_Seq(SSTable ST, KeyType key) {//在顺序表ST中顺序查找其关键字等于key的数据元素。若找到,则函数值为该元素在表中的位置,否则为0 for(i=ST.length; i>=1; --i) if(ST.R[i].key == key) return i; return 0; }
冗余之处:查找过程中每步都要有循环变量是否满足条件i>=1的检测
改进:设置监视哨
int Search_Seq(SSTable ST, KeyType key) { ST.R[0].key = key; //"哨兵" for(i=ST.length; ST.R[i].key!=key; --i); //从后往前找 return i; }
折半查找(二分查找):不适用于数据元素经常变动的线性表
优点:每一次查找比较都使查找范围缩小一半,比较次数少,查找效率高。
ASL = log2(n+1)-1
缺点:对表结构要求高。要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列
int Search_Bin(SSTable ST, KeyType key) {//在有序表ST中折半查找其关键字等于key的数据元素。若找到,则函数值为该元素在表中的位置,否则为0 low = 1; high = ST.length; //置查找区间初值 while(low<=high) { mid = (low+high)/2; if(key == ST.R[mid].key) return mid; //找到待查元素 else if(key<ST.R[mid].key) high = mid-1; else low = mid+1; }//while return 0; }
折半查找的判定树:查找过程可用二叉树来描述,树中每一个结点对应表中一个记录在表中的位置序号。把当前查找区间的中间位置作为根,左子表和右子表分别作为跟的左子树和右子树。
分块查找(索引顺序查找):顺序查找和分块查找的简单合成,满足既要快速查找又经常动态变化
需建立一个“索引表”,对每个子表(块)建立一个索引项,其中包括两项内容:关键字项(其值为该字表内的最大关键字)和指针项(指示该子表的第一个记录在表中位置)。索引表按关键字有序,则表或者有序或者分块有序。
查找过程分两步:①确定待查记录所在的块(子表)②在块中顺序查找
ASL = Lb(查找索引表确定所在块的ASL) + Lw(在块中查找元素的ASL)
ASL = 1/2 (n/s + s) + 1 (表长n、记录个数s)
优点:插入和删除比较容易,无需进行大量移动
缺点:要增加一个索引表的存储空间并对初始索引表进行排序运算
树表:可实现对动态查找表进行高效率的查找
二叉树 —>> 二叉排序树 —>> 平衡二叉树 —>> B-树 —>> B+树
二叉排序树(二叉查找树):中序遍历一棵二叉树时可以得到一个结点值递增的有序序列
非空二叉排序树所具有的性质:(1)若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;(2)若它的右子树不空,则右子树上所有结点的值均小于它的根结点的值;(3)它的左、右子树也分别为二叉排序树
二叉排序树的二叉链表存储表示
typedef struct { KeyType key; //关键字项 InfoType otherinfo; //其他数据项 }ElemType; //每个结点的数据域的类型 typedef struct BSTNode { ElemType data; struct BSTNode *lchild, *rchild; //左右孩子指针 }BSTNode, *BSTree;
BSTree SearchBST(BSTree T, KeyType key) {//在根指针T所指二叉排序树中递归地查找某关键字等于key的数据元素 //若查找成功,则返回指向该数据元素结点的指针,否则返回空指针 if( (!T) || key==T->data.key) return T; //查找结束 else if(key<T->data.key) return SearchBST(T->lchild, key); //在左子树中继续查找 else return SearchBST(T->rchild, key); //在左子树中继续查找 }
void InsertBST (BSTree &T, ElemType e) {//当二叉排序树T中不存在关键字等于e.key的数据元素时,则插入该元素 if(!T) { //找到插入位置,递归结束 S = new BSTNode; //生成新结点*s S->data = e; S->lchild = S->rchild = NULL; //新结点*s作为叶子结点 T = S; } else if(e.key<T->data.key) InsertBST(T->lchild, e); //将*s插入左子树 else if(e.key>T->data.key) InsertBST(T->rchild, e); //将*s插入右子树 }
void CreatBST(BSTree &T) {//依次读入一个关键字为key的结点,将此结点插入二叉排序树T中 T = NULL; cin >> e; while(e.key != ENDFLAG) //ENDFLAG为自定义常量,作为输入结束标志 { InsertBST(T,e); //将此结点插入二叉排序树T中 cin >> e; } }
void DeleteBST(BSTree &T, KeyType key) {//从二叉排序树T中删除关键字等于key的结点 p = T; f = NULL; //初始化 //下面的while循环从根开始查找关键字等于key的结点*p while(p) { if(p->data.key == key) break; f = p; //*f为*p的双亲结点 if(p->data.key > key) p = p->lchild; //在*p的左子树中继续查找 else p = p->rchild; //在*p的右子树中继续查找 } //while if(!p) return; //找不到被删结点则返回 /* 考虑三种情况实现p所指子树内部的处理: *p左右子树均不空、无右子树、无左子树 */ q = p; if( (p->lchild) && (p->rchild) ) //被删结点*p左右子树均不空 { s = p->lchild; while(s->rchild) //在*p的左子树中继续查找其前驱结点,即最右下结点 { q = s; s=s->rchild; //向右到尽头 } p->data = s->data; //s指向被删结点的"前驱" if(q!=p) q->rchild = s->lchild //重接*q的右子树 else q->lchild = s->lchild //重接*q的左子树 delete s; return; } //if else if(!p->rchild) //被删结点*p无右子树,只需重接其左子树 { p = p->lchild; } //else if else if (!p->lchild) //被删结点*p无左子树,只需重接其右子树 { p = p->rchild; } //else if //将p所指的子树挂接到其双亲结点*f相应的位置 if(!f) T=p; //被删结点为根结点 else if(q==f->lchild) f->lchild = p; //挂接到*f的左子树位置 else f->rchild = p; //挂接到*f的右子树位置 delete q; }
二叉排序树查找算法的性能取决于二叉树的结构,而二叉排序树的形状则取决于其数据集。
事实上,树的高度越小,查找速度越快。
平衡二叉树(ALV树):
非空平衡二叉树是具有如下性质的二叉排序树:(1)左子树和右子树的深度之差的绝对值不超过1;(2)左子树和右子树也是平衡二叉树。
将二叉树上结点的平衡因子定义为该结点左子树和右子树的深度之差,则平衡二叉树上所有结点的平衡因子只可能是-1、0和1。
平衡二叉树的平衡调整方法:找到离插入结点最近且平衡因子绝对值超过1的祖先结点,以该结点为根的子树称为最小不平衡子树,可将重新平衡的范围局限于这棵子树。
一般情况下,假设最小不平衡子树的根结点为A,则失去平衡后进行调整的规律可归纳为下列4种情况:
1、LL型:在A左子树根结点的左子树上插入结点
需进行一次向右的顺时针旋转操作
2、RR型:在A右子树根结点的右子树上插入结点
需进行一次向左的逆时针旋转操作
3、LR型:在A的左子树根结点的右子树上插入结点
需进行两次旋转操作,第一次对B及其右子树进行逆时针旋转,第二次进行顺时针
4、RL型:在A的右子树根结点的左子树上插入结点
需进行两次旋转操作,先顺时针右旋,再逆时针左旋
上述4种情况,1和2对称,3和4对称。当平衡的二叉排序树因插入结点而失去平衡时,仅需对最小不平衡子树进行平衡旋转处理即可。
--------内查找法(适用于存储在计算机内存中较小的文件)的分界线---------------------------------------------------------------------------------------------------
B-树:适用于外查找的平衡多叉树
B+树:B-树的变形树,更适合用于文件索引系统
散列查找法(杂凑法、散列法):在元素的存储位置和其关键字之间建立某种直接关系,按照这种关系直接由关键字找到相应的记录,即使用关键字到地址的直接转换方法。
术语:散列函数和散列地址,散列表,冲突和同义词
散列查找法主要研究两方面的问题:(1)如何构造散列函数;(2)如何处理冲突。
散列函数的构造方法:1、数字分析法;2、平方取中法;3、折叠法;4、除留余数法。
处理冲突的方法:1、开放地址法;2、链地址法。
以开放地址法为例,给出散列表的存储表示:
#define m 20 //散列表的表长 typedef struct{ KeyType key; //关键字项 InfoType otherinfo; //其他数据项 }HashTable[m];
#define NULLKEY 0 //单位为空的标记 int SearchHash(HashTable HT, KeyType key) { //在散列表HT中查找关键字为key的元素,若查找成功,返回散列表的单元标号,否则返回-1 H0 = H(key); //根据散列函数H(key)计算散列地址 if(HT[H0].key==NULLKEY) return -1; //若单元H0为空,则所查元素不存在 else if(HT[H0].key == key) return H0; //若单元H0中元素的关键字为key,则查找成功 else { for(i=1; i<m; ++i) { Hi = (H0+i)%m; //按照线性测探法计算下一个散列地址Hi if(HT[Hi].key == NULLKEY) return -1; //若单元Hi为空,则所查元素不存在 else if(HT.[Hi].key == key) return Hi; //若单元Hi中元素的关键字为key,则查找成功 } //for return -1; } //else }
第七章结束了之前几章的数据结构,在学期临近末期重新适应新的学习模式有一定的难度,很多的东西尚不能完全消化。