【鏈表應用】約瑟夫問題、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;
}

組織結構

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章