【链表应用】约瑟夫问题、LRU缓存淘汰算法


https://zh.wikipedia.org/wiki/约瑟夫斯问题

约瑟夫问题

约瑟夫问题(有时也称为约瑟夫斯置换),是一个出现在计算机科学和数学中的问题。在计算机编程的算法中,类似问题又称为约瑟夫环。
人们站在一个等待被处决的圈子里。 计数从圆圈中的指定点开始,并沿指定方向围绕圆圈进行。 在跳过指定数量的人之后,执行下一个人。 对剩下的人重复该过程,从下一个人开始,朝同一方向跳过相同数量的人,直到只剩下一个人,并被释放。
问题即,给定人数、起点、方向和要跳过的数字,选择初始圆圈中的位置以避免被处决

解法

比较简单的做法是用循环单链表模拟整个过程,时间复杂度是O(n*m)。如果只是想求得最后剩下的人,则可以用数学推导的方式得出公式。且先看看模拟过程的解法。

C++实现

#include <iostream>
using namespace std;

typedef struct _LinkNode {//函数内部使用,所以加下划线
	int _value;
	struct _LinkNode* _pNext;
	_LinkNode() :_value(0), _pNext(nullptr) {};
}LinkNode, *LinkNodePtr;

LinkNodePtr createCycle(int total) {//建立循环链表,值为人索引
	int index = 1;
	LinkNodePtr head = new LinkNode();
	head->_value = index;//从第一个人开始
	LinkNodePtr cur = head;

	while (--total > 0) {
		LinkNodePtr newLinkNode = new LinkNode();
		newLinkNode->_value = (++index);
		cur->_pNext = newLinkNode;
		cur = newLinkNode;
	}
	cur->_pNext = head;
	return head;
}

void run(int total, int tag) {//total:人数,tag:每次越过的人数
	LinkNodePtr node = createCycle(total);
	LinkNodePtr prev = nullptr;//处决prev->_pNext
	int start = 1;
	int index = start;//node索引
	while (node && node->_pNext) {//直到node为空
		if (index == tag){//到达下一个位置
			cout << node->_value << endl;//打印出要处决的人
			//删除当前节点node
			if (tag == start) {//只有一个人
				prev = node->_pNext;
				node->_pNext = nullptr;
				node = prev;
			}else {
				prev->_pNext = node->_pNext;
				node->_pNext = nullptr;//删除node
				node = prev->_pNext;
			}
			index = start;//重新下一波
		}else {//移动
			index++;
			prev = node;
			node = node->_pNext;
		}
	}
}

int main()
{
	run(5, 999999);
	return 0;
}

运行结果:

4
2
1
3
5

LRU缓存淘汰算法

缓存满时,淘汰策略:

  1. FIFO
  2. LFU(Least Frequently Used),最少使用策略,
  3. LRU(Least Recently Used),最近最少使用策略(页面置换算法),

思路

我的思路是这样的:我们维护一个有序单链表,越靠近链表尾部的结点是越早之前访问的。当有一个新的数据被访问时,我们从链表头开始顺序遍历链表

  1. 如果此数据之前已经被缓存在链表中了,我们遍历得到这个数据对应的结点,并将其从原来的位置删除,然后再插入到链表的头部。
  2. 如果此数据没有在缓存链表中,又可以分为两种情况:
    • 如果此时缓存未满,则将此结点直接插入到链表的头部;
    • 如果此时缓存已满,则链表尾结点删除,将新的数据结点插入链表的头部。

利用链表实现

因为不管缓存有没有满,我们都需要遍历一遍链表,所以这种基于链表的实现思路,缓存访问的时间复杂度为O(n)。

#include <iostream>
#include <list>
#include <vector>
#include <algorithm>
using namespace std;

void print(list<int> & physical_block) {
	for (auto i : physical_block)
		cout << i << " ";
	cout << endl;
}

int main()
{
	//规定缓存中放置的页面不超过4(list < 4)
	list<int> physical_block;
	vector<int> pages = { 7, 0, 1, 2, 0, 3, 0, 4, 2, 3,
		0, 3, 2, 1, 2, 0, 1, 7, 0, 1 };

	for (auto i : pages) {//依次访问页面
		auto it = find(physical_block.begin(), physical_block.end(), i);
		if (it != physical_block.end()) {
			cout << i << "在缓存中,移到头部" << endl;
			physical_block.erase(it);
			physical_block.push_front(i);
		}
		else {
			cout << i << "不在缓存中,放到头部" << endl;
			if (physical_block.size() >= 4)
				physical_block.pop_back();
			physical_block.push_front(i);
		}
		//打印缓存中的页面
		print(physical_block);
		cout << "******************************" << endl;
	}
	return 0;
}

运行结果:

7不在缓存中,放到头部
7
******************************
0不在缓存中,放到头部
0 7
******************************
1不在缓存中,放到头部
1 0 7
******************************
2不在缓存中,放到头部
2 1 0 7
******************************
0在缓存中,移到头部
0 2 1 7
******************************
3不在缓存中,放到头部
3 0 2 1
******************************
0在缓存中,移到头部
0 3 2 1
******************************
4不在缓存中,放到头部
4 0 3 2
******************************
2在缓存中,移到头部
2 4 0 3
******************************
3在缓存中,移到头部
3 2 4 0
******************************
0在缓存中,移到头部
0 3 2 4
******************************
3在缓存中,移到头部
3 0 2 4
******************************
2在缓存中,移到头部
2 3 0 4
******************************
1不在缓存中,放到头部
1 2 3 0
******************************
2在缓存中,移到头部
2 1 3 0
******************************
0在缓存中,移到头部
0 2 1 3
******************************
1在缓存中,移到头部
1 0 2 3
******************************
7不在缓存中,放到头部
7 1 0 2
******************************
0在缓存中,移到头部
0 7 1 2
******************************
1在缓存中,移到头部
1 0 7 2
******************************

利用散列表实现LRU(优化)

使用stl中的hash_map即(unordered_map)
引入散列表(Hash table)来记录每个数据的位置,将缓存访问的时间复杂度降到O(1)。

#include <iostream>
#include <list>
#include <vector>
#include <algorithm>
#include <string>
#include <functional>
#include <unordered_map>
using namespace std;
class Node {
public:
	Node(string str) :m_data(str) {}
	string m_data;
};

namespace std {
	template <>
	class hash<Node>
	{
	public:
		int operator()(const Node &s) const {
			return stoi(s.m_data);
		}
	};
}

class LruCache {
public:
	LruCache() :m_capacity(0) {}
	//cpu访问数据,需要动态更新缓存
	void PutCache(string &str) {
		Node node(str);
		int key = m_hash_fn(node);//重载()获得键值
		auto it = m_hash_table.find(key);

		if (it != m_hash_table.end()) {
			auto list_iter = it->second;
			cout << node.m_data << " 数据已经在内存中..." << endl;
			//把list_iter剪接到m_double_list
			m_double_list.splice(m_double_list.begin(), m_double_list, list_iter);
		}
		else {
			cout << node.m_data << " 数据未在缓存中..." << endl;
			if (m_capacity >= 4) {
				int key = m_hash_fn(m_double_list.back());
				m_double_list.pop_back();
				m_hash_table.erase(key);
				m_capacity--;
			}
			m_double_list.push_front(node);
			m_hash_table.insert({ key,m_double_list.begin() });
			m_capacity++;
		}
		for (auto &tt : m_double_list)
			cout << tt.m_data << " ";
		cout << endl;
	}
private:
	hash<Node> m_hash_fn;
	int m_capacity;//cache capacity,其实就是list的容量
	//注意是:只用了一条 std::list
	//对于list中只有元素的删除操作会导致指向该元素的迭代器失效,
	//其他元素迭代器不受影响,当删除元素时,将迭代器置为空就行了
	//或者直接在 hash_map 中 erase 即可
	list<Node> m_double_list;
	unordered_map<int, list<Node>::iterator> m_hash_table;
};

int main()
{
	string str[] = { "7", "0", "1", "2", "0", 
		"3", "0", "4", "2", "3", "0", 
		"3", "2", "1", "2", "0", "1", "7", "0" };
	vector<string> pages(str, str + 19);
	LruCache lru;
	for (auto tt : pages)
		lru.PutCache(tt);
	return 0;
}

组织结构

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值