当前位置: 代码网 > it编程>编程语言>C/C++ > Map和Set(哈希表)

Map和Set(哈希表)

2024年08月01日 C/C++ 我要评论
介绍了map和set的常用的方法和一些注意点并进行代码运行演示,并引出了哈希表的概念,冲突,避免和解决的两个方法闭散列和开散列(哈希桶)。

目录

map:

map说明:

map.entry的说明:,v>

map 的常用方法:

演示:

注意:

treemap和hashmap的区别

 set:

常见方法说明:

注意:

treeset和hashset的区别 

哈希表:

冲突:

冲突-避免:

冲突-避免-负载因子调节:

冲突-解决:

冲突-解决-闭散列:

冲突-解决-开散列/哈希桶:

结语:


map:

map说明:

map是一个接口类,该类没有继承自collection,该类中存储的是结构的键值对,并且k一定是唯一的,不能重复。

map.entry<k,v>的说明:

map.entry<k,v>是map内部实现的用来存放键值对映射关系的内部类,该内部类中主要提供了<key,value> 的获取,value的设置以及key的比较方式。

方法解释
k getkey()返回 entry 中的 key
v getvalue()返回 entry 中的 value
v setvalue(v value)将键值对中的value替换为指定value

注意:map.entry并没有提供设置key的方法.

map 的常用方法:

如下图所示:

map底层可以用hashmap和treemap实现,由于hashmap的效率比较高故下面我用hashmap来进行演示。

演示:

import java.util.map;
import java.util.hashmap;
import java.util.set;
public class hashmap {
    public static void main(string[] args) {
        map<string,integer> map = new hashmap<>();
        map.put("aaa",3);
        map.put("bbb",3);
        system.out.println(map.get("aaa"));
        set<map.entry<string,integer>> set = map.entryset();
        for(map.entry<string,integer> entry:set){
            system.out.println("key :"+entry.getkey() + " value :" + entry.getvalue());
        }
    }
}

entryset是比较重要的故进行演示。

效果如下:

这里采用foreach进行遍历,可以不用直到set的长度。

注意:

treemap和hashmap的区别

 set:

set与map主要的不同有两点:set是继承自collection的接口类,set中只存储了key。

常见方法说明:

方法解释
boolean add(e e)添加元素,但重复元素不会被添加成功
void clear()清空集合
boolean contains(object o)判断 o 是否在集合中
iterator iterator()返回迭代器
boolean remove(object o)删除集合中的 o
int size()返回set中元素的个数
boolean isempty()检测set是否为空,空返回true,否则返回false
object[] toarray()将set中的元素转换为数组返回
boolean containsall(collection c)集合c中的元素是否在set中全部存在,是返回true,否则返回 false
boolean addall(collection c)将集合c中的元素添加到set中,可以达到去重的效果

演示:

public class test1 {
    public static void main(string[] args) {
        set<string> set = new hashset<>();
        set.add("aaa");
        set.add("bbb");
        system.out.println(set.size());
        system.out.println(set.isempty());
        set.clear();
    }
}

效果如下:

注意:

treeset和hashset的区别 

哈希表:

概念:

顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素时,必须要经过关键 码的多次比较。顺序查找时间复杂度为o(n),平衡树中为树的高度,即o(logn ),搜索的效率取决于搜索过程中 元素的比较次数。

理想的搜索方法:可以不经过任何比较,一次直接从表中得到要搜索的元素。 如果构造一种存储结构,通过某种函 数(hashfunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快 找到该元素。

插入元素:

根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放。

搜索元素:

对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比较,若关键码相等,则搜索成功。

该方式即为哈希(散列)方法,哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称为哈希表(hash table)(或者称散列表)

冲突:

对于两个数据元素的关键字和 (i != j),有 != ,但有:hash( ) == hash( ),即:不同关键字通过相同哈 希哈数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞。 把具有不同关键码而具有相同哈希地址的数据元素称为“同义词”。

例如:

下图的4和7就是发生了冲突。

冲突-避免:

 首先,我们需要明确一点,由于我们哈希表底层数组的容量往往是小于实际要存储的关键字的数量的,这就导致一 个问题,冲突的发生是必然的,但我们能做的应该是尽量的降低冲突率。

函数设计:

常见哈希函数:

冲突-避免-负载因子调节:

已知哈希表中已有的关键字个数是不可变的,那我们能调整的就只有哈希表中的数组的大小。

冲突-解决:

解决哈希冲突两种常见的方法是:闭散列和开散列。

冲突-解决-闭散列:

闭散列:也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以 把key存放到冲突位置中的“下一个” 空位置中去。

寻找方法:

(1)线性探测

线性探测:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止。

注意:这里的删除都是伪删除。

(2) 二次探测:

找下一个空位置的方法为: = ( + )% m, 或者: = ( - )% m。其中:i = 1,2,3…, 是通过散列函数hash(x)对元素的关键码 key 进行计算得到的位置, m是表的大小。

冲突-解决-开散列/哈希桶:

开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子 集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。

简单来说就是数组加链表。

如图:

开散列,可以认为是把一个在大集合中的搜索问题转化为在小集合中做搜索了。

源代码的实现:


public class hashbucket {
    static class node{
        private int key;
        private int value;
        node next;
        public node(int key,int value){
            this.key = key;
            this.value = value;
        }
    }
    private node[] array;
    private int size;
    public hashbucket(){
        array = new node[8];
        size = 0;
    }
    private static final double load_factor = 0.75;
    public int put(int key,int value){
        int index = key % array.length;
        node cur = array[index];
        for(;cur != null; cur = cur.next){
            if(cur.key == key){
                int oldvalue = cur.value;
                cur.value = value;
                return oldvalue;
            }
        }
        node node = new node(key,value);
        node.next = array[index];
        array[index] = node;
        size++;
        if(loadfactor() >= load_factor){
            resize();
        }
        return -1;
    }
    //重新哈希
    private void resize(){
        node[] newarray = new node[array.length * 2];
        for(int i = 0;i < array.length; i++){
            node next;
            for(node cur = array[i]; cur != null; cur = next){
                next = cur.next;
                int index = cur.key % newarray.length;
                cur.next = newarray[index];
                newarray[index] = cur;
            }
        }
        array = newarray;
    }
    private double loadfactor(){
        return size * 1.0 / array.length;
    }
    public int get(int key){
        int index = key % array.length;
        for(node cur = array[index]; cur != null; cur = cur.next){
            if(key == cur.key){
                return cur.value;
            }
        }
        return -1;
    }

}

性能分析:

虽然哈希表一直在和冲突做斗争,但在实际使用过程中,我们认为哈希表的冲突率是不高的,冲突个数是可控的, 也就是每个桶中的链表的长度是一个常数,所以,通常意义下,我们认为哈希表的插入/删除/查找时间复杂度是 o(1) 。

结语:

其实写博客不仅仅是为了教大家,同时这也有利于我巩固自己的知识点,和一个学习的总结,由于作者水平有限,对文章有任何问题的还请指出,接受大家的批评,让我改进,如果大家有所收获的话还请不要吝啬你们的点赞收藏和关注,这可以激励我写出更加优秀的文章。

                                                 

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com