<C++> 哈希表模拟实现STL_unordered_set/map

news2024/11/29 8:48:14

哈希表模板参数的控制

首先需要明确的是,unordered_set是K模型的容器,而unordered_map是KV模型的容器。

要想只用一份哈希表代码同时封装出K模型和KV模型的容器,我们必定要对哈希表的模板参数进行控制。

为了与原哈希表的模板参数进行区分,这里将哈希表的第二个模板参数的名字改为T。

template<class K, class T>
class HashTable

如果上层使用的是unordered_set容器,那么传入哈希表的模板参数就是key和key。

template<class K>
class unordered_set {
public:
    //...
private:
    HashTable<K, K> _ht;//传入底层哈希表的是K和K
};

但如果上层使用的是unordered_map容器,那么传入哈希表的模板参数就是key以及key和value构成的键值对。

template<class K, class V>
class unordered_map {
public:
    //...
private:
    HashTable<K, pair<K, V>> _ht;//传入底层哈希表的是K以及K和V构成的键值对
};

也就是说,哈希表中的模板参数T的类型到底是什么,完全却决于上层所使用容器的种类。

在这里插入图片描述

而哈希结点的模板参数也应该由原来的K、V变为T:

  • 上层容器是unordered_set时,传入的T是键值,哈希结点中存储的就是键值。
  • 上层容器是unordered_map时,传入的T是键值对,哈希结点中存储的就是键值对。

更改模板参数后,哈希结点的定义如下:

template<class T>
struct HashNode {
    HashNode<T> *_next;
    T _data;

    HashNode(const T &data)
        : _data(data), _next(nullptr) {}
};

在哈希映射过程中,我们需要获得元素的键值,然后通过哈希函数计算出对应的哈希地址进行映射。

现在由于我们在哈希结点当中存储的数据类型是T,这个T可能就是一个键值,也可能是一个键值对,对于底层的哈希表来说,它并不知道哈希结点当中存储的数据究竟是什么类型,因此需要由上层容器提供一个仿函数,用于获取T类型数据当中的键值。

因此,unordered_map容器需要向底层哈希表提供一个仿函数,该仿函数返回键值对当中的键值。

template<class K, class V>
class unordered_map {
public:
    struct MapKeyOft {
        const K &operator()(const pair<K, V> &kv) {
            return kv.first;
        }
    };

private:
    HashTable<K, pair<const K, V>, MapKeyOft> _ht;
};

而虽然unordered_set容器传入哈希表的T就是键值,但是底层哈希表并不知道上层容器的种类,底层哈希表在获取键值时会统一通过传入的仿函数进行获取,因此unordered_set容器也需要向底层哈希表提供一个仿函数。

template<class K>
class unordered_set {
public:
    struct SetKeyOfT {
        const K &operator()(const K &key) {
            return key;
        }
    };

private:
    HashTable<K, K, SetKeyOfT> _ht;
};

因此,底层哈希表的模板参数现在需要增加一个,用于接收上层容器提供的仿函数。

template<class K, class T, class KeyOfT>
class HashTable

string类型无法取模问题

经过上面的分析后,我们让哈希表增加了一个模板参数,此时无论上层容器是unordered_set还是unordered_map,我们都能够通过上层容器提供的仿函数获取到元素的键值。

但是在我们日常编写的代码中,用字符串去做键值key是非常常见的事,比如我们用unordered_map容器统计水果出现的次数时,就需要用各个水果的名字作为键值。

而字符串并不是整型,也就意味着字符串不能直接用于计算哈希地址,我们需要通过某种方法将字符串转换成整型后,才能代入哈希函数计算哈希地址。

但遗憾的是,我们无法找到一种能实现字符串和整型之间一对一转换的方法,因为在计算机中,整型的大小是有限的,比如用无符号整型能存储的最大数字是4294967295,而众多字符能构成的字符串的种类却是无限的。

鉴于此,无论我们用什么方法将字符串转换成整型,都会存在哈希冲突,只是产生冲突的概率不同而已。

因此,现在我们需要在哈希表的模板参数中再增加一个仿函数,用于将键值key转换成对应的整型。

template<class K, class T, class KeyOfT, class HashFunc = Hash<K>>
class HashTable

若是上层没有传入该仿函数,我们则使用默认的仿函数,该默认仿函数直接返回键值key即可,但是用字符串作为键值key是比较常见的,因此我们可以针对string类型写一个类模板的特化

template<class K>
struct HashFunc {
    size_t operator()(const K &key) {
        return key;
    }
};

// 特化模板,传string的话,就走这个
template<>
struct HashFunc<string> {
    size_t operator()(const string &s) {
        size_t hash = 0;
        for (auto ch: s) {
            hash += ch;
            hash *= 31;
        }
        return hash;
    }
};

哈希表正向迭代器的实现

哈希表的正向迭代器实际上就是对哈希结点指针进行了封装,但是由于在实现++运算符重载时,可能需要在哈希表中去寻找下一个非空哈希桶,因此每一个正向迭代器中都应该存储哈希表的地址。

代码

//前置声明
template<class K, class T, class KeyOft, class Hash = HashFunc<K>>
class HashTable;

template<class K, class T, class Ref, class Ptr, class KeyOft, class Hash>
struct HashIterator {
    typedef HashNode<T> Node;
    typedef HashTable<K, T, KeyOft, Hash> HT;
    //Ref和Ptr可能是T&和T*,也可能是const T&/const T*,需要创建一个支持普通转换为const的迭代器
    typedef HashIterator<K, T, Ref, Ptr, KeyOft, Hash> Self;
    typedef HashIterator<K, T, T &, T *, KeyOft, Hash> iterator;//正向迭代器

    HashIterator(Node *node, HT *ht)
        : _node(node), _ht(ht) {}

    //正向迭代器实现反向迭代器,不能只靠self,如果self传的就是const迭代器,再加上const就有问题了
    HashIterator(const iterator &it)
        : _node(it._node), _ht(it._ht) {}

    Ref operator*() {
        return _node->_data;
    }

    Ptr operator->() {
        return &_node->_data;
    }

    bool operator!=(const Self &s) {
        return _node != s._node;
    }

    bool operator==(const Self &s) {
        return _node == s._node;
    }

    Self &operator++() {
        if (_node->_next != nullptr) {
            _node = _node->_next;
        } else {
            //找下一个不为空的桶
            KeyOft kot;
            Hash hash;
            // 算出我当前的桶位置
            size_t hashi = hash(kot(_node->_data)) % _ht->_tables.size();
            ++hashi;
            while (hashi < _ht->_tables.size()) {
                if (_ht->_tables[hashi] != nullptr) {
                    _node = _ht->_tables[hashi];
                    break;
                } else {
                    ++hashi;
                }
            }

            //没有找到的话,返回_node为空
            if (hashi == _ht->_tables.size()) {
                _node = nullptr;
            }
            return *this;
        }
        return *this;
    }
    Node *_node;//迭代器指针
    HT *_ht;    //哈希表,用于定位下一个桶
};

注意: 哈希表的迭代器类型是单向迭代器,没有反向迭代器,即没有实现–运算符的重载,若是想让哈希表支持双向遍历,可以考虑将哈希桶中存储的单链表结构换为双链表结构。

正向迭代器实现后,我们需要在哈希表的实现当中进行如下操作:

  1. 进行正向迭代器类型的typedef,需要注意的是,为了让外部能够使用typedef后的正向迭代器类型iterator,我们需要在public区域进行typedef。
  2. 由于正向迭代器中++运算符重载函数在寻找下一个结点时,会访问哈希表中的成员变量_table,而_table成员变量是哈希表的私有成员,因此我们需要将正向迭代器类声明为哈希表类的友元。
  3. 将哈希表中查找函数返回的结点指针,改为返回由结点指针和哈希表地址构成的正向迭代器。
  4. 将哈希表中插入函数的返回值类型,改为由正向迭代器类型和布尔类型所构成的键值对。

完整的HashTable

#pragma once
#include <cstdlib>
#include <ctime>
#include <iostream>
#include <utility>
#include <vector>
using namespace std;

template<class K>
struct HashFunc {
    size_t operator()(const K &key) {
        return key;
    }
};

// 特化模板,传string的话,就走这个
template<>
struct HashFunc<string> {
    size_t operator()(const string &s) {
        size_t hash = 0;
        for (auto ch: s) {
            hash += ch;
            hash *= 31;
        }
        return hash;
    }
};

template<class T>
struct HashNode {
    HashNode<T> *_next;
    T _data;

    HashNode(const T &data)
        : _data(data), _next(nullptr) {}
};


//前置声明
template<class K, class T, class KeyOft, class Hash = HashFunc<K>>
class HashTable;

template<class K, class T, class Ref, class Ptr, class KeyOft, class Hash>
struct HashIterator {
    typedef HashNode<T> Node;
    typedef HashTable<K, T, KeyOft, Hash> HT;
    //Ref和Ptr可能是T&和T*,也可能是const T&/const T*,需要创建一个支持普通转换为const的迭代器
    typedef HashIterator<K, T, Ref, Ptr, KeyOft, Hash> Self;
    typedef HashIterator<K, T, T &, T *, KeyOft, Hash> iterator;//正向迭代器

    HashIterator(Node *node, HT *ht)
        : _node(node), _ht(ht) {}

    //正向迭代器实现反向迭代器,不能只靠self,如果self传的就是const迭代器,再加上const就有问题了
    HashIterator(const iterator &it)
        : _node(it._node), _ht(it._ht) {}

    Ref operator*() {
        return _node->_data;
    }

    Ptr operator->() {
        return &_node->_data;
    }

    bool operator!=(const Self &s) {
        return _node != s._node;
    }

    bool operator==(const Self &s) {
        return _node == s._node;
    }

    Self &operator++() {
        if (_node->_next != nullptr) {
            _node = _node->_next;
        } else {
            //找下一个不为空的桶
            KeyOft kot;
            Hash hash;
            // 算出我当前的桶位置
            size_t hashi = hash(kot(_node->_data)) % _ht->_tables.size();
            ++hashi;
            while (hashi < _ht->_tables.size()) {
                if (_ht->_tables[hashi] != nullptr) {
                    _node = _ht->_tables[hashi];
                    break;
                } else {
                    ++hashi;
                }
            }

            //没有找到的话,返回_node为空
            if (hashi == _ht->_tables.size()) {
                _node = nullptr;
            }
            return *this;
        }
        return *this;
    }
    Node *_node;//迭代器指针
    HT *_ht;    //哈希表,用于定位下一个桶
};

template<class K, class T, class KeyOft, class Hash>// Hash用于将key转换成可以取模的类型
class HashTable {
public:
    typedef HashNode<T> Node;
    typedef HashIterator<K, T, T &, T *, KeyOft, Hash> iterator;
    typedef HashIterator<K, T, const T &, const T *, KeyOft, Hash> const_iterator;

    template<class K1, class T1, class Ref1, class Ptr1, class KeyOft1, class Hash1>
    friend struct HashIterator;//用于迭代器访问HashTable中的private成员变量,即_tables、

public:
    ~HashTable() {
        for (auto &cur: this->_tables) {
            while (cur) {
                Node *next = cur->_next;
                delete cur;
                cur = next;
            }
            cur = nullptr;
        }
    }

    iterator begin() {
        Node *cur = nullptr;
        for (size_t i = 0; i < _tables.size(); i++) {
            cur = _tables[i];
            if (cur != nullptr) {
                break;
            }
        }
        return iterator(cur, this);
    }

    iterator end() {
        return iterator(nullptr, this);
    }

    const_iterator begin() const {
        Node *cur = nullptr;
        for (size_t i = 0; i < _tables.size(); i++) {
            cur = _tables[i];
            if (cur != nullptr) {
                break;
            }
        }
        return const_iterator(cur, this);
    }

    const_iterator end() const {
        return const_iterator(nullptr, this);
    }

    //查找Key也是K类型
    iterator Find(const K &key) {
        if (this->_tables.size() == 0) {
            return iterator(nullptr, this);
        }

        KeyOft kot;//模板参数,用来区分是kv,还是v由上层map、set传模板参数过来(通过仿函数实现)
        Hash hash;
        size_t hashi = hash(key) % this->_tables.size();
        Node *cur = this->_tables[hashi];
        while (cur) {
            if (kot(cur->_data) == key) {
                return iterator(cur, this);
            }
            cur = cur->_next;
        }
        return iterator(nullptr, this);
    }

    //删除的值key为K类型
    bool Erase(const K &key) {
        Hash hash;
        KeyOft kot;
        size_t hashi = hash(key) % this->_tables.size();
        Node *prev = nullptr;
        Node *cur = this->_tables[hashi];
        while (cur) {
            if (kot(cur->_data) == key) {
                if (prev == nullptr) {
                    this->_tables[hashi] = cur->_next;
                } else {
                    prev->_next = cur->_next;
                }
                delete cur;
                return true;
            } else {
                prev = cur;
                cur = cur->_next;
            }
        }
        return false;
    }

    // 扩容优化,使用素数扩容
    size_t GetNextPrime(size_t prime) {
        // SGI
        static const int _stl_num_primes = 28;
        static const uint64_t _stl_prime_list[_stl_num_primes] = {
                53, 97, 193, 389, 769, 1543,
                3079, 6151, 12289, 24593, 49157, 98317,
                196613, 393241, 786433, 1572869, 3145739, 6291469,
                12582917, 25165843, 50331653, 100663319, 201326611, 402653189,
                805306457, 1610612741, 3221225473, 4294967291};

        size_t i = 0;
        for (; i < _stl_num_primes; ++i) {
            if (_stl_prime_list[i] > prime)
                return _stl_prime_list[i];
        }

        return _stl_prime_list[_stl_num_primes - 1];
    }

    //插入的类型是T类型,可能是K可能是pair<K,V> 通过模板参数传过来
    pair<iterator, bool> Insert(const T &data) {
        Hash hash;// 仿函数用于不能取模的值
        KeyOft kot;
        // 已经有这个数,就不用插入了
        iterator it = Find(kot(data));
        //如果it不是end(),说明找到了数,就不用插入,返回迭代器和false
        if (it != end()) {
            return make_pair(it, false);
        }


        // 负载因子 == 1时扩容
        if (this->n == this->_tables.size()) {
            // size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;
            size_t newsize = this->GetNextPrime(_tables.size());
            vector<Node *> newtables(newsize, nullptr);
            for (auto &cur: this->_tables) {// cur是Node*
                while (cur) {
                    // 保存下一个
                    Node *next = cur->_next;
                    // 头插到新表
                    size_t hashi = hash(kot(cur->_data)) % newtables.size();
                    cur->_next = newtables[hashi];
                    newtables[hashi] = cur;

                    cur = next;
                }
            }
            _tables.swap(newtables);
        }

        size_t hashi = hash(kot(data)) % this->_tables.size();
        // 头插
        Node *newnode = new Node(data);
        newnode->_next = _tables[hashi];
        _tables[hashi] = newnode;
        this->n++;

        //插入成功返回,通过newnode,和this构造迭代器,返回true。
        return make_pair(iterator(newnode, this), true);
    }

    // 获取哈希表索引最大长度(哈希桶长度)
    size_t MaxBucketSize() {
        size_t max = 0;
        for (int i = 0; i < _tables.size(); ++i) {
            auto cur = _tables[i];
            size_t size = 0;
            while (cur) {
                ++size;
                cur = cur->_next;
            }

            printf("[%d]->%d\n", i, size);

            if (size > max) {
                max = size;
            }
            if (max == 5121) {
                printf("%d", i);
                break;
            }
        }

        return max;
    }

private:
    vector<Node *> _tables;
    size_t n = 0;// 存储有效数据的个数
};

封装unordered_set的代码

#pragma once
#include "HashTable.h"


template<class K, class Hash = HashFunc<K>>
class unordered_set {
public:
    struct SetKeyOfT {
        const K &operator()(const K &key) {
            return key;
        }
    };

public:
    typedef typename HashTable<K, K, SetKeyOfT, Hash>::const_iterator iterator;
    typedef typename HashTable<K, K, SetKeyOfT, Hash>::const_iterator const_iterator;


    iterator begin() {
        return _ht.begin();
    }

    iterator end() {
        return _ht.end();
    }

    const_iterator begin() const {
        return _ht.begin();
    }

    const_iterator end() const {
        return _ht.end();
    }

    //这里的pair<iterator,bool>中的iterator是const类型的,而Insert返回的是普通迭代器
    pair<iterator, bool> insert(const K &key) {
        return _ht.Insert(key);
    }

    iterator find(const K &key) {
        return _ht.Find(key);
    }

    bool erase(const K &key) {
        return _ht.Erase(key);
    }

private:
    HashTable<K, K, SetKeyOfT, Hash> _ht;
};

封装unordered_map的代码

#pragma once

#include "HashTable.h"
template<class K, class V, class Hash = HashFunc<K>>
class unordered_map {
public:
    struct MapKeyOft {
        const K &operator()(const pair<K, V> &kv) {
            return kv.first;
        }
    };

    //typename 告诉编译器引入的是一个类型,而不是成员
    typedef typename HashTable<K, pair<const K, V>, MapKeyOft, Hash>::iterator iterator;
    typedef typename HashTable<K, pair<const K, V>, MapKeyOft, Hash>::const_iterator const_iterator;

    iterator begin() {
        return _ht.begin();
    }

    iterator end() {
        return _ht.end();
    }

    const_iterator begin() const {
        return _ht.begin();
    }

    const_iterator end() const {
        return _ht.end();
    }

    pair<iterator, bool> insert(const pair<K, V> kv) {
        return _ht.Insert(kv);
    }

    V &operator[](const K &key) {
        pair<iterator, bool> ret = insert(make_pair(key, V()));
        return ret.first->second;
    }

    iterator find(const K &key) {
        return _ht.Find(key);
    }

    bool erase(const K &key) {
        return _ht.Erase(key);
    }

private:
    HashTable<K, pair<const K, V>, MapKeyOft, Hash> _ht;
};

测试

#include "unordered_map.h"
#include "unordered_set.h"
#include <iostream>
class Date {
    friend struct HashDate;

public:
    Date(int year = 1900, int month = 1, int day = 1)
        : _year(year), _month(month), _day(day) {}

    bool operator<(const Date &d) const {
        return (_year < d._year) ||
               (_year == d._year && _month < d._month) ||
               (_year == d._year && _month == d._month && _day < d._day);
    }

    bool operator>(const Date &d) const {
        return (_year > d._year) ||
               (_year == d._year && _month > d._month) ||
               (_year == d._year && _month == d._month && _day > d._day);
    }

    bool operator==(const Date &d) const {
        return _year == d._year && _month == d._month && _day == d._day;
    }

    friend ostream &operator<<(ostream &_cout, const Date &d);

private:
    int _year;
    int _month;
    int _day;
};

ostream &operator<<(ostream &_cout, const Date &d) {
    _cout << d._year << "-" << d._month << "-" << d._day;
    return _cout;
}

//自定义Hash,模板最后一个参数,传自定义类型的话,需要自己写
struct HashDate {
    size_t operator()(const Date &d) {
        size_t hash = 0;
        hash += d._year;
        hash *= 31;

        hash += d._month;
        hash *= 31;

        hash += d._day;
        hash *= 31;

        return hash;
    }
};

struct unordered_map_Test {
    static void unordered_map_Test1() {
        unordered_map<int, int> mp;
        mp.insert(make_pair(1, 1));
        mp.insert(make_pair(2, 2));
        mp.insert(make_pair(3, 3));

        unordered_map<int, int>::iterator it = mp.begin();
        while (it != mp.end()) {
            cout << it->first << " " << it->second << endl;
            ++it;
        }
        cout << endl;
    }

    static void unordered_map_Test2() {
        string arr[] = {"西瓜", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉", "梨"};
        unordered_map<string, int> countMap;
        for (auto &e: arr) {
            countMap[e]++;
        }

        for (auto &kv: countMap) {
            cout << kv.first << "" << kv.second << endl;
        }
    }

    static void unordered_map_Test3() {
        Date d1(2023, 3, 13);
        Date d2(2023, 3, 13);
        Date d3(2023, 3, 12);
        Date d4(2023, 3, 11);
        Date d5(2023, 3, 12);
        Date d6(2023, 3, 13);

        Date a[] = {d1, d2, d3, d4, d5, d6};

        unordered_map<Date, int, HashDate> countMap;
        for (auto e: a) {
            countMap[e]++;
        }

        for (auto &kv: countMap) {
            cout << kv.first << ":" << kv.second << endl;
        }
    }
};

struct unordered_set_Test {
    static void unordered_set_Test1() {
        unordered_set<int> s;
        s.insert(1);
        s.insert(3);
        s.insert(2);
        s.insert(7);
        s.insert(8);

        unordered_set<int>::iterator it = s.begin();
        while (it != s.end()) {
            cout << *it << " ";
            //(*it) = 1;
            ++it;
        }
        cout << endl;
    }
};

int main() {
    unordered_set_Test::unordered_set_Test1();
    unordered_map_Test::unordered_map_Test1();
    unordered_map_Test::unordered_map_Test2();
    unordered_map_Test::unordered_map_Test3();
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1055291.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

IntelliJ IDEA配置Cplex12.6.3详细步骤

Cplex12.6.3版IntelliJ IDEA配置详细步骤 一、Cplex12.6.3版下载地址二、Cplex安装步骤三、IDEA配置CPLEX3.1 添加CPLEX安装目录的cplex.jar包到项目文件中3.2 将CPLEX的x64_win64文件夹添加到IDEA的VM options中 四、检查IDEA中Cplex是否安装成功卸载Cplex 一、Cplex12.6.3版下…

案例题真题-数据库系统

案例题真题-数据库系统 真题 扩展方式和水平扩展是一个东西 关系数据库向上扩展强&#xff0c;水平扩展弱&#xff08;向外扩展&#xff09; NoSQL模式向上扩展弱&#xff0c;水平扩展强&#xff08;向外扩展&#xff09; 向上扩展就是增加数据库 向外扩展就是将数据库划分开&a…

SmartX 边缘计算解决方案:简单稳定,支持各类应用负载

在《一文了解近端边缘 IT 基础架构技术需求》文章中&#xff0c;我们为大家分析了边缘应用对 IT 基础架构的技术要求&#xff0c;以及为什么超融合架构是支持边缘场景的最佳选择。值得一提的是&#xff0c;IDC 近日发布的《中国软件定义存储&#xff08;SDS&#xff09;及超融合…

Halcon中灰度直方图的使用与学习

目录 第一步:当前打开窗口的显示灰度图或者mono图片第二步:激活后,我们可以去调整调整右边直方图灰阶值的中蓝色和红色竖线,获取左边图上的灰阶值的范围内的特征显示。第三步:插入代码:总结:它的直观目的,就是查看灰度的分布情况!灰度直方图,是我们经常使用,抓取不同…

objective-c 基础学习

目录 第一节&#xff1a;OC 介绍 ​​第二节&#xff1a;Fundation 框架 ​第三节&#xff1a;NSLog 相对于print 的增强 ​第四节&#xff1a;NSString ​第五节&#xff1a;oc新增数据类型 第六节&#xff1a; 类和对象 ​类的方法的声明与实现 ​第七节&#xff1a;类…

思维模型 冷热水效应

本系列文章 主要是 分享 思维模型&#xff0c;涉及各个领域&#xff0c;重在提升认知。冷热水效应可以改变你90%的人际关系。对于技术人员的沟通大有助益。 1 冷热水效应的应用 1.1 生活中的冷热水效应 恋爱和恋爱关系&#xff1a;在恋爱关系中&#xff0c;一对情侣可能会经历…

【人工智能导论】机器学习环境配置

一、Anaconda 1、安装Anaconda Anaconda的安装&#xff1a;https://www.anaconda.com/ Anaconda-国内镜像站 在terminal上看到(base)说明安装已完成 2、conda换源 添加中科大源 conda config --add channels https://mirrors.ustc.edu.cn/anaconda/pkgs/main/ conda confi…

Secureboot从入门到精通

关键词&#xff1a;trustzone视频、tee视频、ATF视频、secureboot视频、安全启动视频、selinux视频&#xff0c;cache视频、mmu视频&#xff0c;armv8视频、armv9视频 FF-A视频、密码学视频、RME/CCA视频、学习资料下载、免费学习资料、免费 周贺贺&#xff0c;baron&#xff0…

zemax对称式目镜

两个几乎对称的双胶合透镜相对放置&#xff0c;可以达到25度的半视场 为了加工方便&#xff0c;这两个透镜组采用相同的结构 对称式目镜要求各组透镜自行校正色差&#xff0c;这样倍率色差也随之而校正。 它还能校正两种像差&#xff0c;慧差和象散。 对称目镜的结构更紧&…

QGIS文章一——实现天地图加载

无论是农业科学还是海洋科学&#xff0c;对地图的处理和数值模型的计算是少不了的&#xff0c;地图是可视化的基础&#xff0c;先有了基础再进行开始某些复杂处理&#xff0c;进而开始模拟推演&#xff0c;最后进行数值模拟和计算。 QGIS&#xff08;原称Quantum GIS&#xff0…

黑豹程序员-架构师学习路线图-百科:Git/Gitee(版本控制)

文章目录 1、什么是版本控制2、特点3、发展历史4、SVN和Git比较5、Git6、GitHub7、Gitee&#xff08;国产&#xff09;8、Git的基础命令 1、什么是版本控制 版本控制系统&#xff08; Version Control &#xff09;版本控制是一种管理和跟踪软件开发过程中的代码变化的系统。它…

力扣设计循环队列

1.使用了数组来表达循环 typedef struct {int k;//数据个数int *a;//数组int rear;//尾int front;//头 } MyCircularQueue;2.循环队列是否为空 bool myCircularQueueIsEmpty(MyCircularQueue* obj) {return obj->rear obj->front; }3.循环队列是否已满。 bool myCircula…

质数的判定和质因数分解

质数的判定&#xff1a; 质数&#xff1a;i>1&#xff0c;并且i的因子只有1和它本身。 思路&#xff1a; 对于n如果n%i0那么n/i和i都是n的因子&#xff0c;对于n的每一对因子&#xff0c;至少有一个在1-,所以我们只需要判断1-是否有能整数n的数即可。时间复杂度o(). 代码…

把握现在,热爱生活

博客主页&#xff1a;https://tomcat.blog.csdn.net 博主昵称&#xff1a;农民工老王 主要领域&#xff1a;Java、Linux、K8S 期待大家的关注&#x1f496;点赞&#x1f44d;收藏⭐留言&#x1f4ac; 目录 厨艺房价琐事计划随想 今年的中秋国庆假期放8天&#xff0c;比春节假期…

基于Cplex的人员排班问题建模求解(JavaAPI)

使用Java调用Cplex实现了阿里mindopt求解器的案例&#xff08;https://opt.aliyun.com/platform/case&#xff09;人员排班问题。 这里写目录标题 人员排班问题问题描述数学建模编程求解&#xff08;CplexJavaAPI&#xff09;求解结果 人员排班问题 随着现在产业的发展&#…

Leetcode 2119.反转两次的数字

反转 一个整数意味着倒置它的所有位。 例如&#xff0c;反转 2021 得到 1202 。反转 12300 得到 321 &#xff0c;不保留前导零 。 给你一个整数 num &#xff0c;反转 num 得到 reversed1 &#xff0c;接着反转 reversed1 得到 reversed2 。如果 reversed2 等于 num &#x…

Centos7配置firewalld防火墙规则

这里写自定义目录标题 欢迎使用Markdown编辑器一、简单介绍二、特点和功能2.1、区域&#xff08;Zone&#xff09;2.2、运行时和永久配置2.3、服务和端口2.4、动态更新2.5、连接跟踪2.6、D-Bus接口 三、设置规则3.1、启动防火墙服务3.2、新建防火墙规则的服务&#xff0c;添加端…

C/C++进程超详细详解【中部分】(系统性学习day07)

目录 前言 一、守护进程 1.概念 2.守护进程创建的原理&#xff08;如图清晰可见&#xff09; 3.守护进程的实现&#xff08;代码块&#xff09; 二、dup和dup2 1&#xff0c;复制文件描述符 2.文件描述符重定向 三、系统日志 1&#xff0c;打开日志 2&#xff0c;向日…

【MATLAB】字体美化和乱码

文章目录 前言首先说说说字体美化乱码到底是怎么导致的&#xff1f;1 字体导致的乱码2 编码导致的乱码总结 前言 最近打开MATLAB&#xff0c;又发现了一个问题&#xff1a;编辑器中的中文输入在命令行或者说终端输出竟然是乱码&#xff0c;然后赶紧翻阅了一下此前的博客以及未发…

基于Java的校园资料分享平台设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…