数据结构6查找/排序

八、查找(算法:折半查找,其他画图)

查找:给定一个值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)比较次数(算法快慢)插气泡

直接插入,希尔,冒泡:序列越有序,比较次数越小快速排序:序列越有序,快速排序越慢
归并排序的比较次数与初始序列有关

简单选择排序比较次数与初始序列无关(固定)


初始状态有序序列:
①直接插入比较快②归并排序比较慢


发布了46 篇原创文章 · 获赞 15 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qq_41850194/article/details/100713175