算法可视化
- Java GUI可视化
运动的小球
小球实体类
@AllArgsConstructor public class Circle { @Getter @Setter private int x; //圆心横坐标 @Getter @Setter private int y; //圆心纵坐标 @Getter private int r; //半径 @Getter @Setter private int vx; //横坐标运动速度 @Getter @Setter private int vy; //纵坐标运动速度 @Getter @Setter private boolean isFilled; //是否是一个实心圆 /** * 小球的移动 * @param minx * @param miny * @param maxx * @param maxy */ public void move(int minx,int miny,int maxx,int maxy) { x += vx; y += vy; checkCollision(minx,miny,maxx,maxy); } /** * 小球的边缘碰撞 * @param minx * @param miny * @param maxx * @param maxy */ private void checkCollision(int minx,int miny,int maxx,int maxy) { if (x - r < minx) { x = r; vx = -vx; } if (x + r >= maxx) { x = maxx - r; vx = -vx; } if (y - r < miny) { y = r; vy = -vy; } if (y + r >= maxy) { y = maxy - r; vy = -vy; } } /** * 点是否在圆中 * @param p * @return */ public boolean contain(Point p) { return (x - p.x) * (x - p.x) + (y - p.y) * (y - p.y) <= r * r; } }
Java窗体类
@Getter public class AlgoFrame extends JFrame { private class AlgoCanvas extends JPanel { public AlgoCanvas() { super(true); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2d = (Graphics2D) g; RenderingHints hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2d.addRenderingHints(hints); AlgoVisHelper.setStrokeWidth(g2d,1); AlgoVisHelper.setColor(g2d,Color.RED); for (Circle circle : circles) { if (!circle.isFilled()) { AlgoVisHelper.strokeCircle(g2d,circle.getX(),circle.getY(),circle.getR()); }else { AlgoVisHelper.fillCircle(g2d,circle.getX(),circle.getY(),circle.getR()); } } } @Override public Dimension getPreferredSize() { return new Dimension(canvasWidth,canvasHeight); } } private int canvasWidth; private int canvasHeight; private Circle[] circles; public AlgoFrame(String title,int canvasWidth,int canvasHeight) { super(title); this.canvasWidth = canvasWidth; this.canvasHeight = canvasHeight; AlgoCanvas canvas = new AlgoCanvas(); canvas.setOpaque(true); this.setContentPane(canvas); this.pack(); this.setResizable(false); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setVisible(true); } public AlgoFrame(String title) { this(title,1024,768); } public void render(Circle[] circles) { this.circles = circles; this.repaint(); } }
绘制工具类
public class AlgoVisHelper { private AlgoVisHelper() { } public static void setStrokeWidth(Graphics2D g2d,int w) { int strokeWidth = w; g2d.setStroke(new BasicStroke(strokeWidth,BasicStroke.CAP_ROUND,BasicStroke.JOIN_ROUND)); } public static void setColor(Graphics2D g2d,Color color) { g2d.setColor(color); } public static void strokeCircle(Graphics2D g2d,int x,int y,int r) { Ellipse2D circle = new Ellipse2D.Double(x - r,y - r,2 * r,2 * r); g2d.draw(circle); } public static void fillCircle(Graphics2D g2d,int x,int y,int r) { Ellipse2D circle = new Ellipse2D.Double(x - r,y - r,2 * r,2 * r); g2d.fill(circle); } public static void pause(int t) { try { Thread.sleep(t); } catch (InterruptedException e) { e.printStackTrace(); } } }
可视化显示器类
public class AlgoVisualizer { private class AlgoKeyListener extends KeyAdapter { @Override public void keyReleased(KeyEvent e) { if (e.getKeyChar() == ' ') { isAnimated = !isAnimated; } } } private class AlgoMouseListener extends MouseAdapter { @Override public void mousePressed(MouseEvent e) { e.translatePoint(0,-(frame.getBounds().height - frame.getCanvasHeight())); Stream.of(circles).filter(circle -> circle.contain(e.getPoint())) .forEach(circle -> circle.setFilled(!circle.isFilled())); } } private Circle[] circles; private AlgoFrame frame; private boolean isAnimated = true; public AlgoVisualizer(int sceneWidth,int sceneHeight,int N) { circles = new Circle ; int R = 50; for (int i = 0;i < N;i++) { int x = (int) (Math.random() * (sceneWidth - 2 * R)) + R; int y = (int) (Math.random() * (sceneHeight - 2 * R)) + R; int vx = (int) (Math.random() * 11) - 5; int vy = (int) (Math.random() * 11) - 5; circles[i] = new Circle(x,y,R,vx,vy,false); } EventQueue.invokeLater(() -> { frame = new AlgoFrame("欢迎",sceneWidth,sceneHeight); frame.addKeyListener(new AlgoKeyListener()); frame.addMouseListener(new AlgoMouseListener()); new Thread(() -> { run(); }).start(); }); } private void run() { while (true) { frame.render(circles); AlgoVisHelper.pause(20); if (isAnimated) { Stream.of(circles) .forEach(circle -> circle.move(0, 0, frame.getCanvasWidth(), frame.getCanvasHeight())); } } } }
main方法
public class FrameMain { public static void main(String[] args) { int sceneWidth = 800; int sceneHeight = 800; int N = 10; AlgoVisualizer visualizer = new AlgoVisualizer(sceneWidth,sceneHeight,N); } }
显示效果
这里面有两个交互事件,一个是点击空格暂停,一个是鼠标点到圆的内部的时候变换颜色。
- 绘制模版
根据以上的圆球的代码,我们将其抽象成一个以后用于填充各种算法的绘制模版,根据MVC的原理
显示层
@Getter public class AlgoFrame extends JFrame { private class AlgoCanvas extends JPanel { public AlgoCanvas() { //双缓存 super(true); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2d = (Graphics2D) g; //抗锯齿 RenderingHints hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2d.addRenderingHints(hints); //具体绘制 // TODO: 绘制自己的数据data } @Override public Dimension getPreferredSize() { return new Dimension(canvasWidth,canvasHeight); } } private int canvasWidth; private int canvasHeight; //设置自己的数据 private Object data; public AlgoFrame(String title,int canvasWidth,int canvasHeight) { super(title); this.canvasWidth = canvasWidth; this.canvasHeight = canvasHeight; AlgoCanvas canvas = new AlgoCanvas(); canvas.setOpaque(true); this.setContentPane(canvas); this.pack(); this.setResizable(false); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setVisible(true); } public AlgoFrame(String title) { this(title,1024,768); } public void render(Object data) { this.data = data; this.repaint(); } }
控制层
public class AlgoVisualizer { // TODO: 根据情况决定是否实现键盘鼠标交互事件监听器类 private class AlgoKeyListener extends KeyAdapter { } private class AlgoMouseListener extends MouseAdapter { } //创建自己的数据 private Object data; //数据 private AlgoFrame frame; //视图 public AlgoVisualizer(int sceneWidth,int sceneHeight,int N) { //初始化数据 // TODO: 初始化数据 //初始化视图 EventQueue.invokeLater(() -> { frame = new AlgoFrame("欢迎",sceneWidth,sceneHeight); //根据情况决定是否加入键盘鼠标监听器 frame.addKeyListener(new AlgoKeyListener()); frame.addMouseListener(new AlgoMouseListener()); new Thread(() -> { run(); }).start(); }); } /** * 动画逻辑 */ private void run() { // TODO: 编写自己的动画逻辑 } }
显示工具类
public class AlgoVisHelper { private AlgoVisHelper() { } public static final Color Red = new Color(0xF44336); public static final Color Pink = new Color(0xE91E63); public static final Color Purple = new Color(0x9C27B0); public static final Color DeepPurple = new Color(0x673AB7); public static final Color Indigo = new Color(0x3F51B5); public static final Color Blue = new Color(0x2196F3); public static final Color LightBlue = new Color(0x03A9F4); public static final Color Cyan = new Color(0x00BCD4); public static final Color Teal = new Color(0x009688); public static final Color Green = new Color(0x4CAF50); public static final Color LightGreen = new Color(0x8BC34A); public static final Color Lime = new Color(0xCDDC39); public static final Color Yellow = new Color(0xFFEB3B); public static final Color Amber = new Color(0xFFC107); public static final Color Orange = new Color(0xFF9800); public static final Color DeepOrange = new Color(0xFF5722); public static final Color Brown = new Color(0x795548); public static final Color Grey = new Color(0x9E9E9E); public static final Color BlueGrey = new Color(0x607D8B); public static final Color Black = new Color(0x000000); public static final Color Whitr = new Color(0xFFFFFF); /** * 设置线条宽度 * @param g2d * @param w */ public static void setStrokeWidth(Graphics2D g2d,int w) { int strokeWidth = w; g2d.setStroke(new BasicStroke(strokeWidth,BasicStroke.CAP_ROUND,BasicStroke.JOIN_ROUND)); } /** * 设置颜色 * @param g2d * @param color */ public static void setColor(Graphics2D g2d,Color color) { g2d.setColor(color); } /** * 绘制空心圆形 * @param g2d * @param x * @param y * @param r */ public static void strokeCircle(Graphics2D g2d,int x,int y,int r) { Ellipse2D circle = new Ellipse2D.Double(x - r,y - r,2 * r,2 * r); g2d.draw(circle); } /** * 绘制实心圆形 * @param g2d * @param x * @param y * @param r */ public static void fillCircle(Graphics2D g2d,int x,int y,int r) { Ellipse2D circle = new Ellipse2D.Double(x - r,y - r,2 * r,2 * r); g2d.fill(circle); } /** * 绘制空心矩形 * @param g2d * @param x * @param y * @param w * @param h */ public static void strokeRectangle(Graphics2D g2d,int x,int y,int w,int h) { Rectangle2D rectangle = new Rectangle2D.Double(x,y,w,h); g2d.draw(rectangle); } /** * 绘制实心矩形 * @param g2d * @param x * @param y * @param w * @param h */ public static void fillRectangel(Graphics2D g2d,int x,int y,int w,int h) { Rectangle2D rectangle = new Rectangle2D.Double(x,y,w,h); g2d.fill(rectangle); } public static void pause(int t) { try { Thread.sleep(t); } catch (InterruptedException e) { e.printStackTrace(); } } /** * 导入图片 * @param g2d * @param x * @param y * @param imageURL */ public static void putImage(Graphics2D g2d,int x,int y,String imageURL) { ImageIcon icon = new ImageIcon(imageURL); Image image = icon.getImage(); g2d.drawImage(image,x,y,null); } /** * 绘制文字 * @param g2d * @param text * @param centerx * @param centery */ public static void drawText(Graphics2D g2d,String text,int centerx,int centery) { if (text == null) { throw new IllegalArgumentException("文字为空"); } FontMetrics metrics = g2d.getFontMetrics(); int w = metrics.stringWidth(text); int h = metrics.getDescent(); g2d.drawString(text,centerx - w / 2,centery + h); } }
- 一个有意思的分钱问题
房间里有100个人,每个人都有100元钱,他们在玩一个游戏。每轮游戏中,每个人都要拿出1元钱随机给另一个人,最后这100个人的财富分布是怎样的?
通过之前的模版进行修改。
显示层
@Getter public class AlgoFrame extends JFrame { private class AlgoCanvas extends JPanel { public AlgoCanvas() { //双缓存 super(true); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2d = (Graphics2D) g; //抗锯齿 RenderingHints hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2d.addRenderingHints(hints); //具体绘制 // TODO: 绘制自己的数据data AlgoVisHelper.setColor(g2d,AlgoVisHelper.Blue); int w = canvasWidth / money.length; for (int i = 0;i < money.length;i++) { AlgoVisHelper.fillRectangel(g2d,i * w + 1,canvasHeight - money[i], w - 1,money[i]); } } @Override public Dimension getPreferredSize() { return new Dimension(canvasWidth,canvasHeight); } } private int canvasWidth; private int canvasHeight; //设置自己的数据 private int[] money; public AlgoFrame(String title,int canvasWidth,int canvasHeight) { super(title); this.canvasWidth = canvasWidth; this.canvasHeight = canvasHeight; AlgoCanvas canvas = new AlgoCanvas(); canvas.setOpaque(true); this.setContentPane(canvas); this.pack(); this.setResizable(false); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setVisible(true); } public AlgoFrame(String title) { this(title,1024,768); } public void render(int[] money) { this.money = money; this.repaint(); } }
控制层
public class AlgoVisualizer { //创建自己的数据 private int[] money; //数据 private AlgoFrame frame; //视图 private static int DELAY = 10; public AlgoVisualizer(int sceneWidth,int sceneHeight) { //初始化数据 // TODO: 初始化数据 money = new int[100]; for (int i = 0;i < money.length;i++) { money[i] = 100; } //初始化视图 EventQueue.invokeLater(() -> { frame = new AlgoFrame("欢迎",sceneWidth,sceneHeight); new Thread(() -> { run(); }).start(); }); } /** * 动画逻辑 */ private void run() { // TODO: 编写自己的动画逻辑 while (true) { frame.render(money); AlgoVisHelper.pause(DELAY); for (int i = 0;i < money.length;i++) { if (money[i] > 0) { int j = (int) (Math.random() * money.length); money[i] -= 1; money[j] += 1; } } } } }
main方法
public class MoneyMain { public static void main(String[] args) { int sceneWidth = 1000; int sceneHeight = 800; AlgoVisualizer visualizer = new AlgoVisualizer(sceneWidth,sceneHeight); } }
图像显示
加快该进程,并进行排序
public class AlgoVisualizer { //创建自己的数据 private int[] money; //数据 private AlgoFrame frame; //视图 private static int DELAY = 10; public AlgoVisualizer(int sceneWidth,int sceneHeight) { //初始化数据 // TODO: 初始化数据 money = new int[100]; for (int i = 0;i < money.length;i++) { money[i] = 100; } //初始化视图 EventQueue.invokeLater(() -> { frame = new AlgoFrame("欢迎",sceneWidth,sceneHeight); new Thread(() -> { run(); }).start(); }); } /** * 动画逻辑 */ private void run() { // TODO: 编写自己的动画逻辑 while (true) { Arrays.sort(money); frame.render(money); AlgoVisHelper.pause(DELAY); for (int k = 0;k < 50;k++) { for (int i = 0; i < money.length; i++) { if (money[i] > 0) { int j = (int) (Math.random() * money.length); money[i] -= 1; money[j] += 1; } } } } } }
图像显示
这样我们就可以看到,经过一段时间后,整个屋子的人的财富分配情况。
现在我们把它调整为允许为负值
@Getter public class AlgoFrame extends JFrame { private class AlgoCanvas extends JPanel { public AlgoCanvas() { //双缓存 super(true); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2d = (Graphics2D) g; //抗锯齿 RenderingHints hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2d.addRenderingHints(hints); //具体绘制 // TODO: 绘制自己的数据data int w = canvasWidth / money.length; for (int i = 0;i < money.length;i++) { if (money[i] > 0) { AlgoVisHelper.setColor(g2d, AlgoVisHelper.Blue); AlgoVisHelper.fillRectangel(g2d, i * w + 1, canvasHeight / 2 - money[i], w - 1, money[i]); }else if (money[i] < 0) { AlgoVisHelper.setColor(g2d, AlgoVisHelper.Red); AlgoVisHelper.fillRectangel(g2d, i * w + 1, canvasHeight / 2, w - 1, -money[i]); } } } @Override public Dimension getPreferredSize() { return new Dimension(canvasWidth,canvasHeight); } } private int canvasWidth; private int canvasHeight; //设置自己的数据
public class AlgoVisualizer { //创建自己的数据 private int[] money; //数据 private AlgoFrame frame; //视图 private static int DELAY = 10; public AlgoVisualizer(int sceneWidth,int sceneHeight) { //初始化数据 // TODO: 初始化数据 money = new int[100]; for (int i = 0;i < money.length;i++) { money[i] = 100; } //初始化视图 EventQueue.invokeLater(() -> { frame = new AlgoFrame("欢迎",sceneWidth,sceneHeight); new Thread(() -> { run(); }).start(); }); } /** * 动画逻辑 */ private void run() { // TODO: 编写自己的动画逻辑 while (true) { Arrays.sort(money); frame.render(money); AlgoVisHelper.pause(DELAY); for (int k = 0;k < 50;k++) { for (int i = 0; i < money.length; i++) { // if (money[i] > 0) { int j = (int) (Math.random() * money.length); money[i] -= 1; money[j] += 1; // } } } } } }
图像显示
- 蒙特卡洛方法
蒙特卡洛方法是一种统计学的方法;是一种模拟。
蒙特卡洛模拟是二战期间,为解决原子弹研制工作中,裂变物质的中子随机扩散问题,美国数学家冯诺伊曼和乌拉姆等提出的一种统计方法,代号:蒙特卡洛。
通过大量的随机样本,去了解一个系统,进而得到所要计算的值。
求PI的值
这里我们依然修改之前的模版
圆的实体类
@AllArgsConstructor public class Circle { @Getter private int x; //圆心横坐标 @Getter private int y; //圆心纵坐标 @Getter private int r; //半径 /** * 点是否在圆中 * @param p * @return */ public boolean contain(Point p) { return Math.pow(p.getX() - x,2) + Math.pow(p.getY() - y,2) <= r * r; } }
视图层
@Getter public class AlgoFrame extends JFrame { private class AlgoCanvas extends JPanel { public AlgoCanvas() { //双缓存 super(true); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2d = (Graphics2D) g; //抗锯齿 RenderingHints hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2d.addRenderingHints(hints); //具体绘制 // TODO: 绘制自己的数据data AlgoVisHelper.setStrokeWidth(g2d,3); AlgoVisHelper.setColor(g2d,AlgoVisHelper.Blue); AlgoVisHelper.strokeCircle(g2d,circle.getX(),circle.getY(),circle.getR()); for (int i = 0;i < points.size();i++) { Point p = points.get(i); if (circle.contain(p)) { AlgoVisHelper.setColor(g2d,AlgoVisHelper.Red); }else { AlgoVisHelper.setColor(g2d,AlgoVisHelper.Green); } AlgoVisHelper.fillCircle(g2d,p.x,p.y,3); } } @Override public Dimension getPreferredSize() { return new Dimension(canvasWidth,canvasHeight); } } private int canvasWidth; private int canvasHeight; //设置自己的数据 private Circle circle; private List<Point> points; public AlgoFrame(String title,int canvasWidth,int canvasHeight) { super(title); this.canvasWidth = canvasWidth; this.canvasHeight = canvasHeight; AlgoCanvas canvas = new AlgoCanvas(); canvas.setOpaque(true); this.setContentPane(canvas); this.pack(); this.setResizable(false); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setVisible(true); } public AlgoFrame(String title) { this(title,1024,768); } public void render(Circle circle,List<Point> points) { this.circle = circle; this.points = points; this.repaint(); } }
控制层
public class AlgoVisualizer { private static int DELAY = 10; //创建自己的数据 private Circle circle; //数据 private List<Point> points; private int insideCircle = 0; private AlgoFrame frame; //视图 private int N; public AlgoVisualizer(int sceneWidth,int sceneHeight,int N) { if (sceneWidth != sceneHeight) { throw new IllegalArgumentException("必须创建为一个正方形窗口"); } //初始化数据 // TODO: 初始化数据 this.N = N; circle = new Circle(sceneWidth / 2,sceneHeight / 2,sceneWidth / 2); points = new LinkedList<>(); //初始化视图 EventQueue.invokeLater(() -> { frame = new AlgoFrame("欢迎",sceneWidth,sceneHeight); new Thread(() -> { run(); }).start(); }); } /** * 动画逻辑 */ private void run() { // TODO: 编写自己的动画逻辑 for (int i = 0;i < N;i++) { frame.render(circle,points); AlgoVisHelper.pause(DELAY); int circleArea = insideCircle; int squareArea = points.size(); double piEstimation = 4 * (double) circleArea / squareArea; System.out.println(piEstimation); int x = (int) (Math.random() * frame.getCanvasWidth()); int y = (int) (Math.random() * frame.getCanvasHeight()); Point p = new Point(x,y); points.add(p); if (circle.contain(p)) { insideCircle++; } } } }
main方法
public class PIMain { public static void main(String[] args) { int sceneWidth = 800; int sceneHeight = 800; int N = 10000; AlgoVisualizer visualizer = new AlgoVisualizer(sceneWidth,sceneHeight,N); } }
显示图
圆周率打印结果
...............
3.1404947656197906
3.140523420570095
3.1405520736098147
3.1404473780711406
3.140342689512634
3.140238007933598
建立数据层,来分离控制层的数据运算。
public class MonteCarloPiData { @Getter private Circle circle; private List<Point> points; private int insideCircle = 0; public MonteCarloPiData(Circle circle) { this.circle = circle; points = new LinkedList<>(); } public Point getPoint(int i) { if (i < 0 || i >= points.size()) { throw new IllegalArgumentException("超出点列表的范围"); } return points.get(i); } public int getPointsNumber() { return points.size(); } public void addPoint(Point p) { points.add(p); if (circle.contain(p)) { insideCircle++; } } public double estimatePi() { if (points.size() == 0) { return 0.0; } int circleArea = insideCircle; int squareArea = points.size(); return (double) circleArea * 4 / squareArea; } }
视图层
@Getter public class AlgoFrame extends JFrame { private class AlgoCanvas extends JPanel { public AlgoCanvas() { //双缓存 super(true); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2d = (Graphics2D) g; //抗锯齿 RenderingHints hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2d.addRenderingHints(hints); //具体绘制 // TODO: 绘制自己的数据data Circle circle = data.getCircle(); AlgoVisHelper.setStrokeWidth(g2d,3); AlgoVisHelper.setColor(g2d,AlgoVisHelper.Blue); AlgoVisHelper.strokeCircle(g2d,circle.getX(),circle.getY(),circle.getR()); for (int i = 0;i < data.getPointsNumber();i++) { Point p = data.getPoint(i); if (circle.contain(p)) { AlgoVisHelper.setColor(g2d,AlgoVisHelper.Red); }else { AlgoVisHelper.setColor(g2d,AlgoVisHelper.Green); } AlgoVisHelper.fillCircle(g2d,p.x,p.y,3); } } @Override public Dimension getPreferredSize() { return new Dimension(canvasWidth,canvasHeight); } } private int canvasWidth; private int canvasHeight; //设置自己的数据 private MonteCarloPiData data; public AlgoFrame(String title,int canvasWidth,int canvasHeight) { super(title); this.canvasWidth = canvasWidth; this.canvasHeight = canvasHeight; AlgoCanvas canvas = new AlgoCanvas(); canvas.setOpaque(true); this.setContentPane(canvas); this.pack(); this.setResizable(false); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setVisible(true); } public AlgoFrame(String title) { this(title,1024,768); } public void render(MonteCarloPiData data) { this.data = data; this.repaint(); } }
控制层
public class AlgoVisualizer { private static int DELAY = 10; //创建自己的数据 private MonteCarloPiData data; //数据 private AlgoFrame frame; //视图 private int N; public AlgoVisualizer(int sceneWidth,int sceneHeight,int N) { if (sceneWidth != sceneHeight) { throw new IllegalArgumentException("必须创建为一个正方形窗口"); } //初始化数据 // TODO: 初始化数据 this.N = N; Circle circle = new Circle(sceneWidth / 2,sceneHeight / 2,sceneWidth / 2); data = new MonteCarloPiData(circle); //初始化视图 EventQueue.invokeLater(() -> { frame = new AlgoFrame("欢迎",sceneWidth,sceneHeight); new Thread(() -> { run(); }).start(); }); } /** * 动画逻辑 */ private void run() { // TODO: 编写自己的动画逻辑 for (int i = 0;i < N;i++) { if (i % 100 == 0) { frame.render(data); AlgoVisHelper.pause(DELAY); System.out.println(data.estimatePi()); } int x = (int) (Math.random() * frame.getCanvasWidth()); int y = (int) (Math.random() * frame.getCanvasHeight()); data.addPoint(new Point(x,y)); } } }
由于可视化在屏幕上绘制占用的内存较大,我们很难用更大的数据来进行模拟,所以我们使用非可视化,控制台的输出来进行模拟
public class MonteCarloExperiment { private int squareSide; private int N; private int outputInterval = 100; public MonteCarloExperiment(int squareSide,int N) { if (squareSide <= 0 || N <= 0) { throw new IllegalArgumentException("squareSide,N必须大于0"); } this.squareSide = squareSide; this.N = N; } public void setOutputInterval(int outputInterval) { if (outputInterval <= 0) { throw new IllegalArgumentException("outputInterval必须大于0"); } this.outputInterval = outputInterval; } public void run() { Circle circle = new Circle(squareSide / 2,squareSide / 2,squareSide / 2); MonteCarloPiData data = new MonteCarloPiData(circle); for (int i = 0;i < N;i++) { if (i % outputInterval == 0) { System.out.println(data.estimatePi()); } int x = (int) (Math.random() * squareSide); int y = (int) (Math.random() * squareSide); data.addPoint(new Point(x,y)); } } public static void main(String[] args) { int squareSide = 800; int N = 10000000; MonteCarloExperiment exp = new MonteCarloExperiment(squareSide,N); exp.setOutputInterval(10000); exp.run(); } }
这里我们看到,我们将模拟数据调到了一千万。
运行结果
................
3.14157139979859
3.1415686116700203
3.141554974874372
3.141551004016064
3.14154704112337
3.1415038076152304
3.141508308308308
- 三门问题
三门问题(Monty Hall Problem)
出自美国的电视游戏节目Let's Make a Deal。问题的名字来自该节目的主持人蒙提.霍尔(Monty Hall)。
参赛者会看见三扇关闭了的门,其中一扇的后面有一辆汽车,选中后面有车的那扇门就可以赢得该汽车,而另外两扇门后面则什么都没有。当参赛者选定一扇门,但开启的时候,节目主持人会开启剩下两扇门的其中一扇,这扇门背后一定没有汽车。主持人会问参赛者要不要换另一扇门。问题是:换另一扇门会否会增加参赛者获奖概率?
由于该问题需要大量模拟数据,就不进行可视化操作了,我们直接使用控制台输出
public class ThreeGatesExperiment { private int N; public ThreeGatesExperiment(int N) { if (N <= 0) { throw new IllegalArgumentException("N必须大于0"); } this.N = N; } public void run(boolean changeDoor) { int wins = 0; for (int i = 0; i < N; i++) { if (play(changeDoor)) { wins++; } } System.out.println(changeDoor ? "换门" : "不换门"); System.out.println("中奖概率: " + (double) wins / N); } private boolean play(boolean changeDoor) { int prizeDoor = (int) (Math.random() * 3); int playerChoice = (int) (Math.random() * 3); if (playerChoice == prizeDoor) { return !changeDoor; } else { return changeDoor; } } public static void main(String[] args) { int N = 10000000; ThreeGatesExperiment exp = new ThreeGatesExperiment(N); exp.run(true); exp.run(false); } }
运行结果
换门
中奖概率: 0.6667727
不换门
中奖概率: 0.3335902
- 抽宝箱的概率
在游戏里有一种宝箱,打开这个宝箱获得传奇武器的概率是20%,现在你开5个这样的宝箱,获得传奇武器的概率是多少?
这里我们依然使用控制台来输出。
public class WinningPrize { private double chance; private int playTime; private int N; public WinningPrize(double chance,int playTime,int N) { if (chance < 0.0 || chance > 1.0) { throw new IllegalArgumentException("chance必须在0和1之间"); } if (playTime <= 0 || N <= 0) { throw new IllegalArgumentException("playTime和N必须大于0"); } this.chance = chance; this.playTime = playTime; this.N = N; } public void run() { int wins = 0; for (int i = 0;i < N; i++) { if (play()) { wins++; } } System.out.println("中奖概率为: " + (double) wins / N); } private boolean play() { for (int i = 0;i < playTime;i++) { if (Math.random() < chance) { return true; } } return false; } public static void main(String[] args) { double chance = 0.2; int playTime = 5; int N = 1000000; WinningPrize winningPrize = new WinningPrize(chance,playTime,N); winningPrize.run(); } }
这段代码的意思是我们连续开100万次,每次开5个宝箱。
运行结果
中奖概率为: 0.672504
根据结果可知,我们连续开5个宝箱的概率大概在67%。当然在概率论中也有一个基本的公式
1 - (0.8)^5 = 0.67232
现在我们可以来看一下,如果我们的心理值是95%的概率能拿到传奇武器,那么我们需要开多少次呢?
1 - (0.8)^x > 0.95,根据该公式,我们可以反解x的值。
而具有迷惑的1 / 0.2 = 5的真实意义是平均值(期望值),也就是说我们是有可能被平均的,比如别人可能开1个宝箱就拿到了传奇武器,而我们却可能要开10个,20个。
- 选择排序可视化
选择排序算法就是通过扫描数组中的最小值然后跟数组最前端的值交换来达到排序的目的的算法,它是一个O(n^2)时间复杂度的算法。以下红色表示发生变动的元素,蓝色表示固定下来的元素。
现在我们还是通过我们的模版来进行修改
数据层
public class SelectionSortData { private int[] numbers; public SelectionSortData(int N,int randomBound) { numbers = new int ; for (int i = 0;i < N;i++) { numbers[i] = (int) (Math.random() * randomBound) + 1; } } public int N() { return numbers.length; } public int get(int index) { if (index < 0 || index >= numbers.length) { throw new IllegalArgumentException("无效的排序数组索引"); } return numbers[index]; } public void swap(int i,int j) { int temp = numbers[i]; numbers[i] = numbers[j]; numbers[j] = temp; } }
视图层
@Getter public class AlgoFrame extends JFrame { private class AlgoCanvas extends JPanel { public AlgoCanvas() { //双缓存 super(true); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2d = (Graphics2D) g; //抗锯齿 RenderingHints hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2d.addRenderingHints(hints); //具体绘制 // TODO: 绘制自己的数据data int w = canvasWidth / data.N(); AlgoVisHelper.setColor(g2d,AlgoVisHelper.LightBlue); for (int i = 0;i < data.N();i++) { AlgoVisHelper.fillRectangel(g2d,i * w,canvasHeight - data.get(i), w - 1,data.get(i)); } } @Override public Dimension getPreferredSize() { return new Dimension(canvasWidth,canvasHeight); } } private int canvasWidth; private int canvasHeight; //设置自己的数据 private SelectionSortData data; public AlgoFrame(String title,int canvasWidth,int canvasHeight) { super(title); this.canvasWidth = canvasWidth; this.canvasHeight = canvasHeight; AlgoCanvas canvas = new AlgoCanvas(); canvas.setOpaque(true); this.setContentPane(canvas); this.pack(); this.setResizable(false); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setVisible(true); } public AlgoFrame(String title) { this(title,1024,768); } public void render(SelectionSortData data) { this.data = data; this.repaint(); } }
控制层
public class AlgoVisualizer { private static int DELAY = 10; //创建自己的数据 private SelectionSortData data; //数据 private AlgoFrame frame; //视图 public AlgoVisualizer(int sceneWidth,int sceneHeight,int N) { //初始化数据 // TODO: 初始化数据 data = new SelectionSortData(N,sceneHeight); //初始化视图 EventQueue.invokeLater(() -> { frame = new AlgoFrame("欢迎",sceneWidth,sceneHeight); new Thread(() -> { run(); }).start(); }); } /** * 动画逻辑 */ private void run() { frame.render(data); AlgoVisHelper.pause(DELAY); // TODO: 编写自己的动画逻辑 for (int i = 0;i < data.N();i++) { //寻找[i,n)区间里的最小值索引 int minIndex = i; for (int j = i + 1;j < data.N();j++) { if (data.get(j) < data.get(minIndex)) { minIndex = j; } } data.swap(i,minIndex); frame.render(data); AlgoVisHelper.pause(DELAY); } frame.render(data); AlgoVisHelper.pause(DELAY); } }
main方法
public class SelectionSortMain { public static void main(String[] args) { int sceneWidth = 800; int sceneHeight = 800; int N = 100; AlgoVisualizer visualizer = new AlgoVisualizer(sceneWidth,sceneHeight,N); } }
显示的图样
由于此种方式无法展现排序的主要过程,所以我们做出修改。
数据层
public class SelectionSortData { private int[] numbers; @Getter @Setter private int orderedIndex = -1; //[0..orderedIndex)是有序的 @Getter @Setter private int currentMinIndex = -1; //当前找到的最小元素的索引 @Getter @Setter private int currentCompareIndex = -1; //当前正在比较的元素索引 public SelectionSortData(int N,int randomBound) { numbers = new int ; for (int i = 0;i < N;i++) { numbers[i] = (int) (Math.random() * randomBound) + 1; } } public int N() { return numbers.length; } public int get(int index) { if (index < 0 || index >= numbers.length) { throw new IllegalArgumentException("无效的排序数组索引"); } return numbers[index]; } public void swap(int i,int j) { int temp = numbers[i]; numbers[i] = numbers[j]; numbers[j] = temp; } }
视图层
@Getter public class AlgoFrame extends JFrame { private class AlgoCanvas extends JPanel { public AlgoCanvas() { //双缓存 super(true); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2d = (Graphics2D) g; //抗锯齿 RenderingHints hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2d.addRenderingHints(hints); //具体绘制 // TODO: 绘制自己的数据data int w = canvasWidth / data.N(); for (int i = 0;i < data.N();i++) { if (i < data.getOrderedIndex()) { AlgoVisHelper.setColor(g2d,AlgoVisHelper.Red); }else { AlgoVisHelper.setColor(g2d,AlgoVisHelper.Grey); } if (i == data.getCurrentCompareIndex()) { AlgoVisHelper.setColor(g2d,AlgoVisHelper.LightBlue); } if (i == data.getCurrentMinIndex()) { AlgoVisHelper.setColor(g2d,AlgoVisHelper.Indigo); } AlgoVisHelper.fillRectangel(g2d,i * w,canvasHeight - data.get(i), w - 1,data.get(i)); } } @Override public Dimension getPreferredSize() { return new Dimension(canvasWidth,canvasHeight); } } private int canvasWidth; private int canvasHeight; //设置自己的数据 private SelectionSortData data; public AlgoFrame(String title,int canvasWidth,int canvasHeight) { super(title); this.canvasWidth = canvasWidth; this.canvasHeight = canvasHeight; AlgoCanvas canvas = new AlgoCanvas(); canvas.setOpaque(true); this.setContentPane(canvas); this.pack(); this.setResizable(false); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setVisible(true); } public AlgoFrame(String title) { this(title,1024,768); } public void render(SelectionSortData data) { this.data = data; this.repaint(); } }
控制层
public class AlgoVisualizer { private static int DELAY = 20; //创建自己的数据 private SelectionSortData data; //数据 private AlgoFrame frame; //视图 public AlgoVisualizer(int sceneWidth,int sceneHeight,int N) { //初始化数据 // TODO: 初始化数据 data = new SelectionSortData(N,sceneHeight); //初始化视图 EventQueue.invokeLater(() -> { frame = new AlgoFrame("欢迎",sceneWidth,sceneHeight); new Thread(() -> { run(); }).start(); }); } /** * 动画逻辑 */ private void run() { setData(0,-1,-1); // TODO: 编写自己的动画逻辑 for (int i = 0;i < data.N();i++) { //寻找[i,n)区间里的最小值索引 int minIndex = i; setData(i,-1,minIndex); for (int j = i + 1;j < data.N();j++) { setData(i,j,minIndex); if (data.get(j) < data.get(minIndex)) { minIndex = j; setData(i,j,minIndex); } } data.swap(i,minIndex); setData(i + 1,-1,-1); } setData(data.N(),-1,-1); } private void setData(int orderedIndex,int currentCompareIndex,int currentMinIndex) { data.setOrderedIndex(orderedIndex); data.setCurrentCompareIndex(currentCompareIndex); data.setCurrentMinIndex(currentMinIndex); frame.render(data); AlgoVisHelper.pause(DELAY); } }
运行效果图
- 插入排序可视化
插入排序算法是将数组中的元素不断向前比较,直到放入到一个适当合适的位置的排序算法,它就好像我们在玩扑克牌的时候进行整理牌面一样。插入排序也是一个O(n^2)时间复杂度的算法。
由于8是第一个元素,它前面没有元素,所以8不需要移动,并被确定下来
我们来看第二个元素6,6与前面的元素8比较,6比8小,所以6要放到8的前面,并被确定下来。
然后我们来看第三个元素2,2与之前的8比较,2比8小,与8交换位置,再与6比较,2比6小,再与6交换位置,此时确定了下来。
然后我们来看第四个元素3
数据层
public class InsertionSortData { private int[] numbers; @Getter @Setter private int orderedIndex = -1; //[0..orderedIndex)是有序的 @Getter @Setter private int currentIndex = -1; //当前正在处理的元素的索引 public InsertionSortData(int N,int randomBound) { numbers = new int ; for (int i = 0;i < N;i++) { numbers[i] = (int) (Math.random() * randomBound) + 1; } } public int N() { return numbers.length; } public int get(int index) { if (index < 0 || index >= numbers.length) { throw new IllegalArgumentException("无效的排序数组索引"); } return numbers[index]; } public void swap(int i,int j) { if (i < 0 || i >= numbers.length || j <0 || j >= numbers.length) { throw new IllegalArgumentException("无效的排序数组索引"); } int temp = numbers[i]; numbers[i] = numbers[j]; numbers[j] = temp; } }
视图层
@Getter public class AlgoFrame extends JFrame { private class AlgoCanvas extends JPanel { public AlgoCanvas() { //双缓存 super(true); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2d = (Graphics2D) g; //抗锯齿 RenderingHints hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2d.addRenderingHints(hints); //具体绘制 // TODO: 绘制自己的数据data int w = canvasWidth / data.N(); for (int i = 0;i < data.N();i++) { if (i < data.getOrderedIndex()) { AlgoVisHelper.setColor(g2d,AlgoVisHelper.Red); }else { AlgoVisHelper.setColor(g2d,AlgoVisHelper.Grey); } if (i == data.getCurrentIndex()) { AlgoVisHelper.setColor(g2d,AlgoVisHelper.LightBlue); } AlgoVisHelper.fillRectangel(g2d,i * w,canvasHeight - data.get(i), w - 1,data.get(i)); } } @Override public Dimension getPreferredSize() { return new Dimension(canvasWidth,canvasHeight); } } private int canvasWidth; private int canvasHeight; //设置自己的数据 private InsertionSortData data; public AlgoFrame(String title,int canvasWidth,int canvasHeight) { super(title); this.canvasWidth = canvasWidth; this.canvasHeight = canvasHeight; AlgoCanvas canvas = new AlgoCanvas(); canvas.setOpaque(true); this.setContentPane(canvas); this.pack(); this.setResizable(false); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setVisible(true); } public AlgoFrame(String title) { this(title,1024,768); } public void render(InsertionSortData data) { this.data = data; this.repaint(); } }
控制层
public class AlgoVisualizer { private static int DELAY = 20; //创建自己的数据 private InsertionSortData data; //数据 private AlgoFrame frame; //视图 public AlgoVisualizer(int sceneWidth,int sceneHeight,int N) { //初始化数据 // TODO: 初始化数据 data = new InsertionSortData(N,sceneHeight); //初始化视图 EventQueue.invokeLater(() -> { frame = new AlgoFrame("欢迎",sceneWidth,sceneHeight); //根据情况决定是否加入键盘鼠标监听器 new Thread(() -> { run(); }).start(); }); } /** * 动画逻辑 */ private void run() { setData(0,-1); // TODO: 编写自己的动画逻辑 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(data.N(),-1); } private void setData(int orderedIndex,int currentIndex) { data.setOrderedIndex(orderedIndex); data.setCurrentIndex(currentIndex); frame.render(data); AlgoVisHelper.pause(DELAY); } }
main方法
public class InsertionSortMain { public static void main(String[] args) { int sceneWidth = 800; int sceneHeight = 800; int N = 100; AlgoVisualizer visualizer = new AlgoVisualizer(sceneWidth,sceneHeight,N); } }
展示图形
插入排序的优化
由于以上的插入排序算法需要经过大量的交换的过程,我们可以对这个交换的过程进行优化
此时我们不直接将6与8比较,而是将6复制一份出来。
由于8比6大,我们将8后移一位。
由于第0个位置前面没有元素,6不需要再做比较,就将6放入该位置,并确认。
,
然后考察2的位置,将2复制一份。
由于8比2大,所以8后移一位
由于6比2大,所以将6也后移一位。
由于6是第0个位置,没有可比较的了,我们将2放到该位置。
现在我们将3复制一份出来
8比3大,将8后移一位
6比3大,6后移一位
2比3小,所以3放到第1个位置,并确认
优化后跟原方法的对比
public class InsertionSort { private InsertionSort() { } @SuppressWarnings("unchecked") public static Comparable[] betterSort(Comparable[] arr) { for (int i = 0;i < arr.length;i++) { Comparable e = arr[i]; int j; for (j = i;j > 0 && arr[j - 1].compareTo(e) > 0;j--) { arr[j] = arr[j - 1]; } arr[j] = e; } return arr; } @SuppressWarnings("unchecked") public static Comparable[] sort(Comparable[] arr) { for (int i = 0;i < arr.length;i++) { for (int j = i;j > 0 && arr[j].compareTo(arr[j - 1]) < 0;j--) { swap(j,j - 1,arr); } } return arr; } private static void swap(int i,int j,Comparable[] arr) { Comparable temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } public static void main(String[] args) { Integer[] numbers1 = new Integer[1000]; for (int i = 0;i < numbers1.length;i++) { numbers1[i] = (int) (Math.random() * 1000) + 1; } Integer[] numbers2 = numbers1.clone(); long start1 = System.nanoTime(); System.out.println(Arrays.toString(InsertionSort.sort(numbers1))); System.out.println((System.nanoTime() - start1) / 1000000000.0); long start2 = System.nanoTime(); System.out.println(Arrays.toString(InsertionSort.betterSort((numbers2)))); System.out.println((System.nanoTime() - start2) / 1000000000.0); } }
运行结果
[2, 5, 5, 5, 6, 6, 9, 10, 12, 12, 14, 14, 14, 15, 15, 15, 18, 19, ...]
0.007879546
[2, 5, 5, 5, 6, 6, 9, 10, 12, 12, 14, 14, 14, 15, 15, 15, 18, 19, ...]
0.00452523
在一个近乎有序的数组进行排序时,我们的插入排序的时间复杂度可以进化到O(n)级别,在这种特殊的情况下,它是最快的一种排序方式。
数据层
public class InsertionSortData { public enum Type { Default, NearlyOrdered; } private int[] numbers; @Getter @Setter private int orderedIndex = -1; //[0..orderedIndex)是有序的 @Getter @Setter private int currentIndex = -1; //当前正在处理的元素的索引 public InsertionSortData(int N,int randomBound,Type dataType) { numbers = new int ; for (int i = 0;i < N;i++) { numbers[i] = (int) (Math.random() * randomBound) + 1; } //生成一个近乎有序的数组 if (dataType == Type.NearlyOrdered) { Arrays.sort(numbers); int swapTime = (int)(0.02 * N); for (int i = 0;i < swapTime;i++) { int a = (int)(Math.random() * N); int b = (int)(Math.random() * N); swap(a,b); } } } public InsertionSortData(int N,int randomBound) { this(N,randomBound,Type.Default); } public int N() { return numbers.length; } public int get(int index) { if (index < 0 || index >= numbers.length) { throw new IllegalArgumentException("无效的排序数组索引"); } return numbers[index]; } public void swap(int i,int j) { if (i < 0 || i >= numbers.length || j <0 || j >= numbers.length) { throw new IllegalArgumentException("无效的排序数组索引"); } int temp = numbers[i]; numbers[i] = numbers[j]; numbers[j] = temp; } }
控制层
public class AlgoVisualizer { private static int DELAY = 20; //创建自己的数据 private InsertionSortData data; //数据 private AlgoFrame frame; //视图 public AlgoVisualizer(int sceneWidth,int sceneHeight,int N,InsertionSortData.Type dataType) { //初始化数据 // TODO: 初始化数据 data = new InsertionSortData(N,sceneHeight,dataType); //初始化视图 EventQueue.invokeLater(() -> { frame = new AlgoFrame("欢迎",sceneWidth,sceneHeight); //根据情况决定是否加入键盘鼠标监听器 new Thread(() -> { run(); }).start(); }); } public AlgoVisualizer(int sceneWidth,int sceneHeight,int N) { this(sceneWidth,sceneHeight,N,InsertionSortData.Type.Default); } /** * 动画逻辑 */ private void run() { setData(0,-1); // TODO: 编写自己的动画逻辑 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(data.N(),-1); } private void setData(int orderedIndex,int currentIndex) { data.setOrderedIndex(orderedIndex); data.setCurrentIndex(currentIndex); frame.render(data); AlgoVisHelper.pause(DELAY); } }
main方法
public class InsertionSortMain { public static void main(String[] args) { int sceneWidth = 800; int sceneHeight = 800; int N = 100; AlgoVisualizer visualizer = new AlgoVisualizer(sceneWidth,sceneHeight,N,InsertionSortData.Type.NearlyOrdered); } }
显示图形
最后值得一提的是,在n比较小的时候,插入排序比O(nlog n)的排序算法有优势。插入排序算法经常用作是高级排序算法在处理到小样本时的一个优化。
- 归并排序可视化
归并排序算法是将一个数组分成两部分——左边和右边,然后使用同样的算法对左边进行排序,再使用同样的算法对右边进行排序。之后将两个有序的数组,归并成一个有序的数组。
对于划分成一个元素的部分,它本身就是有序的
然后我们对其不同的划分,进行两两归并
再通过相应的划分,两两归并成有序数组
最后将剩下的两个有序数组,归并成一个有序的数组
所以我们这里给出一个归并排序的算法类
public class MergeSort { private MergeSort() { } public static void sort(Comparable[] arr) { int n = arr.length; sort(arr,0,n - 1); } /** * 递归使用归并排序,对arr[l..r]的范围进行排序 * @param arr * @param l * @param r */ private static void sort(Comparable[] arr,int l,int r) { if (l >= r) { return; } //此处l + r是有可能造成整型溢出的 // int mid = (l + r) /2; int mid = l + (r - l) / 2; sort(arr,l,mid); sort(arr,mid + 1,r); merge(arr,l,mid,r); } /** * 将arr[l..mid]和arr[mid+1..r]两部分进行归并 * @param arr * @param l * @param mid * @param r */ @SuppressWarnings("unchecked") private static void merge(Comparable[] arr,int l,int mid,int r) { Comparable[] aux = Arrays.copyOfRange(arr,l,r + 1); int i = l; // i指向左半部分的起始索引位置l int j = mid + 1; // j指向右半部分起始索引位置mid + 1 for (int k = l;k <= r;k++) { if (i > mid) { //如果左半部分元素已经全部处理完毕 arr[k] = aux[j - l]; j++; }else if (j > r) { //如果右半部分元素已经全部处理完毕 arr[k] = aux[i - l]; i++; }else if (aux[i - l].compareTo(aux[j - l]) < 0) { //左半部分所指元素 < 右半部分所指元素 arr[k] = aux[i - l]; i++; }else { //左半部分所指元素 >= 右半部分所指元素 arr[k] = aux[j - l]; j++; } } } }
现在我们来写可视化的部分
数据层
public class MergeSortData { @Getter private int[] numbers; @Getter @Setter private int l; @Getter @Setter private int r; @Getter @Setter private int mergeIndex; public MergeSortData(int N, int randomBound) { numbers = new int ; for (int i = 0;i < N;i++) { numbers[i] = (int) (Math.random() * randomBound) + 1; } } public int N() { return numbers.length; } public int get(int index) { if (index < 0 || index >= numbers.length) { throw new IllegalArgumentException("无效的排序数组索引"); } return numbers[index]; } public void set(int index,int value) { if (index < 0 || index >= numbers.length) { throw new IllegalArgumentException("无效的排序数组索引"); } numbers[index] = value; } public void swap(int i,int j) { if (i < 0 || i >= numbers.length || j <0 || j >= numbers.length) { throw new IllegalArgumentException("无效的排序数组索引"); } int temp = numbers[i]; numbers[i] = numbers[j]; numbers[j] = temp; } }
视图层
@Getter public class AlgoFrame extends JFrame { private class AlgoCanvas extends JPanel { public AlgoCanvas() { //双缓存 super(true); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2d = (Graphics2D) g; //抗锯齿 RenderingHints hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2d.addRenderingHints(hints); //具体绘制 // TODO: 绘制自己的数据data int w = canvasWidth / data.N(); for (int i = 0;i < data.N();i++) { if (i >= data.getL() && i <= data.getR()) { AlgoVisHelper.setColor(g2d,AlgoVisHelper.Green); }else { AlgoVisHelper.setColor(g2d,AlgoVisHelper.Grey); } if (i >= data.getL() && i <= data.getMergeIndex()) { AlgoVisHelper.setColor(g2d,AlgoVisHelper.Red); } AlgoVisHelper.fillRectangel(g2d,i * w,canvasHeight - data.get(i), w - 1,data.get(i)); } } @Override public Dimension getPreferredSize() { return new Dimension(canvasWidth,canvasHeight); } } private int canvasWidth; private int canvasHeight; //设置自己的数据 private MergeSortData data; public AlgoFrame(String title,int canvasWidth,int canvasHeight) { super(title); this.canvasWidth = canvasWidth; this.canvasHeight = canvasHeight; AlgoCanvas canvas = new AlgoCanvas(); canvas.setOpaque(true); this.setContentPane(canvas); this.pack(); this.setResizable(false); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setVisible(true); } public AlgoFrame(String title) { this(title,1024,768); } public void render(MergeSortData data) { this.data = data; this.repaint(); } }
控制层
public class AlgoVisualizer { private static int DEALY = 20; //创建自己的数据 private MergeSortData data; //数据 private AlgoFrame frame; //视图 public AlgoVisualizer(int sceneWidth,int sceneHeight,int N) { //初始化数据 // TODO: 初始化数据 data = new MergeSortData(N,sceneHeight); //初始化视图 EventQueue.invokeLater(() -> { frame = new AlgoFrame("欢迎",sceneWidth,sceneHeight); new Thread(() -> { run(); }).start(); }); } /** * 动画逻辑 */ private void run() { setData(-1,-1,-1); // TODO: 编写自己的动画逻辑 mergeSort(0,data.N() - 1); setData(0,data.N() - 1,data.N() - 1); } private void mergeSort(int l,int r) { if (l >= r) { return; } setData(l,r,-1); // int mid = (l + r) / 2; int mid = l + (r - l) / 2; mergeSort(l,mid); mergeSort(mid + 1,r); merge(l,mid,r); } private void merge(int l,int mid,int r) { int[] aux = Arrays.copyOfRange(data.getNumbers(),l,r + 1); int i = l; int j = mid + 1; for (int k = l;k <= r;k++) { if (i > mid) { data.set(k,aux[j - l]); j++; }else if (j > r) { data.set(k,aux[i - l]); i++; }else if (aux[i - l] < aux[j - l]) { data.set(k,aux[i - l]); i++; }else { data.set(k,aux[j - l]); j++; } setData(l,r,k); } } private void setData(int l,int r,int mergeIndex) { data.setL(l); data.setR(r); data.setMergeIndex(mergeIndex); frame.render(data); AlgoVisHelper.pause(DEALY); } }
main方法
public class MergeSortMain { public static void main(String[] args) { int sceneWidth = 800; int sceneHeight = 800; int N = 100; AlgoVisualizer visualizer = new AlgoVisualizer(sceneWidth,sceneHeight,N); } }
图像显示
- 快速排序算法可视化
快速排序的基本思想就是将排序数组的第一个值放到一个合适的位置,将小于这个值的元素放到该值前面的位置,将大于这个值的元素放到该值后面的位置,再对前面的值以及后面的值进行递归的运算,再进行若干次排序。
对于如何将小于该值的元素放到前面,大于该值的元素放到后面的过程,我们称之为Partition.
现在我们假定第一个元素V的索引为l,而小于V的元素与大于V的元素的中间间隔元素(最后一个小于V的元素)的索引为j,那么还未处理的元素e的索引为i。
此时如果e > V,我们只需要把e添加到>V的部分。然后索引i后移
那如果我们下一个元素e < V,我们只需要将e与>V的第一个元素交换位置,索引j+1即可。
然后继续i++来看下一个元素就好了
当所有的元素遍历完之后,我们将V与小于V的最后一个元素交换位置,就完成了Partition的过程。而j索引指向的位置则是我们标定点所在的位置。
数据层
public class QuickSortData { private int[] numbers; @Getter @Setter private int l; @Getter @Setter private int r; @Getter @Setter private int curPivot; @Getter @Setter private int curElement; @Getter @Setter private boolean[] fixedPivot; //在排好序之后元素应该处在的位置 public QuickSortData(int N,int randomBound) { numbers = new int ; fixedPivot = new boolean ; for (int i = 0;i < N;i++) { numbers[i] = (int) (Math.random() * randomBound) + 1; fixedPivot[i] = false; } } public int N() { return numbers.length; } public int get(int index) { if (index < 0 || index >= numbers.length) { throw new IllegalArgumentException("无效索引越界"); } return numbers[index]; } public void swap(int i,int j) { if (i < 0 || i >= numbers.length || j < 0 || j >= numbers.length) { throw new IllegalArgumentException("无效的索引越界"); } int temp = numbers[i]; numbers[i] = numbers[j]; numbers[j] = temp; } }
视图层
@Getter public class AlgoFrame extends JFrame { private class AlgoCanvas extends JPanel { public AlgoCanvas() { //双缓存 super(true); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2d = (Graphics2D) g; //抗锯齿 RenderingHints hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2d.addRenderingHints(hints); //具体绘制 // TODO: 绘制自己的数据data int w = canvasWidth / data.N(); for (int i = 0;i < data.N();i++) { if (i >= data.getL() && i <= data.getR()) { AlgoVisHelper.setColor(g2d,AlgoVisHelper.Green); } else { AlgoVisHelper.setColor(g2d,AlgoVisHelper.Grey); } if (i == data.getCurPivot()) { AlgoVisHelper.setColor(g2d,AlgoVisHelper.Indigo); } if (i == data.getCurElement()) { AlgoVisHelper.setColor(g2d,AlgoVisHelper.LightBlue); } if (data.getFixedPivot()[i]) { AlgoVisHelper.setColor(g2d,AlgoVisHelper.Red); } AlgoVisHelper.fillRectangel(g2d,i * w,canvasHeight - data.get(i), w - 1,data.get(i)); } } @Override public Dimension getPreferredSize() { return new Dimension(canvasWidth,canvasHeight); } } private int canvasWidth; private int canvasHeight; //设置自己的数据 private QuickSortData data; public AlgoFrame(String title,int canvasWidth,int canvasHeight) { super(title); this.canvasWidth = canvasWidth; this.canvasHeight = canvasHeight; AlgoCanvas canvas = new AlgoCanvas(); canvas.setOpaque(true); this.setContentPane(canvas); this.pack(); this.setResizable(false); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setVisible(true); } public AlgoFrame(String title) { this(title,1024,768); } public void render(QuickSortData data) { this.data = data; this.repaint(); } }
控制层
public class AlgoVisualizer { private static int DELAY = 20; //创建自己的数据 private QuickSortData data; //数据 private AlgoFrame frame; //视图 public AlgoVisualizer(int sceneWidth,int sceneHeight,int N) { //初始化数据 // TODO: 初始化数据 data = new QuickSortData(N,sceneHeight); //初始化视图 EventQueue.invokeLater(() -> { frame = new AlgoFrame("欢迎",sceneWidth,sceneHeight); new Thread(() -> { run(); }).start(); }); } /** * 动画逻辑 */ private void run() { // TODO: 编写自己的动画逻辑 setData(-1,-1,-1,-1,-1); quickSort(0,data.N() - 1); setData(-1,-1,-1,-1,-1); } private void quickSort(int l,int r) { if (l > r) { return; } if (l == r) { setData(l,r,l,-1,-1); return; } setData(l,r,-1,-1,-1); int p = partition(l,r); quickSort(l,p - 1); quickSort(p + 1,r); } private int partition(int l,int r) { int v = data.get(l); setData(l,r,-1,l,-1); int j = l; for (int i = l + 1;i <= r;i++) { setData(l,r,-1,l,i); if (data.get(i) < v) { j++; data.swap(j,i); setData(l,r,-1,l,i); } } data.swap(l,j); setData(l,r,j,-1,-1); return j; } private void setData(int l,int r,int fixedPivot,int curPivot,int curElement) { data.setL(l); data.setR(r); if (fixedPivot != -1) { data.getFixedPivot()[fixedPivot] = true; } data.setCurPivot(curPivot); data.setCurElement(curElement); frame.render(data); AlgoVisHelper.pause(DELAY); } }
main方法
public class QuickSortMain { public static void main(String[] args) { int sceneWidth = 800; int sceneHeight = 800; int N = 100; AlgoVisualizer visualizer = new AlgoVisualizer(sceneWidth,sceneHeight,N); } }
图像显示
快速排序算法的平均时间复杂度为O(nlog n).
在近乎有序的数组中进行快速排序
我们假设该数组为一个完全有序的数组,那么就没有比v小的值的部分,后面所有的元素都比v要大
所以e元素要融合到>v的部分
之后i++到新的元素
最终的结果就是这个样子
这个时候我们可以通过随机选择一个标定点来解决这个问题。
修改数据层
public class QuickSortData { public enum Type { Default, NearlyOrderd; } private int[] numbers; @Getter @Setter private int l; @Getter @Setter private int r; @Getter @Setter private int curPivot; @Getter @Setter private int curElement; @Getter @Setter private boolean[] fixedPivot; //在排好序之后元素应该处在的位置 public QuickSortData(int N,int randomBound,Type dataType) { numbers = new int ; fixedPivot = new boolean ; for (int i = 0;i < N;i++) { numbers[i] = (int) (Math.random() * randomBound) + 1; fixedPivot[i] = false; } if (dataType == Type.NearlyOrderd) { Arrays.sort(numbers); int swapTime = (int) (0.01 * N); for (int i = 0;i < swapTime;i++) { int a = (int) (Math.random() * N); int b = (int) (Math.random() * N); swap(a,b); } } } public QuickSortData(int N,int randomBound) { this(N,randomBound,Type.Default); } public int N() { return numbers.length; } public int get(int index) { if (index < 0 || index >= numbers.length) { throw new IllegalArgumentException("无效索引越界"); } return numbers[index]; } public void swap(int i,int j) { if (i < 0 || i >= numbers.length || j < 0 || j >= numbers.length) { throw new IllegalArgumentException("无效的索引越界"); } int temp = numbers[i]; numbers[i] = numbers[j]; numbers[j] = temp; } }
修改视图层
public class AlgoVisualizer { private static int DELAY = 20; //创建自己的数据 private QuickSortData data; //数据 private AlgoFrame frame; //视图 public AlgoVisualizer(int sceneWidth,int sceneHeight,int N,QuickSortData.Type dataType) { //初始化数据 // TODO: 初始化数据 data = new QuickSortData(N,sceneHeight,dataType); //初始化视图 EventQueue.invokeLater(() -> { frame = new AlgoFrame("欢迎",sceneWidth,sceneHeight); new Thread(() -> { run(); }).start(); }); } public AlgoVisualizer(int sceneWidth,int sceneHeight,int N) { this(sceneWidth,sceneHeight,N,QuickSortData.Type.Default); } /** * 动画逻辑 */ private void run() { // TODO: 编写自己的动画逻辑 setData(-1,-1,-1,-1,-1); quickSort(0,data.N() - 1); setData(-1,-1,-1,-1,-1); } private void quickSort(int l,int r) { if (l > r) { return; } if (l == r) { setData(l,r,l,-1,-1); return; } setData(l,r,-1,-1,-1); int p = partition(l,r); quickSort(l,p - 1); quickSort(p + 1,r); } private int partition(int l,int r) { int v = data.get(l); setData(l,r,-1,l,-1); int j = l; for (int i = l + 1;i <= r;i++) { setData(l,r,-1,l,i); if (data.get(i) < v) { j++; data.swap(j,i); setData(l,r,-1,l,i); } } data.swap(l,j); setData(l,r,j,-1,-1); return j; } private void setData(int l,int r,int fixedPivot,int curPivot,int curElement) { data.setL(l); data.setR(r); if (fixedPivot != -1) { data.getFixedPivot()[fixedPivot] = true; } data.setCurPivot(curPivot); data.setCurElement(curElement); frame.render(data); AlgoVisHelper.pause(DELAY); } }
修改main方法
public class QuickSortMain { public static void main(String[] args) { int sceneWidth = 800; int sceneHeight = 800; int N = 100; AlgoVisualizer visualizer = new AlgoVisualizer(sceneWidth,sceneHeight,N,QuickSortData.Type.NearlyOrderd); } }
图像显示
此时快速排序的时间复杂度为O(n^2)
产生这个原因是我们的标定点总是选择第一个元素,但如果我们随机来选择一个标定点,会如何呢?
修改控制层的partition方法
public class AlgoVisualizer { private static int DELAY = 20; //创建自己的数据 private QuickSortData data; //数据 private AlgoFrame frame; //视图 public AlgoVisualizer(int sceneWidth,int sceneHeight,int N,QuickSortData.Type dataType) { //初始化数据 // TODO: 初始化数据 data = new QuickSortData(N,sceneHeight,dataType); //初始化视图 EventQueue.invokeLater(() -> { frame = new AlgoFrame("欢迎",sceneWidth,sceneHeight); new Thread(() -> { run(); }).start(); }); } public AlgoVisualizer(int sceneWidth,int sceneHeight,int N) { this(sceneWidth,sceneHeight,N,QuickSortData.Type.Default); } /** * 动画逻辑 */ private void run() { // TODO: 编写自己的动画逻辑 setData(-1,-1,-1,-1,-1); quickSort(0,data.N() - 1); setData(-1,-1,-1,-1,-1); } private void quickSort(int l,int r) { if (l > r) { return; } if (l == r) { setData(l,r,l,-1,-1); return; } setData(l,r,-1,-1,-1); int p = partition(l,r); quickSort(l,p - 1); quickSort(p + 1,r); } private int partition(int l,int r) { int p = (int) (Math.random() * (r - l + 1)) + l; setData(l,r,-1,p,-1); data.swap(l,p); int v = data.get(l); setData(l,r,-1,l,-1); int j = l; for (int i = l + 1;i <= r;i++) { setData(l,r,-1,l,i); if (data.get(i) < v) { j++; data.swap(j,i); setData(l,r,-1,l,i); } } data.swap(l,j); setData(l,r,j,-1,-1); return j; } private void setData(int l,int r,int fixedPivot,int curPivot,int curElement) { data.setL(l); data.setR(r); if (fixedPivot != -1) { data.getFixedPivot()[fixedPivot] = true; } data.setCurPivot(curPivot); data.setCurElement(curElement); frame.render(data); AlgoVisHelper.pause(DELAY); } }
图像显示
这样它的时间复杂度依然为O(nlog n)的算法
数组所有元素都相等
如果所有的e都等于v,则最终依然是上图中的样子。
修改数据层
public class QuickSortData { public enum Type { Default, NearlyOrderd, Identical; } private int[] numbers; @Getter @Setter private int l; @Getter @Setter private int r; @Getter @Setter private int curPivot; @Getter @Setter private int curElement; @Getter @Setter private boolean[] fixedPivot; //在排好序之后元素应该处在的位置 public QuickSortData(int N,int randomBound,Type dataType) { numbers = new int ; fixedPivot = new boolean ; int lBound = 1; int rBound = randomBound; if (dataType == Type.Identical) { int avgNumber = (lBound + rBound) / 2; lBound = avgNumber; rBound = avgNumber; } for (int i = 0;i < N;i++) { numbers[i] = (int) (Math.random() * (rBound - lBound + 1)) + lBound; fixedPivot[i] = false; } if (dataType == Type.NearlyOrderd) { Arrays.sort(numbers); int swapTime = (int) (0.01 * N); for (int i = 0;i < swapTime;i++) { int a = (int) (Math.random() * N); int b = (int) (Math.random() * N); swap(a,b); } } } public QuickSortData(int N,int randomBound) { this(N,randomBound,Type.Default); } public int N() { return numbers.length; } public int get(int index) { if (index < 0 || index >= numbers.length) { throw new IllegalArgumentException("无效索引越界"); } return numbers[index]; } public void swap(int i,int j) { if (i < 0 || i >= numbers.length || j < 0 || j >= numbers.length) { throw new IllegalArgumentException("无效的索引越界"); } int temp = numbers[i]; numbers[i] = numbers[j]; numbers[j] = temp; } }
修改main方法
public class QuickSortMain { public static void main(String[] args) { int sceneWidth = 800; int sceneHeight = 800; int N = 100; AlgoVisualizer visualizer = new AlgoVisualizer(sceneWidth,sceneHeight,N,QuickSortData.Type.Identical); } }
图像显示
- 双路快排算法可视化
双路快速排序的Partition
这里的i索引是数组中所有小于v的元素的后一个位置,j是所有大于v元素的前一个位置,当i不断向后移动的过程跳过所有小于v的元素,并停到了大于等于v的元素e的位置
而j向前移动,跳过所有大于v的元素,停到了小于等于v的位置e
此时交换一下i和j索引位置上的元素
然后i索引后移一位,j索引前移一位
现在我们来看一下数组中所有元素都相等的时候的情况
当我们代码运行的时候,由于所有元素相等,所以前后端的第一个元素,i和j索引就都停止了,然后交换两边的元素
由于这两个e都等于v,我们整个数组的数据没有发生变化,但是i索引向后移动了,j索引也向前移动了。
通过这样的机制,i索引就会不断的向后移动,j索引就会不断的向前移动。
使得最终橙色部分和紫色部分的分割处于整个数组中央的位置。进而使得中央这个位置的元素与l索引交换位置的时候,让我们整个数组尽量平均的分成了两部分。从而避免了单路快速排序中时间复杂度退化成了O(n^2)的思路。
数据层
public class TwoWaysQuickSortData { public enum Type { Default, NearlyOrderd, Identical; } private int[] numbers; @Getter @Setter private int l; @Getter @Setter private int r; @Getter @Setter private int curPivot; @Getter @Setter private int curL; @Getter @Setter private int curR; @Getter @Setter private boolean[] fixedPivot; //在排好序之后元素应该处在的位置 public TwoWaysQuickSortData(int N, int randomBound, Type dataType) { numbers = new int ; fixedPivot = new boolean ; int lBound = 1; int rBound = randomBound; if (dataType == Type.Identical) { int avgNumber = (lBound + rBound) / 2; lBound = avgNumber; rBound = avgNumber; } for (int i = 0;i < N;i++) { numbers[i] = (int) (Math.random() * (rBound - lBound + 1)) + lBound; fixedPivot[i] = false; } if (dataType == Type.NearlyOrderd) { Arrays.sort(numbers); int swapTime = (int) (0.01 * N); for (int i = 0;i < swapTime;i++) { int a = (int) (Math.random() * N); int b = (int) (Math.random() * N); swap(a,b); } } } public TwoWaysQuickSortData(int N, int randomBound) { this(N,randomBound, Type.Default); } public int N() { return numbers.length; } public int get(int index) { if (index < 0 || index >= numbers.length) { throw new IllegalArgumentException("无效索引越界"); } return numbers[index]; } public void swap(int i,int j) { if (i < 0 || i >= numbers.length || j < 0 || j >= numbers.length) { throw new IllegalArgumentException("无效的索引越界"); } int temp = numbers[i]; numbers[i] = numbers[j]; numbers[j] = temp; } }
视图层
@Getter public class AlgoFrame extends JFrame { private class AlgoCanvas extends JPanel { public AlgoCanvas() { //双缓存 super(true); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2d = (Graphics2D) g; //抗锯齿 RenderingHints hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2d.addRenderingHints(hints); //具体绘制 // TODO: 绘制自己的数据data int w = canvasWidth / data.N(); for (int i = 0;i < data.N();i++) { if (i >= data.getL() && i <= data.getR()) { AlgoVisHelper.setColor(g2d, AlgoVisHelper.Green); } else { AlgoVisHelper.setColor(g2d, AlgoVisHelper.Grey); } if (i == data.getCurPivot()) { AlgoVisHelper.setColor(g2d, AlgoVisHelper.Indigo); } if (i >= data.getL() + 1 && i <= data.getCurL()) { AlgoVisHelper.setColor(g2d,AlgoVisHelper.LightBlue); } if (i >= data.getCurR() && i <= data.getR()) { AlgoVisHelper.setColor(g2d,AlgoVisHelper.LightBlue); } if (data.getFixedPivot()[i]) { AlgoVisHelper.setColor(g2d, AlgoVisHelper.Red); } AlgoVisHelper.fillRectangel(g2d,i * w,canvasHeight - data.get(i), w - 1,data.get(i)); } } @Override public Dimension getPreferredSize() { return new Dimension(canvasWidth,canvasHeight); } } private int canvasWidth; private int canvasHeight; //设置自己的数据 private TwoWaysQuickSortData data; public AlgoFrame(String title,int canvasWidth,int canvasHeight) { super(title); this.canvasWidth = canvasWidth; this.canvasHeight = canvasHeight; AlgoCanvas canvas = new AlgoCanvas(); canvas.setOpaque(true); this.setContentPane(canvas); this.pack(); this.setResizable(false); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setVisible(true); } public AlgoFrame(String title) { this(title,1024,768); } public void render(TwoWaysQuickSortData data) { this.data = data; this.repaint(); } }
控制层
public class AlgoVisualizer { private static int DELAY = 20; //创建自己的数据 private TwoWaysQuickSortData data; //数据 private AlgoFrame frame; //视图 public AlgoVisualizer(int sceneWidth,int sceneHeight,int N,TwoWaysQuickSortData.Type dataType) { //初始化数据 // TODO: 初始化数据 data = new TwoWaysQuickSortData(N,sceneHeight,dataType); //初始化视图 EventQueue.invokeLater(() -> { frame = new AlgoFrame("欢迎",sceneWidth,sceneHeight); new Thread(() -> { run(); }).start(); }); } public AlgoVisualizer(int sceneWidth,int sceneHeight,int N) { this(sceneWidth,sceneHeight,N, TwoWaysQuickSortData.Type.Default); } /** * 动画逻辑 */ private void run() { // TODO: 编写自己的动画逻辑 setData(-1,-1,-1,-1,-1,-1); quickSort2way(0,data.N() - 1); setData(-1,-1,-1,-1,-1,-1); } private void quickSort2way(int l,int r) { if (l > r) { return; } if (l == r) { setData(l,r,l,-1,-1,-1); return; } setData(l,r,-1,-1,-1,-1); int p = partition(l,r); quickSort2way(l,p - 1); quickSort2way(p + 1,r); } private int partition(int l,int r) { int p = (int) (Math.random() * (r - l + 1)) + l; setData(l,r,-1,p,-1,-1); data.swap(l,p); int v = data.get(l); setData(l,r,-1,l,-1,-1); int i = l + 1; int j = r; setData(l,r,-1,l,i,j); while (true) { while (i <= r && data.get(i) < v) { i++; setData(l,r,-1,l,i,j); } while (j >= l + 1 && data.get(j) > v) { j--; setData(l,r,-1,l,i,j); } if (i > j) { break; } data.swap(i,j); i++; j--; setData(l,r,-1,l,i,j); } data.swap(l,j); setData(l,r,j,-1,-1,-1); return j; } private void setData(int l,int r,int fixedPivot,int curPivot,int curL,int curR) { data.setL(l); data.setR(r); if (fixedPivot != -1) { data.getFixedPivot()[fixedPivot] = true; } data.setCurPivot(curPivot); data.setCurL(curL); data.setCurR(curR); frame.render(data); AlgoVisHelper.pause(DELAY); } }
main方法(随机元素)
public class QuickSortMain { public static void main(String[] args) { int sceneWidth = 800; int sceneHeight = 800; int N = 100; AlgoVisualizer visualizer = new AlgoVisualizer(sceneWidth,sceneHeight,N); // AlgoVisualizer visualizer = new AlgoVisualizer(sceneWidth,sceneHeight,N, TwoWaysQuickSortData.Type.Identical); } }
图形展示
main方法(相同元素)
public class QuickSortMain { public static void main(String[] args) { int sceneWidth = 800; int sceneHeight = 800; int N = 100; // AlgoVisualizer visualizer = new AlgoVisualizer(sceneWidth,sceneHeight,N); AlgoVisualizer visualizer = new AlgoVisualizer(sceneWidth,sceneHeight,N, TwoWaysQuickSortData.Type.Identical); } }
图形展示
- 三路快排算法可视化
三路快排就是将我们已经遍历过的元素分成三部分,这三部分分别是小于v(l+1到lt的区间),等于v(lt+1到i-1的区间)和大于v(gt到r的区间),标定点依然为l。
当我们遍历到i的时候发现i==v,则e直接融入到等于v的部分,然后i++
此时i所在的元素如果是小于v的,我们只需要将i的元素与等于v的第一个元素(lt+1)的元素交换位置
此时e将融入小于v的部分,然后lt++,i++就好了
此时i所在的元素如果是大于v的,则将i的元素与大于v的前一个元素(gt-1)交换位置,然后再gt--
但此时i并不能够进行++操作,因为交换过来的元素还未进行考察。我们只需要继续考察i这个元素就好了。在最终考察完所有元素之后,我们整个数组的元素如下,而终止条件为i >= gt
此时我们需要将标定点l的元素与lt的元素交换一下位置,并最终融入到等于v的部分
由于等于v的部分的左边都是小于v的,右边都是大于v的,所以等于v的这部分我们将不再考虑,而将小于v的部分和大于v的部分以相同的方式(递归)再处理一遍,直到所有递归结束就好了。
使用这种方式,如果我们的数组中有相等的元素非常多的话,我们就能非常快的跳过很多的相等的元素的考察。在极端情况下,如果我们的数组中所有的元素都相等,经过第一轮的三路快排以后,就已经完成了对数组的所有的操作。此时我们的三路快排的算法进化成了一个O(n)级别的算法。而我们JDK本身的例如Arrays.sort()使用的就是三路快排的算法。
数据层
public class ThreeWaysQuickSortData { public enum Type { Default, NearlyOrderd, Identical; } private int[] numbers; @Getter @Setter private int l; @Getter @Setter private int r; @Getter @Setter private int curPivot; @Getter @Setter private int curL; @Getter @Setter private int curR; @Getter @Setter private boolean[] fixedPivot; //在排好序之后元素应该处在的位置 public ThreeWaysQuickSortData(int N, int randomBound, Type dataType) { numbers = new int ; fixedPivot = new boolean ; int lBound = 1; int rBound = randomBound; if (dataType == Type.Identical) { int avgNumber = (lBound + rBound) / 2; lBound = avgNumber; rBound = avgNumber; } for (int i = 0;i < N;i++) { numbers[i] = (int) (Math.random() * (rBound - lBound + 1)) + lBound; fixedPivot[i] = false; } if (dataType == Type.NearlyOrderd) { Arrays.sort(numbers); int swapTime = (int) (0.01 * N); for (int i = 0;i < swapTime;i++) { int a = (int) (Math.random() * N); int b = (int) (Math.random() * N); swap(a,b); } } } public ThreeWaysQuickSortData(int N, int randomBound) { this(N,randomBound, Type.Default); } public int N() { return numbers.length; } public int get(int index) { if (index < 0 || index >= numbers.length) { throw new IllegalArgumentException("无效索引越界"); } return numbers[index]; } public void swap(int i,int j) { if (i < 0 || i >= numbers.length || j < 0 || j >= numbers.length) { throw new IllegalArgumentException("无效的索引越界"); } int temp = numbers[i]; numbers[i] = numbers[j]; numbers[j] = temp; } }
视图层
@Getter public class AlgoFrame extends JFrame { private class AlgoCanvas extends JPanel { public AlgoCanvas() { //双缓存 super(true); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2d = (Graphics2D) g; //抗锯齿 RenderingHints hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2d.addRenderingHints(hints); //具体绘制 // TODO: 绘制自己的数据data int w = canvasWidth / data.N(); for (int i = 0;i < data.N();i++) { if (i >= data.getL() && i <= data.getR()) { AlgoVisHelper.setColor(g2d, AlgoVisHelper.Green); } else { AlgoVisHelper.setColor(g2d, AlgoVisHelper.Grey); } if (i == data.getCurPivot()) { AlgoVisHelper.setColor(g2d, AlgoVisHelper.Indigo); } if (i >= data.getL() + 1 && i <= data.getCurL()) { AlgoVisHelper.setColor(g2d, AlgoVisHelper.LightBlue); } if (i >= data.getCurR() && i <= data.getR()) { AlgoVisHelper.setColor(g2d, AlgoVisHelper.LightBlue); } if (data.getFixedPivot()[i]) { AlgoVisHelper.setColor(g2d, AlgoVisHelper.Red); } AlgoVisHelper.fillRectangel(g2d,i * w,canvasHeight - data.get(i), w - 1,data.get(i)); } } @Override public Dimension getPreferredSize() { return new Dimension(canvasWidth,canvasHeight); } } private int canvasWidth; private int canvasHeight; //设置自己的数据 private ThreeWaysQuickSortData data; public AlgoFrame(String title,int canvasWidth,int canvasHeight) { super(title); this.canvasWidth = canvasWidth; this.canvasHeight = canvasHeight; AlgoCanvas canvas = new AlgoCanvas(); canvas.setOpaque(true); this.setContentPane(canvas); this.pack(); this.setResizable(false); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setVisible(true); } public AlgoFrame(String title) { this(title,1024,768); } public void render(ThreeWaysQuickSortData data) { this.data = data; this.repaint(); } }
控制层
public class AlgoVisualizer { private static int DELAY = 20; //创建自己的数据 private ThreeWaysQuickSortData data; //数据 private AlgoFrame frame; //视图 public AlgoVisualizer(int sceneWidth,int sceneHeight,int N,ThreeWaysQuickSortData.Type dataType) { //初始化数据 // TODO: 初始化数据 data = new ThreeWaysQuickSortData(N,sceneHeight,dataType); //初始化视图 EventQueue.invokeLater(() -> { frame = new AlgoFrame("欢迎",sceneWidth,sceneHeight); new Thread(() -> { run(); }).start(); }); } public AlgoVisualizer(int sceneWidth,int sceneHeight,int N) { this(sceneWidth,sceneHeight,N, ThreeWaysQuickSortData.Type.Default); } /** * 动画逻辑 */ private void run() { // TODO: 编写自己的动画逻辑 setData(-1,-1,-1,-1,-1,-1); quickSort3ways(0,data.N() - 1); setData(-1,-1,-1,-1,-1,-1); } private void quickSort3ways(int l,int r) { if (l > r) { return; } if (l == r) { setData(l,r,l,-1,-1,-1); return; } //随机在arr[l..r]的范围中,选择一个数值作为标定点pivot int p = (int) (Math.random() * (r - l + 1)) + l; setData(l,r,-1,p,-1,-1); data.swap(l,p); int v = data.get(l); setData(l,r,-1,l,-1,-1); //三路快排的partition int lt = l; int gt = r + 1; int i = l + 1; setData(l,r,-1,l,lt,gt); while (i < gt) { if (data.get(i) < v) { data.swap(i,lt + 1); i++; lt++; }else if (data.get(i) > v) { data.swap(i,gt - 1); gt--; }else { i++; } setData(l,r,-1,l,i,gt); } data.swap(l,lt); setData(l,r,lt,-1,-1,-1); quickSort3ways(l,lt - 1); quickSort3ways(gt,r); } private void setData(int l,int r,int fixedPivot,int curPivot,int curL,int curR) { data.setL(l); data.setR(r); if (fixedPivot != -1) { data.getFixedPivot()[fixedPivot] = true; int i = fixedPivot; while (i < data.N() && data.get(i) == data.get(fixedPivot)) { data.getFixedPivot()[i] = true; i++; } } data.setCurPivot(curPivot); data.setCurL(curL); data.setCurR(curR); frame.render(data); AlgoVisHelper.pause(DELAY); } }
正常数据的main方法
public class QuickSortMain { public static void main(String[] args) { int sceneWidth = 800; int sceneHeight = 800; int N = 100; AlgoVisualizer visualizer = new AlgoVisualizer(sceneWidth,sceneHeight,N); // AlgoVisualizer visualizer = new AlgoVisualizer(sceneWidth,sceneHeight,N, ThreeWaysQuickSortData.Type.Identical); } }
图形展示
数组元素都相等的main方法
public class QuickSortMain { public static void main(String[] args) { int sceneWidth = 800; int sceneHeight = 800; int N = 100; // AlgoVisualizer visualizer = new AlgoVisualizer(sceneWidth,sceneHeight,N); AlgoVisualizer visualizer = new AlgoVisualizer(sceneWidth,sceneHeight,N, ThreeWaysQuickSortData.Type.Identical); } }
- 堆排序可视化
对于整个最大堆来说,最大的那个元素一定是数组开始的位置,关于最大堆的概念可以参考数据结构整理 中最大堆一节
而我们要做的就是将最大的元素v与数组最后一个元素w交换位置
此时v已经排好序了,而w则不是剩下的元素的最大值,所以此时剩下的元素不再满足最大堆的性质
而此时,我们要将剩下的元素重新整理成最大堆,再进行一次交换
此时v又进入了正确的位置,再以此反复。
数据层
public class HeapSortData { private int[] numbers; @Getter @Setter private int heapIndex; public HeapSortData(int N,int randomBound) { numbers = new int ; heapIndex = N; for (int i = 0;i < N;i++) { numbers[i] = (int) (Math.random() * randomBound) + 1; } } public int N() { return numbers.length; } public int get(int index) { if (index < 0 || index >= numbers.length) { throw new IllegalArgumentException("无效的索引越界"); } return numbers[index]; } public void swap(int i,int j) { if (i < 0 || i >= numbers.length || j < 0 || j >= numbers.length) { throw new IllegalArgumentException("无效的索引越界"); } int temp = numbers[i]; numbers[i] = numbers[j]; numbers[j] = temp; } }
视图层
@Getter public class AlgoFrame extends JFrame { private class AlgoCanvas extends JPanel { public AlgoCanvas() { //双缓存 super(true); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2d = (Graphics2D) g; //抗锯齿 RenderingHints hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2d.addRenderingHints(hints); //具体绘制 // TODO: 绘制自己的数据data int w = canvasWidth / data.N(); for (int i = 0;i < data.N();i++) { if (i >= data.getHeapIndex()) { AlgoVisHelper.setColor(g2d,AlgoVisHelper.Red); }else { AlgoVisHelper.setColor(g2d,AlgoVisHelper.Grey); } AlgoVisHelper.fillRectangel(g2d,i * w,canvasHeight - data.get(i), w - 1,data.get(i)); } } @Override public Dimension getPreferredSize() { return new Dimension(canvasWidth,canvasHeight); } } private int canvasWidth; private int canvasHeight; //设置自己的数据 private HeapSortData data; public AlgoFrame(String title,int canvasWidth,int canvasHeight) { super(title); this.canvasWidth = canvasWidth; this.canvasHeight = canvasHeight; AlgoCanvas canvas = new AlgoCanvas(); canvas.setOpaque(true); this.setContentPane(canvas); this.pack(); this.setResizable(false); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setVisible(true); } public AlgoFrame(String title) { this(title,1024,768); } public void render(HeapSortData data) { this.data = data; this.repaint(); } }
控制层
public class AlgoVisualizer { private static int DELAY = 20; //创建自己的数据 private HeapSortData data; //数据 private AlgoFrame frame; //视图 public AlgoVisualizer(int sceneWidth,int sceneHeight,int N) { //初始化数据 // TODO: 初始化数据 data = new HeapSortData(N,sceneHeight); //初始化视图 EventQueue.invokeLater(() -> { frame = new AlgoFrame("欢迎",sceneWidth,sceneHeight); //根据情况决定是否加入键盘鼠标监听器 new Thread(() -> { run(); }).start(); }); } /** * 动画逻辑 */ private void run() { // TODO: 编写自己的动画逻辑 setData(data.N()); //建堆 for (int i = (data.N() - 1 - 1) / 2;i >= 0;i--) { shiftDown(data.N(),i); } //堆排序 for (int i = data.N() - 1;i > 0;i--) { data.swap(0,i); shiftDown(i,0); setData(i); } setData(0); } private void shiftDown(int n,int k) { while (2 * k + 1 < n) { int j = 2 * k + 1; if (j + 1 < n && data.get(j + 1) > data.get(j)) { j += 1; } if (data.get(k) >= data.get(j)) { break; } data.swap(k,j); setData(data.getHeapIndex()); k = j; } } private void setData(int heapIndex) { data.setHeapIndex(heapIndex); frame.render(data); AlgoVisHelper.pause(DELAY); } }
main方法
public class HeapSortMain { public static void main(String[] args) { int sceneWidth = 800; int sceneHeight = 800; int N = 100; AlgoVisualizer visualizer = new AlgoVisualizer(sceneWidth,sceneHeight,N); } }
展示图形
- 一个信息可视化Demo的设计(三):算法设计
- 超好的数据结构算法可视化网站
- 从数据预处理开始学习网络可视化——社团发现算法
- 图形算法可视化
- 可视化的数据结构 - 各种算法动画演示
- VTK教程之十二&nbsp;可视化基础算法-几…
- 旧金山大学的算法可视化学习教程 赞的教程,将抽象的算法可视化,易于理解
- 可视化的数据结构和算法
- 太惊艳了,原来算法可视化后可以这么艺术(多gif图)
- 一个信息可视化Demo的设计(三):算法设计
- D3临摹_力引导算法(西安交大国家艺术基金数据可视化培训第23天)
- 数据结构和算法动态可视化
- [js - 算法可视化] 汉诺塔(Hanoi)演示程序
- 学习数据结构和算法动态可视化工具
- 可视化的数据结构 - 各种算法动画演示
- 推荐一个可视化数据结构、算法演示网站
- 数据结构和算法可视化工具---visualgo
- 可视化的数据结构和算法
- 可视化的数据结构和算法
- 【猜测】使用神经网络算法的时候权值可视化观察