avatar

查找算法

数据结构–七大查找算法总结

查找定义:根据给定的某个值,在查找表中确定一个其关键字等于给定值的数据元素(或记录)。
查找算法分类:

  • 静态查找和动态查找;注:静态或者动态都是针对查找表而言的。动态表指查找表中有删除和插入操作的表。
  • 无序查找和有序查找。无序查找:被查找数列有序无序均可;有序查找:被查找数列必须为有序数列。

平均查找长度(Average Search Length,ASL):需和指定key进行比较的关键字的个数的期望值,称为查找算法在查找成功时的平均查找长度。
对于含有n个数据元素的查找表,查找成功的平均查找长度为:ASL = Pi*Ci的和。

  • Pi:查找表中第i个数据元素的概率。
  • Ci:找到第i个数据元素时已经比较过的次数。

顺序查找

说明:顺序查找适合于存储结构为顺序存储或链接存储的线性表。

基本思想:

顺序查找也称为线形查找,属于无序查找算法。从数据结构线形表的一端开始,顺序扫描,依次将扫描到的结点关键字与给定值k相比较,若相等则表示查找成功;若扫描结束仍没有找到关键字等于k的结点,表示查找失败。

复杂度分析: 

  • 查找成功时的平均查找长度为:(假设每个数据元素的概率相等) ASL = 1/n(1+2+3+…+n) = (n+1)/2 ;
  • 当查找不成功时,需要n+1次比较,时间复杂度为O(n);
    所以,顺序查找的时间复杂度为O(n)。

代码实现

public class findclass {
public static void main(String[] args) {
int[] array = new int[]{1,45,32,7,4,38,4,8,90,4};
System.out.print(SequenceSearch(array,45));
}
/**
* 顺序查找算法
* @param array 用于被查找的数组
* @param value 被查找的值
* @return 返回int值,是数组中的第几个,如果没有返回-1
*/
private static int SequenceSearch(int[] array,int value)
{
for(int i = 0;i<array.length;i++)
{
if(array[i] == value) return i;
}
return -1;
}
}

二分查找

说明:元素必须是有序的,如果是无序的则要先进行排序操作。

基本思想

也称为是折半查找,属于有序查找算法。用给定值k先与中间结点的关键字比较,中间结点把线形表分成两个子表,若相等则查找成功;若不相等,再根据k与该中间结点关键字的比较结果确定下一步查找哪个子表,这样递归进行,直到查找到或查找结束发现表中没有这样的结点。

复杂度分析

最坏情况下,关键词比较次数为log2(n+1),且期望时间复杂度为O(log2n);
注:折半查找的前提条件是需要有序表顺序存储,对于静态查找表,一次排序后不再变化,折半查找能得到不错的效率。但对于需要频繁执行插入或删除操作的数据集来说,维护有序的排序会带来不小的工作量,那就不建议使用。

代码实现

public class findclass {
public static void main(String[] args) {
int[] array = new int[]{1,2,3,7,8,9,449,496};
System.out.print(BinarySearch(array,9));
}
/**
* 二分查找算法
* @param array 用于被查找的数组
* @param value 被查找的值
* @return 返回int值,是数组中的第几个,如果没有返回-1
*/
private static int BinarySearch(int[] array,int value)
{
int low = 0;//开始指针
int high = array.length-1;//数组结束指针
while (low <= high)
{
int mid = (low+high) >> 1;//位移除以二
if(value< array[mid]) high = mid-1;//数字就在前半段
else if(value>array[mid]) low = mid+1;//数字在后半段
else return mid;
}
return -1;
}
}//二分查找的前提条件是数组有序

插值查找

插值查找是对二分查找的改进,因为二分查找是折半的,不能自适应,如果出现1~1000个数字,找第五个性能就会变得很差,所以有了插值查找,能够让查找的折半变成自适应的。

基本思想

基于二分查找算法,将查找点的选择改进为自适应选择,可以提高查找效率。当然,差值查找也属于有序查找。
注:对于表长较大,而关键字分布又比较均匀的查找表来说,插值查找算法的平均性能比折半查找要好的多。反之,数组中如果分布非常不均匀,那么插值查找未必是很合适的选择。
二分查找的计算公式是mid = (high-low)/2。 插值查找的计算公式为mid = low +(high-low)*(key-arr[low])/(arr[high]-arr[low])

复杂度分析

查找成功或者失败的时间复杂度均为O(log2(log2n))。

代码实现

public class findclass {
public static void main(String[] args) {
int[] array = new int[]{1,2,3,7,8,9,449,496};
System.out.print(InsertionSearch(array,9));
}
/**
* 插值查找算法
* @param array 用于被查找的数组
* @param value 被查找的值
* @return 返回int值,是数组中的第几个,如果没有返回-1
*/
private static int InsertionSearch(int[] array,int value)
{
int low = 0;//开始指针
int high = array.length-1;//数组结束指针
int mid;
while (low <= high)
{
mid =low + ( (high-low)*(value - array[low]) / (array[high] - array[low]));//插值 ,一定不要忘记这个 还有个low
if(value< array[mid]) high = mid-1;//数字就在前半段
else if(value>array[mid]) low = mid+1;//数字在后半段
else return mid;
}
return -1;
}
}

斐波那契查找

在介绍斐波那契查找算法之前,我们先介绍一下很它紧密相连并且大家都熟知的一个概念——黄金分割。
黄金比例又称黄金分割,是指事物各部分间一定的数学比例关系,即将整体一分为二,较大部分与较小部分之比等于整体与较大部分之比,其比值约为1:0.618或1.618:1。
大家记不记得斐波那契数列:1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89…….(从第三个数开始,后边每一个数都是前两个数的和)。然后我们会发现,随着斐波那契数列的递增,前后两个数的比值会越来越接近0.618,利用这个特性,我们就可以将黄金比例运用到查找技术中。
斐波那契查找原理详解与实现
二分查找、插值查找、斐波那契(黄金分割)查找分析与实现(Java)

基本思想

也是二分查找的一种提升算法,通过运用黄金比例的概念在数列中选择查找点进行查找,提高查找效率。同样地,斐波那契查找也属于一种有序查找算法。
相对于折半查找,一般将待比较的key值与第mid=(low+high)/2位置的元素比较,比较结果分三种情况:

  • 相等,mid位置的元素即为所求
  • >,low=mid+1;
  • <,high=mid-1。

斐波那契查找与折半查找很相似,他是根据斐波那契序列的特点对有序表进行分割的。他要求开始表中记录的个数为某个斐波那契数减1,及n=F(k)-1;
开始将k值与第F(k-1)位置的记录进行比较(及mid=low+F(k-1)-1),比较结果也分为三种

  • 相等,mid位置的元素即为所求
  • >,low=mid+1,k-=2;
    说明:low=mid+1说明待查找的元素在[mid+1,high]范围内,k-=2 说明范围[mid+1,high]内的元素个数为n-(F(k-1))= Fk-1-F(k-1)=Fk-F(k-1)-1=F(k-2)-1个,所以可以递归的应用斐波那契查找。
  • <,high=mid-1,k-=1。
     说明:low=mid+1说明待查找的元素在[low,mid-1]范围内,k-=1 说明范围[low,mid-1]内的元素个数为F(k-1)-1个,所以可以递归 的应用斐波那契查找。
  • 大部分说明都忽略了一个条件的说明:n=F(k)-1, 表中记录的个数为某个斐波那契数减1。这是为什么呢?
    是为了格式上的统一,以方便递归或者循环程序的编写。表中的数据是F(k)-1个,使用mid值进行分割又用掉一个,那么剩下F(k)-2个。正好分给两个子序列,每个子序列的个数分别是F(k-1)-1与F(k-2)-1个,格式上与之前是统一的。不然的话,每个子序列的元素个数有可能是F(k-1),F(k-1)-1,F(k-2),F(k-2)-1个,写程序会非常麻烦。

复杂度分析

最坏情况下,时间复杂度为O(log2n),且其期望复杂度也为O(log2n)。

代码实现

import java.util.Arrays;

public class findclass {
static int MAX_SIZE = 20;
public static void main(String[] args) {
int[] array = new int[]{1,2,3,7,8,9,449,496};
System.out.print(FibonacciSearch(array,9));
}
/**
* 斐波那契查找
* @param array 用于被查找的数组
* @param value 被查找的值
* @return 返回int值,是数组中的第几个,如果没有返回-1
*/
private static int FibonacciSearch(int[] array,int value)
{
int low = 0;//开始指针
int high = array.length-1;//数组结束指针
int[] farray = new int[MAX_SIZE];
//黄金分割点(斐波那契分割点)下标
int mid;
Fibonacci(farray);
//第一个大于数组长度的斐波那契下标
int k=0;
//找到第一个大于数组长度的斐波那契下标
while (array.length>farray[k]-1) ++k;
//斐波那契数列性质:F(k) = F(k-1) + F(k-2)
//两边同时减去1 =》 F(k)-1 = (F(k-1)-1) + (F(k-2)-1) + 1,其中等式右边最后1表示我们需要找的mid的位置,
//那么数组被分为 两部分 =》 左:F(k-1)-1 右:F(k-2)-1 从而可以计算出分割点下标=》 mid = left + F(k-1)-1
//由于farray[k](斐波那契数列第k项-1) 可能大于数组长度, 所以需要新建一个临时数组,扩容至farray[k]大小
int[] temp = Arrays.copyOf(array, farray[k]);
//扩容后填充原数组最后一个数值
for (int i = high + 1; i < temp.length; i++) {
temp[i] = array[high];
}
while (low <= high) {
mid = low + farray[k - 1] - 1;
if (value > temp[mid]) { //向右查找
low = mid + 1;
// F(k) = F(k-1) + F(k-2) ,此时需要向右查找,右边部分长度为F(k-2)
// 继续分割 =》 F(k-2) = F(k-3) + F(k-4),即下一次在F(k-4)那部分中继续查找
k -= 2;
} else if (value < temp[mid]) { //向左查找
high = mid - 1;
// F(k) = F(k-1) + F(k-2) ,此时需要向左查找,左边部分长度为F(k-1)
// 继续分割 =》 F(k-1) = F(k-2) + F(k-3),即下一次在F(k-2)那部分中继续查找
k--;
} else { //找到了
//判断mid是在原数组范围内还是扩充后的范围内
if (mid <= array.length - 1) { //在原数组范围内
return mid;
} else { //在扩充后的范围内(即为原数组最后一个值,返回数组最后一个索引即可)
return array.length - 1;
}
}
}
return -1;
}

/**
* 构造斐波那契数列
* @param farray 空数组
*/
private static void Fibonacci(int[] farray)
{
farray[0] = 0;
farray[1] = 1;
for(int i=2;i<farray.length;i++)
{
farray[i]= farray[i-1]+farray[i-2];
}
}
}

树表查找

这是一个很大的题目,所以留在后面一个整个篇幅去讲树的查找,这里不过多的阐述

  • 二叉树查找算法
  • 平衡查找树之2-3查找树(2-3 Tree)
  • 平衡查找树之红黑树(Red-Black Tree)
  • B树和B+树(B Tree/B+ Tree)

分块查找

分块查找又称索引顺序查找,它是顺序查找的一种改进方法。
常见的查找算法(六):分块查找

算法思想

将n个数据元素”按块有序”划分为m块(m ≤ n)。每一块中的结点不必有序,但块与块之间必须”按块有序”;即第1块中任一元素的关键字都必须小于第2块中任一元素的关键字;而第2块中任一元素又都必须小于第3块中的任一元素,……

算法流程

  • 先选取各块中的最大关键字构成一个索引表;
  • 查找分两个部分:先对索引表进行二分查找或顺序查找,以确定待查记录在哪一块中;然后,在已确定的块中用顺序法进行查找。

算法实现

public class findclass {
// 主表,size=30
static int[] mainList = new int[]{
101, 102, 103, 104, 105, 0, 0, 0, 0, 0,
201, 202, 203, 204, 0, 0, 0, 0, 0, 0,
301, 302, 303, 0, 0, 0, 0, 0, 0, 0
};

// 索引表
static IndexItem[] indexItemList = new IndexItem[]{
new IndexItem(1, 0, 5),
new IndexItem(2, 10, 4),
new IndexItem(3, 20, 3)
};
public static void main(String[] args) {
System.out.println("******** 索引查找 ********");
System.out.println("");
System.out.println("原始数据:");
display(mainList);
System.out.println("");

//分块查找
int key = 203;
System.out.println("元素" + key + "列表中的位置为:" + indexSearch(key) + "\n");

//按规则插入数据并查找
int value = 106;
System.out.println("插入数据:" + value);

// 插入成功,查找插入位置
if (insert(value)) {
System.out.println("插入后的主表:");
display(mainList);
System.out.println("");

System.out.println("元素" + value + "在列表中的位置为:" + indexSearch(value));
}
}
public static int indexSearch(int key) {
IndexItem indexItem = null;

//建立索引规则
int index = key / 100;

//遍历索引表
for(int i = 0;i < indexItemList.length; i++) {
//找到索引项
if(indexItemList[i].index == index) {
indexItem = indexItemList[i];
break;
}
}

//索引表中不存在该索引项
if(indexItem == null)
return -1;

//根据索引项,在主表中查找
for(int i = indexItem.start; i < indexItem.start + indexItem.length; i++) {
if(mainList[i] == key)
return i;
}

return -1;
}
/**
* 插入数据
*
* @param key 要插入的值
* @return true表示插入成功,false表示插入失败
*/
public static boolean insert(int key) {
IndexItem item = null;

// 建立索引规则
int index = key / 100;
int i = 0;
// 遍历索引表,找到对应的索引项
for (i = 0; i < indexItemList.length; i++) {
if (indexItemList[i].index == index) {
item = indexItemList[i];
break;
}
}
// 索引表中不存在该索引项
if (item == null) {
return false;
}

// 根据索引项将值插入到主表中
mainList[item.start + item.length] = key;
// 更新索引表
indexItemList[i].length++;

return true;
}
/**
* 遍历打印
*/
public static void display(int[] list) {
System.out.println("******** 展示开始 ********");
if (list != null && list.length > 0) {
for (int i = 0; i < list.length; i++) {
System.out.print(list[i] + " ");
if ((i + 1) % 10 == 0) {
System.out.println("");
}
}
}
System.out.println("******** 展示结束 ********");
}
}
class IndexItem {
public int index; //值比较的索引
public int start; //开始位置
public int length;//块元素长度(非空)

public IndexItem(int index, int start, int length) {
this.index = index;
this.start = start;
this.length = length;
}

//... getter and setter
}

哈希查找

【数据结构】—哈希表查找算法

算法思想

哈希的思路很简单,如果所有的键都是整数,那么就可以使用一个简单的无序数组来实现:将键作为索引,值即为其对应的值,这样就可以快速访问任意键的值。这是对于简单的键的情况,我们将其扩展到可以处理更加复杂的类型的键。

算法流程

  • 用给定的哈希函数构造哈希表;
  • 根据选择的冲突处理方法解决地址冲突;常见的解决冲突的方法:拉链法和线性探测法
  • 在哈希表的基础上执行哈希查找。

复杂度分析

单纯论查找复杂度:对于无冲突的Hash表而言,查找复杂度为O(1)(注意,在查找之前我们需要构建相应的Hash表)。
查找算法中的概念(排序树和散列表)

文章作者: zenshin
文章链接: https://zlh.giserhub.com/2020/03/22/cl35o0ng500imp4tgacb36oqu/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 zenshin's blog
打赏
  • 微信
    微信
  • 支付宝
    支付宝

评论