专栏首页自学笔记Data Structure_Visualization排序可视化走迷宫生成迷宫扫雷

Data Structure_Visualization排序可视化走迷宫生成迷宫扫雷

所以代码附上GitHub:https://github.com/GreenArrow2017/DataStructure_Java/tree/master/out/production/DataStructure_Java/ApplicationOfAlgorithm

排序可视化

SelectionSort

选择排序很简单,所有的排序算法在前面的博客都有讲解:

https://www.jianshu.com/p/7fbf8671c742

选择排序很简单,遍历所有元素,查看一下他们的之后最小的元素和当前元素交换即可。模板函数使用上面的swing模板。为了更清楚显示出排序的过程,可以用不同颜色代表排好序和未排好序的。

            int w = canvasWidth / data.N();
            AlgorithmHelper.setColor(graphics2D, AlgorithmHelper.LightBlue);
            for (int i = 0; i < data.N(); i++) {
                if (i < data.orderIndex) {
                    AlgorithmHelper.setColor(graphics2D, AlgorithmHelper.Red);
                } else {
                    AlgorithmHelper.setColor(graphics2D, AlgorithmHelper.Grey);
                }
                if (i == data.currentIndex) {
                    AlgorithmHelper.setColor(graphics2D, AlgorithmHelper.Indigo);
                }
                if (i == data.currentComperent) {
                    AlgorithmHelper.setColor(graphics2D, AlgorithmHelper.LightBlue);
                }
                AlgorithmHelper.fillRectangle(graphics2D, i * w, canvasHeight - data.get(i), w - 1, data.get(i));
            }

        }

Frame的画图函数主要构成部分,其余的都是模板,为了抽象性,所以把selection的数据集中起来形成一个新的类,包括了生成数据等等。

public class SelectionSortData {
    private int[] numbers;
    public int orderIndex = -1;
    public int currentIndex = -1;
    public int currentComperent = -1;

    public SelectionSortData(int N, int randomBound) {
        numbers = new int[N];
        for (int i = 0; i < N; i++) {
            numbers[i] = (int) (Math.random() * randomBound) + 1;
            //System.out.println(numbers[i]);
        }
    }

    public void setData(int orderIndex, int currentComperent, int currentIndex){
        this.currentIndex = currentIndex;
        this.currentComperent = currentComperent;
        this.orderIndex = orderIndex;
    }

    public int N(){
        return numbers.length;
    }

    public int get(int index){
        if (index < 0 || index >= numbers.length){
            throw new IllegalArgumentException("index is illgel!");
        }
        return numbers[index];
    }

    public void swap(int i, int j){
        int t = numbers[i];
        numbers[i] = numbers[j];
        numbers[j] = t;
    }


}

在这个数据类里面有三个属性,分别是已经排好序的索引,当前最小值,当前正在比较的索引。在渲染过程中需要改变就是这几个颜色了。所以动态的效果主要来源就是通过改变着几个值即可。

    private void run() {
        data.setData(0,-1,-1);
        frame.render(data);
        AlgorithmHelper.pause(DELAY);

        for (int i = 0; i < data.N(); i++) {
            int midIndex = i;
            data.setData(i, -1, midIndex);
            frame.render(data);
            AlgorithmHelper.pause(DELAY);

            for (int j = i+1; j < data.N(); j++) {
                data.setData(i, j, midIndex);
                frame.render(data);
                AlgorithmHelper.pause(DELAY);

                if (data.get(j) < data.get(midIndex)){
                    midIndex = j;
                    data.setData(i, j, midIndex);
                    frame.render(data);
                    AlgorithmHelper.pause(DELAY);

                }
            }
            data.swap(i, midIndex);
            data.setData(i+1, -1, -1);
            frame.render(data);
            AlgorithmHelper.pause(DELAY);
        }
        data.setData(data.N(), -1,-1);
        frame.render(data);
        AlgorithmHelper.pause(DELAY);

    }

查看一下效果:

InsertionSort

插入排序也很简单,没有涉及到递归操作等等。每遍历一个元素,看看这个元素和之前比较过的位置是在那里,像打牌的时候插排一样。和之前的查找一样,已经排好序的位置就直接用红色表示,当前对比位置用蓝色表示。首先是画图paintComponent:

            int w = canvasWidth / data.N();
            for (int i = 0; i < data.N(); i++) {
                if (i < data.orderIndex){
                    AlgorithmHelper.setColor(graphics2D, AlgorithmHelper.Red );
                }else {
                    AlgorithmHelper.setColor(graphics2D, AlgorithmHelper.Grey);
                }
                if (i == data.currentIndex){
                    AlgorithmHelper.setColor(graphics2D, AlgorithmHelper.LightBlue);
                }
                AlgorithmHelper.fillRectangle(graphics2D, i * w, canvasHeight - data.get(i), w - 1, data.get(i));
            }
        }

和上面的选择排序差不多。

    private void run() {
        setData(-1, -1);
        for (int i = 0; i < data.N(); i++) {
            setData(i, i);
            for (int j = i; j > 0 && data.get(j) < data.get(j - 1); j--) {
                data.swap(j, j - 1);
                setData(i+1, j-1);
            }
            setData(i, -1);
        }
        setData(data.N(), -1);
    }

    private void setData(int orderIndex, int currentIndex){
        data.orderIndex = orderIndex;
        data.currentIndex = currentIndex;
        frame.render(data);
        AlgorithmHelper.pause(DELAY);
    }

都是常规操作。

MergeSort

归并排序本身的思路,面对一个数组想要让他排序,首先把数组分成两部分,用同样的算法把两边排序,最后归并两边。在划分的时候,划分到不能再划分为止。首先同样要有一个归并的数据类:

public class MergeData {
    private int[] numbers;
    public int l, r;
    public int mergeIndex;

    public MergeData(int N, int randomBound) {
        numbers = new int[N];
        for (int i = 0; i < N; i++) {
            numbers[i] = (int) (Math.random() * randomBound) + 1;
            //System.out.println(numbers[i]);
        }
    }


    public int N(){
        return numbers.length;
    }

    public int get(int index){
        if (index < 0 || index >= numbers.length){
            throw new IllegalArgumentException("index is illgel!");
        }
        return numbers[index];
    }

    public void set(int index, int num){
        if (index < 0 || index >= numbers.length){
            throw new IllegalArgumentException("index is illgel!");
        }
        numbers[index] = num;
    }

    public void swap(int i, int j){
        int t = numbers[i];
        numbers[i] = numbers[j];
        numbers[j] = t;
    }

}

用l和r来表示正在归并的数组范围,mergeIndex表示已经进行归并了的集合。归并整个过程前面的博客有写,不再复述了。

    private void run() {
        setData(-1, -1, -1 );
        Merge(0, data.N()-1);
        setData(0, data.N()-1, -1);
    }

    private void Merge(int l, int r) {
        if (l >= r) {
            return;
        }
        setData(l, r, -1);
        int mid = (l + r) / 2;
        Merge(l, mid);
        Merge(mid + 1, r);
        merge(l, r, mid);
    }

    private void merge(int l, int r, int mid) {
        int[] array = new int[r - l + 1];
        for (int i = l; i <= r; i++) {
            array[i - l] = data.get(i);
        }
        int i = l, j = mid + 1;
        int index = l;
        while (i <= mid && j <= r) {
            if (array[i - l] < array[j - l]) {
                data.set(index, array[i - l]);
                i++;
                index++;
            } else {
                data.set(index, array[j - l]);
                j++;
                index++;
            }
            setData(l, r, index);
        }
        if (i <= mid) {
            for (int k = i; k <= mid; k++) {
                data.set(index, array[k - l]);
                index++;
                setData(l, r, index);
            }
        } else if (j <= r) {
            for (int k = j; k <= r; k++) {
                data.set(index, array[k - l]);
                index++;
                setData(l, r, index);
            }
        }
    }

效果:

QuickSort

快速排序,快速排序是在平均情况下比较快的算法了。每一次把第一个元素作为标定的位置,把这个位置放到合适的位置即可。首先还是需要一个快拍数据类:

public class QuickSortData {
    private int[] numbers;
    public int l, r;
    public int Index;

    public QuickSortData(int N, int randomBound) {
        numbers = new int[N];
        for (int i = 0; i < N; i++) {
            numbers[i] = (int) (Math.random() * randomBound) + 1;
            //System.out.println(numbers[i]);
        }
    }


    public int N(){
        return numbers.length;
    }

    public int get(int index){
        if (index < 0 || index >= numbers.length){
            throw new IllegalArgumentException(index + "index is illgel!");
        }
        return numbers[index];
    }

    public void set(int index, int num){
        if (index < 0 || index >= numbers.length){
            throw new IllegalArgumentException("index is illgel!");
        }
        numbers[index] = num;
    }

    public void swap(int i, int j){
        int t = numbers[i];
        numbers[i] = numbers[j];
        numbers[j] = t;
    }

}

和前面的归并排序一样,l和r用不同的颜色。

    private void run() {
        setData(-1, -1, -1);
        QuickSort(0, data.N() - 1);
        setData(0, data.N() - 1, -1);
    }

    private void QuickSort(int l, int r) {
        if (l >= r) {
            return;
        }
        setData(l, r, -1);
        int mid = partition(l, r);
        QuickSort(l, mid - 1);
        QuickSort(mid + 1, r);
        frame.render(data);
        AlgorithmHelper.pause(DELAY);
    }

    private int partition(int l, int r) {
        int v = data.get(l);
        int i = l + 1;
        int j = r;
        setData(l, r, l);
        while (true) {
            while (i <= r && data.get(i) < v) {
                i++;
            }
            while (j >= l + 1 && data.get(j) > v) {
                j--;
            }
            if (i > j) {
                break;
            }
            data.swap(i, j);
            setData(l, r, l);
            i++;
            j--;
        }
        data.swap(j, l);
        setData(l, r, j);
        return j;
    }

和前面基本一致。

走迷宫

显示迷宫

迷宫生成等等再提,先看一下迷宫的读取和显示。

第一行是行数和列数,代表有101行101列,这个迷宫后面可以使用最小生成树生成。读进一个迷宫:

public class MazeData {
    private char[][] maze;
    private int N, M;
    public static final char ROAD = '#';
    public static final char WALL = ' ';
    public MazeData(String fileName) {
        if (fileName == null) {
            throw new IllegalArgumentException("filename can't be null!");
        }
        Scanner scanner = null;
        try {
            File file = new File(fileName);
            if (!file.exists()) {
                throw new IllegalArgumentException("File is not exist!");
            }
            FileInputStream fileInputStream = new FileInputStream(file);
            scanner = new Scanner(new BufferedInputStream(fileInputStream), "UTF-8");
            String nm = scanner.nextLine();
            String[] nmC = nm.trim().split("\\s+");
            N = Integer.parseInt(nmC[0]);
            M = Integer.parseInt(nmC[1]);
            maze = new char[N][M];
            for (int i = 0; i < N; i++) {
                String line = scanner.nextLine();
                if (line.length() != M) {
                    throw new IllegalArgumentException("Message of file is not completed!");
                }
                for (int j = 0; j < M; j++) {
                    maze[i][j] = line.charAt(j);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (scanner != null) {
                scanner.close();
            }
        }
    }

    public char getMaze(int i, int j) {
        if (!inArea(i, j)) {
            throw new IllegalArgumentException("out of range!");
        }
        return maze[i][j];
    }

    public boolean inArea(int x, int y) {
        return x >= 0 && x < N && y >= 0 && y < M;
    }

    public int N() {
        return N;
    }

    public int M() {
        return M;
    }
}

使用File来获得当前文件,如果没有就要抛出异常。scanner获得输入流之后可以通过读取下一行获得每一行的内容,列数在前面已经提到了,所以要检查每一行是不是M列,不是就没得读了。#就是墙,空格是路,可以设置两个静态变量表示。同时还需要各种辅助函数,比如是否是在整区域里面的,返回当前区域的值等等。然后就是显示函数了:

            int w = canvasWidth / data.M();
            int h = canvasHeight / data.N();
            for (int i = 0; i < data.N(); i++) {
                for (int j = 0; j < data.M(); j++) {
                    if (data.getMaze(i, j) == MazeData.ROAD){
                        AlgorithmHelper.setColor(graphics2D, AlgorithmHelper.LightBlue);
                    }else {
                        AlgorithmHelper.setColor(graphics2D, AlgorithmHelper.White);
                    }
                    AlgorithmHelper.fillRectangle(graphics2D, j * w, i * h, w, h);
                }
            }

墙的宽度自适应,这样整个屏幕刚刚够。#号画浅蓝色,其余的白色。之后就是再控制器里面调用repaint即可。

迷宫问题

白色方块是可以走的路径,红色的就是墙。迷宫的本质就是一个图结构。可以把整个迷宫当成是一个图,而走迷宫的过程就可以等价成是图的遍历。从起始点开始遍历,直到遍历到了某一个终止点即可。如果遍历了所有点都没有得到结果,那么就可以确认无解了。 图的遍历可以分成两种遍历。深度优先遍历和广度优先遍历,一种遍历是按照深度,先往一个方向深了走,没有路了再回头,而广度是先广度走一遍再一层一层下去。首先固定了每一个迷宫的出口和入口位置,从一开始,就需要从相邻的四个方向走迷宫,如果可以就继续,不能就回头,其实就是递归实现。

深度优先

首先还是递归实现,递归比较方便,首先要准备递归函数,和上述的一样,走四个方向,一个一个尝试,如果下一个格子是在这个图里面,是一条路,而且还没有被访问过,那么久可以继续走,否则就需要返回。

    private boolean go(int x, int y) {
        if (!data.inArea(x, y)) {
            throw new IllegalArgumentException("Paramenter is illgel!");
        }
        data.visited[x][y] = true;
        setData(x, y, true);
        if (x == data.getExitX() && y == data.getExitY()) {
            return true;
        } else {
            for (int i = 0; i < 4; i++) {
                int nexX = x + direction[i][0];
                int nexY = y + direction[i][1];
                if (data.inArea(nexX, nexY) &&
                        data.getMaze(nexX, nexY) == MazeData.ROAD &&
                        !data.visited[nexX][nexY]) {
                    if (go(nexX, nexY)) {
                        return true;
                    }
                }

            }
            setData(x, y, false);
            return false;
        }
    }

如果四个点都尝试过了,都是走不了的,那么还需要消除画的格子。相对来说还是比较简单的。再消除格子上这个步骤对于递归来说是相对方便,因为再回溯的过程中是有保留之前的点的信息的,所以相对简单。

这就是生成的结果了。 如果是非递归,用栈就可以模拟,因为递归本身就是用栈实现的。对于删除无用路径的情况,其实有点难,因为无用的路径是直接丢弃的,先前的递归可以是因为递归的栈保留了更加多的内容,而这里只是保留了点而已。

    private boolean go_iteration() {
        Stack<Position> stack = new Stack<>();
        Position entrance = new Position(data.getEntanceX(), data.getEntanceY());
        stack.push(entrance);
        data.visited[entrance.getX()][entrance.getY()] = true;
        while (!stack.isEmpty()) {
            Position position = stack.pop();
            setData(position.getX(), position.getY(), true);
            for (int i = 0; i < 4; i++) {
                int newX = position.getX() + direction[i][0];
                int newY = position.getY() + direction[i][1];

                if (newX == data.getExitX() && newY == data.getExitY()) {
                    setData(newX, newY, true);
                    return true;
                }

                Position newPosition = new Position(newX, newY, position);
                if (data.inArea(newPosition.getX(), newPosition.getY()) &&
                        data.getMaze(newPosition.getX(), newPosition.getY()) == MazeData.ROAD
                        && !data.visited[newPosition.getX()][newPosition.getY()]) {
                    stack.push(newPosition);
                    data.visited[newPosition.getX()][newPosition.getY()] = true;
                }
            }
        }
        return false;
    }

广度优先

广度和深度在搜索策略上是不同的。深度是走到死路才回头,广度是对于每一次都是齐头并进。和遍历的深度优先的区别就是在于他们的数据结构不一样,一个是队列,一个是栈,其他的基本差不多。

    private boolean go_level() {
        Queue<Position> queue = new LinkedList<>();
        Position position = new Position(data.getEntanceX(), data.getEntanceY());
        queue.add(position);
        data.visited[position.getX()][position.getY()] = true;
        while (!queue.isEmpty()) {
            Position position1 = queue.poll();
            setData(position1.getX(), position1.getY(), true);
            for (int i = 0; i < 4; i++) {
                int newX = position1.getX() + direction[i][0];
                int newY = position1.getY() + direction[i][1];

                if (newX == data.getExitX() && newY == data.getExitY()) {
                    findPath(position1);
                    setData(newX, newY, true);
                    return true;
                }
                Position newPosition = new Position(newX, newY, position1);


                if (data.inArea(newPosition.getX(), newPosition.getY()) &&
                        data.getMaze(newPosition.getX(), newPosition.getY()) == MazeData.ROAD
                        && !data.visited[newPosition.getX()][newPosition.getY()]) {
                    queue.add(newPosition);
                    data.visited[newPosition.getX()][newPosition.getY()] = true;
                }
            }
        }
        return false;
    }

如果迷宫有很多个解,深度优先遍历那么久只会搜索到第一个碰到的解,搜索到的解那么就是一个随缘排序出来,广度优先就是会查找最短的路径。广度优先可以找到无全图的最短路径。深度和广度的非递归差不多,只是使用的数据结构不同而已。

生成迷宫

刚刚是走迷宫,刚刚生成的那个用例其实就是生成的迷宫。对于一个迷宫,只有一个入口一个出口,为了简单化,入口就是第二行的第一个口,出口是倒数第二行的第一个口。而且只有一个解,并且路径是连续的,有些游戏里面的迷宫是有传送点的,改变起来也很简单。 首先迷宫其实就是一棵树,每一个点都会有分支,任何一个叶子或者是节点都可以作为是一个入口,生成一个迷宫其实就是一个生成树的过程。之前在数据结构有提到过一个最小生成树,但是由于是一个随机的迷宫,所以应该是随机生成树。无论是什么树,都是基于树的。而图的遍历结果就是一颗树,每一个节点只是访问一次,且没有环,深度优先遍历的结果是一颗深度优先树,广度优先的结果是广度优先树。可以先把一张画布分成很多很多小格子,然后每隔一个格子就挖空一个点,没有挖空点的都是墙,用一种遍历方法来遍历这些点所生成的树就是一个迷宫了。但是这样的迷宫其实带有偏见的,随机性不高,所以可以在选择点的进行遍历的时候进行随机选择。 @@@@@ @ _@ _ @ @@@@@ @ _ @ _@ @@@@@ 可以看的出无论怎么看,行和列一定要是基数,限制还是蛮多的。深度优先生成迷宫其实和之前的差不多,没有上面打的差别。首先是要得到一个格子布。然后通过深度遍历把格子全部连接起来。递归实现:

    private void run() {
        setData(-1, -1);
        go(data.getEntranceX(), data.getEntranceY() + 1);
        setData(-1, -1);
    }

    private void go(int x, int y) {
        if (!data.inArea(x, y)) {
            throw new IllegalArgumentException("x or y is illegal!");
        }
        data.visited[x][y] = true;
        for (int i = 0; i < 4; i++) {
            int newX = x + direction[i][0] * 2;
            int newY = y + direction[i][1] * 2;
            if (data.inArea(newX, newY) &&
                    !data.visited[newX][newY]) {
                setData(x + direction[i][0], y + direction[i][1]);
                go(newX, newY);
            }
        }
    }

这里要注意每两个格子之间的差距是2,因为中间都隔着一堵墙。go的参数是开始的参数,开始不能直接从入口开始,因为入口是我们新加的,不符合迷宫节点的规矩,比如每一个格子相差两个点,但是出口和第一个点差一个而已。

            int newX = x + direction[i][0] * 2;
            int newY = y + direction[i][1] * 2;

乘上2的原因就是因为两个格子之间相差了2。而渲染都放在了setData里面处理。渲染的点就不是我们newX和newY了,因为那两个点本来就是road,渲染的应该是两个点之间的墙,所以是加1的。和前面深度搜索对比区别就是,这里没有终止点,不是到了exit就退出,事实上是不一定有解的。因为我们这里是要全部生成而不是生成到了终点就停止,所以是无终止条件的。但是for循环里面是隐含了的。还有一个就是条件确认是不是一条路,这个决策是不必要的,因为就要生成路的。但是这样导致的迷宫很无随机性:

因为方向都是一样的,从左上右下这样。 这是递归方法,非递归方法

    private void go_iterator(){
        Stack<Position> stack = new Stack<>();
        Position firstPosition = new Position(data.getEntranceX(), data.getEntranceY() + 1);
        stack.push(firstPosition);
        data.visited[firstPosition.getX()][firstPosition.getY()] = true;
        while (!stack.isEmpty()){
            Position position = stack.pop();
            for (int i = 0; i < 4; i++) {
                int newX = position.getX() + direction[i][0] * 2;
                int newY = position.getY() + direction[i][1] * 2;
                if (data.inArea(newX, newY) &&
                        !data.visited[newX][newY]) {
                    setData(position.getX() + direction[i][0], position.getY() + direction[i][1]);
                    stack.push(new Position(newX, newY));
                    data.visited[newX][newY] = true;
                }
            }
        }
    }

不一样的原因就是因为在加入栈的时候就已经上下左右看了,看看能不能走,走就直接把墙消去了,所以会出现锯齿型。至于大体的trend的不一样的因为两个方向是相反的。 广度遍历:之前提到过了和深度遍历差不多:

    private void go_level(){
        LinkedList<Position> stack = new LinkedList<>();
        Position firstPosition = new Position(data.getEntranceX(), data.getEntranceY() + 1);
        stack.addLast(firstPosition);
        data.visited[firstPosition.getX()][firstPosition.getY()] = true;
        while (stack.size() != 0){
            Position position = stack.removeFirst();
            for (int i = 0; i < 4; i++) {
                int newX = position.getX() + direction[i][0] * 2;
                int newY = position.getY() + direction[i][1] * 2;
                if (data.inArea(newX, newY) &&
                        !data.visited[newX][newY]) {
                    setData(position.getX() + direction[i][0], position.getY() + direction[i][1]);
                    stack.addLast(new Position(newX, newY));
                    data.visited[newX][newY] = true;
                }
            }
        }

    }

生成的图像都很规则。 一个很重要的原因的因为我们在数据结构的选择过程中都是栈和队列,可预期性太强了。我们只需要在数据结构中加上随机性就好了。出队或者是删除都是随机队列。

public class RandomQueue<E> {
    private ArrayList<E> queue;

    public RandomQueue() {
        queue = new ArrayList<>();
    }

    public void add(E e) {
        queue.add(e);
    }

    public E remove() {
        if (queue.size() == 0) {
            throw new IllegalArgumentException("no elements!");
        }
        int randomIndex = (int) (Math.random() * queue.size());
        E Ele = queue.get(randomIndex);
        queue.set(randomIndex, queue.get(queue.size() - 1));
        queue.remove(queue.size() - 1);
        return Ele;
    }

    public boolean isEmpty(){
        return queue.isEmpty();
    }
}

在广度优先里面把队列改一下:

这样就有一定的随机性了。可以看到在很多空白的小格子很容易让别人猜到我们是怎么生成的。所以可以加上如果没有遍历到的格子全部变黑色。

    private void go_level(){
        RandomQueue<Position> stack = new RandomQueue<>();
        Position firstPosition = new Position(data.getEntranceX(), data.getEntranceY() + 1);
        stack.add(firstPosition);
        data.openMinst(firstPosition.getX(), firstPosition.getY());
        data.visited[firstPosition.getX()][firstPosition.getY()] = true;
        while (!stack.isEmpty()){
            Position position = stack.remove();
            for (int i = 0; i < 4; i++) {
                int newX = position.getX() + direction[i][0] * 2;
                int newY = position.getY() + direction[i][1] * 2;
                if (data.inArea(newX, newY) &&
                        !data.visited[newX][newY]) {
                    data.openMinst(newX, newY);
                    setData(position.getX() + direction[i][0], position.getY() + direction[i][1]);
                    stack.add(new Position(newX, newY));
                    data.visited[newX][newY] = true;
                }
            }
        }

    }

但是其实还有一个问题,很多时候这个迷宫的路径顺序是都是斜向下的趋势,所以有时候是可以猜到怎么走的。可以通过改进随机队列:

    public void add(E e) {
        if (Math.random() < 0.5){
            queue.addFirst(e);
        }else {
            queue.addLast(e);
        }
    }

    public E remove() {
        if (queue.size() == 0) {
            throw new IllegalArgumentException("no elements!");
        }
//        int randomIndex = (int) (Math.random() * queue.size());
//        E Ele = queue.get(randomIndex);
//        queue.set(randomIndex, queue.get(queue.size() - 1));
//        queue.remove(queue.size() - 1);
//        return Ele;
        if (Math.random() < 0.5){
            return queue.removeFirst();
        }else {
            return queue.removeLast();
        }
    }

这个时候随机性就更强了:

扫雷

一开始的时候,会展开一片区域,这片区域随机找一个点点开,如果没有雷,那么就会展开一片区域,

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Data Structure_Visualization概率模拟排序可视化走迷宫生成迷宫

    概率问题对于人脑来说很多时候都是反直觉的,所以有时候得到的结果并不是这么完美。首先来看一个分钱问题。假设房间里面有100个人,每个人都有100元钱,他们在玩一个...

    西红柿炒鸡蛋
  • Data Structure_Visualization

    选择排序很简单,遍历所有元素,查看一下他们的之后最小的元素和当前元素交换即可。模板函数使用上面的swing模板。为了更清楚显示出排序的过程,可以用不同颜色代表排...

    西红柿炒鸡蛋
  • Random Forest

    随机森林还是没有脱离聚合模型这块,之前学过两个aggregation model,bagging和decision tree,一个是边learning边unif...

    西红柿炒鸡蛋
  • Java代码常见的十种错误

    每一个程序员在编写代码的过程中都免不了出现错误或是小的失误,这些小的错误和失误往往使得程序员还得返工。那么,如何才能尽量避免这些错误的发生呢?笔者总结只有在日常...

    庞小明
  • 05-树7 堆中的路径 (25分)

    将一系列给定数字插入一个初始为空的小顶堆H[]。随后对任意给定的下标i,打印从H[i]到根结点的路径。

    AI那点小事
  • 数据结构基础-栈和队列

    栈是一个有序线性表,只能在表的一端(成为栈顶,top)执行插入和删除操作。最后插入的元素将第一个被删除。所以栈也称为后进先出(Last In First Out...

    1025645
  • 数据科学家易犯的十大编码错误,你中招了吗?

    我是一名高级数据科学家,在 Stackoverflow 的 python 编码中排前 1%,而且还与众多(初级)数据科学家一起工作。下文列出了我常见到的 10 ...

    统计学家
  • 数据科学家易犯的十大编码错误,你中招了吗?

    我是一名高级数据科学家,在 Stackoverflow 的 python 编码中排前 1%,而且还与众多(初级)数据科学家一起工作。下文列出了我常见到的 10 ...

    机器之心
  • java基础多线程之共享数据

    java基础巩固笔记5-多线程之共享数据 线程范围内共享数据 ThreadLocal类 多线程访问共享数据 几种方式 本文主要总结线程共享数据的相关知...

    xiangzhihong
  • 委托学习过程及委托、Lambda表达式和匿名方法的关系总结及事件总结

    委托是一个类,它定义了方法的类型,使得可以将方法当作另一个方法的参数来进行传递,这种将方法动态地赋给参数的做法,可以避免在程序中大量使用If-Else(Swit...

    wfaceboss

扫码关注云+社区

领取腾讯云代金券