`

散列表的demo实现,二分查找

阅读更多
散列表,散列算法
[size=small]一、概念
首先,回顾下散列的概念。散列同顺序、链接和索引一样,是一中数据存储方法。
定义:以数据集合中的每个元素的关键字k为自变量,通过一个函数h(k)计算出函数值,用这个值作为一块连续的存储空间(数组或文件空间)中的元素存储位置,将该元素存放在这块位置上。其中h(k) 成为散列函数或哈希函数,而h(k)的值成为散列地址或者哈希地址。
散列表
根据散列函数计算出的值作为地址存放相关元素值,这样的一组数据存放方式称为散列表。
冲突
多个元素的key值计算出的哈希值为同一个时,就造成了冲突。通常来讲,这种具有不同关键字而具有相同散列地址的元素称为"同义词",由同义词引起的存储冲突称为同义词冲突

    发生冲突的因素:
        1、装填因子α,其中α=n/m,其中n为带插入的元素个数,m为散列表长度。一般来讲,α越小,发生冲突的可能性就越小,但是随之而来的问题是,空间利用率越低。
        2、散列函数的选择,若散列函数选择得当,就能使散列地址均匀的分布在散列空间上,从而减少冲突的发生。
        3、解决冲突的方法。
散列桶
   散列表(散列存储)中每个散列地址对应的存储位置被称为一个

二、散列函数
1、直接定址法
   h(k) = k + C 其中k为关键字本身,C为常数。
   优点:简单,没有冲突发生,如果有冲突,则说明是关键字重复。
   缺点:若关键字不连续,空号较多,将造成存储空间的较大浪费。
2、除留取余数法
   h(k)=k%m
   该方法适用范围广,是目前最常见的方法。
3、其他
   数字分析法、平方取中法、折叠法

三、处理冲突的方法
    主要包含开放定址法(线性探查法、平方探查法、双散列函数探查法)、链接法(即链地址法)

    散列性能分析,从这本书《数据结构使用教程.徐孝凯》上得到的结果是链接地址法的平均查找长度是比较优秀的。
    
四、散列表的运算
    进行散列表的运算,首先定义散列表在Java中的接口类,说明该中存储结构需要有哪些功能,然后给出不同处理冲突方式的实现类。一般常用的有两种方式:一种是采用开放定址法处理;另一种是链接法处理冲突的实现类。

1、根据散列表的存储性质,定义接口类:
package com.algorithm.hash;

/**
 * 散列表的接口类
 * Created by xiaoyun on 2016/8/4.
 */
public interface HashTable {
    /**
     * 向散列表中插入一个关键字为theKey的元素obj,若插入成功返回真否则返回假
     * @param theKey
     * @param obj
     * @return
     */
    boolean insert(Object theKey, Object obj);

    /**
     * 从散列表中查找并返回与给定关键字theKey的元素值,若查找失败则返回假
     * @param theKey
     * @return
     */
    Object search(Object theKey);

    /**
     * 从散列表中删除关键字为theKey的元素,若查找失败返回空
     * @param theKey
     * @return
     */
    boolean delete(Object theKey);

    /**
     * 返回散列表中的元素个数
     * @return
     */
    int size();

    /**
     * 返回散列表的容量,即散列表的空间大小m的值
     * @return
     */
    int capacity();

    /**
     * 判断该散列表是否为空,如果为空则返回true,否则返回false
     * @return
     */
    boolean isEmpty();

    /**
     * 清空散列表
     */
    void clear();

    /**
     * 输出散列表中保存的所有关键字和对应元素
     */
    void output();
}

2、开放定址法处理冲突的数组存储类
package com.algorithm.hash;

/**
 * 采用开放定址法(这里采用线性探查法)处理冲突的数组存储类
 * Created by xiaoyun on 2016/8/4.
 */
public class SeqHashTable implements HashTable {

    /** 保存散列表的容量 */
    private int m;

    /** 定义保存元素关键字的数组 */
    private Object[] key;

    /** 定义保存散列桶的数组 */
    private Object[] ht;

    /** 散列表中已有的元素个数 */
    private int n;

    /** 元素内容被删除后的关键字删除标记 */
    private Object tag;

    /**
     * 私有方法-散列函数,假定采用除留取余数法,如果参数不是整数,应设法转换为整数
     * @param theKey
     * @return
     */
    private int h(Object theKey) {
        return (Integer)theKey % m;
    }

    /**
     * 构造方法
     * @param mm 散列表容量
     * @param tag 删除标记
     */
    public SeqHashTable(int mm, Object tag) {
        if(mm < 13) {
            this.m = 13; // 假定散列表的容量至少等于13
        }else {
            this.m = mm;
        }
        n = 0;
        key = new Object[m];
        ht = new Object[m];
        this.tag = tag;
    }

    /**
     * 向散列表中插入一个关键字为theKey的新元素obj,插入成功返回true,无可用空间直接退出
     * @param theKey
     * @param obj
     * @return
     */
    public boolean insert(Object theKey, Object obj) {
        int d = h(theKey);
        int temp = d;
        while(key[d] != null && !key[d].equals(this.tag)){
            if(key[d].equals(theKey)){
                break;
            }
            d = (d + 1)%m; // 探查下一个单元
            if(d == temp) {
                System.out.println("散列表无该元素的存储空间,退出运行!");
                System.exit(1);
            }
        }
        if(key[d] == null || key[d].equals(this.tag)) { // 找到插入位置,将该元素插入,返回true
            key[d] = theKey;
            ht[d] = obj;
            this.n++;
            return true;
        }else { // 用新元素obj替换已存在的元素并返回false
            ht[d] = obj;
            return false;
        }
    }

    /**
     * 从散列表中查找并返回与给定关键字theKey的元素值,若查找失败则返回假
     * @param theKey
     * @return
     */
    public Object search(Object theKey) {
        int d = h(theKey); // 求theKey的散列地址
        int temp = d; // 用temp暂存所求得的散列地址d
        while(key[d] != null) { // 当散列地址中的关键字不为空时进行循环
            if(key[d].equals(theKey)) {
                return ht[d];
            }else {
                d = (d + 1) % m;
            }
            if(d == temp) { // 查找一周后失败返回空值
                return null;
            }
        }
        return null;
    }

    /**
     * 从散列表中删除关键字为theKey的元素,若查找失败返回空
     * @param theKey
     * @return
     */
    public boolean delete(Object theKey) {
        int d = h(theKey);
        int temp = d;
        while (key[d] != null) { // 散列地址为空时退出循环
            if(key[d].equals(theKey)) { // 找到需要删除的元素
                key[d] = this.tag; // 设置删除标记
                ht[d] = null; // 元素值变为空
                n--; // 散列桶长度减一
                return true;
            } else {
                d = (d + 1) % m; // 线性探测法
            }
            if(d == temp) {
                return false;
            }
        }
        return false;
    }

    public int size() {
        return this.n;
    }

    public int capacity() {
        return this.m;
    }

    public boolean isEmpty() {
        return this.n == 0;
    }

    public void clear() {
        for (int i = 0; i < m; i++) {
            key[i] = null;
            ht[i] = null;
        }
        this.n = 0;
    }

    public void output() {
        for (int i = 0; i < m; i++) {
            if(key[i] == null || key[i].equals(tag)){
                continue;
            }
            System.out.print("(" + key[i] + " " + ht[i] + "),");
        }
        System.out.println();
    }
}

3、采用链接法处理冲突的链接存储类
定义结点类型
package com.algorithm.hash;

/**
 * 链地址法处理冲突的散列表中的结点类型
 * Created by xiaoyun on 2016/8/6.
 */
public class HashNode {
    Object key;
    Object element;
    HashNode next;
    public HashNode(Object key, Object element) {
        this.key = key;
        this.element = element;
        next = null;
    }
}

定义存储结构:
package com.algorithm.hash;

/**
 * 链接法处理冲突的散列表
 * Created by xiaoyun on 2016/8/6.
 */
public class LinkHashTable implements HashTable {

    private int m; // 保存散列表的容量

    private HashNode[] ht; // 定义保存散列表的数组

    private int n; // 散列表中已有元素的个数

    /**
     * 定义散列函数,除留取余数法
     * @param key
     * @return
     */
    private int h(Object key) {
        return (Integer)key%this.m;
    }

    public LinkHashTable(int m) {
        if(m < 13) {
            this.m = 13;
        }else {
            this.m = m;
        }
        n = 0;
        ht = new HashNode[this.m];
    }

    /**
     * 向散列表中插入一个关键字为theKey的元素obj,若插入成功返回真否则返回假
     * @param theKey
     * @param obj
     * @return
     */
    public boolean insert(Object theKey, Object obj) {
        int d = h(theKey);
        HashNode p = ht[d];
        while (p != null) {
            if(p.key.equals(theKey)){
                break;
            }else {
                p = p.next;
            }
        }
        if(p != null) { // 用新值替换旧值,返回false
            p.element = obj;
            return false;
        }else { // 将新结点插入到对应单链表的表头并返回真
            p = new HashNode(theKey, obj);
            p.next = ht[d];
            ht[d] = p;
            n++;
            return true;
        }
    }

    /**
     * 从散列表中查找并返回与给定关键字theKey的元素值,若查找失败则返回假
     * @param theKey
     * @return
     */
    public Object search(Object theKey) {
        int d = h(theKey);
        HashNode p = ht[d];
        while (p != null) {
            if(p.key.equals(theKey)){
                return p.element;
            }
            p = p.next;
        }
        return null;
    }

    public boolean delete(Object theKey) {
        int d = h(theKey);
        HashNode p = ht[d]; // p指向表头结点
        HashNode q = null; // q指向前驱结点
        while (p != null) { // 循环查找被删除的结点
            if(p.key.equals(theKey)){
                break;
            }else {
                q = p;
                p = p.next;
            }
        }
        if(p == null){
            return false;
        }
        if(q == null) {
            ht[d] = p.next;
        }else {
            q.next = p.next;
        }
        n--;
        return true;
    }

    public int size() {
        return this.n;
    }

    public int capacity() {
        return this.m;
    }

    public boolean isEmpty() {
        return this.n == 0;
    }

    public void clear() {
        for (int i = 0; i < m; i++) {
            ht[i] = null;
        }
        n = 0;
    }

    public void output() {
        for (int i = 0; i < m; i++) {
            HashNode p = ht[i];
            while(p != null) {
                System.out.print("(" + p.key +":" + p.element+"),");
                p = p.next;
            }
        }
        System.out.println();
    }
}

4、散列表运算的调试程序
import com.algorithm.hash.HashTable;
import com.algorithm.hash.LinkHashTable;
import com.algorithm.hash.SeqHashTable;

/**
 * 散列表算法测试类
 * Created by admin on 2016/8/6.
 */
public class Example10_3 {
    public static void main(String[] args) {
        // 关键字数组
        int[] a = {18,75,60,43,54,90,46,31,58,73,15,34};
        // 要存放的元素数组
        String[] b = {"180","750","600","430", "540", "900","460","310","580","730","150","340"};
        // 初始化开放地址法处理冲突的散列表
        //HashTable tb = new SeqHashTable(17, -1);
        // 链地址法处理冲突的散列表
        HashTable tb = new LinkHashTable(17);
        for (int i = 0; i < a.length; i++) {
            tb.insert(a[i], b[i]);
        }
        System.out.println("输出散列表中的所有元素:");
        tb.output();
        System.out.println("散列表容量:" + tb.capacity());
        System.out.println("散列表中的元素个数:" + tb.size());
        // 删除元素
        for (int i = 0; i < a.length; i += 3) {
            tb.delete(a[i]);
        }
        // 修改元素
        tb.insert(75, "880");
        System.out.println("经插入、删除、修改后的散列表为:");
        tb.output();
        System.out.println("散列表容量:" + tb.capacity());
        System.out.println("散列表中的元素个数:" + tb.size());
        System.out.println("查找元素的结果:");
        for (int i = 0; i < 4; i++) {
            Object x = tb.search(a[i]);
            if(x == null) {
                System.out.println("key为 " + a[i] + "关键字的元素未找到!");
            }else {
                System.out.println("key为 " + a[i] + "对应的元素值为:" + x);
            }
        }
    }
}

运行结果:
输出散列表中的所有元素:
(34:340),(18:180),(54:540),(73:730),(90:900),(58:580),(75:750),(43:430),(60:600),(46:460),(31:310),(15:150),
散列表容量:17
散列表中的元素个数:12
经插入、删除、修改后的散列表为:
(34:340),(54:540),(90:900),(58:580),(75:880),(60:600),(31:310),(15:150),
散列表容量:17
散列表中的元素个数:8
查找元素的结果:
key为 18关键字的元素未找到!
key为 75对应的元素值为:880
key为 60对应的元素值为:600
key为 43关键字的元素未找到!

以上为使用链接法处理冲突的算法调试结果。
将Example10_3.java的18行注释掉,去掉16行的注释,则为测试开放定址法的数组存储类,执行结果为:
输出散列表中的所有元素:
(34 340),(18 180),(54 540),(90 900),(73 730),(75 750),(58 580),(60 600),(43 430),(46 460),(31 310),(15 150),
散列表容量:17
散列表中的元素个数:12
经插入、删除、修改后的散列表为:
(34 340),(54 540),(90 900),(75 880),(58 580),(60 600),(31 310),(15 150),
散列表容量:17
散列表中的元素个数:8
查找元素的结果:
key为 18关键字的元素未找到!
key为 75对应的元素值为:880
key为 60对应的元素值为:600
key为 43关键字的元素未找到!
[/size]

二分查找
定义:二分查找又称为折半查找,是一种能对有序表进行快速查找的方法,时间复杂度为O(log₂N)。
package com.data.search.binary;

/**
 * 二分查找算法
 * 二分查找定义:二分查找又称为折半查找,首先,二分查找是针对有序表a(这里认为是升序)进行查找,首先取数组中的中间值a[mid],同给定的值x进行比较,如果x小于中间值,那么max=mid-1,min=0,mid=(max-min)/2,
 *               继续比较a[mid]与x的大小,然后依次递归,直到找到对应的元素或者查找区间为空为止。
 * Created by xiaoyun on 2016/8/1.
 */
public class BinarySearch {
    /**
     * 从数组的前n个元素中二分查找值为x的元素
     * @param a
     * @param x
     * @param n
     * @return
     * 时间复杂度:二分查找过程可用一棵二叉树来描述,它的左子树和右子树分别代码对应区间的左子表和右子表,通常把这样的二叉树称为判定树,
     *             进行二分查找的判定树不仅是一棵二叉搜索树,而且是一颗理想二叉树,因为除了最后一层外,其余所有层的结点数都是满的。
     *             所以二分查找的时间复杂度为:O(log₂N),其中N为元素个数。
     * 优点:比较次数少,查找速度快。
     * 缺点:查找前需要建立有序表,这需要付出一定代价,同时对该有序表进行插入和删除操作都需要平均比较和移动表中的一半元素,是浪费时间的操作。
     * 应用场景:适用于顺序存储、并且不经常进行插入和删除的有序表。不适用于链接存储的有序表。
     */
    public static int binarySearch(Object[] a, Object x, int n) {
        int low = 0;
        int high = n - 1;
        while(low <= high) {
            int mid = (low + high)/2;
            // 比较该下标代表的值与目标值
            int result = ((Comparable)a[mid]).compareTo(x);
            if(result == 0) {
                return mid;
            }else if(result > 0) { // 查找左区间
                high = mid  - 1;
            }else { // 查找右区间
                low = mid + 1;
            }
        }
        return -1; // 查找失败返回-1
    }

    public static void main(String[] args) {
        Object[] a = {1,2,3,4,5,6,7,8,9,10,11};
        System.out.println(binarySearch(a, 5, 5));
    }
}

优点:比较次数少,查找速度快。
缺点:查找前需要建立有序表,而频繁的插入和删除平均每次需要移动表中的一半元素,浪费时间。
适用场景:数据稳定,很少进行插入或删除运算的情况。也就是说,二分查找只适用于顺序存储的有序表,不适于连接存储的有序表。
分享到:
评论

相关推荐

    第11讲:深入理解指针(1).pdf

    第11讲:深入理解指针(1)

    springboot整合 freemarker方法

    springboot整合 freemarker方法

    第14讲:深入理解指针(4).pdf

    第14讲:深入理解指针(4)

    同行者4.1.2语音助手

    《同行者4.1.2语音助手:车机版安装详解》 在现代科技日新月异的时代,智能车载设备已经成为了汽车生活的重要组成部分。"同行者4.1.2"便是这样一款专为车机设计的语音助手,旨在提供更为便捷、安全的驾驶体验。该版本针对掌讯全系列设备进行了兼容优化,让车主能够轻松实现语音控制,减少驾驶过程中的手动操作,提升行车安全性。 我们来了解下"同行者4.1.2"的核心功能。这款语音助手集成了智能语音识别技术,用户可以通过简单的语音指令完成导航、音乐播放、电话拨打等一系列操作,有效避免了因操作手机或车机带来的分心。此外,其强大的语义理解和自学习能力,使得它能逐步适应用户的口音和习惯,提供更个性化的服务。 在安装过程中,用户需要注意的是,"同行者4.1.2"包含了四个核心组件,分别是: 1. TXZCore.apk:这是同行者语音助手的基础框架,包含了语音识别和处理的核心算法,是整个应用运行的基础。 2. com.txznet.comm.base.BaseApplication.apk:这个文件可能包含了应用的公共模块和基础服务,为其他组件提供支持。 3. TXZsetting.apk:这

    市场拓展主管绩效考核表.xls

    市场拓展主管绩效考核表

    “线上购车3D全方位体验:汽车模型展示与个性化定制功能”,three.js案例- 线上购车3d展示(源码) 包含内容:1.汽车模型展示;2.汽车肤;3.轮毂部件更;4.开关车门动画;5.汽车尺寸测量

    “线上购车3D全方位体验:汽车模型展示与个性化定制功能”,three.js案例- 线上购车3d展示(源码) 包含内容:1.汽车模型展示;2.汽车肤;3.轮毂部件更;4.开关车门动画;5.汽车尺寸测量;6.自动驾驶;7.镜面倒影;8.hdr运用;9.移动端适配; 本为html+css+three.js源码 ,核心关键词:three.js案例; 线上购车3D展示; 汽车模型展示; 汽车换肤; 轮毂部件更换; 开关车门动画; 汽车尺寸测量; 自动驾驶; 镜面倒影; HDR运用; 移动端适配; HTML+CSS+three.js源码。,"Three.js源码:线上购车3D展示案例,含汽车模型、换肤、轮毂更换等九大功能"

    (数据权威)中国城市_县域统计面板数据二合一

    数据名称:2000-2022年各县市区主要社会经济发展指标面板数据 数据类型:dta格式 数据来源:中国县域统计

    120页-环卫车项目初步方案.pdf

    一、智慧环卫管理平台的建设背景与目标 智慧环卫管理平台的建设源于对环卫管理全面升级的需求。当前,城管局已拥有139辆配备车载GPS系统、摄像头和油耗传感器的环卫车辆,但环卫人员尚未配备智能移动终端,公厕也缺乏信息化系统和智能终端设备。为了提升环卫作业效率、实现精细化管理并节省开支,智慧环卫管理平台应运而生。该平台旨在通过信息化技术和软硬件设备,如车载智能终端和环卫手机App,实时了解环卫人员、车辆的工作状态、信息和历史记录,使环卫作业管理透明化、精细化。同时,平台还期望通过数据模型搭建和数据研读,实现更合理的环卫动态资源配置,为环卫工作的科学、健康、持续发展提供决策支持。 二、智慧环卫管理平台的建设内容与功能 智慧环卫管理平台的建设内容包括运行机制体制建设、业务流程设计、智慧公厕系统建设、网络建设、主机和储存平台需求、平台运维管理体系、硬件标准规范体系以及考核评价体系等多个方面。其中,智慧公厕系统建设尤为关键,它能实时监控公厕运行状态,保障公厕的清洁和正常运行。平台建设还充分利用了现有的电子政务网络资源,并考虑了有线和无线网络的需求。在功能上,平台通过普查、整合等手段全面收集环卫车辆、企业、人员、设施、设备等数据,建立智慧环卫基础数据库。利用智能传感、卫星定位等技术实现环卫作业的在线监管和远程监控,实现对道路、公共场所等的作业状况和卫生状况的全面监管。此外,平台还建立了环卫作业网格化管理责任机制,实现从作业过程到结果的全面监管,科学评价区域、部门、单位和人员的作业效果。 三、智慧环卫管理平台的效益与风险规避 智慧环卫管理平台的建设将带来显著的环境、经济和管理效益。环境方面,它将有力推进环境卫生监管服务工作,改善环境卫生状况,为人民群众创造更加清洁、卫生的工作和生活环境。经济方面,通过智慧化监管,大大降低了传统管理手段的成本,提高了监管的准确性和效率。管理方面,平台能够追踪溯源市民反映的问题,如公厕异味、渣土车辆抛洒等,并找到相应的责任单位进行处置,防止类似事件再次发生。同时,平台还拥有强大的预警机制功能,能够在很多环卫问题尚未出现前进行处置。然而,平台建设也面临一定的风险,如部门协调、配合问题,建设单位选择风险以及不可预测的自然灾害等。为了规避这些风险,需要加强领导、统一思想,选择优秀的系统集成商承接项目建设,并做好计算机和应用系统的培训工作。同时,也要注意标准制定工作和相关法律法规的制定工作,以保证系统建设完成后能够真正为环卫管理工作带来便利。

    36 -企业管理主管绩效考核表1.xlsx

    36 -企业管理主管绩效考核表1

    1.1 -1.4 工程代码

    1.1 -1.4 工程代码

    USDT合约,USDT智能合约

    USDT合约,USDT智能合约

    基于姿态估计三维人脸形状重建.pdf

    基于姿态估计三维人脸形状重建.pdf

    一般员工绩效考核表模板(通用版) (2).xls

    一般员工绩效考核表模板(通用版) (2)

    全国295个地级市2011-2022互联网宽带接入用户数互联网普及率(数据权威)

    全国各省295地级市互联网普及率、互联网用户数、每百人互联网宽带用户(2011-2022年) 数据年份:2011-2022年(2022存在部分缺失) 数据范围:全国各省295个地级市 数据来源:地方统计局

    (数据权威)碳排放、碳中和、碳交易、碳金融、碳计算、碳建模资料

    一、各省、分行业CO2排放、283个地级市碳排放及计算过程 2.分行业二氧化碳排放量 在这里插入图片描述 3、280多个地级市碳排放及计算过程 二、碳中和文献、最新政策、碳金融数据+数学建模 1.二氧化碳减排规划,碳金融数据收集及数学建模 2.碳中和政策和下载量最高的碳中和论文 三、碳排放+碳市场+碳交易+碳中和+碳排放核算Excel自动计算表 全行业碳排放核算Excel自动计算表 四、碳交易数据 五、主要能源碳排放计算参数

    第20讲:自定义类型:结构体.pdf

    第20讲:自定义类型:结构体

    视觉跟踪算法综述.pdf

    视觉跟踪算法综述.pdf

    MATLAB超效率SBM-DEA模型代码详解:简易操作指南及期望与非期望产出的超效率分析,附Malmquist指数与分解功能,MATLAB的超效率SBM-DEA模型代码(有安装教程和内容讲解之类的东西

    MATLAB超效率SBM-DEA模型代码详解:简易操作指南及期望与非期望产出的超效率分析,附Malmquist指数与分解功能,MATLAB的超效率SBM-DEA模型代码(有安装教程和内容讲解之类的东西),操作很简单 可以做期望产出和非期望产出的超效率和非超效率sbm模型和Malmquist指数和分解 ,MATLAB; SBM-DEA模型; 超效率SBM-DEA; 安装教程; 内容讲解; 期望产出; 非期望产出; 超效率与非超效率sbm模型; Malmquist指数; 分解。,"MATLAB超效SBM-DEA模型代码:非期望产出分析的便捷工具"

    人事行政主管绩效考核评分表.xls

    人事行政主管绩效考核评分表

    人力资源管理工具绩效考核excel模板.xlsx

    人力资源管理工具绩效考核excel模板

Global site tag (gtag.js) - Google Analytics