hashMap
//KVMap,首先,HashMap存储的是键值对,所以需要一个键值对类型。
//链表结构里数据的数据类型 键值对
type KV struct {
Key string
Value string
}
//LinkNode,键值对又是主要存储在链表里的,所以需要一个链表类。
//链表结构
type LinkNode struct {
//节点数据
Data KV
//下一个节点
NextNode *LinkNode
}
//创建只有头结点的链表
func CreateLink() *LinkNode {
//头结点数据为空 是为了标识这个链表还没有存储键值对
var linkNode = &LinkNode{KV{"",""}, nil}
return linkNode
}
//当发生哈希碰撞时,键值对会存储在新建的链表节点上。这里需要一个添加节点的功能,我们这里采用尾插法添加节点。
//尾插法添加节点,返回链表总长度
func (link *LinkNode) AddNode(data KV) int {
var count = 0
//找到当前链表尾节点
tail := link
for {
count += 1
if tail.NextNode == nil {
break
}else {
tail = tail.NextNode
}
}
var newNode = &LinkNode{data, nil}
tail.NextNode = newNode
return count+1
}
//HashMap 接下来,就是猪脚HashMap登场了。
//HashMap木桶(数组)的个数
const BucketCount = 16
type HashMap struct {
//HashMap木桶
Buckets [BucketCount]*LinkNode
}
//创建HashMap
func CreateHashMap() *HashMap {
myMap := &HashMap{}
//为每个元素添加一个链表对象
for i := 0; i < BucketCount ; i++ {
myMap.Buckets[i] = CreateLink()
}
return myMap
}
//我们需要一个哈希散列算法,将key转化为一个0-BucketCount的整数,作为存放它的数组的下标。这里这个散列算法,应尽可能随机地使新增的键值对均匀地分布在每个数组下。
//一般像go的map和Java的HashMap都会有一个复杂的散列算法来达到这个目的,我们这里只是为了讲HashMap原理,暂且就用一个简单的方法来求出下标。
//自定义一个简单的散列算法,它可以将不同长度的key散列成0-BucketCount的整数
func HashCode(key string) int {
var sum = 0
for i := 0; i < len(key); i++ {
sum += int(key[i])
}
return (sum % BucketCount)
}
//往HashMap里添加键值对
//添加键值对
func (myMap *HashMap)AddKeyValue(key string, value string) {
//1.将key散列成0-BucketCount的整数作为Map的数组下标
var mapIndex = HashCode(key)
//2.获取对应数组头结点
var link = myMap.Buckets[mapIndex]
//3.在此链表添加结点
if link.Data.Key == "" && link.NextNode == nil {
//如果当前链表只有一个节点,说明之前未有值插入 修改第一个节点的值 即未发生哈希碰撞
link.Data.Key = key
link.Data.Value = value
fmt.Printf("node key:%v add to buckets %d first node\n", key, mapIndex)
}else {
//发生哈希碰撞
index := link.AddNode(KV{key, value})
fmt.Printf("node key:%v add to buckets %d %dth node\n", key, mapIndex, index)
}
}
//根据键从HashMap里取出对应的值,按键取值
func (myMap *HashMap)GetValueForKey(key string) string {
//1.将key散列成0-BucketCount的整数作为Map的数组下标
var mapIndex = HashCode(key)
//2.获取对应数组头结点
var link = myMap.Buckets[mapIndex]
var value string
//遍历找到key对应的节点
head := link
for {
if head.Data.Key == key {
value = head.Data.Value
break
}else {
head = head.NextNode
}
}
return value
}
LRU算法
LRU是Least Recently Used的缩写,即最近最少使用,常用于页面置换算法,是为虚拟页式存储管理服务的。如果要自己实现一个LRU算法,可以用哈希表加双向链表实现:
设计思路是:使用哈希表存储 key,值为链表中的节点,节点中存储值,双向链表来记录节点的顺序,头部为最近访问节点。
LRU算法中有两种基本操作:
- get(key):查询key对应的节点,如果key存在,将节点移动至链表头部。
- set(key, value): 设置key对应的节点的值。如果key不存在,则新建节点,置于链表开头。如果链表长度超标,则将处于尾部的最后一个节点去掉。如果节点存在,更新节点的值,同时将节点置于链表头部。
lru缓存**
设计LRU缓存结构,该结构在构造时确定大小,假设大小为K,并有如下两个功能
- set(key, value):将记录(key, value)插入该结构
- get(key):返回key对应的value值
[要求]
- set和get方法的时间复杂度为O(1)
- 某个key的set或get操作一旦发生,认为这个key的记录成了最常使用的。
- 当缓存的大小超过K时,移除最不经常使用的记录,即set或get最久远的。
若opt=1,接下来两个整数x, y,表示set(x, y)
若opt=2,接下来一个整数x,表示get(x),若x未出现过或已被移除,则返回-1
对于每个操作2,输出一个答案
示例:输入:
[[1,1,1],[1,2,2],[1,3,2],[2,1],[1,4,4],[2,2]],3
返回值:
[1,-1]
说明:
第一次操作后:最常使用的记录为("1", 1)
第二次操作后:最常使用的记录为("2", 2),("1", 1)变为最不常用的
第三次操作后:最常使用的记录为("3", 2),("1", 1)还是最不常用的
第四次操作后:最常用的记录为("1", 1),("2", 2)变为最不常用的
第五次操作后:大小超过了3,所以移除此时最不常使用的记录("2", 2),加入记录("4", 4),并且为最常使用的记录,然后("3", 2)变为最不常使用的记录
#include <unordered_map>
struct DListNode{
int key, val;
DListNode* pre;
DListNode* next;
DListNode(int k, int v): key(k), val(v), pre(nullptr), next(nullptr){};
};
class Solution {
private:
int size = 0;
DListNode* head;
DListNode* tail;
unordered_map<int, DListNode*> mp;
public:
/**
* lru design
* @param operators int整型vector<vector<>> the ops
* @param k int整型 the k
* @return int整型vector
*/
vector<int> LRU(vector<vector<int> >& operators, int k) {
// write code here
if(k < 1) return {};
this->size = k;
this->head = new DListNode(0,0);
this->tail = new DListNode(0,0);
this->head->next = this->tail;
this->tail->pre = this->head;
if(operators.size() == 0) return {};
vector<int> res;
for(vector<int> op : operators){
if(op[0] == 1) {
set(op[1], op[2]);
}else if(op[0] == 2){
int value = get(op[1]);
res.push_back(value);
}
}
return res;
}
void set(int key, int val){
if(mp.find(key) == mp.end()){ // hashmap 中没找到
DListNode* node = new DListNode(key, val);
mp[key] = node;
if(this->size <= 0){
removeLast();
}
else{
this->size--;
}
insertFirst(node);
}
else{ // hashmap 中已经有了,也就是链表里也已经有了
mp[key]->val = val;
moveToHead(mp[key]);
}
}
int get(int key){
int ret = -1;
if(mp.find(key) != mp.end()){
ret = mp[key]->val;
moveToHead(mp[key]);
}
return ret;
}
void moveToHead(DListNode* node){
if(node->pre == this->head) return;
node->pre->next = node->next;
node->next->pre = node->pre;
insertFirst(node);
}
void removeLast(){
mp.erase(this->tail->pre->key);
this->tail->pre->pre->next = this->tail; // remove the last node in dlist
this->tail->pre = this->tail->pre->pre;
}
void insertFirst(DListNode* node){
node->pre = this->head;
node->next = this->head->next;
this->head->next->pre = node;
this->head->next = node;
}
};
生产者消费者
type Product struct {
name int
value int
}
func producer(wg *sync.WaitGroup, products chan<- Product, name int, stop *bool) {
for !*stop {
product := Product{name: name, value: rand.Int()}
products <- product
fmt.Printf("producer %v produce a product: %#v\n", name, product)
time.Sleep(time.Duration(200+rand.Intn(1000)) * time.Millisecond)
}
wg.Done()
}
func consumer(wg *sync.WaitGroup, products <-chan Product, name int) {
for product := range products {
fmt.Printf("consumer %v consume a product: %#v\n", name, product)
time.Sleep(time.Duration(200+rand.Intn(1000)) * time.Millisecond)
}
wg.Done()
}
//主线程
var wgp sync.WaitGroup
var wgc sync.WaitGroup
stop := false
products := make(chan Product, 10)
// 创建 5 个生产者和 5 个消费者
for i := 0; i < 5; i++ {
go producer(&wgp, products, i, &stop)
go consumer(&wgc, products, i)
wgp.Add(1)
wgc.Add(1)
}
time.Sleep(time.Duration(1) * time.Second)
stop = true // 设置生产者终止信号
wgp.Wait() // 等待生产者退出
close(products) // 关闭通道
wgc.Wait() // 等待消费者退出
bitMap的简单实现
对于任意一个数x,x / 32对应着它在vector的第几个位置,x % 32对应它的比特位。
下面给出C++代码的实现。
class BitMap
{
public:
BitMap(size_t num)
{
_v.resize((num >> 5) + 1); // 相当于num/32 + 1
}
void Set(size_t num) //set 1
{
size_t index = num >> 5; // 相当于num/32
size_t pos = num % 32;
_v[index] |= (1 << pos);
}
void ReSet(size_t num) //set 0
{
size_t index = num >> 5; // 相当于num/32
size_t pos = num % 32;
_v[index] &= ~(1 << pos);
}
bool get(size_t num)//check whether it exists
{
size_t index = num >> 5;
size_t pos = num % 32;
bool flag = false;
if (_v[index] & (1 << pos))
flag = true;
return flag;
}
private:
vector<size_t> _v;
};
进程交替打印
让两个线程互相唤醒对方来交替打印数字
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#include <stdio.h>
int g_num = 1;
pthread_mutex_t mutex;
pthread_cond_t cond1,cond2;
void* thread1(void* arg)
{
while(1)
{
pthread_mutex_lock(&mutex);
//如果需要交替打印一定范围(例如1-10)内的数字,那么可以加上下面两行代码
//if(g_num > 10)
//exit(1);
printf("Thread1: %d \n",g_num);
g_num ++;
pthread_cond_signal(&cond2);
pthread_cond_wait(&cond1,&mutex);
pthread_mutex_unlock(&mutex);
sleep(1);
}
return NULL;
}
void* thread2(void* arg)
{
while(1)
{
//这个sleep(1)加在前面是因为开启线程时有可能是线程2先打印,
//导致变成thread2输出奇数,threa1输出偶数。为了避免这种情况,可以在延迟下线程2的打印。
sleep(1);
pthread_mutex_lock(&mutex);
printf("Thread2: %d \n",g_num);
g_num++;
pthread_cond_signal(&cond1);
pthread_cond_wait(&cond2,&mutex);
pthread_mutex_unlock(&mutex);
}
return NULL;
}
int main()
{
pthread_t p1,p2;
pthread_mutex_init(&mutex,NULL);
pthread_cond_init(&cond1,NULL);
pthread_cond_init(&cond2,NULL);
pthread_create(&p1,NULL,thread1,NULL);
pthread_create(&p2,NULL,thread2,NULL);
pthread_join(p1,NULL);
pthread_join(p2,NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond1);
pthread_cond_destroy(&cond2);
return 0;
}