第九章 查找(Search) 9.1 基本概念 9.2 静态查找表 9.3 动态查找表 9.4 Hash表.

Slides:



Advertisements
Similar presentations
第 9 章 查找.
Advertisements

主讲:计算机工程学院 李兰 答疑地点:主教学楼B区213
第九章 查找.
主讲:计算机工程学院 李兰 答疑地点:主教学楼B区213
《高等数学》(理学) 常数项级数的概念 袁安锋
数据结构课程的内容.
数据结构——树和二叉树 1/96.
数据结构作业及答案 (C语言版).
第九章. 查找 (Chapter 9. Searching)
第9章 查找 9.1 基本概念和术语 9.2 静态查找表 9.3 动态查找表 9.4 哈希表 9.5 小结 顺序查找
第8章 查找 在英汉字典中查找某个英文单词的中文解释;在新华字典中查找某个汉字的读音、含义;在对数表、平方根表中查找某个数的对数、平方根;邮递员送信件要按收件人的地址确定位置等等。可以说查找是为了得到某个信息而常常进行的工作。 计算机、计算机网络使信息查询更快捷、方便、准确。要从计算机、计算机网络中查找特定的信息,就需要在计算机中存储包含该特定信息的表。如要从计算机中查找英文单词的中文解释,就需要存储类似英汉字典这样的信息表,以及对该表进行的查找操作。本章将讨论的问题即是“信息的存储和查找”。
第八章 查找.
第8章 查找 8.1 静态查找表 8.2 动态查找表 8.3 哈希表.
第十章 搜索 静态搜索结构 动态搜索结构 散列 可扩充散列.
第九章 查找 £9.1 概述 £9.2 静态查找表 £9.3 动态查找表 £9.4 哈希表 £9.1.1 查找表 £9.2.1 概述
第九章 查找.
第8章 查找 数据结构(C++描述).
第7章 查找 丽水学院工学院.
Chapter 7 Search.
第九章查找.
教 师:曾晓东 电 话: E_mail: 计算机软件技术基础 教 师:曾晓东 电 话: E_mail:
第11章 查找 主要知识点 查找的基本概念 静态查找表 动态查找表 哈希表.
第八章 查找表 8.1概述 8.2静态查找表 8.3动态查找表 8.4哈希表及其查找.
第12章 樹狀搜尋結構 (Search Trees)
辅导课程六.
第九章 查找 2018/12/9.
第八章 查找表 8.1概述 8.2静态查找表 8.3动态查找表 8.4哈希表及其查找.
第八章 查找表 8.1静态查找表 8.2动态查找表 8.3哈希表及其查找.
本章重点难点 重点:顺序查找、二分查找、二叉排序树查找以及散列表查找的基本思想和算法实现。
第7章 查找 北京师范大学 教育技术学院 杨开城.
第十章 IDL访问数据库 10.1 数据库与数据库访问 1、数据库 数据库中数据的组织由低到高分为四级:字段、记录、表、数据库四种。
何谓查找表 ? 查找表是由同一类型的数据元素(或记录)构成的集合。
第7章 查找 本章中介绍下列主要内容: 静态查找表及查找算法:顺序查找、折半查找 动态查找表及查找算法:二叉排序树 哈希表及查找算法.
第11讲 树和二叉树(二).
图内容回顾 图 Dijkstra算法 最短路径 Floyd算法 拓扑排序 邻接矩阵 有向图的应用 DAG图 关键路径 邻 接 表 存储结构
Ch.9 查找 §9.1 基本概念 查找和排序是两个重要的运算
报告人:陈思同 数据结构与数据库习题课 报告人:陈思同
数据结构 -Maple Related- 牟克典 数学科学学院信息科学系 2012秋季 1/16/2019.
Ch.9 查找 查找和排序是两个重要的运算 §9.1 基本概念
数据结构 第9章 查找 什么是查找?? 静态查找 动态查找 哈希表 数据结构.
第九章 查找表 9.1静态查找表 9.2动态查找表 9.3哈希表及其查找.
第九章 查找 2019/2/16.
顺序表的插入.
简单介绍 用C++实现简单的模板数据结构 ArrayList(数组, 类似std::vector)
计算机软件技术基础 数据结构与算法(5).
第7章 查找 7.1 查找的基本概念 7.2 静态查找表 7.3动态查找表 7.4 哈希表.
顺序表的删除.
第六章 树和二叉树 学习要点 理解树的定义和基本术语,重点了解二叉树的定义、性质、存储结构; 掌握二叉树遍历的递归算法及它的典型运算;
9.1 基本概念 9.2 线性表的查找 9.3 树表的查找 9.4 哈希表的查找 9.5 各种查找方法的比较
单链表的基本概念.
顺序查找.
Ch.9 查找 §9.1 基本概念 查找和排序是两个重要的运算
第 四 讲 线性表(二).
3.16 枚举算法及其程序实现 ——数组的作用.
多层循环 Private Sub Command1_Click() Dim i As Integer, j As Integer
Visual Basic程序设计 第13章 访问数据库
第15讲 特征值与特征向量的性质 主要内容:特征值与特征向量的性质.
实验目的:掌握数据的顺序存储结构及它们在计算机中的操作。 实验内容:
第8章 查找 预备知识 8.1 静态查找表 8.2 动态查找表 8.3 哈希表.
第9章 查找 静态查找表 二叉排序树 平衡二叉树(AVL树) 小结 B树 哈希表.
基于列存储的RDF数据管理 朱敏
1 School of Computing and Information Engineering.
插入排序的正确性证明 以及各种改进方法.
第七章 搜索结构 东南大学计算机学院 方效林 本课件借鉴了清华大学殷人昆老师 和哈尔滨工业大学张岩老师的课件.
本节内容 1.二叉排序树的定义 2.二叉排序树的查找操作 3.二叉排序树的插入操作 4.二叉排序树的删除操作 5.二叉排序树的总结
第9章 查找 9.1 查找的基本概念 9.2 线性表的查找 9.3 树表的查找 9.4 哈希表查找.
本节内容 1. 平衡二叉树的定义 2.平衡化旋转 3.平衡二叉排序树的插入操作 昆山爱达人信息技术有限公司
第二章 线性表 东南大学计算机学院 方效林 本课件借鉴了清华大学殷人昆老师 和哈尔滨工业大学张岩老师的课件.
最小生成树 最优二叉树.
§2 自由代数 定义19.7:设X是集合,G是一个T-代数,为X到G的函数,若对每个T-代数A和X到A的函数,都存在唯一的G到A的同态映射,使得=,则称G(更严格的说是(G,))是生成集X上的自由T-代数。X中的元素称为生成元。 A变, 变 变, 也变 对给定的 和A,是唯一的.
Presentation transcript:

第九章 查找(Search) 9.1 基本概念 9.2 静态查找表 9.3 动态查找表 9.4 Hash表

9.1 基本概念 查找表 查 找 查找成功 查找不成功 静态查找 动态查找 关键字 主关键字 次关键字 查 找 查找成功 查找不成功 静态查找 动态查找 关键字 主关键字 次关键字 ——由同一类型的数据元素(或记录)构成的集合。 ——查询(Searching)特定元素是否在表中。 ——若表中存在特定元素,称查找成功; ——否则,称查找不成功(应输出失败标志或失败位置); ——只查找,不改变集合内的数据元素。 ——既查找,又改变(增减)集合内的数据元素。 ——记录中某个数据项的值,可用来识别一个记录 ——可以唯一标识一个记录的关键字 例如“学号” ——识别若干记录的关键字 例如“女”

9.1 基本概念 查找的过程是怎样的? 对查找表常用的操作有哪些? 给定一个值K,在含有n个记录的文件中进行搜索,寻找一个关键字值等于K的记录,如找到则输出该记录,否则输出查找不成功的信息。 对查找表常用的操作有哪些? 查询某个“特定的”数据元素是否在表中; 查询某个“特定的”数据元素的各种属性; 在查找表中插入一元素; 从查找表中删除一元素。

9.1 基本概念 如何评估查找方法的优劣? 查找的过程就是将给定的值与文件中各记录的关键字逐项进行比较的过程。所以用比较次数的平均值来评估算法的优劣,称为平均查找长度(ASL:average search length)。 其中: n是文件记录个数; Pi是查找第i个记录的查找概率; Ci是找到第i个记录时所经历的比较次数。 显然,ASL值越小,时间效率越高。

9.2 静态查找(搜索)表 静态查找表抽象数据类型 ADT StaticSearchTable { Data: SET Operations: Create(&ST, n): 创建具有n个元素的查找表。 Destroy( &ST): 销毁查找表。 Search(ST,key): 查找具有关键字key 的元素。 Traverse( ST,visit()): 遍历查找表。 }ADT StaticSearchTable

9.1 静态查找(搜索)表 静态查找表可用顺序表或单链表存储, 本章只讨论顺序表。 一、顺序查找(线性查找) 二、折半查找(二分或对分查找) 三、分块查找(索引顺序查找)

一、顺序查找(Linear search,又称线性查找) 所谓顺序查找,又称线性查找,主要用于在线性结构中进行查找。 设若表中有 n 个记录,则顺序查找从表的先端开始,顺序用各记录的关键码与给定值x进行比较,直到找到与其值相等的记录,则称查找成功,给出该记录在表中的位置。 若整个表都已检测完仍未找到关键码与x相等的记录,则查找失败,给出失败信息。

一、顺序查找 1).顺序表的机内存储结构: typedef struct { ElemType *elem; //表基址,0号单元留空。表容量为全部元素 int length; //表长,即表中数据元素个数 }SSTable;

一、顺序查找 2).算法的实现: 编程技巧:把待查关键字key存入表头或表尾(俗称“哨兵”),这样可以加快执行速度(避免每一次查找都检测整个表是否查找完毕)。 若将待查找的特定值key存入顺序表的首部 (如0号单元),则顺序查找的算法思想为: 从后向前逐个比较! 例:

一、顺序查找 2).算法的实现: int Search_Seq( SSTable ST , KeyType key ){ ST.elem[0].key =key; for( i=ST.length; ST.elem[ i ].key!=key; - - i ); //不要用for(i=n; i>0; - -i) 或 for(i=1; i<=n; i++) return i; } // Search_Seq 同学们自己考虑对单链表如何线性查找?

一、顺序查找 3).顺序查找算法的平均查找长度(ASL) 为确定记录在查找表中的位置,需给定值进行比较的关键字个数的期望值 其中: n 为表长,Pi 为查找表中第i个记录的概率,假设每次查找都是成功的,则有pi=1 Ci为找到该记录时,比较过的关键字的个数, Ci = n-i+1,ASL = nP1 +(n-1)P2 +…+2Pn-1+Pn,如果要查找的元素在表中的任何位置的概率是相等的,p=1/n,ASL=(n+1)/2

3).顺序查找算法的平均查找长度(ASL) 如果有查找不成功的情况时,假设要查找的元素在表中的概率是p,查找不成功的概率为q=1-p 查找成功的平均查找长度为ASL=p(n+1)/2 查找不成功的平均查找长度为q(n+1) 平均查找长度为

3).顺序查找算法的平均查找长度(ASL) 在不等概率查找的情况下, ASLss 在Pn≥Pn-1≥···≥P2≥P1时取极小值 若查找概率事先无法测定,则为了提高查找效率,可以在每个记录中附设一个访问频度域,使顺序表中的记录保持按访问频度非递减有序排列,使得查找频率大的记录在查找过程中不断往后移,以便在以后的查找中减少比较次数,或者将刚刚查找到的记录直接移至表尾的位置上。

4)优缺点 优点:算法简单且适应面广。 缺点:平均查找长度较大。

要求n个记录存放在一个顺序表L中,并按其关键码从小到大排好了序,称此表为有序表。 二、折半查找(二分或对分查找) 1)算法思想: 要求n个记录存放在一个顺序表L中,并按其关键码从小到大排好了序,称此表为有序表。 先求位于查找区间正中的记录的下标mid,用其关键码与给定值x比较: L[mid].Key == x,查找成功; L[mid].Key> x,把查找区间缩小到表的前半部分,再继续进行对分查找; L[mid].Key< x,把查找区间缩小到表的后半部分,再继续进行对分查找。 每比较一次,查找区间缩小一半。如果查找区间已缩小到一个记录,仍未找到想要查找的记录,则查找失败。

查找成功的例子 查找失败的例子 比较序列:4[4],6[8],5[6] 4[4],6[8],5[6]

2)算法思想: 1 设n个记录存放在一个有序顺序表L中,并按其关键码从小到大排好了序。查找范围为l=0, r=n-1; 2 求区间中间位置mid=(l+r)/2; 3 比较: L[mid].Key = x,查找成功,返回mid,结束; L[mid].Key > x,r=mid-1; L[mid].Key < x,l=mid+1; 4 若l<=r 转2,否则查找失败,返回 0; 考虑对单链表结构如何折半查找? ——无法实现!

2)算法实现: int Search_Bin ( SSTable ST, KeyType key ) { // 若找到,则函数值为该元素在表中的位置,否则为0。 low = 1; high = ST.length; // 置区间初值 while (low <= high) { mid = (low + high) / 2; if (key == ST.elem[mid].key) return mid; // 找到待查元素 else if ( key < ST.elem[mid].key) high = mid - 1; // 继续在前半区间进行查找 else low = mid + 1; // 继续在后半区间进行查找 } return 0; // 顺序表中不存在待查元素 } // Search_Bin

3)算法分析: List[]: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 2 4 6 8 10 13 15 18 20 22 24 25 27 30 15 6 24 27 20 10 2 4 8 13 18 22 25 30 折半查找的判定树:有2n+1个结点, 高度不超过完全二叉树。

最多的比较次数不超过完全二叉树的高度: 所以: 最坏情况下的查找长度是: 查找成功的平均查找长度: 设查找任一记录的概率都相等,即:pi=1/n 在第k层上最多有2k-1 个结点,在第k层上的结点需 比较k次。

可以用归纳法证明 这样

,且关键字有序

三、静态树表的查找(自学) 在概率不等的查找情况下,折半查找不是有序表最好的查找方法。 关键字: A B C D E 例如: Pi: 0.2 0.3 0.05 0.3 0.15 Ci: 2 3 1 2 3 例如: 此时 ASL=20.2+30.3+10.05+20.3+30.15=2.4 若改变Ci的值 2 1 3 2 3 则 ASL=20.2+10.3+30.05+20.3+30.15=1.9

定义: 使 达最小的判定树称为最优查找树, 其中:

介绍一种次优二叉树的构造方法: 选择二叉树的根结点, 使 达最小 为便于计算,引入累计权值和 并设 wl-1 = 0 和 swl-1 = 0, 则推导可得

l h h 例如: l h h 2 3 8 11 15 18 23 21 18 12 4 3 3 10 18 9 6 8 5 3 3 1 1 2 A C E G

所得次优二叉树如下: E 和折半查找相比较 C G D B F A D F A C E G B 查找比较“总次数” = 32+21+35+13 +34+23+35 = 59 查找比较“总次数” = 32+41+25+33 +14+33+25 = 52

构造次优二叉树的算法 Status SecondOptimal(BiTree &T, ElemType R[], float sw[], int low, int high) { // 由有序表R[low..high]及其累计权值表sw // 递归构造次优查找树T。 选择最小的ΔPi值 if (!(T = (BiTree)malloc(sizeof(BiTNode)))) return ERROR; T->data = R[i]; // 生成结点

if (i==low) T->lchild = NULL; // 左子树空 else SecondOptimal(T->lchild, R, sw, low, i-1); // 构造左子树 if (i==high) T->rchild = NULL; // 右子树空 else SecondOptimal(T->rchild, R, sw, i+1, high); // 构造右子树 return OK; } // SecondOptimal

Status CreateSOSTre(SOSTree &T, SSTable ST) { // 由有序表 ST 构造一棵次优查找树 T。 次优查找树采用二叉链表的存储结构 Status CreateSOSTre(SOSTree &T, SSTable ST) { // 由有序表 ST 构造一棵次优查找树 T。 // ST 的数据元素含有权域 weight if (ST.length = 0) T = NULL; else { FindSW(sw, ST); // 按照有序表 ST 中各数据元素 // 的 weight 值求累计权值表 SecondOpiamal(T, ST.elem, sw, 1, ST.length); } return OK; } // CreatSOSTree

三、索引顺序表的查找(分块查找) 索引顺序表的查找是鉴于顺序查找和折半查找的一种查找。 索引顺序表是由索引表和顺序表两部分组成。 索引顺序表的特点是把若干个数据元素分成若干块,每一块称为一个顺序表,要求块与块之间要有序,即第i+1块的所有元素的关键字要大于第i块的所有元素的关键字。 为了查找方便,为每一块建立一个索引项: 索引项:(key,addr), key 域标记该块中最大记录的关键字, addr域指向该块的起始地址。 索引表是由若干索引项组成的有序表。

例: 22 15 13 8 17 20 35 24 33 40 36 29 50 48 60 49 54 61 22 40 61 1 7 13 最大关键字(key) 起始地址(addr) 第一块 索引表 第三块 第二块

三、索引顺序表的查找(分块查找) 有必要时可以在索引表上再建立索引表 稠密索引: 为每条记录设一个索引项。 稀疏索引:一组记录设一个索引项。

三、索引顺序表的查找(分块查找) 分块查找的数据结构: D={d1,d2,….,dn} 1. 将n个数据元素分为s个块 B1,B2, …, Bs ; 2. 块之间有序:Bi+1中的任一元素小于Bi中的任一元 素(i=1,2,…,n-1); 3. 为每个Bi块设一索引项(keyi, addri); 4. s个索引项构成索引表; 5. 块内可有序也可无序;

三、索引顺序表的查找(分块查找) 在索引顺序表中查找的基本思想: 首先在索引表中查找,确定该查找的元素在哪个块中(可以是折半查找,也可以是顺序查找)。 2. 然后在块中查找(只能是顺序查找)。

三、索引顺序表的查找(分块查找) 算法分析: 设在索引表中和块内都是顺序查找。 索引表中查找:ALSindex=(s+1)/2 块中查找: ALSlock=(n/s+1)/2 所以: ALSbs= (s+1)/2 + (n/s+1)/2=(n/s+s)/2+1 显然它与s 的取值有关。当 时,ASLbs有 最小值

例 n=10000; s=100; 则顺序查找: ASL=(n+1)/2 ≈ 5000次 ≈ 100次 分块查找: ASLbs= (n/s+s)/2+1 ≈ 100次 分块查找: Min(ASLbs)= ( ) 折半查找: ASL ≈14 次 当s=n时, 索引顺序表退化成有序表, 即可进行折半查找 当s=1时, 索引顺序表退化成顺序表, 即可进行顺序查找

9.2 动态查找(搜索)表 动态查找的特点是,表结构本身是在查找过程中动态生成的,即对给定关键字值key ,表中有具有此值的记录,则查找成功返回,否则插入此关键字的新记录。

数据对象D:具有相同特性的数据元素的集合。每个数据元素含有类型相同的关键字,可唯一标识数据元素。 数据关系R:数据元素同属一个集合。 动态查找表的抽象数据类型 ADT DynamicSearchTable { 数据对象D:具有相同特性的数据元素的集合。每个数据元素含有类型相同的关键字,可唯一标识数据元素。 数据关系R:数据元素同属一个集合。 基本操作: InitDSTable(&DT); //构造一个空的动态查找表DT DestroyDStable( &DT); // 销毁查找表DT。 SearchDSTable(DT,key); //查找具有关键字key 的元素。 InsertDSTable(&DT,e); //插入新记录。 DeleteDSTable(&DT,key );//删除记录。 TraverseDSTable(DT,visit()); //遍历查找表。 } ADT DynamicSearchTable

9.2 动态查找(搜索)表 一、二叉排序树 二、平衡二叉树

一、二叉排序树 1.二叉排序树的定义 二叉排序树或者是一棵空树,或者是具有下列性质的二叉树:  若它的左子树不空,则左子树上所有结点的关键字都 小于根结点的关键字。  若它的右子树不空,则右子树上所有结点的关键字都 大于根结点的关键字。  左子树和右子树分别是二叉排序树。

二叉排序树的例子 不是二叉排序树 50 30 80 20 90 10 85 40 35 25 23 88 66

二叉排序树的定义 如果对一棵二叉排序树进行中序遍历,可以按从小到大的顺序,将各结点关键码排列起来, 2.二叉排序树的存储结构(二叉链表) typedef struct BiTNode { // 结点结构 struct BiTNode *lchild, *rchild; ElemType data; } BiTNode, *BiTree;

3.二叉排序树的查找 算法思想: (1) p指向根; (2) p的关键字与key比较,有三种情况: p->data.key==key; 查找成功,返回p, 结束; p->data.key>key; 到左子树中找,p指向它左子女; p->data.key<key; 到右子树中找, p指向它右子女; (3) 重复(2)直到p为空,查找失败,返回空;

二叉排序树的查找 50 50 50 50 50 50 30 30 80 80 20 40 40 90 90 35 35 85 32 88 查找关键字 == 50 , 35 , 90 , 95 ,

二叉排序树的查找 从上述查找过程可见, 在查找过程中,生成了一条查找路径: 从根结点出发,沿着左分支或右分支逐层向下直至关键字等于给定值的结点; ——查找成功 或者 从根结点出发,沿着左分支或右分支逐层向下直至指针指向空树为止。 ——查找不成功

该算法可设计为递归算法,请大家自己设计。 二叉排序树的查找 查找算法: BiTree search(BiTree T, KeyType k) { p=T; while (p) if (k==p->data.key) return p; //查找成功,返回p else if (k<p->data.key) p=p->lchild; //到左子树中找 else p=p->rchild; //到右子树中找 return p; //查找失败,返回空 } // search 该算法可设计为递归算法,请大家自己设计。

在插入之前,先使用查找算法在树中检查要插入元素有还是没有。 查找成功: 树中已有这个元素,不再插入。 4.二叉排序树的插入 6 2 8 1 4 7 9 3的插入位置 在插入之前,先使用查找算法在树中检查要插入元素有还是没有。 查找成功: 树中已有这个元素,不再插入。 查找不成功: 树中原来没有关键码等于给定值的结点,把新元素加到查找操作停止的地方。

二叉排序树的插入 为找到插入位置的双亲,查找算法做如下改动: BiTree search(KeyType k,BiTree &pre, BiTree T) { pre=NULL; p=T; while (p) if (k==p->data.key) return p; //查找成功,返回p else { pre=p; if (k<p->data.key) p=p.lchild; //到左子树中找 else p=p->rchild; } //到右子树中找 return p; //查找失败,返回空 } // search

二叉排序树的插入 void insert(ElemType e, BiTree &T) { p=search(e.key, &pre,T); if (!p) { //树中无e,插入。 q=(BiTree)malloc(sizeof( BiTNode)); q->data=e; q->lchild=q->rchild=NULL; if (!pre) T=q; else if (e.key<pre->data.key) pre->lchild=q; //插到左子女 else pre->rchild=q; //插到右子女 } } // insert

每次结点的插入,都要从根结点出发查找插入位置,然后把新结点作为叶结点插入。 二叉排序树的插入 二叉查找树的查找 插入新结点88 每次结点的插入,都要从根结点出发查找插入位置,然后把新结点作为叶结点插入。

建立二叉排序树 输入数据序列 { 53,78,65,17,87,09,81,45,23}

输入数据,建立二叉排序树 输入顺序不同,建立起来的二叉排序树的形态也不同。 这直接影响到二叉排序树的查找性能。 如果输入序列选得不好,会建立起一棵单支树,使得二叉查找树的高度达到最大,这样必然会降低查找性能。 例:3 个数据{ 1, 2, 3 }, 1 1 2 3 3 2 3 1 3 1 2 3 2 {2, 1, 3} {2, 3, 1} 2 1 {3, 2, 1} {3, 1, 2} {1, 3, 2} {1, 2, 3}

5.二叉排序树的删除 和插入相反,删除是在查找成功时进行的,并且要求在删除二叉排序树上某个结点之后,仍然保持二叉排序树的特性。 三种情况讨论: (1)被删除的结点是叶子; (2)被删除的结点只有左子树或者只有右子树; (3)被删除的结点既有左子树,也有右子树。

二叉排序树的删除 (1)被删除的结点是叶子结点 其双亲结点中相应指针域的值改为“空” 被删关键字 = 20 88 例如: 50 30 80 40 90 35 85 32 88

(2)被删除的结点只有左子树或者只有右子树 二叉排序树的删除 (2)被删除的结点只有左子树或者只有右子树 其双亲结点的相应指针域的值改为 “指向被删除结点的左子树或右子树”。 删除40,80 50 30 80 20 40 90 35 85 32 88

将被删结点的左孩子代替被删结点,被删结点的右子树改为B的右子树; 二叉排序树的删除 (3)被删结点的左、右子树都存在 找到被删结点在中序下的前驱,设为B 将被删结点的左孩子代替被删结点,被删结点的右子树改为B的右子树; 将B代替被删结点,被删结点的右子树改为B的右子树,B的左子树成为B的双亲的右子树.

二叉排序树的删除 删除50 40 50 30 80 20 40 40 90 35 85 32 88

二叉排序树的删除算法 Void delete(BiTree T,KeyType k) { if (T) { // 树不空 p=search(k,&pre,T); //查找到被删除结点p及其双亲pre; if (p) { // 查找成功 if (!p->lchild) // p无左子女 if (!pre) T=p -> rchild; else if (pre -> lchild==p) pre -> lchild=p -> rchild; else pre -> rchild=p -> rchild; else if (!p -> rchild) // p无右子女 if (!pre) T=p -> lchild; else if (pre -> lchild==p) pre -> lchild=p -> lchild; else pre -> rchild=p -> lchild;

二叉排序树的删除算法 else { // p左右子女都不空 rr=p; r=p->lchild; //在 p 的左子树中找出最大结点 while (r->rchild) { rr=r; r=r->rchild; } rr->rchild=r->lchild; p->data=r->data; //将r中元素复制到 p中 free(r); } } // if (p) } // if (root)

查找、插入和删除算法的T(n) 均与二叉排序树的高度成正比。 6. 插入删除算法分析 查找、插入和删除算法的T(n) 均与二叉排序树的高度成正比。 1 6 2 2 8 4 6 7 1 4 7 9 8 9

若树退化成单分支,设树的深度为n, 则ASL=(n+1)/2 在最坏情况下: 在平均情况下,二叉排序树的的平均查找长度和 logn是同数量级的.

二、平衡二叉树(Balanced Binary Tree) 当确定搜索树的高度总是O(logn)时,能够保证每个搜索树操作所占用的时间为O(logn)。高度为O(logn)的树称为平衡树(balanced tree) 1962年,俄国数学家提出了一种非常流行的平衡树——AVL树(AVL tree)。

二、平衡二叉树(Balanced Binary Tree) 一个结点的左子树的高度减去右子树的高度所得的高度差称为该结点的平衡因子 。 一棵AVL树或者是空树,或者是具有下列性质的二叉排序树:任一个结点的平衡因子的绝对值不超过1(只能取 -1,0和 1)。 如果一棵二叉查找树是高度平衡的,它就成为 AVL树,如果它有n个结点,其高度可保持在O(log2n),平均查找长度也可保持在O(log2n)。

2. 平衡化调整 如果在一棵平衡的二叉排序树中插入一个新结点,造成了不平衡。此时必须调整树的结构,使之平衡化。 平衡化调整有4类: LL型调整 LR型调整 RR型调整 RL型调整

1) RR型调整 A A C B C B C A E h D h D D E E h + 1 B h h h h + 1 h h -1 A C -2 -1 B C B C A E h D h D D E E h + 1 B h h h h + 1 h h (a) (b) (c) 失衡原因:当在A的右孩子的右子树上插入一个新结点而导致A的平衡因子由-1变成-2。 调整操作:将C成为新子树的根结点上,A成为C的左孩子,同时将C原来的左子树D调整为A的右子树.

2) LR型调整 D h C E B A E A A B C B D h D E h h h h (a) (b) (c) - 1 A 2 E 1 A A B -1 -1 C B D h D E h h h - 1 h h h - 1 h - 1 (a) (b) (c) 失衡原因:当在A的左孩子的右子树上插入一个新结点而导致A的平衡因子由1变成2。 调整操作:将E成为新子树的根结点,A成为E的右孩子, B成为E的左孩子,同时将E原来的左子树调整为B的右子树,E原来的右子树成为A的左子树.

3) LL型调整 A A B C B B C D A D E h D E h E C h + 1 h h h + 1 h h h 1 A A 2 B C B B 1 C D A D E h D E h E C h + 1 h h h + 1 h h h (a) (b) (c) 失衡原因:当在A的左孩子的左子树上插入一个新结点而导致A的平衡因子由1变成2。 调整操作:将B成为新子树的根结点上,A成为B的右孩子,同时将B原来的右子树E调整为A的左子树.

4) RL型调整 A A C B B B A h C h C h h h h h h (a) (b) (c) h - h 1 - 1 -2 C -1 B 1 B B A -1 h C h C 1 h h h h - 1 h h h - 1 h h - 1 h - 1 (a) (b) (c) 失衡原因:当在A的右孩子的左子树上插入一个新结点而导致A的平衡因子由-1变成-2。 调整操作:将C成为新子树的根结点上,A成为C的左孩子, B成为C的右孩子,同时将C原来的左子树调整为A的右子树,C原来的右子树成为B的左子树.

例:请将下面序列构成一棵平衡二叉排序树: ( 13,24,37,90,53) 例:请将下面序列构成一棵平衡二叉排序树: ( 13,24,37,90,53) 13 37 24 -1 24 需要RL平衡旋转 (绕C先顺后逆) 13 -1 13 -2 13 24 37 90 53 -1 37 -2 37 -1 24 需要RR平衡旋转 (绕B逆转,B为根) 90 37 1 90 53 90 53 37

总结: 在AVL树上进行查找所需时间为 O(log2n)。 二叉排序树适合于组织在内存中的较小的索引(或目录)。对于存放在外存中的较大的文件系统,用二叉排序树来组织索引不太合适。 在文件检索系统中大量使用的是用B_树或B+树做文件索引。

9.3 哈希(HASH)表 一、哈希表是什么? 二、哈希函数的构造方法 三、处理冲突的方法 四、哈希表的查找 五、哈希表的删除操作

一、哈希表是什么? 以上两节讨论的表示查找表的各种结构的共同特点:记录在表中的位置和它的关键字之间不存在一个确定的关系。 查找的过程为给定值依次和关键字集合中各个关键字进行比较, 查找的效率取决于和给定值进行比较的关键字个数。

只有一个办法:预先知道所查关键字在表中的位置, 对于频繁使用的查找表,希望 ASL = 0。 只有一个办法:预先知道所查关键字在表中的位置, 要求:记录在表中位置和其关键字之间存在一种确定的关系。 例如:为每年招收的1000名新生建立一张查找表,其关键字为学号,其值的范围为 xx000 ~ xx999 (前两位为年份)。 若以下标为000 ~ 999 的顺序表表示之。 则查找过程可以简单进行:取给定值(学号)的后三位,不需要经过比较便可直接从顺序表中找到待查关键字。

对于动态查找表而言, 1) 表长不确定; 2) 在设计查找表时,只知道关键字所属范围,而不知道确切的关键字。 因此在一般情况下,需在关键字与记录在表中的存储位置之间建立一个函数关系,以 f(key) 作为关键字为 key 的记录在表中的位置,通常称这个函数 f(key) 为哈希函数。用Hash函数构造的查找表可进行查找、插入、删除,此表称为Hash表。

例1: 学生档案 学号 姓名 性别 专业 年级 01131101 AHZNG 男 CS 2001 01131102 WANG 男 CS 2001 01131103 LI 女 CS 2001 01131104 WU 男 CS 2001 01131135 BAI 男 CS 2001 01131136 GONG 女 CS 2001 01131162 PAN 女 CS 2001 1 2 3 4 34 35 61

学号是关键字, Hash函数:h(key)=key-01131101 如:key=01131135 addr= 01131135 – 011311 01 = 34 有时会出现: keyi <>keyj 但 h(keyi)=h(keyj) 这种现象称为冲突(碰撞) 称 keyi 和 keyj 是同义词;

{Zhao, Qian, Sun, Li, Wu, Chen, Han, Ye, Dei} 例2:对于如下 9 个关键字 {Zhao, Qian, Sun, Li, Wu, Chen, Han, Ye, Dei} 设 哈希函数 f(key) = (Ord(第一个字母) -Ord('A')+1)/2 0 1 2 3 4 5 6 7 8 9 10 11 12 13 Chen Zhao Qian Sun Li Wu Han Ye Dei 问题: 若添加关键字 Zhou , 怎么办?

要解决2个主要问题: (1)如何设计Hash 函数 (2)如何解决冲突 在构造这种特殊的“查找表”时,除了需要选择一个“好”(尽可能少产生冲突)的哈希函数之外;还需要找到一种“处理冲突” 的方法。

ADT HashTable is Data HashTable; Operations initiate() 初始化Hash表 hash(key) Hash函数 search(key) 查找key insert(key) 插入key delete(key) 删除key reCreate(size) 重建Hash表, 新表空间大于旧表,旧表中的元素按新表的Hash函数插入新表中 Ens ADT HashTable

二、哈希函数的构造方法 原则: 值在值域中分布均匀(减少冲突) 计算简单 值域落在表地址范围内; 1 、 直接定址法: h(key)=a* key + b a, b是常数 如上面的例子。 2 、数字分析法:对关键字集要有充分的了解。

2、 数字分析法:对关键字集要有充分的了解。 8 1 3 4 6 5 3 2 表地址为 0…99 8 1 3 7 2 2 4 2 取2位:4,5,6位分布 8 1 3 8 7 4 2 2 比较均匀,因此可取4,5位 8 1 3 0 1 3 6 7 做位Hash值。 8 1 3 2 2 8 1 7 8 1 3 3 8 9 6 7 8 1 3 5 4 1 5 7 8 1 3 6 8 5 3 7 8 1 4 1 9 3 5 5 一般情况: 表地址为: s…t, (t-s+1)是r位; 取分布较均匀的r位,组成addr H(key)=addr*t/10r + s

3 、平方取中法 H(key)=取(key)平方的中间r位; r取决于表长; 例: key=235, key2= 55225 表长是100, 则r=2; 取H(key)=22 4、折叠法与位移法 对key由左到右每r位分为一组,折叠相加; 例: key=24233566567,表长1000, 则:r=3 分组: key=242’335’665’67

242’335’665’67 折叠法:H(key)=516 位移法:H(key)=912 242 335 665 + 67 1912 242 533 665 + 76 1516 5、除留余数法: H(key)=key MOD p p<=m , m 是表长; p的选择是很重要的: 通常取p为小于等于m的质数; 6、随机数法: 选择一个随机函数random(),H(key)=random(key); 通常key的长度不等时易采用此方法。

三、处理冲突的方法 开放定址法 (Open Address Hash) 冲突处理 封闭定址法 (Closed Address Hash) 封闭定址法: Hash表链表数组,地址空间是H[0….n-1]; Hash表单元H[i]是一个链表,存放若干数据元素,他们是同义词; 它的基本思想是: 将同义词防在key的hash 码对应的链表中;

例: H(key)=key mode 8 Keys: 1051 1492 1779 1812 1917 1945 1 2 3 4 5 6 7 1051 1779 1492 1812 1917 1945

查找:对给定的key, 计算hash码 i=H(key); 在链表H[i]中顺序查找key; 算法分析: 计算hash码,很少的工作量,是常数 a; 在链表H[i]中顺序查找,(Li+1)/2, Li是该链表长度; 所以 其中: m是表长,n是数据元素个数; 请大家自己写出插入、删除算法

开放定址法: Hash表是数组,地址空间是H[0….n-1]; Hash表单元H[i]只存放一个数据元素; 它的基本思想是: 如果一个元素的Hash地址对应的Hash单元已被另一个元素占有(冲突),我们需定义一个候选地址序列,每当发生冲突时,选择下一个地址去试探。这个序列称为探测序列 Key的探测序列:d0, d1, ……, dm-1, m是表长; d0=H(key) d1, ……, dm-1,需要我们定义, 计算d1, ……, dm-1,的过程称为rehash(再Hash);

Rehash: 1). 最简单的rehash是线性探测再散列 (Linear Probing Rehash) di=(di-1+1) mod m or di=(d0+i) mod m or rehash(j)=(j+1) mod m j是最近一次探测的地址; 例: H(key)=key mode 8

Keys: 1051 1492 1776 1812 1918 1945 d0= 3 4 0 4 6 5 d1= 5 6 d2= 7 0 1 2 3 4 5 6 7 H 1776 1055 1492 1812 1918 1945 例: H(key)=key mode 8 Keys: 56 16 32 72 64 8 24 d0= 0 0 0 0 0 0 0 d1= 1 1 1 1 1 1 d2= 2 2 2 2 2 …………………………..

发生了大量的冲突,冲突位置集中,这种现象是聚集 2) 随机探测再散列 方法很多: di=(di-1+p) mod m p是一质数; 0 1 2 3 4 5 6 7 H 56 16 32 72 64 8 24 发生了大量的冲突,冲突位置集中,这种现象是聚集 2) 随机探测再散列 方法很多: di=(di-1+p) mod m p是一质数; di=(di-1+hashk(key)+1) mod m di=(di-1+(-1)i i) mod m

四、哈希表的查找 Int search(KeyType K, boolean &empty) { ans=-1; empty=false; // 缺省查找不成功; code=H(K); // 计算d0, code是起始探测位置, loc=code; //loc是当前探测位置 while (H[loc]!=emptyCell) if (H[loc]==K) {ans=loc; break; } //查找成功 loc=rehash(loc); if (loc==code) break; // 查找失败 } // while if (H[loc]!=emptyCell) empty=true; // 找到空位置 return ans; } // search

五、哈希表的插入和删除 插入算法 Boolean insert(KeyType K) { loc=search(K,&empty); if (loc>-1 && !empty) return false; //已有key if (empty) { H[loc]=K; return true; } //插入 reCreate(size*2); // 无空位置, 重建Hash表; H[loc]=K; return true; } // insert

删除 Keys: 1051 1492 1776 1812 1918 1945 d0= 3 4 0 4 6 5 d1= 5 6 d2= 7 0 1 2 3 4 5 6 7 H 1776 1055 1492 1812 1918 1945 插入1945 删除1918后, 查找1945, 探测H[5], 冲突, 再探测 H[6],空,认为无1945 查找1945 断桥现象

解决办法: 删除一个元素后,在其位置上做一删除标记 delete; 查找时遇到delete,认为是冲突; 插入时遇到delete,认为是空,可以插入; 算法分析: 负载因子(Load factor)α=n/m 其中n是表中元素个数, m是表长; 显然, α越大,冲突的可能性越大,从而ASL越大。

查找成功的ASL: 线性探测再散列: 随机探测再散列: 封闭定址法: 查找不成功的ASL:

本章小结 1.顺序表、有序表和索引顺序表的查找方法; 2.二叉查找树的构造方法和查找算法; 3.平衡二叉树的维护平衡的方法; 4.哈希表的构造方法,深刻理解哈希表的与其他结构的表的实质性的差别; 5.各种查找方法在等概率情况下查找成功时的平均查找长度。 本章重点:静态查找表和动态查找表的查找;平衡二叉树的维护平衡的方法;哈希表的构造方法

作 业 书面作业: p56: 9.3题, p57: 9.21题 p58: 9.26题 上机作业: 设计哈希表(p166)