1.11k likes | 1.24k Views
第九章和第十章介绍数据处理中常用的两种操作:查找和排序。 许多软件都为用户提供了查找和排序功能。 例如:文本编辑软件 Word , 各种信息管理软件。 线性表、树、图等,都可以根据需要,将查找或排序作为它们的基本操作。 由于查找和排序操作应用面广,使用频率高,所以对查找和排序的算法的质量,如时间效率,空间效率要求比较高。 这里,讨论应用中常用的几种查找方法、排序方法,它们的特点及适用场合。. 第 9 章 查找. 9.0 概述 9.1 静态查找表 9.2 动态查找表 9.3 哈希表 习题. 9.0 概 述. 一、查找表的定义
E N D
第九章和第十章介绍数据处理中常用的两种操作:查找和排序。第九章和第十章介绍数据处理中常用的两种操作:查找和排序。 • 许多软件都为用户提供了查找和排序功能。 • 例如:文本编辑软件Word, 各种信息管理软件。 • 线性表、树、图等,都可以根据需要,将查找或排序作为它们的基本操作。 • 由于查找和排序操作应用面广,使用频率高,所以对查找和排序的算法的质量,如时间效率,空间效率要求比较高。 • 这里,讨论应用中常用的几种查找方法、排序方法,它们的特点及适用场合。
第9章 查找 9.0 概述 9.1 静态查找表 9.2 动态查找表 9.3 哈希表 习题
9.0 概 述 一、查找表的定义 • 1. 查找表:由同一类型数据元素(或记录)构成的集合。 • 在查找表中数据元素除了同属一个集合,不考虑元素之间其它关系 • 2 查找表基本操作 • 1) 构造查找表 Create(&ST,n) • 2) 销毁查找表 Destroy(&ST) • 3) 各种查找操作
二、查找的有关概念 1. 静态查找表、动态查找表 • 查找表最基本的操作是查找,常用的查找操作: 1)查询某个“特定”元素是否在表中; 2)检索某个“特定”元素的各种属性; 3)查找某个“特定”元素是否在表中,若不在,则将该元素插入表中; 4)从查找表中删除某个“特定”元素; • 静态查找表:若对查找表只有1)、2)两种操作 • 动态查找表:若对查找表还提供3)、4)两种操作
2. 记录、关键字 • 记录:由若干数据项构成的数据元素。 • 关键字:数据元素(或记录)中某个数据项,用它可以标识一个数据元素(或记录) 。 • 例:学生管理系统,若想按姓名查找,可将姓名作为关键字,若想按学号查找,可将学号作为关键字, 学号 姓名 专业 年龄 01 王洪 计算机 17 02 孙文 计算机 18 03 谢军 计算机 18 04 李辉 计算机 20 05 沈祥福 计算机 25 06 余斌 计算机 19 07 巩力 计算机 17 08 王辉 计算机 18
说明: • ①记录的任何数据项都可用作关键字; • ②若此关键字的值能唯一标识记录,则称该关键字为主关键字,否则次关键字; • 例:学号是主关键字,姓名是次关键字
3. 查找 • 查找:给定某个值,在查找表中查找关键字值等于给定值的记录。 • 若表中存在这样的记录,则称查找成功,查找结果为该记录(在表中)的位置; • 否则,称查找不成功,查找结果为0或nu11。
4. 平均查找长度 • 查找算法的效率用平均查找长度衡量(设查找不成功的可能性很小,可忽略不计) • 平均查找长度ASL = 在查找过程中与给定值比较的关键字个数的数学期望值 PiCi • Ci为在查找表中查找第i个记录所需的比较次数, • Pi为在查找表中查找第i个记录的概率 • i=1,2…n
三、查找表的组织与查找 • 如何在查找表中查找?取决于如何组织查找表 • 例1: 查找索引号为TP312.12的图书 • 1)若图书馆中的图书无序地摆放,则只能顺序查找; • 2)若图书按索引号顺序摆放,则可先找到TP类图书,再找到TP3。 • 例2 在词典中查找单词 are • 1)若词典中无序排列,则只能顺序查找。 • 2)先找到a打头的单词,再在a打头的单词中找。 • 本章介绍: • 静态查找表的组织方法与查找 • 动态查找表的组织方法与查找 • 哈希表的组织方法与查找
约定: • 1)本章查找是关于主关键字查找; • 2)假设本章涉及的关键字为整型类型; • 3)为使查找的图示简洁,对于查找表中的每一记录,只写出其关键字。 关键字类型定义 记录类型定义 typedef int KeyType; typedef struct{ KeyType key; //关键字域 … //其它域 }ElemType;
两关键字比较的宏定义 #define EQ(a,b) ((a)==(b)) #define LT(a,b) ((a)< (b)) #define LQ(a,b) ((a)<=(b))
9.1 静态查找表 • 静态查找表:只提供如下两种查找的查找表。 • 1) 查询某个“特定”元素是否在表中; • 2) 检索某个“特定”元素的各种属性; • 对静态查找表介绍两种组织与查找方法 • 顺序表及查找 • 有序表及查找
9.1.1 顺序表及查找 • 查找表组织: • 查找表用线性表表示。即将查找表的记录排成一个记录序列。 • L1=(45,53,12,3,37,24,100,61,90,78) • 设查找表用顺序表存储,其类型定义如下: Tyypedef struct { ElemType *elem; int length; }SSTable;
顺序查找法 • 1)基本思想:从表的最后一个记录(或第一个记录)开始,依次将记录的关键字与给定值比较,若相等,查找成功,否则,继续查找。
elem key 0 1 2 ... length-1 length length 2)算法 int Search_Seq(SSTable ST, KeyType key){ //在顺序表ST中顺序查找其关键字等于key的数据元素。 //若找到,则函数值为该元素在表中的位置,否则为0。 ST.elem[0].key=key; // ST.elem[0].key为“哨兵” for(i=ST.length; !EQ(ST. elem[i]. Key, key); - -i) ; //从后向前找 return i; //若表中不存在待查元素,i=0 }//Search_Seq
比较:算法2.6(P25) int Location( SqList L, ElemType e,Status (*compare)(ElemType, ElemType)) { //在顺序表L中查找第1个值与e满足compare( ) //的元素的位序 k = 1; p = L.elem; while ( k<=L.length && !(*compare)(*p++,e))) k++; if ( k<= L.length) return k; else return 0; } //Location
3)说明: • 1)算法简单; • 2)顺序查找法对于查找表的存储结构没有特别的要求,既可用顺序表,也可用线性链表存储; • 3) 平均查找长度ASLSS=(n+1)/2 取Pi=1/n ASL = Pi Ci = nP1+(n-1)P2+...+2Pn-1+Pn = [n+(n-1) +...+2+1]*1/n = (n+1)/2 若查找成功与不成功的概率相同,则: ASL = (n+1)/4+(n+1) /2= 3(n+1)/4
9.1.2 有序表及查找 • 有序表 • 线性表中的记录按关键字有序 • 查找表组织 • 查找表用有序表表示。 • 即将查找表的记录排成按关键字有序的序列。
二分查找法(也称为折半查找法) • 1)基本思想:将查找范围中间位置的记录关键字与给定值比较,若 < :继续在前半个表中用二分查找法查找 = : 查找成功,返回记录位置 > :继续在后半个表中中用二分查找法查找
1 2 3 4 5 6 7 8 9 10 3 12 24 37 45 53 61 78 90 100 low mid high 1 2 3 4 5 6 7 8 9 10 3 12 24 37 45 53 61 78 90 100 low mid high 1 2 3 4 5 6 7 8 9 10 3 12 24 37 45 53 61 78 90 100 Low mid high • 例1. L2=( 3,12,24,37,45,53,61,78,90,100 ),查找 Key=24的记录 24 < 45 继续在前半个表中用二分查找法查找 24 > 12 继续在后半个表中用二分查找法查找
2)算法 int Search_Bin(SSTable ST, KeyType key){ //在有序表ST中折半查找其关键字等于key的数据元素。 //若找到,则函数值为该元素在表中的位置,否则为0。 low=1; high=ST. length; while(low<=high) { mid=(low+high)/2; if EQ(key, ST. elem[mid].key) return mid; //找到待查元素 else if LT(key, ST. elem[mid]. key)high=mid-1; //继续在前半区间进行查找 else low=mid+1;//继续在后半区间进行查找 } return 0; //表中不存在待查元素}//Search_Bin
5 4 3 1 6 7 9 8 2 10 • 二分查找法查找过程可用判定树描述 1 2 3 4 5 6 7 8 9 10 • 例:L2=( 3,12,24,37,45,53,61,78,90,100 ),查找过程可用如下判定树描述。 查找次数=结点所在的层数。 查找成功时查找次数不会超过判定树的深度。 n个结点的判定树的深度为[log2n]+1. 平均查找长度:ASL = PiCi =(1+22+34+43)/10=2.9
折半搜索性能分析 5 4 3 1 6 7 9 8 2 10 • 折半查找法的平均查找长度 • 假设描述折半查找法的判定树高度为h 的满二叉树(n = 2h-1),则第j层的结点个数为2j-1(j=1,…,h) ASL =PiCi = 1/n j * 2j-1 =(n+1)/n *log(n+1)-1 ≈log(n+1)-1 (证明见书上P221)
3)说明 • 折半查找法效率比顺序查找高;平均查找长度 ASLbs=log2(n+1)-1 • 要求表中的记录按关键字有序; • 表中的记录要用顺序结构存储; • 例:在有1000个记录的查找表查找 • 顺序查找法平均要比较500次 • 折半查找法平均要比较9次 • 可见折半查找法效率比顺序查找高。
插值查找 • 如果事先已经知道数据的分布情况,就可以充分利用数据的分布情况提高搜索的性能。 • 基本思路 • 根据待查找数据的取值预估其在数据结构中的位置 • 与折半查找的主要差别 • 中点的计算考虑了检索数据的取值和数据的分布 • 假设数据是均匀分布, 根据插值公式计算中值,mid= low+(key-ST. elem [low])/(ST. elem[high]-ST. elem [low]) *(high-low)
设有一个有序的线性表{0, 1, 2, 3, 4, 5, 6, 7,8, 9, 10, 11},需要搜索关键字为8的数据。 • 插值查找 • 由于8在0-11的区间中的位置是66.6%,首先和线性表中位于66.6%位置的数据进行比较。 • Mid=1+ (8-0)/(11-0)*(12-1) =9 • 因此通过1次比较就能找到最终的结果。 • 折半查找 • 第1次:mid=(1+12)/2=6 关键字为5 • 第2次:mid=(7+12)/2=9 查找成功
斐波那契查找 • 问题提出 • 插值搜索的前提是数据在线性表中分布比较均匀,但在很多情况下数据的分布是不均匀的,而且其分布也是事先未知的。 • 为了追求更好的平均性能,斐波那契搜索是一个很好的选择,根据斐波那契数列进行分割。 • 斐波那契数列的定义是: • F(0)=0, F(1)=1, F(n)=F(n-1)+F(n-2),n>=2 • 优点: • 在大部分的数据分布的情况下具有比较好的性能。 • 只须做加、减法运算而无须做乘除法运算
长度为n=F(u) -1的搜索空间, 其比较的中点是 • mid=low+F(u-1)-1 • 如果key=ST.elem[mid],查找成功; • 如果key<ST.elem[mid],则high=mid-1,u=u-1; • 如果key>ST.elem[mid],则low=mid+1,u=u-2. F(u)-1 F(u-1)-1 F(u-2)-1
9.3 动态查找表 • 如果应用问题涉及的数据量很大,而且数据经常发生变化,如: • 图书馆经常购进图书,每购进新书,需将新书记录插入图书表 • 对这类表除了提供1)、2)两种查找外,还要提供动态查找功能: • 3)查找某个“特定”元素是否在表中,若不在,则将该元素插入表中; • 4)查找某个“特定”元素是否在表中,若在,从表中删除该元素;
如何组织动态查找表? • 顺序表: • 顺序查找效率低 • 有序表: • 二分查找法须用顺序结构存储有序表,插入删除操作要移动元素; • 本节介绍动态查找表的一种组织形式 • 二叉排序树
9.3.1 二叉排序树 45 53 61 78 12 3 37 90 24 100 1. 二叉排序树的概念 • 二叉排序树(Binary Sort Tree)或者是一棵空树;或者是具有下列性质的二叉树: • (1)若左子树不空,则左子树上所有结点的值均小于它的根结点的值; • (2)若右子树不空,则右子树上所有结点的值均大于它的根结点的值; • (3)左、右子树也分别为二叉排序树; 其中序遍历序列: 3,12,24,37,45,53,61,78,90,100 二叉排序树
2. 二叉排序树的查找 基本思想 • 若二叉排序树为空树,查找失败,返回null或 0; • 否则,将key与根结点的关键字比较: • 若key = 根结点的关键字,查找成功,返回该记录在二叉排序树中的位置; • 若key根结点的关键字,继续在左子树中查找; • 若key 根结点的关键字,继续在右子树中查找;
45 53 61 78 12 3 37 100 90 24 例:在如下二叉排序树中查找关键字为24的记录 2445 继续在左子树中查找 2412 继续在右子树中查找 2437 继续在左子树中查找 查找成功!
45 53 61 78 12 3 37 100 90 24 例:在如下二叉排序树中查找关键字为60的记录 查找路径 左子树为空 查找失败!
算法 BiTree SearchBST(BiTree T, KeyType key) { //二叉排序树用二叉链表存储。 //在根指针T所指二叉排序树中递归地查找关键字等于key的记录, //若查找成功,则返回该记录结点的指针,否则返回空指针 if ((!T)|| EQ(key, T->data. key)) return(T); // 若T为空或查找成功,返回 else if LT(key, T->data. key) return(SearchBST(T->1child, key)); //在左子树中继续查找 else return(SearchBST(T->rchild, key)); //在右子树中继续查找 }//SearchBST 算法9.5(a)
3. 二叉排序树的插入、删除 • 在一般二叉树的基本操作中只有插入、删除子树操作,没有插入、删除结点操作。 • 原因是除了叶子结点外,在二叉树中插入、删除结点将破坏树的结构。 • 在二叉排序树中插入、删除结点的原则是: • 插入、删除结点后仍是二叉排序树。
1)二叉排序树的插入 • 新插入的结点一定为叶子结点,并且是查找不成功时,查找路径上最后一个结点的左孩子或右孩子。 • 将新结点插入二叉排序树基本步骤: • 从二叉排序树的根结点开始, • 若二叉排序树为空树,新插入结点作为根结点, • 否则, • 若新结点的关键字当前结点的关键字,则将新结点作为当前结点的左孩子; • 若新结点的关键字>当前结点的关键字,则作为右孩子。
45 53 61 78 12 3 37 40 100 90 24 例:在如下二叉排序树中插入结点 40 为查找插入位置走过的路径 插入结点40!
算法 Status SearchBST(BiTree T, KeyType key, BiTree f, BiTree &p) { //在二叉排序树中递归地查找关键字等于key的记录, //若查找成功,则指针p该记录结点,否则p指向最后访问的结点 //f指向T的双亲,初值为NULL if (!T) {p=f, return FALSE}; //查找不成功 else if EQ(key, T->data. key){p=T; return TRUE;} //查找成功 else if LT(key, T->data. key) return(SearchBST(T->1child, key, T, p)); //在左子树中查找 else return(SearchBST(T->rchild, key , T, p)); //在右子树中查找 }//SearchBST 算法9.5(b)
Status InsertBST(BiTree T, ElemType e) { //二叉排序树不存在关键字等于e.key时,插入e并返回TRUE if (! SearchBST(T, e.key, NULL, p) ) { s=(BiTree)malloc(sizeof(BiTNode)); s->data=e; s->lchild=s->rchild=NULL; if(!p)T=s; //根结点 else if LT(e.key,p->data.key)p->lchild=s; //左孩子 else p->rchild=s; //右孩子 return TRUE; }//if else return FALSE; }//InsertBST 算法9.6
45 45 45 45 45 24 24 24 24 53 53 53 12 12 90 • 例如:对{45,24,53,45,12,24,90} 关键字序列的二叉排序树生成过程如下:
2)二叉排序树的删除 • 在二叉排序树中删除结点的原则是: • 删除结点后仍是二叉排序树。 • 对二叉排序树进行中序遍历,所得到的中序序列是一有序序列。 • 在二叉排序树中删除一个结点相当于在有序序列删除一个结点,只要删除结点后仍保持二叉排序树的特性。
P F f f F P F f PL PR p p • 设:P为F的右孩子 • 被删除结点是P (p是指向P的指针) • 其双亲结点为F (f是指向F的指针) • 分三种情况讨论: • (1)P为叶子结点,则直接删除,即将f->rchild 赋值为null; 删除前 删除后 二叉排序树 P为叶子结点
F P f F f PL PL p • (2)P只有一棵子树(左子树PL或右子树PR),则令PL或PR直接成为双亲结点F的右子树; 删除后 删除前 P只有左子树PL
Q S F Q C C F Q S P C F S SL QL SL PR CL QL PR SL CL QL CL PR • (3)P有两棵子树(左子树PL和右子树PR) • 设S是P的直接前驱,则S是P的左子树的最右下结点。 • 在删除P后,为保持中序序列中其它元素之间的相对位置不变,可有两种作法: • (a)令P的左子树为F的右子树,而P的右子树为S的右子树; • (b)用P的直接前趋S代替P,然后从二叉排序树删除P直接前趋S; (b) 删除P前的二叉排序树 (a)
性能分析 3 78 37 90 53 3 61 12 37 45 53 61 78 12 24 100 100 90 45 24 • 二叉排序树的查找效率与树的形态有关 • 若树的形态为“单枝”,二叉排序树的查找就“退化”为顺序查找; • 若树的形态比较“平衡”,二叉排序树的查找与二分查找类似; 为获得较高的查找效率,需要构造“平衡”的二叉排序树。
4.平衡二叉树(Balanced Binary Tree) • 平衡二叉树, 也称AVL树(Adelson-Velsky-Landis) • 或者是空树 • 或者它的左子树和右子树也都是平衡二叉树 • 平衡因子BF(Balance Factor) • 左子树的深度减去右子树的深度 • 只可能是 -1, 0, 1 • 左子树和右子树的深度之差的绝对值不超过1 • 平衡二叉树的深度为O(log N),平均查找长度也是O(log N)
结点的平衡因子 -1 2 -1 1 -1 1 0 1 -2 -1 0 0 0 0 0 0 0 0 1 1 1 0 0 0 • 平衡二叉树 • 不平衡二叉树
平衡化旋转 • 如果在一棵平衡的二叉搜索树中插入一个新结点,造成了不平衡。此时必须调整树的结构,使之平衡化。 • 平衡化旋转有两类: • 单旋转 (左旋和右旋) • 双旋转 (左平衡和右平衡)
具体做法 • 从插入位置向根回溯,检查各结点的平衡因子。 • 从发生不平衡的结点起,沿刚才回溯的路径取直接下两层的结点。 • 如果这三个结点处于一条直线上 • 采用单旋转进行平衡化(左单旋转和右单旋转) • 如果这三个结点处于一条折线上 • 采用双旋转进行平衡化(先左后右和先右后左) 右单旋转 左单旋转 右左双旋转 左右双旋转