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緩存淘汰算法
緩存滿時,淘汰策略:
- FIFO
- LFU(Least Frequently Used),最少使用策略,
- LRU(Least Recently Used),最近最少使用策略(頁面置換算法),
思路
我的思路是這樣的:我們維護一個有序單鏈表,越靠近鏈表尾部的結點是越早之前訪問的。當有一個新的數據被訪問時,我們從鏈表頭開始順序遍歷鏈表。
- 如果此數據之前已經被緩存在鏈表中了,我們遍歷得到這個數據對應的結點,並將其從原來的位置刪除,然後再插入到鏈表的頭部。
- 如果此數據沒有在緩存鏈表中,又可以分爲兩種情況:
- 如果此時緩存未滿,則將此結點直接插入到鏈表的頭部;
- 如果此時緩存已滿,則鏈表尾結點刪除,將新的數據結點插入鏈表的頭部。
利用鏈表實現
因爲不管緩存有沒有滿,我們都需要遍歷一遍鏈表,所以這種基於鏈表的實現思路,緩存訪問的時間複雜度爲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;
}