常用的四种查找算法
- 顺序(线性)查找
- 二分查找(折半查找)
- 插值查找
- 斐波那契查找
查找算法实现
顺序查找
public class SeqSearch {
public static void main(String[] args) {
int arr[] = {1, 3 ,5, 88, 11, 2, -45};
int index = seqSearch(arr, 11);
if (index == -1) {
System.out.println("数组中没有该元素");
} else {
System.out.printf("找到了,下标是%d\n", index);
}
}
/**
* 这里实现的线性查找是找到一个便返回,如果没有找到返回-1
*/
public static int seqSearch(int[] arr, int value) {
for (int i = 0; i < arr.length; ++i) {
if (arr[i] == value) {
return i;
}
}
return -1;
}
}
二分查找
适用范围
适用于有序序列中。不适用无序序列。
代码实现
public class BinarySearch {
public static void main(String[] args) {
int arr[] = {1, 8, 9, 10, 89, 1000, 1256};
binarySearch(arr, 0, arr.length, 1000);
}
public static int binarySearch(int[] arr, int left, int right, int findVal) {
if (left > right) {
return -1;
}
int mid = (left + right) / 2;
if (findVal < arr[mid]) {
return binarySearch(arr, left, mid-1, findVal);
}
else if (findVal > arr[mid]) {
return binarySearch(arr, mid + 1, right, findVal);
}else {
return mid;
}
}
}
问题:利用二分查找数组arr(数组arr有序)中是否存在数a。存在则输出所有下标,否则则输出-1。
插值查找
插值查找原理
- 插值查找算法类似于二分查找,不同的是插值查找每次从自适应mid处开始查找。
- 将折半查找中的求 mid 索引的公式,low 表示左边索引,high表示右边索引。
根据二分公式推出插值查找公式
m i d = l o w + h i g h 2 = 1 2 ( h i g h − l o w ) ⇒ m i d = l o w + k e y − a [ l o w ] a [ h i g h ] − a [ l o w ] ( h i g h − l o w ) mid =\displaystyle \frac{low+high}{2}=\frac{1}{2}(high-low) \Rightarrow mid=\displaystyle low + \frac{key - a[low]}{a[high]-a[low]}(high-low) mid=2low+high=21(high−low)⇒mid=low+a[high]−a[low]key−a[low](high−low)
key为要查找的值。
代码实现
import java.util.Scanner;
public class InsertValueSearch {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int[] arr = new int[n];
for (int i = 0; i < n; i ++) {
arr[i] = i;
}
int index = insertValueSearch(arr, 0, arr.length-1, 10);
if (index != -1) {
System.out.printf("找到了,下标是%d\n", index);
} else {
System.out.println("没找到,不存在数组中");
}
}
public static int insertValueSearch(int[] arr, int left, int right, int findValue) {
if (left > right || findValue < arr[0] || findValue > arr[arr.length-1]) {
return -1;
}
int mid = left + (right - left) * (findValue - arr[left]) / (arr[right] - arr[left]);
int midValue = arr[mid];
if (findValue > midValue) {
return insertValueSearch(arr, left, mid -1, findValue);
} else if (findValue < midValue) {
return insertValueSearch(arr, mid + 1, right, findValue);
} else {
return mid;
}
}
}
插值查找注意事项
- 对于数据量大,关键字分布比较均匀的查找表来说,采用插值查找,速度较快。
- 对于关键字分布不均匀的情况下,该方法不一定比二分查找要好。
斐波那契查找
基本介绍
- 黄金分割点是指把一条线段分割成两部分,使其中一部分于全长之比等于另一部分与这部分之比。取其前三位数字的近似值是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。
- 类似,每一个子段也可以用相同方式分割。
代码实现
import java.util.Arrays;
public class FibonacciSearch {
public static int maxSize = 20;
public static void main(String[] args) {
int[] arr = {1, 8, 10, 89, 1000, 1234};
int index = fibSearch(arr, 10);
if (index != -1) {
System.out.printf("该数在数组中的下标是:%d\n", index);
} else {
System.out.println("该数不在数组中");
}
}
public static int[] fib() {
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;
}
public static int fibSearch(int[] arr, int key) {
int low = 0;
int high = arr.length -1;
int k = 0; //表示斐波那契数列的下标
int mid = 0;
int f[] = fib();
while (high > f[k] -1) {
k++;
}
// 因为f[k]的值可能大于arr.length,因此我们需要使用Arrays类,构造一个新的数组,并指向a[]
int[] temp = Arrays.copyOf(arr, f[k]);
for (int i = high +1; i < temp.length; ++i) {
temp[i] = arr[high];
}
while (low <= high) {
mid = low + f[k-1] -1;
if (key < temp[mid]) {
high = mid -1;
k--;
} else if (key > temp[mid]) {
low = mid + 1;
k-=2;
} else {
return mid <= high ? mid : high;
}
}
return -1;
}
}
哈希表
基本介绍
散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫散列函数,存放记录的数组叫散列表。
散列代码实现
import java.util.Scanner;
public class HashTableDemo {
public static void main(String[] args) {
HashTab hashTab = new HashTab(7);
String key = "";
Scanner scanner = new Scanner(System.in);
while(true) {
System.out.println("add: 添加雇员");
System.out.println("list: 显示雇员");
System.out.println("search: 查找雇员");
System.out.println("exit: 退出系统");
key = scanner.next();
switch (key) {
case "add":
System.out.println("输入id");
int id = scanner.nextInt();
System.out.println("输入名字");
String name = scanner.next();
Emp emp = new Emp(id, name);
hashTab.add(emp);
break;
case "list":
hashTab.list();
break;
case "search":
System.out.println("请输入雇员id:");
int no = scanner.nextInt();
Emp empById = hashTab.findEmpById(no);
if (empById == null) {
System.out.println("没有找到");
} else {
System.out.println("找到了:" + empById);
}
break;
case "exit":
scanner.close();
System.out.println("程序退出啦");
System.exit(0);
default:
System.out.println("输入有误,重新输入");
break;
}
}
}
}
class Emp {
public int id;
public String name;
public Emp next;
public Emp(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "Emp{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
// 创建HashTab 管理
class HashTab {
private EmpLinkedList[] empLinkedListArray;
private int size;
public HashTab(int size) {
this.size = size;
empLinkedListArray = new EmpLinkedList[size];
for (int i = 0; i < size; ++i) {
empLinkedListArray[i] = new EmpLinkedList();
}
}
public void add(Emp emp) {
// 根据员工的id,决定该员工应该加入到哪条链表中
int empLinkedListNo = hashFun(emp.id);
empLinkedListArray[empLinkedListNo].add(emp);
}
public void list() {
for (int i = 0; i < size; ++i) {
empLinkedListArray[i].list(i);
}
}
// 根据id查找雇员
// 如果查找到,就返回Emp;如果没有找到,就返回null
public Emp findEmpById(int id) {
int empListNo = hashFun(id);
EmpLinkedList empLinkedList = empLinkedListArray[empListNo];
Emp emp = empLinkedList.find(id);
return emp;
}
// hash函数
private int hashFun(int n) {
return n % size;
}
}
class EmpLinkedList {
private Emp head;
public void add(Emp emp) {
if (head == null) {
head = emp;
return ;
}
Emp tmp = head;
while (tmp.next != null) {
tmp = tmp.next;
}
tmp.next = emp;
}
public void list(int i) {
if (head == null) {
System.out.println("当前第" + i + "链表为空");
return ;
}
System.out.print("当前" + i + "链表的信息为");
Emp tmp = head;
while (tmp!=null) {
System.out.printf(" => id=%d name=%s\t", tmp.id, tmp.name);
tmp = tmp.next;
}
System.out.println();
}
public Emp find(int id) {
Emp tmp = head;
while (tmp != null) {
if (tmp.id == id) {
return tmp;
}
tmp = tmp.next;
}
return null;
}
}