基础概念
平均查找长度:关键字的平均比较次数(ASL)
查找过程中需要注意的问题:
(重点主要就是在增加了约束关系的基础上提高查找的效率。)
顺序查找算法
应用范围
- 顺序表或线性链表表示的静态查找表
- 表内元素之间无序
顺序表的表示
typedef struct{ KeyType key; //关键字域 ...... //其他域 }ElemType; typedef struct{ //顺序表结构类型定义 ElemType *R; //表基址 int lenght; //表长 }SSTable; SSTable ST; //定义顺序表ST
算法表示形式1(已改进)
int Search_Seq(SSTable ST,KeyType key){ for(i = ST.lenght; ST.R[i].key != key && i > 0; --i); //每次循环需要比较两次 if( i > 0) return i; else return 0; }
但是,这个小算法每一次循环都要进行两次比较,是否能改进。
- 改进:设置监视哨的顺序查找
把待查关键字key存入表头(哨兵),从后往前比较。可免去查找过程中每一步都要检测是否查找完毕,加快速度。
- 算法表示形式2(未改进)
int Search_Seq(SSTable ST,KeyType key){ ST.R[0].key = key; for(i = ST.lenght; ST.R[i].key != key; --i); //每次循环只需要比较一次 return i; }
- 效果
当ST.lenght较大时,此改进能使进行一次查找所需的平均时间几乎减少一半。
- 时间效率分析
假设一共有n个元素,由于是从后往前查找,所以最后的元素查找最快快,第一次就查找出来,如图所示,可见比较次数与key位置有关。
- 查找地i个元素需要比较n-i+1次
- 查找失败,需要比较n+1次
- 时间复杂度与空间复杂度
(其中每个记录被查找的都是1/n,而n个元素总共需要查找的次数是1+2+…+n,所以时间复杂度为(n+1)/2)
- 两个问题与解决方法
- 优点与缺点
优点:算法简单,逻辑次序无要求,且不同存储结构均适用
缺点:ASL太长,时间效率太低
折半查找算法
- 折半查找:每次将待查记录所在区间缩小一半
- 思路:(非递归实现)
- 算法实现
- 非递归实现
int Search_Bin(SSTable ST,KeyType key){ low = 1; high = ST.lengh; //置区间初值 while(low <= high){ mid = (low + high)/2; if(ST.R[mid].key == key) //找到待查元素 return mid; else if(key < ST.R[mid].key)//缩小查找区间 high = mid - 1; //继续在前半区间进行查找 else low = mid + 1; //继续在后半区间进行查找 return 0; //顺序表中不存在待查元素 } }
- 递归实现
int Search_Bin(SSTable ST,KeyType key,int low,int high){ if(low > high) //查找不到时返回0 return 0; mid = (low + high)/2; if(key == ST.elem[mid].key) return mid; else if(key < ST.elem[mid].key) Search_Bin(ST,key,low,mid-1); //递归,在前半区进行查找 else Search_Bin(ST,key,mid+1,high); //递归,在后半区进行查找 }
- 性能分析
根据表格,可以生产一个二叉树,表示最快到最慢的节点。
ps:路径上的结点数 = 结点的层数,比较的次数一定是 <= 树的深度的。对于这11个元素,每个元素的概率都相等,是1/11,而查找的总数为11+22+43+44 = 33,所以平均查找长度ASL = 33/11 = 3。比顺序查找的算法好。
- 时间复杂度分析- log2n
- 优点与缺点
优点:效率比顺序查找高
缺点:只适用于有序表。且限于顺序存储结构(对线性链表无效)。
插值查找
- 条件
线性表中的记录必须是关键字有序(通常从小到大),线性表必须采用顺序存储取中
间记录作为比较对象,若给定值与中间记录的关键字相等,则查找成功;若给定值小于中间记录的关键字,则在中间记录左半区继续查找;否则,在右半区查找。不断重复,
ps:插值查找是在二分查找的基础上优化来的,他将二分查找中的中间记录“(low+high)/2”->>> low + (key - a[low])/(a[high] - a[low])*(high - low);//这个的意思其实是 low 加上key 在整个长度的比例乘以整个长度来作为其在序列的偏移量来作为二分查找中“mid”作为中间记录。所以此处不再分析。
- 时间复杂度 O(log 2 (log 2 n))
- 算法实现
int Search_Bin(SSTable ST,KeyType key){ low = 1; high = ST.lengh; //置区间初值 while(low <= high){ pos = ((key - ST.R[low].key)/(ST.R[high].key - ST.R[low].key)) * (high - low) + low; //pos = (mid + high)/2; //只需要改变这句 if(ST.R[pos].key == key) //找到待查元素 return pos; else if(key < ST.R[pos].key)//缩小查找区间 high = pos - 1; //继续在前半区间进行查找 else low = pos + 1; //继续在后半区间进行查找 return 0; //顺序表中不存在待查元素 } }
- 小结
插值查找和二分查找是类似的,只不过插值查找是从自选的点pos开始而二分查找是从规定好的mid中间开始。
分块查找算法
- 分块查找,索引顺序表的查找
- 条件
ps:
分块有序是之指,块之间是有序的,但是块内是无序。
索引表中另外一个数据存储的是块中的首地址。
- 查找过程:
例子:
加入在上述的例子中,我想要查找38,接下来在索引表中进行比较,由于第一块的最大值只有22,所以不符合条件;第二块的最大值是48,比38大,所以38就是存储在第二块内容中,在索引表中还得知第二块的起始地址是7,所以从元素7开始进行顺序查找,找了4次之后,成功找到38。
算法实现
struct index //定义块的结构 { int key; //块的关键字 int start; //块的起始值 int end; //块的结束值 }index_table[4]; //定义结构体数组 int block_search(int key,int a[]) //自定义实现分块查找 { int i = 1,j; while(i<=3&&key>index_table[i].key) //确定在哪个块中 i++; if(i>3) //大于分得的块数,则返回 0 return 0; j=index_table[i].start; //j 等于块范围的起始值 while(j<=index_table[i].end&&a[j]!=key) //在确定的块内进行顺序查找 j++; if(j>index_table[i].end) //如果大于块范围的结束值,则说明没有要査找的数 return 0; }
- 时间复杂度 O(logn)
- 优点与缺点
优点:插入和删除比较容易,无需进行大量的移动
缺点:要增加一个索引表的存储空间并对初始索引表进行排序运算
适应情况:如果线性表既要快速查找又经常动态变化,则可采用分块查找
查找方法的比较