数据结构之查找
文章目录
gitee地址
前言
一、顺序查找
LeetCode
//待更新
是什么?
顺序查找也称为线性查找,用逐一比较的办法顺序查找关键字
适用情况
普遍都适用
时间复杂度
O(n)
代码
public static int OrderFind(int[] nums,int target)
{
for (int i = 0; i < nums.Length; i++)
{
if (nums[i] == target) return i;
}
return -1;
}
}
二、二分查找
LeetCode
是什么?
适用情况
升序或者降序序列
时间复杂度
O(Log2n)
代码
public static int ErFind(int[] nums, int target)
{
int left = 0, right = nums.Length - 1;
while (left<=right)
{
int mid = left + (right - left) / 2;
if (nums[mid] == target) return mid;
else if (nums[mid] > target) right = mid - 1;
else left = mid + 1;
}
return -1;
}
三、插值查找
LeetCode
//待更新
是什么?
作为改进版的二分查找算法。
适用情况
升序或者降序序列
时间复杂度
平均情况 O(loglog (n)),最坏 O(log (n))
怎么做?
插值查找算法的解题思路和二分查找算法几乎相同,唯一的区别在于,每次与目标元素做比较的元素并非搜索区域内的中间元素,此元素的位置需要通过如下公式计算得出:
Mid = Begin + ( (End - Begin) / (A[End] - A[Begin]) ) * (X - A[Begin])
-
Mid:计算得出的元素的位置;
-
End:搜索区域内最后一个元素所在的位置;
-
Begin:搜索区域内第一个元素所在的位置;
-
X:要查找的目标元素;
-
A[]:表示整个待搜索序列。
特点
当有序序列中的元素呈现均匀分布时,插值查找算法的查找效率要优于二分查找算法;反之,如果有序序列不满足均匀分布的特征,插值查找算法的查找效率不如二分查找算法。
代码
public static int ErFind(int[] nums, int target)
{
//插值查找
int left = 0, right = nums.Length - 1;
while (left < right)
{
//int mid = left + (right - left) / 2;
int mid = left + ((right - left) / (nums[right] - nums[left])) * (target - nums[left]);
Console.WriteLine(mid);
if (nums[mid] == target)
{
return mid;
}
else if(nums[mid] > target)
{
right = mid - 1;
}
else
{
left = mid + 1;
}
}
return -1;
}
四、斐波那契查找
LeetCode
//待更新
是什么?
作为改进版的二分查找算法。
适用情况
升序或者降序序列
时间复杂度
平均情况 O(log2 n),最坏 O(log2 n)
怎么做?
基础思想
斐波那契搜索(Fibonacci search) ,又称斐波那契查找,是区间中单峰函数的搜索技术。斐波那契查找就是在二分查找的基础上根据斐波那契数列进行分割的。(mid的关系式不同)
斐波那契数列(Fibonacci sequence),又称黄金分割数列,因数学家莱昂纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为 “兔子数列”。
如下图所示,就是一个斐波那契数列:
在数学上,斐波那契数列被以如下递推的方法定义:
F
(
0
)
=
0
,
F
(
1
)
=
1
F(0) = 0,F(1) = 1
F(0)=0,F(1)=1
F
(
n
)
=
F
(
n
−
1
)
+
F
(
n
−
2
)
(
n
>
=
2
,
n
∈
N
∗
)
F(n) = F(n-1) + F(n-2) (n>=2,n\in\mathbb N*)
F(n)=F(n−1)+F(n−2)(n>=2,n∈N∗)
从斐波那契的递推公式我们可以总结出重要一点:斐波那契数列从第三项开始,每一项都等于前两项之和。
如上图所示:
F[k]-1
是待搜索的数组中数组的长度,之所以使F[k]-1为数组的长度目的是满足以下关系式:
F ( n ) − 1 = F ( n − 1 ) − 1 + F ( − 2 ) − 1 ( n > = 2 , n ∈ N ∗ ) F(n)-1 = F(n-1)-1+F(-2)-1(n>=2,n\in\mathbb N*) F(n)−1=F(n−1)−1+F(−2)−1(n>=2,n∈N∗)
以实现mid将一个数组分为两个数组
正是上面这样的区间分割想法,使斐波那契数列和数组联系到了一起。这种分割思想亦是斐波那契查找算法的基础。
斐波那契查找算法相对于二分查找和插值查找的基本思路是一致的,其中最重要的区别就是它们的查找点(或称中间值)的确定。斐波那契查找算法的查找点的计算公式如下:
m i d = l e f t + F ( n − 1 ) mid = left + F(n-1) mid=left+F(n−1)
所以,斐波那契查找和二分查找或插值查找没有本质的区别,都是对待搜索的数组进行分割,分割的边界就是mid的值。只不过mid值生成方程式不同。
特点
黄金分割
代码
internal class MainProject
{
public const int FIB_MAX_SIZE = 40;
static void Main(string[] args)
{
int[] array = new int[] { -1, 0, 3, 5, 9, 12,45 };
Console.WriteLine(FibFind(array, 9));
Console.ReadKey();
}
public static int FibFind(int[] nums, int val)
{
int[] fibArray = new int[FIB_MAX_SIZE];
Fibonaccii(fibArray);
int k = 0;
while (fibArray[k] < nums.Length - 1) k++;
int size = fibArray[k] + 1;
List<int> temp = new List<int>();
temp.AddRange(nums);
for (int i = nums.Length; i <= fibArray[k]; i++)
{
temp.Add(nums[nums.Length - 1]);
}
int[] tempArrat = temp.ToArray();
int left = 0;
int right = fibArray[k];
while (left <= right)
{
int mid = left + fibArray[k - 1];
if (tempArrat[mid] < val)
{
left = mid + 1;
k -= 2;//因为是第二段,代表的是区间长度
}
else if (tempArrat[mid] > val)
{
right = mid - 1;
k -= 1; ;//因为是第二段,代表的是区间长度
}
else
{
if (mid < nums.Length)
return mid; //若相等则说明mid即为查找到的位置
else
return nums.Length - 1; //若mid>=n则说明是扩展的数值,返回n-1
}
}
return -1;
}
public static void Fibonaccii(int[] array)
{
if (array == null) return;
if (array.Length == 1) array[0] = 0;
if (array.Length >= 2)
{
array[0] = 0;
array[1] = 1;
}
for (int i = 2; i < array.Length; i++)
{
array[i] = array[i - 1] + array[i - 2];
}
}
}
参考文献
更新记录
- 2023-2-20 更新二分查找
- 2023-2-23 更新插值查找
- 2023-4-10 更新斐波那契查找