标签:数据类型 方式 有序性 基础上 代码 过程 char s man 处理
目录
???? ??顺序查找的方法就不说了,下面给出的算法,主要是为了说明其中引入的"哨兵"的作用。如果忘了线性表的动态存储结构点击这里。
typedef struct//查找表的数据结构
{
int *data;//元素存储空间基质,建表时按实际长度分配,0号单源留空。
int TableLen;// 表的长度。
}SSTable;
int Search_Seq(SSTable ST,int key)
{
ST.data[0]=key;
for(int i=ST.TableLen;ST.data[i]!=key;i--)
return i;
}
???? ??在上述算法中,将\(ST.data[0]\)称为"哨兵"。引入目的是使得Search_Seq内的循环不必判断数组是否会越界,因为当满足i==0时,循环一定会跳出。需要说明的是,在程序中引入哨兵这个操作,并不是这个算法独有的。因为引入哨兵而带来的便利,我们可以将哨兵放到其他许多的程序当中去使用。这样可以避免很多不必要的判断语句,从而提高程序的效率。
???? ??对于有n个元素的表,给定值key与表中第i个元素的关键字相等,即定位第i个元素时,需要进行\(n-i+1\)次比较,即\(C_i=n-i+1\)。查找成功时平均长度为:
???? ??如果在查找之前就已经知道表是按照关键字有序的,那么当查找失败时可以不用再比较到表的另一端才能返回失败信息了,这样的话能够大大的减少,查找失败的平均查找长度。
???? ??假设表L是按照关键字从小到大排列的,查找的顺序是从前向后查找,带查找元素的关键字为key,当查找到第i个元素的时候发现:第i个元素的关键字小于key,但是第i+1个元素的关键字大于key,所以表中不存在关键字为key的元素。
???? ?? 在有序表的顺序查找中,查找成功的平均查找长度和一般线性表的顺序查找一样。查找失败时,查找指针一定走到了某个失败的结点。这些失败结点使我们虚构的空节点,实际上是不存在的,所以到达失败结点所查找的长度等于它上面一个圆形结点的所在层数。查找不成功的平均查找长度在相等查找概率的情形下有:
???? ??折半查找:和二分查找的原理是差不多的。它仅适用于有序的顺序表。基本思路是:首先将给定值key与表中间位置元素比较,如果相等,则查找成功,返回元素位置;如果不等,则需差汇总啊的元素只能在中间元素以外的前半部分或者后半部分中(例如:在查找表升序排列时,若给定值key大于中间元素的关键字,则所查找的元素只可能在后半部分)。然后在缩小的范围内继续进行同样的查找,如此反复知道找到为止,或者确定表中没有所需要查找的元素,则查找不成功,返回查找失败的信息。
算法如下:
typedef struct
{
int *data;
int MaxSize,length;
}SeqList;
int Binary_Search(SeqList L,int key)
{
int low=0,high=L.length-1,mid;
while(low<=high)
{
mid=(low+high)/2;
if(L.data[mid]==key)
return mid;
else if(L.data[mid]>key)
high=mid-1;
else
low=mid+1;
}
return -1;
}
上述算法过程,如果形象描述一下就能画出来平衡二叉树。对平衡二叉树(二叉排序树)进行中序遍历可以得到有序的序列。
???? ??分块查找:又称为索引顺序查找,吸取了顺序查找和折半查找各自的优点,既有动态结构,有适于快速查找。
???? ??分块查找的基本思想:将查找表分为若干个字块,块内的元素可以无序,但块之间是有序的。即即第一个块中的关键字小于第二个快中的所有记录的关键字,第二个块中的最大关键字小于第三个块中所有记录的关键字,以此类推。再建立一个索引表,索引表中的每个元素含有各块最大关键字和各块中第一个元素的地址,索引表按关键字有序排列。
???? ??分块查找的过程分为两步:第一步 在建立索引表中确定待查记录所在的块,可以顺序查找或者折半查找索引表;第二步在块内顺序查找。
???? ??分块查找的平均查找长度为索引查找和块内查找的平均长度之和,设索引查找和块内查找的平均查找长度分别为\(L_1,L_2\),则分块查找的平均长度为:\(ASL=L_1+L_2=[\log_2(b+1)]+\frac{s+1}{2}\)。
???? ??B树,又称为多路平衡查找树,B树中所有结点的孩子结点数的最大值称为B树的阶,通常用m表示。一个m阶B树或者为空树,或者为满足如下特性的m叉树。
???? ??其中,\(k_i\)为结点的关键字,且满足\(K_1<K_2<K_3<\ldots<K_n\);为指向子树根节点的指针,且指针\(P_{i-1}\)所指子树中所有结点的关键字均小于\(K_i\),\(P_i\)所指子树中所有结点的关键字均大于\(K_i\),\(n(\frac m 2-1\leq n\leq m-1)\)为结点中关键字的个数。
???? ??因为排序二叉树的高度可能会太高,所以发现了平衡二叉树,但是因为在实际应用中的缺陷:数据库分为关系型数据库和非关系型数据库(MangoDB)数据的索引是存储在磁盘上的,当数据库量比较大的时候,索引的大小可能有几个G甚至更多。当我们利用索引查询的时候,如要将整个索引加载大内存中是不可能的,这样对运行内存大小的压力太大,所以能做的就是逐一加载每一个磁盘页,这里的磁盘页对应着索引树的结点。如果我们利用平衡二叉树的话,要进行的磁盘读取次数太多,而现在磁盘读取正是在计算机中拖后腿的存在,CPU的计算速度(比较速度)已经很快了,这个时候就有了B树(又称为B-树),想办法降低树的高度,尽量减少内存的读取次数,以加快数据库索引的速度。
???? ??下面介绍一下B树,一个m阶的B树具有如下几个特征:
每个结点中的元素从小打到排列,结点当中\(k-1\)个元素正好是k个孩子包含的元素的值域分划。
这棵树中,咱们看一下\((2,6)\)节点。该节点中有两个元素2和6,又有三个孩子\(1,(3,5),8\)。其中1小于元素2,(3,5)在元素在元素2和6之间,\(6<8<9\)。其中的\(9<11<12\)。最后一个叶子节点\((13,15)\)是大于12的话。
???? ??节点3,5已经是两元素节点,无法继续增加。父亲节点2,6也是两元素节点同样无法增加,但是根节点是单元素节点,可以升级为两元素节点。于是拆分节点3,5与节点2,6,让根节点9升级为两个元素的节点4,9。节点6独立为根节点的第二个孩子。
???? ?? 删除11之后节点12只有一个孩子,不符合B树的归法,因此找出12,13,15三个节点的中位数13,取代12,而12自身下移称为第一个孩子。
???? ??散列表的基本概念:在前面介绍的线性表和数表的查找中,记录在表中的位置个记录关键字之间不存在确定关系。因此,在这些表中查找记录时需要进行一系列的关键字比较。这一类查找方式是建立在"比较"的基础上,查找的效率取决于比较的次数。
???? ??一个把查找表中关键字映射成该关键字对应的地址的函数,记为\(Hash(key)=Addr\)。
???? ??散列函数可能会把两个或者两个以上的不同关键字映射到同一地址,我们称这种情况为冲突,这些发生碰撞的不同关键字称为同义词。一方面设计好的散列函数应该尽量减少这样的冲突;另一方面由于这种冲突总是不可避免的,所以还要设计好处理冲突的方法。
???? ??散列表:是根据关键字而进行访问的数据结构。也就是说,散列表建立了关键字和存储地址之间的一种直接映射关系。
???? ??理想情况下,对散列表进行查找的时间复杂度为\(O(1)\),即与表中元素的个数无关。下面将介绍常用的散列函数和处理冲突的方法。
散列函数应该尽量简单,能够在较短的时间内就计算出任意关键字对应的散列地址。
???? ??这是一种最简单,最常用的方法,假定散列表表长为m,取一个不大于m但是最接近于m的质数p,利用以下的公式把关键字转换成散列地址。散列函数为:
???? ??设关键字是r进制数(如十进制数),而r个数码在各位上出现的频率不一定相同,可能在某些位上分布的均匀一些,每种数目出现的机会均等;而在某些位上分布不均匀,只有某几种数码经常出现,则应该选取数码分布较为均匀的若干位作为散列地址。这种方法适合于已知的关键字集合,如果更换了关键字,就需要重新构造新的散列函数。
???? ??顾名思义,取关键字的平方值的中间记为作为散列地址。具体取多少位要看实际情况而定。这种方法得到的散列地址与关键字的每一位都有关系,是的散列地址分布比较均匀。适用于关键字的每一位取值都不够均匀或小于散列地址所需的位数。
???? ??将关键字分割成位数相同的几部分(最后一部分的位数可以短一些),然后取这几部分的叠加和作为散列地址,这种方法称为折叠法。关键字位数很多,而且关键字中每一位上数字分布大致均匀时,可以采用折叠法得到散列地址。
???? ??在不同情况下,不同的散列函数会发挥出不同的性能因此不能笼统的说那种散列函数最好。在实际选择中,采用何种构造散列函数的方法取决于关键字集合的情况,但是目标是为了使产生冲突的可能性尽量降低。
???? ??应该注意到,任何设计出来的散列函数都不可能绝对的避免冲突,为此,必须考虑在发生冲突时应该如何处理,即为产生冲突的关键字寻找写一个“空”的Hash地址。
???? ??假设已经选定散列函数H(key),下面用\(H_i\)表示冲突发生之后的第i粗探测的散列地址。
???? ??开放定值法:所谓开放定值法,指的是可存放新表项的空闲地址即向她的同义词表项开放,又向它的非同一次表项开放。其数学递推公式为:
线性探测法:当\(d_i=1,2,\ldots,m-1\),称为线性探测法。这种方法的特点是:冲突发生时,顺序查看表中下一个单元(当探测到表尾地址\(m-1\)时,下一个探测地址是表首地址0),知道找出一个空闲单位(当表为填满时一定能找到一个空闲单位)或查边全表。线性探测法可能使第i个散列地址的同义词存入第i+1个散列地址,这样本应存入第i+1个散列地址的元素就争夺第i+2个散列地址的元素的地址……,从而造成大量元素在相邻的散列地址上“聚集”(或堆积)起来,大大降低了查找效率。
int Index(SString S,SString T)
{
int i=1,j=1;
while(i<=S[0]&&j<=T[0])
if(S[i]==T[j])
i++,j++;
else
i=i-j+2,j=1;
if(j>T[0])
return i-T[0];
else
return 0;
}
简单模式匹配算法的最坏时间复杂度为\(O(m*n)\),n和m分别为主串和模式串的长度。
???? ??KMP算法可以在时间复杂度为\(O(m*n)\)的级数上完成串的模式匹配操作。其改进在于:每当一趟匹配过程中出现字符比较不相等时,不需回溯i指针,而是利用已经得到的“部分匹配”的结果将模式向右“滑动”尽可能远的一段距离之后,继续进行比较。
???? ??回顾上图的匹配过程,在第三趟的匹配中,当\(i=7,j=5\)字符比较不等时,又从\(i=4,j=1\)重新开始比较。
???? ??然而通过仔细的观察可以发现\(i=4,5,6 .j=1\)这三次的比较都是不需要进行的。因为从第三趟部分匹配的结果就可以得出,主串中第4,5,6个字符必然是‘b’,‘c’和‘a’。因为模式中第一个字符是a,因此它无需再和这3个字符进行比较,而仅需要将模式向右移动两个字符的位置继续进行\(i=3,j=1\)时的字符比较。由此在整个匹配的过程中,i指针没有回溯,如图所示。
void get_next(char T[],int next[])
{
int i=1;
next[1]=0;
int j=0;
while(i<=T[0]) // T[0] 存储 字符的长度。
if(j==0||T[i]==T[j])
i++,j++,next[i]=j;
else
j=next[j];
}
int kmp(char S[],char T[],int next[],int pos)
{
int i=pos;
j=1;
while(i<=S[0]&&j<=T[0])
if(j==0||S[i]==T[j])
i++,j++;
else
j=next[j];
if(j>T[0])
return i-T[0];
else
return 0;
}
标签:数据类型 方式 有序性 基础上 代码 过程 char s man 处理
原文地址:https://www.cnblogs.com/A-FM/p/9694675.html