设计推特--空间动态更新的Java底层实现原理

0x01.问题

设计一个简化版的推特(Twitter),可以让用户实现发送推文,关注/取消关注其他用户,能够看见关注人(包括自己)的最近十条推文。你的设计需要支持以下的几个功能:

  1. postTweet(userId, tweetId): 创建一条新的推文。
  2. getNewsFeed(userId): 检索最近的十条推文。每个推文都必须是由此用户关注的人或者是用户自己发出的。推文必须按照时间顺序由最近的开始排序。
  3. follow(followerId, followeeId): 关注一个用户。
  4. unfollow(followerId, followeeId): 取消关注一个用户

示范:
Twitter twitter = new Twitter();

// 用户1发送了一条新推文 (用户id = 1, 推文id = 5). twitter.postTweet(1, 5);

// 用户1的获取推文应当返回一个列表,其中包含一个id为5的推文. twitter.getNewsFeed(1);

// 用户1关注了用户2. twitter.follow(1, 2);

// 用户2发送了一个新推文 (推文id = 6). twitter.postTweet(2, 6);

// 用户1的获取推文应当返回一个列表,其中包含两个推文,id分别为 -> [6, 5]. //
推文id6应当在推文id5之前,因为它是在5之后发送的. twitter.getNewsFeed(1);

// 用户1取消关注了用户2. twitter.unfollow(1, 2);

// 用户1的获取推文应当返回一个列表,其中包含一个id为5的推文. // 因为用户1已经不再关注用户2.
twitter.getNewsFeed(1);

类形式:

class Twitter {

    /** Initialize your data structure here. */
    public Twitter() {

    }
    
    /** Compose a new tweet. */
    public void postTweet(int userId, int tweetId) {

    }
    
    /** Retrieve the 10 most recent tweet ids in the user's news feed. Each item in the news feed must be posted by users who the user followed or by the user herself. Tweets must be ordered from most recent to least recent. */
    public List<Integer> getNewsFeed(int userId) {

    }
    
    /** Follower follows a followee. If the operation is invalid, it should be a no-op. */
    public void follow(int followerId, int followeeId) {

    }
    
    /** Follower unfollows a followee. If the operation is invalid, it should be a no-op. */
    public void unfollow(int followerId, int followeeId) {

    }
}

/**
 * Your Twitter object will be instantiated and called as such:
 * Twitter obj = new Twitter();
 * obj.postTweet(userId,tweetId);
 * List param_2 = obj.getNewsFeed(userId);
 * obj.follow(followerId,followeeId);
 * obj.unfollow(followerId,followeeId);
 */

0x02.底层实现原理

这其实和真实的空间动态更新,朋友圈更新的远离差不多,不用的是存放数据的位置不一样,这里就存放在内存中。

首先,我们要理清有多少种对应关系:

  • 用户与推特是一对多的关系。
  • 用户与关注列表是一对多的关系。
  • 推特应该与一个时间戳紧密关联。
  • 最终需要得到的10条最新推特也是一对多的关系。

然后,考虑这些关系的用何种数据结构存储:

  • 由于推特有自己的id,还需要时间戳来表示时间的大小,可以考虑单独封装一个类。
  • 由于用户与推特是一对多的关系,所以可以考虑将推特使用单链表存储起来,每次插入使用头插法,这样也保证了链表中推特的有序性,然后将用户id与链表的头部用哈希表对应起来。
  • 用户与关注列表是一对多,因为关注列表也只存储数字类型的用户id,所以可以将关注劣列表用set集合封装,然后用哈希表对应起来。
  • 最终需要得到的10条推特,其实就是将关注列表的链表进行排序,取前10个,这个问题就是合并k个有序链表的问题,可以使用优先队列,大顶堆来实现。
  • 另外,需要一个全局的时间戳,为推特赋予时间。

0x03.代码–哈希表+链表+优先队列

class Twitter {

    //推文类
    private class Tweet{
        private int id;//推特id
        private int timestamp;//时间戳
        private Tweet next;//指针

        //构造推文类
        public Tweet(int id, int timestamp){
            this.id = id;
            this.timestamp = timestamp;
        }
    }
    //用户与推特的对应关系哈希表
    private Map<Integer, Tweet> twitter;

    //用户与关注列表的对应关系哈希表
    private Map<Integer, Set<Integer>> followings;

    //全局时间戳
    private static int timestamp = 0;

    //合并推文的优先队列,大顶堆
    private static PriorityQueue<Tweet> maxHeap;

    /** Initialize your data structure here. */
    public Twitter() {
        followings = new HashMap<>();
        twitter = new HashMap<>();
        //根据时间戳来确定的大顶堆
        maxHeap = new PriorityQueue<>((o1, o2) -> -o1.timestamp + o2.timestamp);
    }
    
    /** Compose a new tweet. */
    public void postTweet(int userId, int tweetId) {
        timestamp++;
        //id已经存在
        if (twitter.containsKey(userId)) {
            Tweet pre=twitter.get(userId);
            Tweet curr=new Tweet(tweetId,timestamp);
            curr.next=pre;//链表的头插法
            twitter.put(userId,curr);
        }else{//新建一个用户
            twitter.put(userId, new Tweet(tweetId, timestamp));
        }
    }
    
    /** Retrieve the 10 most recent tweet ids in the user's news feed. Each item in the news feed must be posted by users who the user followed or by the user herself. Tweets must be ordered from most recent to least recent. */
    public List<Integer> getNewsFeed(int userId) {
        //重置大顶堆
        maxHeap.clear();
        //获取关注列表
        Set<Integer> followingList = followings.get(userId);
        //加上自己发的推文
        if (twitter.containsKey(userId)) {
            maxHeap.offer(twitter.get(userId));
        }
        //遍历关注者的推文,加入大顶堆
        if (followingList != null && followingList.size() > 0) {
            for (Integer followingId : followingList) {
                Tweet tweet = twitter.get(followingId);
                if (tweet != null) {
                    maxHeap.offer(tweet);
                }
            }
        }
        //准备返回的推文集合
        List<Integer> result = new ArrayList<Integer>(10);
        int cou=0;//计数
        while (!maxHeap.isEmpty() && cou < 10) {
            Tweet head = maxHeap.poll();//获取堆顶元素
            result.add(head.id);
            //将头的下一个重新加入大顶堆
            if(head.next!=null){
                maxHeap.offer(head.next);
            }
            cou++;
        }
        return result;
    }
    
    /** Follower follows a followee. If the operation is invalid, it should be a no-op. */
    public void follow(int followerId, int followeeId) {
        //不能关注自己
        if (followeeId == followerId) {
            return;
        }
        //获取关注集合
        Set<Integer> followingList = followings.get(followerId);
        //还未关注任何人,则新建一个哈希表
        if (followingList == null) {
             Set<Integer> init = new HashSet<Integer>();
             init.add(followeeId);
             followings.put(followerId, init);
        }else{
            //已经关注过此人了
            if (followingList.contains(followeeId)) {
                return;
            }
            //加入关注集合
            followingList.add(followeeId);
        }
    }
    
    /** Follower unfollows a followee. If the operation is invalid, it should be a no-op. */
    //和关注差不多
    public void unfollow(int followerId, int followeeId) {
        if (followeeId == followerId) {
             return;
        }
        Set<Integer> followingList = followings.get(followerId);
        if (followingList == null) {
            return;
        }
        followingList.remove(followeeId);
    }
}

你知道为什么车到山前必有路吗?
因为有人已经帮你准备好了路。

ATFWUS --Writing By 2020–04-13

你可能感兴趣的:(算法,算法,设计,底层,优先队列,java)