数据结构和算法学习笔记三_搜索算法_哈希表

数据结构和算法学习笔记三_搜索算法_哈希表

一、查找算法

1.二分查找

单个搜索:找有序序列的中间值,

相等:

  • 返回这个下标

不相等:

  • 中间值小于查找的值,就搜索右半边
  • 中间值大于查找的值,就搜索左半边

多个搜索:找有序序列的中间值,

相等:

  • 将这个值加入集合,然后向左和向右其它相等的值(因为这个序列是有序的)

不相等:

  • 中间值小于查找的值,就搜索右半边
  • 中间值大于查找的值,就搜索左半边
  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;
        }
    }
}
  1. 查找单个值(非递归)
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;
    }
}

  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.插值查找

  1. 插值查找原理介绍:

插值查找算法类似于二分查找,不同的是插值查找每次从自适应 mid 处开始查找。

  1. 将折半查找中的求 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)

扫描二维码关注公众号,回复: 12397937 查看本文章
  1. int mid = low + (high - low) * (key - arr[low]) / (arr[high] - arr[low]) ;//插值索引

对应前面的代码公式:

int mid = left + (right – left) * (findVal – arr[left]) / (arr[right] – arr[left])

  1. 单个查找
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 的理解
  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

  1. 类似的,每一子段也可以用相同的方式分割

  2. 但顺序表长度 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 时,

要求查找到该员工的 所有信息.

要求:

  1. 不使用数据库,速度越快越好=>哈希表(散列)

  2. 添加时,保证按照 id 从低到高插入 [课后思考:如果 id 不是从低到高插入,但要求各条链表仍是从低到

高,怎么解决?]

  1. 使用链表来实现哈希表, 该链表不带表头[即: 链表的第一个结点就存放雇员信息]

  2. 思路分析并画出示意图

在这里插入图片描述

实现代码:

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();
    }
}

猜你喜欢

转载自blog.csdn.net/Inmaturity_7/article/details/111111313
今日推荐