撲克牌洗牌問題

問題來源於<知乎>用java寫出算法:54張撲克,分成上下兩等份有規律的洗牌,多少次可以返回初始值
代碼思路源於<知乎> @Tim Chen


  • 相關代碼:OneTest.java
package pers.mine.test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class OneTest {

    public static void main(String[] args) {
        shufflePoker(54);
    }
    /**
     * 計算洗牌多少次可以回到初始值  奇數-偶數效果一樣 shufflePoker(53)<==>shufflePoker(54)
     * 洗牌規則:後半部分依次間隔插入前半部分 123abc --->1a2b3c
     * @param n 牌數
     */
    public static void shufflePoker(int n){
        if(n<2){return;}
        int[] poker=new int[n];
        //初始化poker
        for(int i=0;i<n;i++){
            poker[i]=i+1;
        }
        //洗一次  54  的話從1-27 28-54  53的話1-27 28-53   索引27
        int halfIndex=(n+1)/2;  //撲克後半部分起始點索引
        int[] newPoker=new int[n];
        for(int i=0;i<n;i++){
            if(i%2==0){
                newPoker[i]=poker[i/2];
            }else{
                newPoker[i]=poker[halfIndex+(i/2)];
            }
        }
        //System.out.println(Arrays.toString(newPoker));
        //存放每一個環
        List<List<Integer>> rings=new ArrayList<List<Integer>>();
        /**標記數組-->觀察環是否查找完畢 索引 0 表示第一張牌 依次到53表示最後一張 
         *  值爲0表示還沒有被包含到環中,已包含則值爲1 
         */
        int[] flagArr=new int[n]; 
        int nextRingStartNum;   //下一個ring開始數字
        while((nextRingStartNum=nextRingStartNum(flagArr))!=-1){
            List<Integer> one=getRing(poker, newPoker, nextRingStartNum, flagArr);
            rings.add(one);
        }
        //存放每個ring大小
        List<Integer> ringLengths=new ArrayList<Integer>();
        //獲取每個ring大小然後計算最大公倍數
        System.out.println("打印所有環:");
        for(List<Integer> one:rings){
            System.out.print("\t");
            for(int x:one){
                System.out.printf("%-3d - ", x);
            }
            System.out.print(one.get(0));
            System.out.println();
            ringLengths.add(one.size());
        }

        System.out.println("每個環的長度:"+ringLengths.toString());
        //計算最大公倍數
        System.out.println("最小洗牌次數:"+nlcm(ringLengths));

    }
    //返回下一個Ring開始的數字  -1代表沒有了 
    public static int nextRingStartNum(int[] flagArr){
        int length=flagArr.length;
        for(int i=0;i<length;i++){
            if(flagArr[i]==0){
                return i+1;
            }
        }
        return -1;
    }
    //從指定數字開始獲取一個環 ,返回環長度  並標記走過的 牌號
    public static List<Integer> getRing(int[] oldArr,int[] newArr,int startNum,int[] flagArr){
        List<Integer> ring=new ArrayList<Integer>();
        //獲取開始時索引位置
        int startIndex=getIndex(oldArr,startNum);

        int nowNum=startNum;
        int nextIndex=0;
        do{
            //記住走過的數字
            flagArr[nowNum-1]=1;
            //加入環
            ring.add(nowNum);
            //查詢當前牌號的下一落腳索引
            nextIndex=getIndex(newArr,nowNum);
            //獲取oldArr中【落腳點索引】處數字,並設置爲當前數字
            nowNum=oldArr[nextIndex];
        }while(startIndex!=nextIndex);
        //System.out.println(ring);
        return ring;
    }
    //遍歷獲取數組中指定數字的索引
    public static int getIndex(int[] arr,int key){
        int length = arr.length;
        for(int i=0;i<length;i++){
            if(arr[i]==key){
                return i;
            }
        }
        return -1;
    }
    //n個數的最大公倍數---先計算兩個的,再慢慢向後推移
    public static long  nlcm(List<Integer> ls){
        if(ls==null||ls.size()<2){
            return 0; 
        }
        int size=ls.size();
        //兩個環長度的公倍數 a*b/gcd(a,b)
        long oneCM=ls.get(0);

        for(int i=1;i<size;i++){
            oneCM=ls.get(i)*oneCM/gcd(oneCM,ls.get(i));
        }
        return oneCM;
    }
    //兩個數的最大公約數--輾轉相除法
    public static long gcd(long x, long y)  
    {  
        long temp=x%y;
        while(temp!=0){
            x=y;
            y=temp;
            temp=x%y;
        }
        return y;
    }  

}
  • 測試結果
    這裏寫圖片描述
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章