数据结构-查找/排序
八、查找(算法:折半查找,其他画图)
查找:给定一个值k,在含有n个记录的表中找出关键字等于k的记录。
1.顺序查找:
①查找成功ASL₁/平均查找长度:(n+1)/2
②
对于同一个表,顺序查找和折半查找的速度大小不能确定
2.折半查找
平均查找长度:log₂(n+1)-1 ⇒ 0(log₂n)
平均比较长度log₂(n+1)
查找不成功比较个数/给定值比较个数最多:
logn下)+1 即折半查找树的高度
③(low+high)/2 ⇔ 向下取整
④折半插入排序与待排记录初始状态无关
不管你初始数据是已经有序、基本有序、无序,比较操作都是只在前面已经排好序部分中进行。。。
3.二叉排序树的查找与折半查找时间性能比较
平衡二叉(排序)树的查找性能与折半查找时间类似(相同)
二叉排序树的查找性能与折半查找不相同
由于二叉排序树不一定是平衡树,通过二叉排序树进行查找不一定能够满足logn⇒与折半查找树性能不相同。
折半查找判定树(279)
折半查找判定树一定是平衡二叉树(不一定是完全二叉树)
构造:①先将n个关键字按照升序排列
②折半查找判定树定义,0-n个元素满足:
第[(0+n)/2下取整]个关键字(即6个元素取中间两个元素靠左的元素)为根节点
其左边的元素为其左子树,右边的元素为其右子树。两个子树同样满足判定树定义
ASL(平均查找长度)
ASL1(平均查找成功比较次数):
各元素的深度(如根节点为1),即为查找该元素成功的比较次数
ASL2(平均查找失败比较次数):
空指针代表查找不成功的位置,不把对空指针的比较次数计算在比较次数⇒不成功的比较次数即为(指针)双亲结点的深度
考点
1.折半查找需要表必须有序,且只能顺序存储
2.折半查找不适用于顺序存储的动态查找表
3.对n个关键字的有序表进行折半查找,在等概率的情况下查找成功时的平均查找长度
画出n个结点的完全二叉树,求ASL(平均查找长度)即可
原理:折半查找判定树的ASL=仅最后一层可能缺少结点的二叉树ASL
3.分块(索引顺序)查找
块內无序,块间有序
把线性表分成若干块,块中元素顺序任意;块与块之间按照关键字大小有序排列。
分块查找是折半查找和顺序查找的一种改进方法,分块查找由于只要求索引表是有序的,对块内节点没有排序要求,因此特别适合于节点动态变化的情况。
建立一个索引表,每个索引项(对应线性表中的一块)由键值分量(存放对应块的最大关键字)和链值分量(存放指向本块第一个元素和最后一个元素的指针,指针可以是元素数组下标/元素地址)组成
分块查找:(1)二分查找法确定待查找的元素属于哪一块(索引表递增有序)
(2)块内顺序查找该元素
考点:
1.分块查找对一个线性表既能较快的查找,又能适应动态变化的要求
4.动态查找(表)
动态查找:查找的同时对表进行插入、删除工作
动态查找表的特点是,表结构本身是在查找过程中动态生成的,即对于给定值key,若表中存在其关键字等于key的记录,则查找成功返回,否则插入关键字等于key的记录。
4-1二叉排序树
(左子树关键字<根<右子树关键字)
二叉排序树相邻关键字:p结点 左子树的右下结点或右子树的左下结点(对应的关键字)
二叉排序树的形状取决于结点的输入顺序
(1)查找关键字:略(天勤280)
(2)插入关键字:查找不成功即为关键字的插入地址
二叉排序树中插入的关键字都存储在新创建的叶子上
(3)构建二叉排序树:建立一颗空树,然后将关键字逐个插入到空树中
(4)删除关键字(在p结点上):
①p结点为叶子结点:直接删除
②p结点只有一个子树:将p删掉,将p的子树直接连接在原来p与其双亲结点f相连的指针上即可
③p结点有两个子树:将关键字(p结点上)与相邻关键字(r结点上)交换,r结点最多有一个子树(按照①/②方法删除r结点)
二叉排序树的查找效率取决于高度,高度越小,效率越高
结点相同的二叉排序树,平衡二叉树的高度最小
考点
1.二叉排序树定义:不可能存在有关键字相同的值
4-2平衡二叉树(AVL树)
(1)概念:(丨平衡因子即左右子树高度差丨≤1)
平衡二叉树首先是二叉排序树,左右子树也是平衡二叉树
(2)平衡二叉树的建立:
①(同二叉排序树建立一样)将关键字逐个插入到空树中
②每插入一个关键字都要检查,是否使得原平衡二叉树失去平衡,即树中出现平衡因子绝对值大于1的结点
失去平衡⇒平衡调整
平衡调整
找最小不平衡子树(失去平衡的最小子树,即距离插入结点最近,且以平衡因子绝对值大于1的结点作为根的子树),调整这颗子树
LL/RR调整:
root的L₁L₂/R₁R₂插入了一个结点导致root1不平衡
①root(最小不平衡子树的根节点)下降一个结点高度,其左/右孩子上升一个结点高度
②左右孩子(现为根节点)多出的子树子挂在root的左/右子树上
LL/RR插入结点导致不平衡,
①root下降+左/右孩子上升 ②多出的子树连在原root上
L1R2/R1L2调整:
root是最小不平衡结点
①孙子结点(R2/L2)拿出来,左右链接
其父亲(R1/L1根节点)和爷爷(root)
②而孙子结点本来的两颗子树分别链接其父亲(R1/L1)和爷爷(root)
考点
1.当每个非叶子结点的平衡因子为0时⇒满二叉树
2.n个结点层数最少:
等同与n个结点的完全二叉树的高度
(logn下取整)+1
3.深度h的平衡二叉树最少结点树:
Nh表示高度为h的平衡二叉树中含有的最少结点数
N0=0,N1=1,N2=2…Nh=N(h-1)+N(h-2)+1
如N5=12⇒12个结点最多有5层
N6=20⇒15个结点最多有五层
由2,3可知,12个结点的平衡二叉树中叶子结点的层数最小为3,最大为5
简便方法:直接画深度为n的单左子树二叉树,然后再补充结点使满足平衡二叉树
4-2-1:B-(B树,上取整)
可以为空树
不为空树时,树中每个结点至多有m棵子树
根节点至少两颗子树/其他非根非叶结点至少有(m/2上取整)子树
m阶B-树(每个结点至多有m个分支,最多有m-1个关键字)⇒m叉平衡排序树
B-树的所有叶子(终端)结点都在同一层(不带信息,看作空指针代表查找不成功)
(1)插入关键字
B-树新关键字的插入总是落在终端结点,插入有可能破坏B-树的特性,需要进行结点的拆分
①按照B-树的查找方法找到插入位置,直接插入
②检查被插入结点内关键字个数,大于m-1则拆分
③取出第(m/2上取整)个关键字k
k的左右关键字做成两个结点连接在k左右的指针
并将k关键字插入其父结点相应位置
④检查父结点
插入只会让B-树逐渐变高不会改变叶子结点为同一层的特性(但不一定每次插入都会使B-树变高)
(2)删除关键字
要删除的关键字不在终端结点,则需要先将其转化在终端结点上:
与相邻关键字交换(在终端结点上),再删除
①关键字个数大于(m/2上取整)-1,直接删除
②借(让兄弟少关键字,自己不少)
③合并(自己少一个,兄弟合并,再问双亲要个
④检查父结点,父结点不够再(与兄弟)合并,如此循环
考点
1.B-树高度包含叶子结点层(但结点数一般不考虑叶子结点,一般会声明求非终端结点即非叶结点)
2.高度为2的5阶B-树中,所含关键字个数最少为5个
(天勤-303)
4-2-2:B+树
B+树上有一个指针指向关键字最小的叶子结点,所有叶子结点链接成一个线性链表
B+树所有的叶子结点包含了全部的关键字信息,且叶子结点本身按照关键字的大小自小到大顺序连接
与B树的相同点:
1.都是平衡多叉树
2.都可用于文件的索引结构
3.都能有效地支持随机检索
区别:
1.B+树能有效地支持顺序查找(检索)
因为所有的叶节点都用指针链接起来了
2.B+树的叶子结点包含关键字,B-树不包含
5.哈希(散列/Hash)表
根据给定的关键字依照函数H来计算关键字key在表中的地址,并把key存在这个地址上
结点的存储结构与其关键字之间存在某种映射关系
Hash函数的构造方法:
(1)直接定址法
H(key)=key 或H(key)=a*key+b
直接定址法不会发生冲突
(2)除余留数法
H(key)=key mod p
Hash冲突处理方法:
按照什么处理方法处理冲突,查找元素时就按照什么方法查找元素
如平方探测法,依次查找d+1²,d-1²,d+2²,d-2²…
开放定址法
(1)线性探查法
从发生冲突的地址(设为d)开始,依次探查d的下一个地址,直到找到一个空位置为止
(2)平方探测(二次探测再散列)
H(key)=d,d处发生冲突,
再依次探查d+1²,d-1²,d+2²,d-2²…
链地址法(封闭/固定地址)
把所有的同义词用单链表连接起来,Hash表中每个单元中存放的不是记录本身,而是相应同义词单链表的表头指针
新来的元素插到地址链表的表尾
考点
1.散列不适合动态查找
2.开放定址法讲究的是逻辑地址连续,删除元素也是逻辑删除而不是物理删除
3.Hash查找法时间复杂度为0(1)
4.Hash函数值应尽量同等概率取其值域的每个值
5.平均查找长度不直接依赖于关键字个数n
(ASL与装填因子a=(n/m)成正比,m为表长度)
装填因子反映哈希表的装满程度
6.装填因子(关键字/内存):与空间利用率/冲突有关,装载因子越小,冲突的可能越小
7.堆积现象直接影响平均查找长度
九、排序。
1.插入类排序
在一个已经有序的序列中,插入一个新的关键字
1-1★直接插入排序(掌握伪代码)
思想:在有序序列的最后插入一个新元素,依次与前面的元素比较大小(比前面的小,则将前面的元素后移),最后停下并且插入新元素
1-2 折半插入排序思想:
采用折半查找法来查找插入位置
1-3 希尔排序
思想:缩小增量排序,每次以一个增量(逐渐减小)分割序列,对每个子序列作直接插入排序,最后增量1分割序列相当于整个序列作一趟直接插入排序
以增量5分割(下标0,5,10,15…为一个子序列;下标1,6,11,16…的为一子序列;共五个子序列)
考点:增量序列的最后一个值一定取1增量序列中的值应尽量没有除1之外的公因子
2.交换类排序
每一趟排序,都有一系列的交换动作
2-1冒泡排序
思想:一趟中 关键字从左到右依次比较,将最大的关键字替换到最后一个位置结束条件:一趟排序过程中没有发生关键字交换
2-2★快速排序(掌握伪代码)
每一趟选择当前所有子序列中的一个关键字(通常是第一个)作为枢轴,将子序列中比枢轴小的移到枢轴前面,比枢轴大的移到枢轴后面把所有子序列都划分完毕称为一趟分布
3.选择类排序
每次选出一个最小/最大关键字和序列第一个/最后一个交换
3-1★简单选择排序(掌握伪代码)
从头到尾扫描序列,找出最小的一个关键字,和(无序序列的)第一个关键字交换
无论最好最坏的情况,其比较次数都是一样多。
第 i 次排序需要进行n-i 次关键字的比较,此时需要比较n-1+n-2+…+1=n(n-1)/2次,时间复杂度为O(n^2)。
比较次数复杂度0(n²)
对于交换次数而言,当最好的时候,交换为0次,最差的时候,也就初始排序,交换次数为n-1次,
交换次数复杂度为O(n)。
3-2堆排序
堆是一种数据结构,可以看作完全二叉树。
非递减排序:大根堆,父大子小(每次选出最大的关键字,放置序列的最后)
3-2-1建堆:
①先将序列对应成一个层次遍历输入的完全二叉树
②从下到上(层次遍历顺序)调整,叶子结点满足堆的定义
③从第一个非叶结点开始调整,比较其与孩子的大小关系,不满足则与最大/最小孩子交换,再检验其交换是否满足,不满足继续调整
3-2-2调整堆、插入结点、删除结点 天勤244 5
①堆排序每次调整堆,相当于从一颗完全二叉树的根走到叶子结点的过程,时间复杂度为0(logn)
②初始建堆需要n次调整 天勤244
③每趟排序需要一次调整,共n躺排序
④基本操作共执行2n*0(logn)
插入(插堆尾):插入堆尾,重新建堆
删除(删堆首):堆尾剪切到堆首,重新建堆
(1)考点
1.可以看作是完全二叉树(用完全二叉树来构造堆),不可以看作平衡二叉树
2:降序排序⇒小根堆;递增排序⇒大根堆;
3:任何情况下堆排序时间复杂度都是0(nlogn)
4:判断序列是否是堆
方法一:画出对应的层次遍历输入的完全二叉树并判断
方法二:通过完全二叉树的特点来判断(编号为i的结点,其孩子编号为2i,2i+1)
4.基数类排序(多关键字排序)
基数类排序先按最低位排序,再按中间位排序,最后按最高位排序
三位数排序⇒ d关键字位数=3
rd关键字符号=10
基数排序不需要进行关键字的比较
5.归并类排序
将两个或两个以上的有序序列合并成一个新的有序序列
二路归并排序两两归并
①生成初始归并段:将原始序列分成只含有一个关键字的有序子序列
②然后两两归并 最后为一个有序序列
归并操作(merge)的执行次数为要归并的两个子序列中关键字个数之和
第一趟归并需要执行2*(n/2)=n次基本操作,
(其中2为两子序列关键字之和,n/2为要归并的子序列对的对数)
…
第x趟归并需要执行2ˣ×(n/2ˣ)=n次基本操作
当n/2ˣ=1,此时剩下一对要归并的子序列对,再执行一次merge归并排序即可结束 此时x=logn
n个记录一共需要共log₂n趟归并(排序),每趟执行n次基本操作
⇒时间复杂度0(nlog₂n),任何情况下都是
⇒归并趟数log₂n上取整)
外部排序
外排序指:排序前后记录在外存,排序记录调入内存的排序算法
k路归并排序
(1)生成初始归并段
n个记录,分成m个规模较小的记录段。
可采用置换选择排序构造初始归并段
(2)归并(对归并段进行逐趟归并)
将一组归并段(一组有k个归并段,即k个有序记录段)归并成一个有序记录段
有m个初始归并段,采用x路归并时,所需归并趟数是 logₓm下取整)
将t组(t=m/k上取整)归并段 归并成t个有序记录段称为一趟归并。
可通过采用最佳归并树来减少归并次数,提高效率
即给定k个初始归并段的长度,将其按照长度为关键字建立最优m叉树(赫夫曼m叉树)
IO次数=WPL(带权路径长度,只考虑关键字结点的带权路径)×2
败者树:从k个值中选取最值的算法
天勤 255
考点:
有一个含8000个记录的文件,对记录的读写是以能容纳100个记录的物理块为单位的
①经过10次内部排序得到10个初始归并段(一次内排得到一个归并段800个记录,。读出需要8次,写入需要8次⇒一个归并段共16次读写,10个归并段共160次读写)
②对归并段进行二路归并(共需要归并4趟,每趟都要将所有的记录重新读入/写出⇒每趟共160次读写)
③160+640=800
总结:
1.不稳定的算法:快些选一堆
快速排序,希尔,简单选择,堆排序
2.时间复杂度
改进时间复杂度 0(nlog₂n):快些归队(快速-希尔-归并-堆排序)
内部排序最好的时间复杂度:直接插入当序列有序时达到最好0(n);
快速排序当序列有序时达到最差0(n²);剩下的算法时间复杂度都是0(n²)
3.空间复杂度:快归基
快排 平均0(logN) 递归进行需要栈
归并 0(N)需要转存整个待排序列
基数 0(rd) rd关键字符号(如数字,rd=10)
其他 0(1),辅助存储空间不随着问题规模变化而变换
4.每趟结束是否有一个关键字到位
交换类和选择类的排序每趟结束都有一个关键字到位;而对于插入类排序,一趟排序并不能保证一个关键字到位
5.序列的原始状态
(1)排序趟数
交换类的排序,其趟数与序列原始状态有关
直接插入/简单选择固定n-1趟(n个关键字)基数排序固定d趟(关键字位数,如三位数3趟)
(2)比较次数(算法快慢)插气泡
直接插入,希尔,冒泡:序列越有序,比较次数越小快速排序:序列越有序,快速排序越慢
归并排序的比较次数与初始序列有关
简单选择排序比较次数与初始序列无关(固定)
初始状态有序序列:
①直接插入比较快②归并排序比较慢