数据结构和算法学习笔记三_搜索算法_哈希表
一、查找算法
1.二分查找
单个搜索:找有序序列的中间值,
相等:
- 返回这个下标
不相等:
- 中间值小于查找的值,就搜索右半边
- 中间值大于查找的值,就搜索左半边
多个搜索:找有序序列的中间值,
相等:
- 将这个值加入集合,然后向左和向右其它相等的值(因为这个序列是有序的)
不相等:
- 中间值小于查找的值,就搜索右半边
- 中间值大于查找的值,就搜索左半边
- 查找单个值(递归)
package com.lxf.search;
import java.util.ArrayList;
public class BinarySearch {
public static void main(String[] args) {
int arr[]={
1,8,10,89,1000,11111,1234};
System.out.println(binarySearch(arr,0,arr.length-1,1000));
}
/**
* @param arr 数组
* @param left 左边的索引
* @param right 右边的索引
* @param findVal 要查找的值
* @return 如果找到直接返回下标,如果没找到返回-1
*/
//二分查找算法
public static int binarySearch(int arr[],int left,int right,int findVal){
//中止条件
if(left>right){
return -1;
}
int mid=(left+right)/2;
int midVal=arr[mid];
if(findVal>midVal){
//向右递归
return binarySearch(arr,mid+1,right,findVal);
}else if(findVal<midVal){
//向左递归
return binarySearch(arr,left,mid-1,findVal);
}else{
//返回这个下标
return mid;
}
}
}
- 查找单个值(非递归)
package com.lxf.search;
public class BinarySearchNoRecur {
public static void main(String[] args) {
int[] arr = {
1,3,5,7,9,18,19,22,100};
int result = binarySearch(arr, 100);
if(result>=0){
System.out.println("查找到目标值的下标:"+result);
}else{
System.out.println("未在数组查找到对应值");
}
}
/**
* 二分查找的非递归查找
* @param arr 查找数组
* @param target 目标值
* @return
*/
public static int binarySearch(int[] arr,int target){
int left=0;
int right=arr.length-1;
while (left<=right){
int mid=(left+right)/2;
if(arr[mid]==target){
return mid;//找到,返回对应值
}else if(arr[mid]>target){
right=mid-1;//继续向左查找
}else{
left=mid+1;//继续向右查找
}
}
//未找到返回-1
return -1;
}
}
- 查找多个值
package com.lxf.search;
import java.util.ArrayList;
import java.util.List;
public class BinarySearch {
private static List<Integer> list=new ArrayList<>();
public static void main(String[] args) {
int arr[]={
1,8,10,89,1000,1000,1234};
binarySearch(arr,0,arr.length-1,1000);
for (Integer integer : list) {
System.out.print(integer+" ");
}
}
/**
* @param arr 数组
* @param left 左边的索引
* @param right 右边的索引
* @param findVal 要查找的值
* @return 如果找到直接返回下标,如果没找到返回-1
*/
//二分查找算法
public static void binarySearch(int[] arr,int left,int right,int findVal){
//中止条件
if(left>right){
return;
}
int mid=(left+right)/2;
int midVal=arr[mid];
if(findVal>midVal){
//向右递归
binarySearch(arr,mid+1,right,findVal);
}else if(findVal<midVal){
//向左递归
binarySearch(arr,left,mid-1,findVal);
}else{
list.add(mid);
int i=mid+1;
int j=mid-1;
while(i<=right){
if(arr[i]==findVal){
list.add(i);
}
i++;
}
while(j>=left){
if(arr[j]==findVal){
list.add(j);
}
j--;
}
}
}
}
2.插值查找
- 插值查找原理介绍:
插值查找算法类似于二分查找,不同的是插值查找每次从自适应 mid 处开始查找。
- 将折半查找中的求 mid 索引的公式 , low 表示左边索引 left, high 表示右边索引 right.
key 就是前面我们讲的 findVal
**二分:**mid=(low+high)/2=low+(high-low)/2
=>
**插值:**mid=low+((key-a[low])/a[high]-a[low])*(high-low)
- int mid = low + (high - low) * (key - arr[low]) / (arr[high] - arr[low]) ;//插值索引
对应前面的代码公式:
int mid = left + (right – left) * (findVal – arr[left]) / (arr[right] – arr[left])
- 单个查找
package com.lxf.search;
public class InsertValueSearch {
public static void main(String[] args) {
int arr[]={
1,8,10,89,1000,11111,1234};
System.out.println(insertValue(arr,0,arr.length-1,1000));
}
public static int insertValue(int[] arr,int left,int right,int findVal){
if(left>right||findVal<arr[0]||findVal>arr[arr.length-1]){
return -1;
}
//求出mid
int mid=left+(right-left)*(findVal-arr[left])/(arr[right]-arr[left]);
int midVal=arr[mid];
if(findVal>midVal){
//向右查找
return insertValue(arr, mid+1, right, findVal);
}else if(findVal<midVal){
//向左查找
return insertValue(arr, left, mid-1, findVal);
}else{
return mid;
}
}
}
2.多个查找
package com.lxf.search;
import java.util.ArrayList;
import java.util.List;
public class BinarySearch {
private static List<Integer> list=new ArrayList<>();
public static void main(String[] args) {
int arr[]={
1,8,10,89,1000,1000,1234};
binarySearch(arr,0,arr.length-1,1000);
for (Integer integer : list) {
System.out.print(integer+" ");
}
}
/**
* @param arr 数组
* @param left 左边的索引
* @param right 右边的索引
* @param findVal 要查找的值
* @return 如果找到直接返回下标,如果没找到返回-1
*/
//二分查找算法
public static void binarySearch(int[] arr,int left,int right,int findVal){
//中止条件
if(left>right||findVal<arr[0]||findVal>arr[arr.length-1]){
return -1;
}
int mid=left+(right-left)*(findVal-arr[left])/(arr[right]-arr[left]);
int midVal=arr[mid];
if(findVal>midVal){
//向右递归
binarySearch(arr,mid+1,right,findVal);
}else if(findVal<midVal){
//向左递归
binarySearch(arr,left,mid-1,findVal);
}else{
list.add(mid);
int i=mid+1;
int j=mid-1;
while(i<=right){
if(arr[i]==findVal){
list.add(i);
}
i++;
}
while(j>=left){
if(arr[j]==findVal){
list.add(j);
}
j--;
}
}
}
}
3.斐波那契查找算法
斐波那契查找原理与前两种相似,仅仅改变了中间结点(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.lxf.search;
import java.util.Arrays;
public class FibonacciSearch {
private static int count=0;
public static void main(String[] args) {
int arr[]={
1,2,3,4,5,6,7,8,9,10};
System.out.println("index="+fibSearch(arr,7));
System.out.println("次数="+count);
}
private static int maxSize=20;
/**
* 因为后面我们mid=low+F(k-1)-1,需要使用到斐波那契数列,因此我们需要先获取到一个斐波那契数列
* 非递归的方式2
* @return
*/
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;
}
/**
* 非递归的方式编写
* @param a 数组
* @param key 我们需要查找的值
* @return 返回对应的下标,如果没有就返回-1
*/
public static int fibSearch(int[] a,int key){
int low=0;
int high=a.length-1;
int k=0;//表示斐波那契数列分割数值的下标
int mid=0;//存放mid值
int f[]=fib();//获取到斐波那契数列
//int arr[]={1,8,10,89,1000,11111,1234};
//1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765
//获取斐波那契数列分割数组的下标
while(high>f[k]-1){
k++;
}
//因为f[k]可能>数组a的长度,因此我们需要使用Arrays类,构造一个新的数组,并指向a
//不足的部分会使用0填充
int[] temp= Arrays.copyOf(a,f[k]);
//实际上需要使用a数组最后的数填充temp
for (int i=high+1;i<temp.length;i++){
temp[i]=a[high];
}
//使用while来循环处理,找到我们的数 key
while(low<=high){
count++;
mid=low+f[k-1]-1;
if(key<temp[mid]){
//我们应该继续向数组的左边查找
high=mid-1;
k--;
//为什么是k--
//说明:
//1.全部元素=前面的元素+后边元素
//2.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;
}else if(key > temp[mid]){
//我们应该继续向数组的后面查找(右边)
low=mid+1;
//为什么是k-=2
//说明
//1.全部元素=前面的元素+后面的元素
//2.f[k]=f[k-1]+f[k-2]
//3.因为后面我们有f[k-2] 所以可以继续拆分f[k-1]=f[k-3]+f[k-4]
//4.即在f[k-2]的前面进行查找 k -=2
//5.即下次循环 mid=f[k-1-2]-1
k-=2;
}else{
//找到这个值
//返回这个值对应的下标
if(mid<=high){
return mid;
}else{
return high;
}
}
}
return -1;
}
}
二、哈希表
1.哈希表的基本介绍
散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通
过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组
叫做散列表。
2.google 公司的一个上机题:
有一个公司,当有新的员工来报道时,要求将该员工的信息加入(id,性别,年龄,名字,住址…),当输入该员工的 id 时,
要求查找到该员工的 所有信息.
要求:
-
不使用数据库,速度越快越好=>哈希表(散列)
-
添加时,保证按照 id 从低到高插入 [课后思考:如果 id 不是从低到高插入,但要求各条链表仍是从低到
高,怎么解决?]
-
使用链表来实现哈希表, 该链表不带表头[即: 链表的第一个结点就存放雇员信息]
-
思路分析并画出示意图
实现代码:
package com.lxf.hashtable;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class HashTabDemo {
public static void main(String[] args) throws IOException {
//创建哈希表
HashTab hashTab = new HashTab(7);
//写一个简单的菜单
String key = "";
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
while (true) {
System.out.println("add:添加雇员");
System.out.println("list:显示雇员");
System.out.println("exit:退出系统");
System.out.println("find:查找id");
System.out.println("delete:删除id对应的雇员");
key = br.readLine();
switch (key) {
case "add":
System.out.println("输入id:");
int id = Integer.valueOf(br.readLine());
System.out.println("输入名字:");
String name = br.readLine();
//创建雇员
Emp emp = new Emp(id, name);
hashTab.add(emp);
break;
case "list":
hashTab.list();
break;
case "find":
System.out.println("请输入要查找的id");
id = Integer.valueOf(br.readLine());
hashTab.findEmpById(id);
break;
case "delete":
System.out.println("请输入要删除的id");
id = Integer.valueOf(br.readLine());
hashTab.deleteEmpById(id);
break;
case "exit":
br.close();
System.exit(0);
default:
break;
}
}
}
}
class HashTab {
private EmpLinkedList[] empLinkedListsArray;
private int size;
//构造器
public HashTab(int size) {
//初始化empLinkedListArray
this.empLinkedListsArray = new EmpLinkedList[size];
for (int i = 0; i < size; i++) {
empLinkedListsArray[i] = new EmpLinkedList();
}
this.size = size;
}
//添加雇员
public void add(Emp emp) {
//根据员工的id,得到该员工应当添加到哪条链表
int empLinkedListNO = hashFun(emp.id);
//将emp添加到对应的链表中
empLinkedListsArray[empLinkedListNO].add(emp);
}
/**
* 遍历所有的链表
*/
public void list() {
for (int i = 0; i < size; i++) {
empLinkedListsArray[i].list(i);
}
}
/**
* 编写一个散列函数,使用一个简单取模法
*
* @param id
* @return
*/
public int hashFun(int id) {
return id % size;
}
/**
* 查找id在哪个链表
*
* @param id
*/
public void findEmpById(int id) {
//使用散列函数确定到哪条链表查找
int empLinkedListNo = hashFun(id);
Emp emp = empLinkedListsArray[empLinkedListNo].findEmpById(id);
if (emp != null) {
//找到
System.out.printf("在第%d条链表中找到雇员id=%d\n", (empLinkedListNo + 1), id);
} else {
System.out.println("在哈希表中,没有找到该雇员~");
}
}
/**
* 删除对应id的雇员
*
* @param id
*/
public void deleteEmpById(int id) {
//使用散列函数确定到哪条链表查找
int empLinkedListNo = hashFun(id);
empLinkedListsArray[empLinkedListNo].deleteEmpById(id);
}
}
//表示一个雇员
class Emp {
public int id;
public String name;
public Emp next;//next默认为null
public Emp(int id, String name) {
this.id = id;
this.name = name;
}
}
//创建EmpLinkedList,表示链表
class EmpLinkedList {
//头指针,指向第一个emp,因此我们这个链表的head,是直接指向第一个Emp
private Emp head;//默认null
/**
* 添加雇员到链表
* 说明:
* 1.假定:当添加雇员时,id是自增长,即id的分配总是从小到大
* 因此我们将该雇员直接加入到本链表的最后即可
*
* @param emp
*/
public void add(Emp emp) {
//如果是添加第一个雇员
if (head == null) {
head = emp;
return;
}
//如果不是第一个雇员,则使用一个辅助指针,帮助定位到最后
Emp curEmp = head;
while (true) {
if (curEmp.next == null) {
break;
}
curEmp = curEmp.next;//后移
}
//退出时直接将emp加入链表
curEmp.next = emp;
}
/**
*
*/
public void list(int no) {
if (head == null) {
//说明链表为空
System.out.println("第" + (no + 1) + "链表为空");
return;
}
System.out.print("第" + (no + 1) + "链表的信息为:");
Emp curEmp = head;//辅助指针
while (true) {
System.out.printf("=>id=%d name=%s\t", curEmp.id, curEmp.name);
if (curEmp.next == null) {
//说明curEmp已经是最后节点
break;
}
curEmp = curEmp.next;//后移,遍历
}
System.out.println();
}
/**
* 根据id查找雇员
* 如果查到,就返回Emp,如果没有找到,就返回null
*
* @param id
* @return
*/
public Emp findEmpById(int id) {
if (head == null) {
System.out.println("链表为空");
return null;
}
//辅助指针
Emp curEmp = head;
while (true) {
if (curEmp.id == id) {
//找到
break;
}
//退出
if (curEmp.next == null) {
//说明遍历当前链表没有找到该雇员
curEmp = null;
break;
}
curEmp = curEmp.next;
}
return curEmp;
}
/**
* 删除链表中的元素
*
* @param id
*/
public void deleteEmpById(int id) {
if (head == null) {
System.out.println("链表为空");
}
//如果删除head
if(head.id==id){
head=head.next;
System.out.printf("成功删除编号为%d的元素", id);
System.out.println();
return;
}
//辅助指针
Emp temp = head;
while (true) {
if (temp.next.id == id) {
temp.next = temp.next.next;
System.out.printf("成功删除编号为%d的元素", id);
break;
}
if (temp.next == null) {
System.out.println("未找到要删除的元素");
break;
}
temp = temp.next;
}
System.out.println();
}
}