你真的懂随机吗——shuffle一个简单实用的随机算法

版权声明:博主原创,未经允许不得转载。 https://blog.csdn.net/qq_31748587/article/details/84589124

正经学徒,佛系记录,不搞事情

随机数的定义:

其实随机数是分为随机数和随机数的

所谓真随机数,根据百科的解释,真随机数必须依赖于物理现象,例如大乐透,抛硬币,公路上第五辆过来的车是单号还是双号

而伪随机数又区分为强伪随机和弱伪随机

弱随机数:首先要满足是随机的,但容易找出随机的规律性

强随机数:首先要满足是随机的,且无法预测规律

综上所述,平时项目中用纯算法实现的随机其实都属于伪随机数,跟是否使用种子等因素无关。而真随机数的生成,听闻有人使用鼠标移动的轨迹,排风扇的噪音大小等物理现象来控制随机数的生成。

实战:
这里以qq音乐为例(谁让网易云下架了杰伦的歌),分析qq音乐的随机播放功能
假设有ABCDEF六首歌,随机播放功能有以下规律:

  1. 从播放第一首音乐开始,点击下一首,接下来的五首歌都不会重复,即随机播放在一轮播放内音乐是不会重复的
  2. 从播放第一首音乐开始,点击上一首,接下来的五首歌都不会重复,即随机播放在一轮播放内音乐是不会重复的
  3. 通过上下切换音乐,在六首歌的范围内,已经播放过的音乐,顺序是固定的,当再进行一次切换时,歌曲是随机的,且回到规律1、2

通过上述规律,可以推测出一种随机的过程:
当播放第一首音乐开始,后台将会把所有歌曲的顺序打乱,并保存歌曲的顺序,如当前歌单顺序是ABCDEF,此时播放音乐C,后台会打乱音乐的顺序:CDABFE,
且C和E收尾相连,形成一个环状,此时点击下一首的顺序将会是DABFE,点击上一首的顺序是EFBAD,上下切换的顺序将会是DABADCEFEFB。
当播放的音乐最后一首是B时,此时点击下一首将不会是C,而是重新排序的一个数组,同理当播放的音乐最后一首是D时,此时点击上一首将不会是C,而是重新排序的一个数组。
知道随机的过程后,问题就落在了怎么去随机歌单,这里就涉及到了shuffle洗牌算法
java的Collections类已经内置提供了shuffle算法,代码如下

    private static Random r;    
    public static void shuffle(List<?> list) {
        Random rnd = r;
        if (rnd == null)
            r = rnd = new Random(); // harmless race.
        shuffle(list, rnd);
    }
    public static void shuffle(List<?> list, Random rnd) {
        int size = list.size();
        if (size < SHUFFLE_THRESHOLD || list instanceof RandomAccess) {
            for (int i=size; i>1; i--)
                swap(list, i-1, rnd.nextInt(i));
        } else {
            Object arr[] = list.toArray();
            for (int i=size; i>1; i--)
                swap(arr, i-1, rnd.nextInt(i));
            ListIterator it = list.listIterator();
            for (int i=0; i<arr.length; i++) {
                it.next();
                it.set(arr[i]);
            }
        }
    }
    public static void swap(List<?> list, int i, int j) {
        final List l = list;
        l.set(i, l.set(j, l.get(i)));
    }
    private static void swap(Object[] arr, int i, int j) {
        Object tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }

可见这里重载了两个shuffle方法,如果不传递Random参数,则默认使用当前时间作为种子
接下来对集合进行替换,这里的洗牌算法是直接对集合本身进行替换,如ABCDEF,首先从F开始,随机与六位中的某一位替换位置,之后是E,与五位中的某一位替换位置,具体与哪一位通过Random的随机数来决定。

最后就是实现播放器随机播放功能的java程序:

创建一个实体类 MusicOrder,用于模拟存储歌单顺序等信息,实际项目应持久化到数据库中,如redis等。

    //歌单
    List<String> order = new ArrayList<>(Arrays.asList("NO1.安静", "NO2.手写的从前", "NO3.以父之名", "NO4.一路向北", "NO5.你好吗"));
    //当前播放下标
    private int currentPlayIndex = 0;
    //最上一首的下标
    private int prevIndex = 0;
    //最下一首的下标
    private int nextIndex = 0;

创建一个工具类MusicShuffleUtil,用于打乱歌单顺序,切歌,播放。

    public static MusicOrder musicOrder = new MusicOrder();
    public static void startMusic(String name){
        //获取歌单
        List<String> order = musicOrder.getOrder();
        if(name != null && name.length()>0){
            int index = order.indexOf(name);
            order.remove(index);
            //洗牌算法打乱除当前音乐的其它音乐顺序
            Collections.shuffle(order);
            //将当前播放的音乐置为第一位
            order.add(0, name);
        }else{
            //洗牌算法打乱全部顺序
            Collections.shuffle(order);
            name = order.get(0);
        }
        //清零
        musicOrder.setCurrentPlayIndex(0);
        musicOrder.setNextIndex(0);
        musicOrder.setPrevIndex(0);
        System.out.println("洗牌后的歌曲顺序:"+musicOrder.getOrder());
        //播放
        playMusic(name);
    }

    public static void nextMusic(){
        int currentPlayIndex = musicOrder.getCurrentPlayIndex()+1;
        if(currentPlayIndex==musicOrder.getOrder().size()){
            currentPlayIndex -= musicOrder.getOrder().size();
        }
        //当前下标大于下一首的最大下标则修改最大下一首下标
        if(currentPlayIndex>musicOrder.getNextIndex()){
            musicOrder.setNextIndex(currentPlayIndex);
        }
        //歌单已播完,从新刷新歌单顺序
        if(currentPlayIndex == musicOrder.getPrevIndex()){
            startMusic("");
        }else{
            musicOrder.setCurrentPlayIndex(currentPlayIndex);
            //播放
            playMusic(musicOrder.getOrder().get(currentPlayIndex));
        }
    }

    public static void prevMusic(){
        int currentPlayIndex = musicOrder.getCurrentPlayIndex()-1;
        if(currentPlayIndex<0){
            currentPlayIndex += musicOrder.getOrder().size();
        }
        //当前下标小于上一首的最小下标则修改最小上一首下标
        if(currentPlayIndex<musicOrder.getPrevIndex()){
            musicOrder.setPrevIndex(currentPlayIndex);
        }
        //歌单已播完,从新刷新歌单顺序
        if(currentPlayIndex == musicOrder.getNextIndex()){
            startMusic("");
        }else{
            musicOrder.setCurrentPlayIndex(currentPlayIndex);
            //播放
            playMusic(musicOrder.getOrder().get(currentPlayIndex));
        }
    }

    public static void playMusic(String name){
        //播放音乐操作
        System.out.println("播放:"+name);
    }

测试:以NO3音乐开始随机播放,控制输入1切换上一首,其它切换下一首

    public static void main(String[] args) {
        //开始播放
        MusicShuffleUtil.startMusic("NO3.以父之名");
        Scanner sc=new Scanner(System.in);
        while(sc.hasNext())
        {
            if("1".equals(sc.next())){
                MusicShuffleUtil.prevMusic();
            }else{
                MusicShuffleUtil.nextMusic();
            }
        }
    }

结果:

项目地址:

https://pan.baidu.com/s/1cFrSBPZv4eh6NV8LpV1ZPg 提取码: xbaq

猜你喜欢

转载自blog.csdn.net/qq_31748587/article/details/84589124
今日推荐