C/C++小知识(35)

100 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第4天,点击查看活动详情

  1. 冲突的解决

    ​ 如果产生了Hash冲突,就需要办法来解决,通常有如下两种方法:

    开放定址法

    ​ 即当一个关键字和另一个关键字发生冲突时,使用某种探测技术在Hash表中形成一个探测序列,然后沿着这个探测序列依次查找下去,当碰到一个空的单元时,则插入其中。比较常用的探测方法有线性探测法,比如有一组关键字 {12,13,25,23,38,34,6,84,91},Hash表长为14,Hash函数为address(key)=key%11 ,当插入12,13,25时可以直接插入,而当插入23时,地址1被占用了,因此沿着地址1依次往下探测(探测步长可以根据情况而定),直到探测到地址4,发现为空,则将23插入其中。

    链地址法

    ​ 采用数组和链表相结合的办法,将Hash地址相同的记录存储在一张线性表中,而每张表的表头的序号即为计算得到的Hash地址。

  2. 需注意的点

    (1)Hashtable的默认容量为11,默认负载因子为0.75(HashMap默认容量为16,默认负载因子也是0.75);

    (2)Hashtable的容量可以为任意整数,最小值为1,而HashMap的容量始终为2的n次方;

    (3)为避免扩容带来的性能问题,建议指定合理容量;

    (4)跟HashMap一样,Hashtable内部也有一个静态类叫Entry,其实是个键值对对象,保存了键和值的引用;

    (5)HashMap和Hashtable存储的是键值对对象,而不是单独的键或值。

  3. 哈希表的构造函数

    public Hashtable(int initialCapacity, float loadFactor) {//可指定初始容量和加载因子  
            if (initialCapacity < 0)  
                throw new IllegalArgumentException("Illegal Capacity: "+  
                                                   initialCapacity);  
            if (loadFactor <= 0 || Float.isNaN(loadFactor))  
                throw new IllegalArgumentException("Illegal Load: "+loadFactor);  
            if (initialCapacity==0)  
                initialCapacity = 1;//初始容量最小值为1  
            this.loadFactor = loadFactor;  
            table = new Entry[initialCapacity];//创建桶数组  
            threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);//初始化容量阈值  
            useAltHashing = sun.misc.VM.isBooted() &&  
                    (initialCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);  
        }  
        /** 
         * Constructs a new, empty hashtable with the specified initial capacity 
         * and default load factor (0.75). 
         */  
        public Hashtable(int initialCapacity) {  
            this(initialCapacity, 0.75f);//默认负载因子为0.75  
        }  
        public Hashtable() {  
            this(11, 0.75f);//默认容量为11,负载因子为0.75  
        }  
        /** 
         * Constructs a new hashtable with the same mappings as the given 
         * Map.  The hashtable is created with an initial capacity sufficient to 
         * hold the mappings in the given Map and a default load factor (0.75). 
         */  
        public Hashtable(Map<? extends K, ? extends V> t) {  
            this(Math.max(2*t.size(), 11), 0.75f);  
            putAll(t);  
        }
    

1.4.18 说说 push_back 和 emplace_back 的区别

参考回答

​ 如果要将一个临时变量push到容器的末尾,push_back()需要先构造临时对象,再将这个对象拷贝到容器的末尾,而emplace_back()则直接在容器的末尾构造对象,这样就省去了拷贝的过程。

答案解析

​ 参考代码:

#include <iostream>
#include <cstring>
#include <vector>
using namespace std;

class A {
public:
    A(int i){
        str = to_string(i);
        cout << "构造函数" << endl; 
    }
    ~A(){}
    A(const A& other): str(other.str){
        cout << "拷贝构造" << endl;
    }

public:
    string str;
};

int main()
{
    vector<A> vec;
    vec.reserve(10);
    for(int i=0;i<10;i++){
        vec.push_back(A(i)); //调用了10次构造函数和10次拷贝构造函数,
//        vec.emplace_back(i);  //调用了10次构造函数一次拷贝构造函数都没有调用过
    }

1.4.19 STL 中 vector 与 list 具体是怎么实现的?常见操作的时间复杂度是多少?

参考回答

  1. vector 一维数组(元素在内存连续存放)

    ​ 是动态数组,在堆中分配内存,元素连续存放,有保留内存,如果减少大小后,内存也不会释放;如果新增大小当前大小时才会重新分配内存。

    ​ 扩容方式: a. 倍放开辟三倍的内存

    ​ b. 旧的数据开辟到新的内存

    ​ c. 释放旧的内存

    ​ d. 指向新内存

  2. list 双向链表(元素存放在堆中)

    ​ 元素存放在堆中,每个元素都是放在一块内存中,它的内存空间可以是不连续的,通过指针来进行数据的访问,这个特点,使得它的随机存取变得非常没有效率,因此它没有提供[ ]操作符的重载。但是由于链表的特点,它可以很有效的支持任意地方的删除和插入操作。

    ​ 特点:a. 随机访问不方便

    ​ b. 删除插入操作方便

  3. 常见时间复杂度

    (1)vector插入、查找、删除时间复杂度分别为:O(n)、O(1)、O(n);

    (2)list插入、查找、删除时间复杂度分别为:O(1)、O(n)、O(1)。