• hihocoder1198 Memory Allocating Algorithm(链表~)


    题意:

    小Hi和小Ho最近在研究内存分配的机制,他们写了一个比较简单的内存。内存可以表示成M个连续的存储空间,下标为0..M-1:

    每当有数据写入时,内存分配程序会从下标0开始向右找一块足够存放下该数据的区域,将该数据写入。比如写入一个长度为2的数据,因为是第一个数据,我们用1来表示:

    之后继续依次写入长度为3的数据和长度为2的数据,则有:

    当数据足够多后,我们可能会遇到剩下的空间不足以写下新的数据。这时内存程序会从最早的数据开始进行删除。假设我们现在写到第8个数据把内存写满了:

    这时我们需要写入第9个数据,该数据长度为4。则内存程序会删掉第1个数据:

    但是仍然不够,于是删除掉第2个数据。这时就足够放下第9个数据了。于是内存程序存下第9个数据:

    当有足够的空间存放数据数,数据总是尽可能靠左(起点下标尽可能小)存放。

    小Hi和小Ho写好了内存之后,打算测试一下。他们会连续写入的N个数据,在所有数据写入完成后,他们想知道现在内存中各个存储单元的情况。

    思路:

    一个简单的思路是我们使用长度为M的数组来模拟整个内存使用的情况。

    对于其中60%的数据:1≤N≤200,10≤M≤100,1≤K[i]≤5,这种简单的方法是可行的。

    而对于另外40%的数据,由于M的长度最大可能为10^9,显然无法处理,因此我们需要进一步优化。

    通过对简单数据的分析,我们可以发现这样一个情况:

    一个相同的数字总是占用了一段连续的内存

    那么我们可以使用(a,b)来表示一段长度为b的数字a,我们把这样表示的一段数据叫做一个数据块

    比如内存情况1 1 1 1 1 2 2 3 3 0,则可以表示为:

    (1,5),(2,2),(3,2),(0,1)

    那么在简化的表示方法之下,我们应该如何来进行内存上的操作呢?

    • 添加一个数据(i, k[i])

    在内存中寻找最左边的(0, b),同时满足b≥k[i]

    b>k[i],将(0,b)分割为两个数据块(i, k[i]),(0, b-k[i])

    b=k[i],将(0,b)直接改为(i, b)

    记录第i个数据对应的(i, k[i])的位置,方便删除

    • 删除一个数据(i, k[i])

    根据记录,找到到i的数据块(i, k[i]),同时找到其前一个块(prev, k[prev])和后一个块(next, k[next])

    prev=0,讲(prev, k[prev])合并至(i, k[i])

    next=0,讲(next, k[next])合并至(i, k[i])

    (i,k[i])置为(0,k[i])

    由于在简化的表示下,内存最多有2N个块,因此即便是顺序查找一个满足b≥k[i](0, b)数据块,需要的时间复杂度也不超过O(N)

    而删除操作借助已经记录的数据位置,实现的时间复杂度为O(1)

    根据题目,最多可能添加N个数据块,因此总的时间复杂度为O(N^2),可以解决问题。

    有了算法,接下来就是考虑如何使用代码实现,这里我们采用链表来做:

    对于每一个数据块,我们用如下方式表示:

    Block {
        key: 表示数据编号,对应(a,b)中的a
        length: 表示数据长度,对应(a,b)中的b
        prev: 表示该数据块的前一个块
        next: 表示该数据块的后一个块
    }

    由于每一个块都有前后指针,因此为了简化处理,我们给链表加上头尾两个哨兵节点,得到初始的链表状态为:

    (-1, 0), (0, M), (-1, 0)

    初始化为:

    init(M):
        head = new Block
        head -> key = -1
        head -> length = 0
        head -> prev = NULL
        head -> next = NULL
    
        p = new Block
        p -> key = 0
        p -> length = M
        p -> prev = head
        p -> next = NULL
        head -> next = p
    
        p2 = new Block
        p2 -> key = -1
        p2 -> length = 0
        p2 -> prev = p
        p2 -> next = NULL
        p -> next = p

    这样就将初始的三个节点加入了链表,接下来就是几个操作。

    • 查找

    直接按顺序进行扫描,查找符合要求的(0, b)

    findEmpty(len):
        p = head -> next
        While (p -> key != -1)
            If (p -> key == 0 && p -> length >= len) Then
                Return p // 找到合适的空内存段
            End If
            p = p -> next
        End While
        Return NULL // 找不到空的内存
    • 添加

    通过findEmpty找到的空数据块,并进行插入操作

    insert(emptyBlock, key, len):
        // emptyBlock是找到的符合要求的空数据块
        If (emptyBlock -> lenght == len) Then
            emptyBlock -> key = key
        Else
            p = new Block // 新的(0, b - k[i])
            p -> key = 0
            p -> length = emptyBlock -> length - len
    
            p -> prev = emptyBlock
            p -> next = emptyBlock -> next
            p -> next -> prev = p
    
            emptyBlock -> key = key
            emptyBlock -> length = len
            emptyBlock -> next = p
        End If
        pos[ key ] = emptyBlock // 记录第key个数据的位置
    • 删除

    通过pos[key]来获取数据块

    delete(key):
        p = pos[ key ]
    
        tp = p -> prev // 合并前一个
        If (tp -> key == 0) Then
            p -> length = p -> length + tp -> length
            p -> prev = tp -> prev
            p -> prev -> next = p
            Delete tp
        End If
    
        tp = p -> next // 合并后一个
        If (tp -> key == 0) Then
            p -> length = p -> length + tp -> length
            p -> next = tp -> next
            p -> next -> prev = p
            Delete tp
        End If
    
        p -> key = 0
    • 主函数

    如下

    main()
        Input N, M
        init(M)
        lastDeleteData = 0
        For i = 1 .. N
            Input K
            While (true)
                p = findEmpty(K)
                If (p != NULL) Then
                    insert(p, i, K)
                    Break
                Else
                    lastDeleteData = lastDeleteData + 1
                    delete(lastDeleteData)
                End If
            End While
        End For
        //Type Ans 最后根据链表输出结果

    本题在这次笔试中是作为中等偏简单的题目设计的,所以时间复杂度在O(N^2)的算法就能够拿满分。

    而实际上本题还有O(NlogM)的线段树算法,这里留作大家思考。 如果你用O(NlogM)的线段树算法AC了这道题,欢迎跟帖分享。

    /* ***********************************************
    Author        :devil
    ************************************************ */
    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    #include <vector>
    #include <queue>
    #include <set>
    #include <map>
    #include <string>
    #include <cmath>
    #include <stdlib.h>
    #define inf 0x3f3f3f3f
    #define LL long long
    #define rep(i,a,b) for(int i=a;i<=b;i++)
    #define dec(i,a,b) for(int i=a;i>=b;i--)
    #define ou(a) printf("%d
    ",a)
    #define pb push_back
    #define mkp make_pair
    template<class T>inline void rd(T &x){char c=getchar();x=0;while(!isdigit(c))c=getchar();while(isdigit(c)){x=x*10+c-'0';c=getchar();}}
    #define IN freopen("in.txt","r",stdin);
    #define OUT freopen("out.txt","w",stdout);
    using namespace std;
    const int mod=1e9+7;
    const int N=2e3+10;
    struct wq
    {
        int id,len;
        wq *pre,*nxt;
    };
    int n,m,k,ans[N];
    wq *head,*p,*p2,*tmp,*pos[N];
    void init()
    {
        head=new wq();
        head->id=-1;
        head->len=0;
        head->pre=NULL;
        head->nxt=NULL;
        p=new wq();
        p->id=0;
        p->len=m;
        p->pre=head;
        p->nxt=NULL;
        head->nxt=p;
        p2=new wq();
        p2->id=-1;
        p2->len=0;
        p2->pre=p;
        p2->nxt=NULL;
        p->nxt=p2;
    }
    wq *findEmpty(int l)
    {
        p=head->nxt;
        while(p->id!=-1)
        {
            if(p->id==0&&p->len>=l) return p;
            p=p->nxt;
        }
        return NULL;
    }
    void Insert(wq *now,int id,int l)
    {
        if(now->len==l) now->id=id;
        else
        {
            p=new wq();
            p->id=0;
            p->len=now->len-l;
            p->pre=now;
            p->nxt=now->nxt;
            p->nxt->pre=p;
            now->id=id;
            now->len=l;
            now->nxt=p;
        }
        pos[id]=now;
    }
    void Delete(int id)
    {
        p=pos[id];
        tmp=p->pre;
        if(tmp->id==0)
        {
            p->len+=tmp->len;
            p->pre=tmp->pre;
            p->pre->nxt=p;
            delete tmp;
        }
        tmp=p->nxt;
        if(tmp->id==0)
        {
            p->len+=tmp->len;
            p->nxt=tmp->nxt;
            p->nxt->pre=p;
            delete tmp;
        }
        p->id=0;
    }
    int main()
    {
        rd(n),rd(m);
        init();
        int last=0;
        rep(i,1,n)
        {
            rd(k);
            while(1)
            {
                p=findEmpty(k);
                if(p!=NULL)
                {
                    Insert(p,i,k);
                    break;
                }
                else
                {
                    last++;
                    Delete(last);
                }
            }
        }
        p=head->nxt;
        int start=0;
        memset(ans,-1,sizeof(ans));
        while(p->id!=-1)
        {
            if(p->id!=0) ans[p->id]=start;
            start+=p->len;
            p=p->nxt;
        }
        rep(i,0,n) if(ans[i]!=-1) printf("%d %d
    ",i,ans[i]);
        return 0;
    }
    View Code
  • 相关阅读:
    如何开发优秀的HTML5游戏?迪斯尼《寻找奥兹之路》游戏技术详解(一)
    C++ Prime学习过程中的细节摘记(三)
    android学习笔记53_采用网页设计软件界面,以及使用android系统内置的浏览器,利用js调用java方法
    黑马韩前成linux从入门到精通の轻松搞定负载均衡
    thinkphp一键清除缓存的方法
    使用treeNMS管理及监控Redis
    ThinkPHP 的缓存大概多久更新一次
    redis查数据
    redis可视化客户端工具TreeNMS
    MeeGo开发进程通信核心 DBus调试工具 狼人:
  • 原文地址:https://www.cnblogs.com/d-e-v-i-l/p/5847600.html
Copyright © 2020-2023  润新知