本篇文章:
主要是关于java数据结构与算法的一些基本知识:查找算法。
正文如下:
一、查找算法
1)查找算法的介绍:
java中,我们常用的查找算法有四种:
① 顺序(线性)查找
② 二分查找/折半查找
③ 插值查找
④ 斐波那契查找
2)线性查找算法:
① 线性查找算法实例:
有一个数列: {1,8, 10, 89, 1000, 1234} ,判断数列中是否包含此名称【顺序查找】 要求: 如果找到了,就提示找到,并给出下标值。
package com.immort.dataStructure.search;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* 线性查找算法:在数组中线性查找指定数据
* @author ppmy
* @create 2021-03-31 09:05
*/
public class Study0SeqSearchDemo1 {
public static void main(String[] args) {
int[] array = {1,2,3,4,5,1,3,4,1,1,3};
//查找数据3
List<Integer> list = seqSearch(array, 3);
//获取迭代器
Iterator<Integer> it = list.iterator();
//遍历迭代器
while (it.hasNext()){
System.out.print(it.next()+" ");
}
}
/**
* 在数组中查找某一个数据
* @param array 要查找的数组
* @param values 要查找的数据
* @return 返回要查找数据的对应下标的集合
*/
private static List<Integer> seqSearch(int[] array, int values){
List<Integer> arrays = new ArrayList<>();
for (int i = 0; i < array.length; i++){
if(array[i] == values){
arrays.add(i);
}
}
return arrays;
}
}
3)二分查找算法
① 二分查找算法的介绍
当我们要从一个序列中查找一个元素的时候,二分查找是一种非常快速的查找算法,二分查找又叫折半查找。它对要查找的序列有两个要求,一是该序列必须是有序的(即该序列中的所有元素都是按照大小关系排好序的,升序和降序都可以。
② 二分查找算法的原理【该数组为升序数组】
1)如果左索引大于右索引的时候,无法找到该数据,返回-1,并退出算法。
2)如果待查序列不为空,则将它的中间元素与要查找的目标元素进行匹配,看它们是否相等。
3)如果相等,则返回该中间元素的索引,并退出算法;此时就查找成功了。
4)如果不相等,就再比较这两个元素的大小。
5)如果该中间元素大于目标元素,那么就将当前序列的前半部分作为新的待查序列;这是因为后半部分的所有元素都大于目标元素,它们全都被排除了。
6)如果该中间元素小于目标元素,那么就将当前序列的后半部分作为新的待查序列;这是因为前半部分的所有元素都小于目标元素,它们全都被排除了。
7)在新的待查序列上重新开始第1步的工作。
二分查找之所以快速,是因为它在匹配不成功的时候,每次都能排除剩余元素中一半的元素。因此可能包含目标元素的有效范围就收缩得很快,而不像顺序查找那样,每次仅能排除一个元素。
③ 二分查找算法的分析
④ 二分查找算法代码:
package com.immort.dataStructure.search;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
/**
* 二分查找:有序数组才可以进行二分查找
* @author ppmy
* @create 2021-03-31 09:29
*/
public class Study1BinarySearchDemo1 {
public static void main(String[] args) {
int[] array = {1,2,3,4,5,6,6,6,6,6,6,7,8,9,10};
List<Integer> list = new ArrayList<>();
// int i = binarySearch(array, 0, array.length-1, 11);
binarySearch1(list,array, 0, array.length-1, 6);
// if(i==-1){
// System.out.println("没有找到这个数 ");
// }else {
// System.out.println("数据位置在"+i);
// }
list.sort(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;
}
});
for (int i:list){
System.out.print(i+" ");
}
}
/**
* 升序排序下的二分查找算法:返回当前数组中第一个匹配数据的索引值
* @param array 要进行查找的数组
* @param left 左索引
* @param right 右索引
* @param value 要查找的数据
* @return 查找数据的索引:未找到: -1:找到:索引值
*/
//二分查找算法
private static int binarySearch(int[] array, int left, int right,int value){
//递归结束的条件1:
//如果当前的left大于了right,表示没有找到该数据
if(left > right){
return -1;
}
//获取中间值
int middle = (left+right)/2;
//要查找到的值,在当前middle的右边
if(array[middle]<value){
return binarySearch(array, middle+1, right, value);
}
//要查找的值在middle的左边
else if(array[middle]>value){
return binarySearch(array, left, middle-1, value);
}
//递归结束的条件2:
//查找到返回对应下标
else{
return middle;
}
}
/**
* 返回当前数组中所有匹配数据的索引值集合
* @param list 封装查找到的数据索引的集合
* @param array 要进行查找的数组
* @param left 左索引
* @param right 右索引
* @param value 要查找的数据
*/
//二分查找算法
private static void binarySearch1(List<Integer> list,int[] array, int left, int right,int value){
//递归结束的条件:
//如果当前的left大于了right,表示没有找到该数据
if(left > right){
return;
}
//获取中间值
int middle = (left+right)/2;
//要查找到的值,在当前middle的右边
if(array[middle]<value){
binarySearch1(list,array, middle+1, right, value);
}
//要查找的值在middle的左边
else if(array[middle]>value){
binarySearch1(list,array, left, middle-1, value);
}
//查找到了指定的元素
//1.将该数据放到
else{
//将找到的数据的索引添加到集合中
list.add(middle);
int index = middle -1;
while (index >= 0){
if(array[index]==value){
list.add(index);
index--;
}else{
break;
}
}
//向右进行扫描,扫描是否存在其他相等的值
index = middle+1;
while (index<array.length){
if(array[index]==value){
list.add(index);
index++;
}else{
break;
}
}
return;
}
}
}
4)插值查找算法【查找较为方便】:
① 插值查找算法的介绍:
1)插值查找算法类似于二分查找,不同的是插值查找每次从自适应mid处开始查找。
2)将折半查找中的求mid 索引的公式 ,更改为:
int mid = left + (right – left) * (findVal – arr[left]) / (arr[right] – arr[left]);
② 插值查找算法的图解分析:
③ 插值查找的代码:
package com.immort.dataStructure.search;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
/**
* 插值查找算法:适合数据分布较为均匀的时候,查找数据的次数会大大的减小
* @author ppmy
* @create 2021-04-02 10:49
*/
public class Study2InterSearchDemo1 {
public static void main(String[] args) {
int[] array = {1,2,3,4,5,6,6,6,6,7,7,7,8,8,8};
List<Integer> list = new ArrayList<>();
insertSort(list, array, 0, array.length-1, 6);
list.sort(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;
}
});
Iterator<Integer> it = list.iterator();
while (it.hasNext()){
System.out.print(it.next()+" ");
}
}
/**
* 使用插值算法查找数组中是否存在某数据
* @param list 用来存储索引的集合
* @param array 查找数据的数组
* @param left 左索引
* @param right 右索引
* @param value 要查找的数据
*/
private static void insertSort(List<Integer> list, int[] array,int left, int right,int value){
//如果要查找的数据太大或者太小的话,会发生指针越界,必须加上value < array[0]和value > array[array.length-1]这两个比较
if(left>right || value < array[0] || value > array[array.length-1]){
return;
}
//自适应index,
int index = left + (right-left)*((value-array[left])/(array[right]-array[left]));
//如果要查找的值大于当前数组的中值,向右进行递归
if(array[index] < value){
insertSort(list, array, index+1, right, value);
}
//要查找的值小于当前数组的中值,向左进行递归
else if(array[index] > value){
insertSort(list,array, left,index-1,value);
}
//找到要匹配的值:添加到集合中,并遍历左右两边,查看是否还有其他相同数据
else{
list.add(index);
//向左进行找
int current = index-1;
while (current>=0){
//没有找到,跳出循环
if(array[current]!=value){
break;
}
//找到,放到集合中
list.add(current);
current--;
}
//向右进行找
current = index+1;
while (current<array.length){
//没有找到,跳出循环
if(array[current]!=value){
break;
}
//找到,放到集合中
list.add(current);
current++;
}
}
}
}
④ 插值查找算法的注意事项:
1)对于数据量较大,关键字分布比较均匀的查找表来说,采用插值查找, 速度较快
2)关键字分布不均匀的情况下,该方法不一定比折半查找要好
5)斐波那契查找算法
① 斐波那契查找算法的介绍:
黄金分割点:
黄金分割点是指把一条线段分割为两部分,使其中一部分与全长之比等于另一部分与这部分之比。取其前三位数字的近似值是0.618。由于按此比例设计的造型十分美丽,因此称为黄金分割,也称为中外比。这是一个神奇的数字,会带来意向不大的效果。
斐波那契数列:
{1, 1, 2, 3, 5, 8, 13, 21, 34, 55 } 发现斐波那契数列的两个相邻数 的比例,无限接近黄金分割值0.618
② 斐波那契查找算法原理
斐波那契查找原理与前两种相似,仅仅改变了中间结点(mid)的位置,mid不再是中间或插值得到,而是位于黄金分割点附近,即mid=low+F(k-1)-1(F代表斐波那契数列),如下图所示:
对F(k-1)-1的理解:
由斐波那契数列有F[k] = F[k-1]+F[k-2] 的性质,可以得到 (F[k]-1)=(F[k-1]-1)+(F[k-2]-1)+1 。该式说明:只要顺序表的长度为F[k]-1,则可以将该表分成长度为F[k-1]-1和F[k-2]-1的两段,即如上图所示。从而中间位置为mid = low + F(k-1)-1
类似的,每一子段也可以用相同的方式分割。但顺序表长度n不一定刚好等于F[k]-1,所以需要将原来的顺序表长度n增加至F[k]-1。这里的k值只要能使得F[k]-1恰好大于或等于n即可,由以下代码得到,顺序表长度增加后,新增的位置(从n+1到F[k]-1位置),都赋为n位置的值即可。
while(n>fib[k]-1){
k++;
}
③ 斐波那契查找算法代码
package com.immort.dataStructure.search;
import java.util.Arrays;
/**
* 斐波那契数列查找算法:
* 1.创建一个斐波那契的数组
* 2.获取到当前要排序的数组,计算当前排序数组的长度满足哪一个斐波那契值
* 3.创建一个与当前斐波那契值相等长度的数组
* 4.将要排序数组的数据放到该数组中,将该数组剩余空间全部填上排序数组的最大值
* 5.开始进行查找:
* 5.1 获取第黄金分割点【循环判断】
* 5.1.1 判断当前分割点上的数据与要查找数据的大小
* 如果当前分割点上的数据大于要查找的数据,向左查找,寻找上一个斐波那契的值
* 如果当前分割点上的数据小于要查找的数据,向右查找,寻找上两个斐波那契的值
* 如果当前分割点上的数据等于要查找的数据,
* 比较当前mid值和high值,
* 如果mid值大于high值,返回high值
* 如果mid值小于high值,返回mid值
* 5.2 left值大于了right的值,没有找到该数据
* @author ppmy
* @create 2021-04-02 19:58
*/
public class Study3FibonacciSearchDemo1 {
private static final int MAXSIZE = 20;
public static void main(String[] args) {
int[] array = {1,2,3,4,6,7,8,9};
int i = fibSearch(array, 5);
System.out.println("index:"+i);
}
//先获取一个斐波那契数组
//非递归方法创建斐波那契数组
private static int[] fib(){
//创建最大容量为MAXSIZE的数组
int[] f = new int[MAXSIZE];
f[0] = 1;
f[1] = 1;
for (int i = 2; i < MAXSIZE; i++){
f[i] = f[i-1] + f[i-2];
}
return f;
}
/**
*
* @param array 查找数据的数组
* @param key 需要查找的数据
* @return 返回对应的下标
*/
private static int fibSearch(int[] array, int key){
//左索引
int low = 0;
//右索引
int high = array.length - 1;
//表示斐波那契分割数值的下标
int k = 0;
//存放mid值
int mid = 0;
//获取斐波那契数组
int[] f = fib();
//获取到斐波那契分割数值的下标
while (high > f[k]-1){
k++;
}
//因为f[k]可能大于a的长度,使用arrays构建一个新的数组,指向a[]
int[] temp = Arrays.copyOf(array,f[k]);
//当前数组不足的部分需要使用array数组最后的数据进行填充
for (int i = high+1; i < temp.length; i++){
temp[i] = array[high];
}
//使用while来循环处理,找到对应的斐波那契的key
while (low <= high){
mid = low + f[k-1] - 1;
if(key < temp[mid]){//向数组的左边进行查找
//更改high的值
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-1]的前半部分继续查找k--
// 下次循环的时候,mid = f[k-1-1] - 1;
k--;
}else if(key > temp[mid]){
//更改low的值
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-1]的后半部分继续查找k-=2
// 下次循环的时候,mid = f[k-1-2] - 1;
k -= 2;
}else{//找到了
//需要确定的,需要返回的是哪一个下标
if(mid <= high){
return mid;
}else{
return high;
}
}
}
return -1;
}
}
希望本篇文章对大家有所帮助,后续会继续分享java数据结构与算法相关学习知识…