快速排序、归并排序、LRU和LFU

快速排序、归并排序、LRU和LFU


这四种算法毕竟经典,难度适中,经常面试手撕代码遇到。

一、快速排序

快速排序可以看成是一个利用前序遍历的递归方法,关键在于切分函数,使得每次切分后的元素左边的数小于这个数,元素右边的数大于这个数,此时元素左右两边的数不一定顺序排列。

算法分析:(参考链接)

  • 一般选取数组的首元素作为基准元素P,同时利用双指针方法,int i = nums[low]; int j = nums[hi];
  • 如果选择首元素,从右向左找比P小的元素,若找到则交换nums[i] 和 nums[j],同时i++,如果没有找到则 j–;
  • 此时你要从左向右查找,因为已经交换元素了,基准元素到了右边,找比P大的元素,找到了就交换同时j–,没有找到则 i++;
  • 重复步骤2和3,直到i与j指针重合,返回值是基准元素此时的索引,i或者j都可以;

代码实现:

//快速排序
#include<string>
#include <iostream>
#include <cmath>
#include <algorithm>
#include<unordered_map>
using namespace std;
int part(int* r, int low, int hight)  //划分函数
{
	int i = low, j = hight, pivot = r[low]; //基准元素
	while (i < j)
	{
		while (i<j && r[j]>pivot) //从右向左开始找一个 小于等于 pivot的数值
		{
			j--;
		}
		if (i < j)
		{
			swap(r[i++], r[j]);  //r[i]和r[j]交换后 i 向右移动一位
		}
		while (i < j && r[i] <= pivot) //从左向右开始找一个 大于 pivot的数值
		{
			i++;
		}
		if (i < j)
		{
			swap(r[i], r[j--]);  //r[i]和r[j]交换后 i 向左移动一位
		}
	}
	return i;  //返回最终划分完成后基准元素所在的位置
}
void Quicksort(int* r, int low, int hight)
{
	int mid;
	if (low < hight)
	{
		mid = part(r, low, hight);  // 返回基准元素位置
		Quicksort(r, low, mid - 1); // 左区间递归快速排序
		Quicksort(r, mid + 1, hight); // 右区间递归快速排序
	}
}
unordered_map<int, int>valtoindex;
int main()
{
	int a[10001];
	int  n;
	cout << "请输入要排序的数据的个数: " << endl;
	cin >> n;
	cout << "请输入要排序的数据: " << endl;
	for (int i = 0; i < n; i++)
	{
		cin >> a[i];
	}
	cout << endl;
	Quicksort(a, 0, n - 1);
	cout << "排序后的序列为: " << endl;
	for (int i = 0; i < n; i++)
	{
		cout << a[i] << " ";
	}
	cout << endl;
	system("pause");
	return 0;
}

二、归并排序

归并排序可以看成一个后序遍历的递归算法,关键在于merge函数,利用递归函数的意义去理解,将数组的左边一半和右边一半分别排序好,再合并两边,得到最终的排序数组。

算法分析:

  • 如图所示,理解递归函数的意义,就是排序,假设已经排序好了;递归本质会递进到递归树的叶子节点,明确递归函数意义就好了。

​[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tNbBE0zV-1684330256623)(assets/image-20230517195656-wjo3o0f.png)]​

代码实现:(核心代码模式)

class Merge
{
private:
    vector<int> temp;
public:
    void sort(vector<int>& nums)
    {
        temp.resize(nums.size());
        sort(nums,0,nums.size()-1);
    }
    void sort(vector<int>& nums,int low, int high)
    {
        if(low==high)   return;
        int mid = low +(high-low)/2;  // 防止溢出
        sort(nums,low,mid);
        sort(nums,mid+1,high);      //两边都排序好进行合并
        merge(nums,low,mid,high);
    }
    void merge(vector<int>& nums,int low, int mid, int high)
    {
        for(int i = low;i<=high;i++)
        {
            temp[i] = nums[i];
        }
        int i = low, j = mid+1;
        for(int k = low;k<=high;k++)
        {
            if(i==mid+1)
            {
                nums[k] = temp[j++];
            }
            else if(j == high+1)
            {
                nums[k] = temp[i++];
            }
            else if(temp[i]>temp[j])
            {
                nums[k] = temp[j++];
            }
            else
            {
                nums[k] = temp[i++];
            }

        }
    }

};
class Solution {
public:
    vector<int> sortArray(vector<int>& nums) {
        Merge m;
        m.sort(nums);
        return nums;
    }
  
};

三、LRU算法

LRU缓存算法重点在于每次查询和插入操作之后,都需要将节点设置为最新的,超过缓存大小则删除最久没被使用的,可以用hashmap和双链表,将双链表的头部设置为最新的数据,尾部设置为最老的数据,这样超过缓存删除元素只需要删除最后一个结点就行,注意hashmap的更新。

算法分析:

  • 手写一个双链表,和虚拟头尾结点;hashmap的key对应值,value等于链表节点
  • get函数查询是否存在,存在返回值,不存在返回-1,并将节点移到链表头部;
  • put函数先查询是否存在,存在则修改值,并将节点移到链表头部;不存在新建一个结点插入头部;
  • 重点并将节点移到链表头部​,操作是先删除节点,再添加到链表头部;
  • 注意双链表的删除和插入操作,需要对next和prev指针操作。

代码分析:(核心代码模式)

class DlinkNode  //手写一个双链表构造
{
public:
    int key;
    int value;
    DlinkNode* next;
    DlinkNode* prev;
    DlinkNode():key(0),value(0),next(nullptr),prev(nullptr){}
    DlinkNode(int _key, int _value):key(_key),value(_value),next(nullptr),prev(nullptr){}

};

class LRUCache {
private:
    int capacity;
    int size;
    unordered_map<int,DlinkNode*> cache;  //hashmap
    DlinkNode* head;
    DlinkNode* tail;

public:
    LRUCache(int capacity):capacity(capacity),size(0)
    {
        head = new DlinkNode();
        tail = new DlinkNode();//虚拟
        head->next = tail;
        tail->prev = head;   //头尾指针
    }
  
    int get(int key) {
        if(!cache.count(key))
            return -1;
        DlinkNode* node = cache[key];
        movetohead(node);
        return node->value;
    }
  
    void put(int key, int value) {
        if(!cache.count(key))
        {
            if(size==capacity)  //这样毕竟符合逻辑,如果先插入再判断则已经超过内存了
            {
                DlinkNode* remove =  removetail();
                cache.erase(remove->key);  //更新hashmap
            
                size--;
                delete remove;
            }
            DlinkNode* node = new DlinkNode(key,value);
            cache[key]= node;
            addtohead(node);
            size++;
        
        }
        else
        { 
         //存在修改值
        DlinkNode* node = cache[key];
        node->value = value;
        movetohead(node);
        }
  

    }
    void removenode(DlinkNode* node)
    {   
        node->prev->next = node->next; //node的上一个指针的next应该等于node的下一个指针
        node->next->prev = node->prev;

    }

    void movetohead(DlinkNode* node)
    {
        removenode(node);
        addtohead(node);

    }
  
  
    void addtohead(DlinkNode* node)
    {   
        node->prev = head;    //先操作node,插入进去需要构造四个指向
        node->next = head->next;
        head->next->prev = node;  //先操作这一步 
        head->next = node;

    }
    DlinkNode* removetail()
    {
        DlinkNode* last = tail->prev;
        removenode(last);
        return last;

    }

};

四、LFU算法​​

LFU算法重点在于频率的更新,需要维护一个最小频率,当缓存满的时候插入,需要删除操作频率最小的元素,如果有多个,则删除最老的元素。

算法分析:

  • 频率对应多个节点,每次操作完都需要更新频率,删除这个元素对应频率的对应节点,添加这个元素对应频率+1的节点;
  • 利用双hashmap解决,``​ unordered_mapint,listnode::iterator key_table;unordered_map<int,list> freq_table;`​,list需要经常删除元素,所以key_table对应迭代器;
  • get使用前都需要先查询是否存在,如果存在删除这个元素对应频率的对应节点,如果节点数目为0,删除这个频率对应的键,此时如果最小频率等于这个频率,则最小频率+1;
  • put使用前都需要先查询是否存在,存在类似get,不存在插入一个新的节点,频率为1,如果内存满了,则删除操作频率最小的元素且最老的元素;
  • 每次key_table插入元素都对应,freq_table中每个频率的首元素,这样操作频率最小的元素且最老的元素就等于最小频率对应链表的尾部元素。

代码分析:(核心代码模式)

struct  Node
{
    int val, key, freq;
    //gouzao
    Node(int _key, int _val, int _freq):val(_val),key(_key),freq(_freq){}
};

class LFUCache {
private:
        int capacity;
        int minfreq;
        unordered_map<int,list<Node>::iterator> key_table;
        unordered_map<int,list<Node>> freq_table;

public:
    LFUCache(int capacity) {
        this->capacity = capacity;
        minfreq=0;
        key_table.clear();
        freq_table.clear();
    }
  
    int get(int key) {
        if(capacity==0) return -1;
        auto it = key_table.find(key);
        if(it == key_table.end()) return -1;
        int res_val= it->second->val;
        int freq = it->second->freq;
        freq_table[freq].erase(it->second); //删除 这个频率的node
        if(freq_table[freq].size()==0) //判断这个频率还有节点吗
        {
            freq_table.erase(freq);
            if(minfreq == freq)
                minfreq+=1;
        }
        freq_table[freq+1].push_front(Node(key,res_val,freq+1));
        key_table[key] = freq_table[freq+1].begin();
        return res_val;

    }
  
    void put(int key, int value) {
        if(capacity==0) return;
        auto it = key_table.find(key);
        if(it == key_table.end()) //没有这个
        {
            if(key_table.size()==capacity)//满了删除
            {
                int remove_key = freq_table[minfreq].back().key;
                key_table.erase(remove_key);
                freq_table[minfreq].pop_back();
                if(freq_table[minfreq].size()==0)
                    freq_table.erase(minfreq);

            }
            freq_table[1].push_front(Node(key,value,1));
            key_table[key]=freq_table[1].begin();
            minfreq = 1;

        }
        else{
            //存在这个
        int freq = it->second->freq;
        freq_table[freq].erase(it->second); //删除 这个频率的node
        if(freq_table[freq].size()==0)
        {
            freq_table.erase(freq);
            if(minfreq == freq)
                minfreq+=1;
        }
        freq_table[freq+1].push_front(Node(key,value,freq+1));
        key_table[key] = freq_table[freq+1].begin();

        }
    }
};

おすすめ

転載: blog.csdn.net/qq_43377917/article/details/130735340