顺序 (线性) 查找
代码实现:
package com.atguigu.search;
/**
* @ClassName SeqSearchDemo
* @Author Jeri
* @Date 2022-02-21 11:40
* @Description 线性查找
*/
public class SeqSearchDemo {
/*
* @Description 线性查找
* @Date 2022/2/21 11:40
* @param arr 带查找数组
* @param value 查找数值
* @return 返回查找数值的索引 否则 返回-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 static void main(String[] args) {
int arr[] = {1, 9, 11, -1, 34, 89};// 没有顺序的数组
int index = seqSearch(arr, 11);
if (index == -1) {
System.out.println("没有该数值");
} else {
System.out.println("找到,下标为 = " + index);
}
}
}
找到,下标为 = 2
二分查找
思路分析:
说明: 二分查找 针对有序序列 进行查找
每次取中间数值进行比较,其中 mid = low + (high - low) / 2
代码展示:
package com.atguigu.search;
import java.util.ArrayList;
import java.util.List;
/**
* @ClassName BinarySearchDemo
* @Author Jeri
* @Date 2022-02-20 22:04
* @Description TODO
*/
public class BinarySearchDemo {
/*
* @Description 二分简单查找
* @Date 2022/2/20 22:05
* @param arr 待排序数组 默认升序
* @param left 左索引
* @param right 右索引
* @param value 待查找的数值
* @return 查找结果
*/
public static int binarySearch(int[] arr,int left,int right,int value){
//未找到元素,退出递归的条件
if(left > right){
return -1;
}
//正常查找
int mid = (left + right) / 2;
if(arr[mid] < value){//向右递归
return binarySearch(arr,mid + 1,right,value);
}else if(arr[mid] > value){//向左递归
return binarySearch(arr,left,mid - 1,value);
}else{
return mid;
}
}
/*
* @Description 二分查找优化 将所有相同的数值查找出来
* @Date 2022/2/21 10:16
* @param arr 待排序数组 默认升序
* @param left 左索引
* @param right 右索引
* @param value 待查找的数值
* @return [arr, left, right, value] 查找结果
*/
public static List<Integer> binarySearchOptimize(int[] arr, int left, int right, int value){
//当 left > right 时 说明递归整个数组结束 没有找到
if(left > right){
return new ArrayList<Integer>();
}
int mid = (left + right) / 2;
int midValue = arr[mid];
if(midValue < value){//向右递归
return binarySearchOptimize(arr,mid + 1,right,value);
}else if(midValue > value){//向左递归
return binarySearchOptimize(arr,left,mid - 1,value);
}else{
//当前 midValue == value
//根据二分查找有序序列 所以接下来只需要在左右两侧寻找是否存在相同数值
List<Integer> indexList = new ArrayList<>();
//indexList 只创建一次
//添加当前值
indexList.add(mid);
//向左查找
int tempLift = mid - 1;
while(true){
if(tempLift < 0 || arr[tempLift] != value){ break; }
//否则 说明 arr[tempLift] == value
indexList.add(tempLift);
tempLift--;
}
//向右查找
int tempRight = mid + 1;
while(true){
if(tempLift > arr.length - 1 || arr[tempRight] != value){ break; }
//否则 说明 arr[tempRight] == value
indexList.add(tempRight);
tempRight++;
}
//对有序序列左右两边遍历结束
return indexList;
}
}
public static void main(String[] args) {
// int[] array = new int[]{1,8, 10, 89, 1000, 1234};
int[] array = new int[]{1,2,2,2,3,4};
// int findValue = 10;
int findValue = 2;
int index = binarySearch(array,0,array.length - 1,findValue);
System.out.println("使用二分简单查找,结果为---------");
if(index == -1){
System.out.println("未找到该元素:" + findValue );
}else{
System.out.println("找到该元素,索引为" + index);
}
System.out.println();
List<Integer> indexList = binarySearchOptimize(array,0,array.length - 1,findValue);
System.out.println("使用二分优化查找,结果为---------");
System.out.println("indexList = " + indexList);
}
}
使用二分简单查找,结果为---------
找到该元素,索引为2
使用二分优化查找,结果为---------
indexList = [2, 1, 3]
插入查找
思路分析:
说明: 插值查找 针对有序序列 进行查找
根据 ( key - arr[low] ) / (mid - low) = ( arr[high] - arr[low] ) / (high- low) (二维平面的斜率 )
得到 mid 的递推公式 mid = left + high- low) * ( key - arr[low] ) / arr[high] - arr[low]
代码展示:
package com.atguigu.search;
/**
* @ClassName InsertValueSearchDemo
* @Author Jeri
* @Date 2022-02-21 10:38
* @Description 插值查找
*/
public class InsertValueSearchDemo {
/*
* @Description 插值查找
* @Date 2022/2/21 11:50
* @param arr 待查找序列
* @param left 左索引
* @param right 右索引
* @param value 查找数值
* @return 返回查找数值的索引 否则 返回-1
*/
public static int insertSearch(int[] arr,int left,int right,int value){
//排除特殊情况 避免 mid 越界
if(left > right || arr[left] > value || arr[right] < value){
return -1;
}
//采用自适应 找到mid
//原理 (value - arr[left]) / (mid - left) == (arr[right] - arr[left]) / (right - left)
int mid = left + (right - left) * (value - arr[left]) / (arr[right] - arr[left]);
int midValue = arr[mid];
if(midValue < value) {//向右递归
return insertSearch(arr,mid + 1,right,value);
}else if(midValue > value){
return insertSearch(arr,left,mid - 1,value);
}else{
return mid;
}
}
public static void main(String[] args) {
//int[] array = new int[]{1,8, 10, 89, 1000, 1234};
int[] array = new int[]{ 1, 8, 10, 89,1000,1000, 1234};
//int findValue = 10;
int findValue = 8;
int index = insertSearch(array,0,array.length - 1,findValue);
System.out.println("使用插值查找,结果为---------");
System.out.println("index = " + index);
}
}
使用插值查找,结果为---------
index = 1
斐波那契查找
思路分析:
黄金分割点是指把一条线段分割为两部分,使其中一部分与全长之比等于另一部分与这部分之比。取其前三位数字的近似值是 0.618。
斐波那契数列 {1, 1, 2, 3, 5, 8, 13, 21, 34, 55 } 发现斐波那契数列的两个相邻数 的比例,无限接近 黄金分割值0.618
斐波那契查找原理与前两种相似,仅仅改变了中间结点(mid)的位置,mid 不再是中间或插值得到,而是位于黄金分割点附近,即 mid=low+F(k-1)-1(F 代表斐波那契数列)
- 构造数列 temp 使得 //temp.length = f[k] - 1 = (f[k-1] - 1) + 1 + (f[k-2] - 1)
- 上式 1 的位置就是 mid ,则 mid 在数组中的索引为 low + (f[k-1] - 1)
- 循环查找对应的数值
说明: 斐波那契查找 针对有序序列 进行查找
mid = low + 0.618 * (high - low) 针对斐波那契数列 (high - low) = f[k] - 1,0.618 = f[k-1] / f[k]
代码展示:
package com.atguigu.search;
import java.util.Arrays;
/**
* @ClassName FibonacciSearchDemo
* @Author Jeri
* @Date 2022-02-21 19:27
* @Description 斐波那契查找算法
*/
public class FibonacciSearchDemo {
//斐波那契数列最大长度
public static int maxSize = 20;
//获取斐波那契数列
public static int[] fibonacci(){
int[] fib = new int[maxSize];
fib[0] = 1;
fib[1] = 1;
for(int i = 2;i < maxSize;i++){
fib[i] = fib[i - 1] + fib[i - 2];
}
return fib;
}
/*
* @Description 斐波那契查找算法
* @Date 2022/2/21 19:31
* @param arr 带查找数组
* @param key 查找数值
* @return [arr, key] 返回索引 否则 返回 -1
*/
public static int fibonacciSearch(int[] arr,int key){
int low = 0;
int high = arr.length - 1;
int k = 0;//斐波那契分割数值的下标
int mid = 0;//存放中间值
int f[] = fibonacci();//获取斐波那契数列
//获取斐波那契分割数值的下标
while(high > f[k] - 1){
k++;
}
//构造临时数组
int[] temp = Arrays.copyOf(arr,f[k]);
for(int i = high + 1;i < temp.length - 1;i++){
temp[i] = arr[high];
}
//实际上 在 temp 数组中查找 key 数值
//temp.length == f[k] - 1 == (f[k-1] - 1) + 1 + (f[k-2] - 1)
//中间的 1 就是给出的 mid 的位置
//mid 在数组中的索引为 low + (f[k-1] - 1)
//在查找时 选择 向(f[k-1] - 1) 或者 向(f[k-2] - 1)查找
//使用 while 进行循环查找
while(low <= high){
mid = low + f[k-1] - 1;
if(temp[mid] > key){//向 (f[k-1] - 1) 查找
high = mid - 1;
//f[k-1] - 1 == (f[k-1-1] - 1) + 1 + (f[k-2-1] - 1)
k -= 1;
}else if(temp[mid] < key){//向(f[k-2] - 1) 查找
low = mid + 1;
//f[k-2] - 1 == (f[k-1-2] - 1) + 1 + (f[k-2-2] - 1)
k -= 2;
}else{
//需要确定返回的是那个索引
//temp数组经过扩容
if(mid <= high){
return mid;
}else{
return high;
}
}
}
return -1;
}
public static void main(String[] args) {
//int[] array = new int[]{1,8, 10, 89, 1000, 1234};
int[] array = new int[]{ 1, 8, 10, 89,1000, 1234};
//int findValue = 10;
int findValue = 8;
int index = fibonacciSearch(array,findValue);
System.out.println("使用斐波那契查找,结果为---------");
System.out.println("index = " + index);
}
}
使用斐波那契查找,结果为---------
index = 1
哈希表
基本介绍: 散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表
问题描述:
有一个公司,当有新的员工来报道时,要求将该员工的信息加入(id,性别,年龄,名字,住址…),当输入该员工的 id 时, 要求查找到该员工的 所有信息
要求:
- 不使用数据库,速度越快越好=>哈希表(散列)
- 添加时,保证按照 id 从低到高插入 [课后思考:如果 id 不是从低到高插入,但要求各条链表仍是从低到高,怎么解决?]
- 使用链表来实现哈希表, 该链表不带表头[即: 链表的第一个结点就存放雇员信息]
- 思路分析并画出示意图
思路分析:
代码展示:
代码效果:按照 id 顺序添加,冲突时,按照先放现存顺序,同时 存在头节点
- 如果 id 不是从低到高插入,但要求各条链表仍是从低到高,怎么解决?:实现方法 addByOrder()
- 链表的第一个结点就存放雇员信息?:添加第一个员工时 head = emp,但是后续的方法在判断逻辑上需要修改
package com.atguigu.hashtable;
import java.util.Scanner;
/**
* @ClassName HashTableDemo
* @Author Jeri
* @Date 2022-02-21 16:57
* @Description TODO
*/
public class HashTableDemo {
public static void main(String[] args) {
//创建哈希表
HashTable hashTable = new HashTable(7);
//写一个简单的菜单
String key = "";
Scanner scan = new Scanner(System.in);
while(true){
System.out.println("------add:添加雇员----------");
System.out.println("------list:显示雇员---------");
System.out.println("------find:查找雇员---------");
System.out.println("------delete:删除雇员-------");
System.out.println("------exit:退出程序---------");
key = scan.next();
switch(key){
case "add":
System.out.println("输入id:");
int id = scan.nextInt();
System.out.println("输入姓名:");
String name = scan.next();
hashTable.add(new Emp(id,name));
break;
case "list":
hashTable.list();
break;
case "find":
System.out.println("输入编号:");
int searchID = scan.nextInt();
hashTable.findEmpByID(searchID);
break;
case "delete":
System.out.println("输入编号:");
int deleteID = scan.nextInt();
hashTable.deleteEmpByID(deleteID);
break;
case "exit":
scan.close();
return;
default:
break;
}
}
}
}
//创建HashTable 管理多条链表
class HashTable{
private EmpLinkedList[] empLinkedLists;
private int maxSize;
public HashTable(int maxSize){
this.maxSize = maxSize;
//初始化 哈希表
empLinkedLists = new EmpLinkedList[maxSize];
//分别初始化链表 否则为 null
for(int i = 0;i < maxSize;i++){
empLinkedLists[i] = new EmpLinkedList();
}
}
//添加雇员
public void add(Emp emp){
//根据员工的 id 得到员工信息存放在相应链表
int linkedListID = heshFun(emp.id);
//添加至相应的链表
empLinkedLists[linkedListID].add(emp);
}
//散列函数
public int heshFun(int id){
return id % maxSize;
}
//遍历哈希表
public void list(){
for(int i = 0;i < maxSize;i++){
empLinkedLists[i].showLinkedList(i);
}
}
//根据 ID 查找员工
public void findEmpByID(int id){
//根据员工的 id 查找相应链表
int linkedListID = heshFun(id);
//相应的链表中进行查找
Emp emp = empLinkedLists[linkedListID].findEmpByID(id);
if(emp == null){
System.out.println("在哈希表中没有找到该员工");
}else{
System.out.printf("在第%d条链表中找到雇员 id = %d\n", (linkedListID + 1), id);
}
}
//根据 ID 删除员工
public void deleteEmpByID(int id){
//根据员工的 id 查找相应链表
int linkedListID = heshFun(id);
//相应的链表中进行查找
empLinkedLists[linkedListID].deleteEmpByID(id);
}
}
//创建 EmpLinkedList 表示链表 管理雇员对象
class EmpLinkedList{
//头指针 链表的 head 直接指向第一个Emp
private Emp head = new Emp(-1,"start");//默认为空
//添加雇员到链表 添加至末尾
//id限定自增长 即 id 的分配总是从小到大
public void add(Emp emp){
if(head == null){
//添加第一个员工
head.next = emp;
return;
}
//定义辅助变量 找到最后一个节点
Emp current = head;
while (current.next != null){
current = current.next;
}
current.next = emp;
}
//遍历链表的雇员信息
public void showLinkedList(int i){
if(head.next == null){
System.out.printf("第" + (i + 1) + "链表为空\n");
return;
}
System.out.printf("第" + (i + 1) + "链表信息为:");
//定义辅助变量
Emp current = head.next;
while(current != null){
System.out.printf(current + "\t");
current = current.next;
}
System.out.println();
}
//根据 id 查找雇员
//如果找到就返回 Emp 否则返回 null
public Emp findEmpByID(int id){
//判断链表是否为空
if(head.next == null){
System.out.println("该链表为空 无法查找");
return null;
}
//辅助变量
Emp current = head.next;
while (current != null){
if(current.id == id){
return current;
}
current = current.next;
}
return null;
}
//根据 id 删除员工
//如果删除成功 就返回该员工信息 否则 返回null
public void deleteEmpByID(int id){
//判断链表是否为空
if(head.next == null){
System.out.println("该链表为空 无法删除");
return;
}
//辅助变量 查找删除节点的前一个节点
Emp current = head;
while (current.next != null){
if(current.next.id == id){
break;
}
current = current.next;
}
if(current.next == null){
System.out.println("哈希表中没有找到该员工 无法删除");
}else{
Emp temp = current.next;
current.next = temp.next;
System.out.printf("删除雇员 id = %d\n", id);
}
}
}
//使用 Emp 表示一个雇员信息
class Emp{
public int id;
public String name;
//节点类 next指向下一个节点 默认为空
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 + '\'' +
'}';
}
}
------add:添加雇员----------
------list:显示雇员---------
------find:查找雇员---------
------delete:删除雇员-------
------exit:退出程序---------
add
输入id:
2
输入姓名:
john
------add:添加雇员----------
------list:显示雇员---------
------find:查找雇员---------
------delete:删除雇员-------
------exit:退出程序---------
add
输入id:
123
输入姓名:
smith
------add:添加雇员----------
------list:显示雇员---------
------find:查找雇员---------
------delete:删除雇员-------
------exit:退出程序---------
list
第1链表为空
第2链表为空
第3链表信息为:Emp{id=2, name='john'}
第4链表为空
第5链表信息为:Emp{id=123, name='smith'}
第6链表为空
第7链表为空
------add:添加雇员----------
------list:显示雇员---------
------find:查找雇员---------
------delete:删除雇员-------
------exit:退出程序---------
find
输入编号:
8
该链表为空 无法查找
在哈希表中没有找到该员工
------add:添加雇员----------
------list:显示雇员---------
------find:查找雇员---------
------delete:删除雇员-------
------exit:退出程序---------
find
输入编号:
2
在第3条链表中找到雇员 id = 2
------add:添加雇员----------
------list:显示雇员---------
------find:查找雇员---------
------delete:删除雇员-------
------exit:退出程序---------
delete
输入编号:
15
该链表为空 无法删除
------add:添加雇员----------
------list:显示雇员---------
------find:查找雇员---------
------delete:删除雇员-------
------exit:退出程序---------
delete
输入编号:
2
删除雇员 id = 2
------add:添加雇员----------
------list:显示雇员---------
------find:查找雇员---------
------delete:删除雇员-------
------exit:退出程序---------
list
第1链表为空
第2链表为空
第3链表为空
第4链表为空
第5链表信息为:Emp{id=123, name='smith'}
第6链表为空
第7链表为空
------add:添加雇员----------
------list:显示雇员---------
------find:查找雇员---------
------delete:删除雇员-------
------exit:退出程序---------
exit
Process finished with exit code 0