04-20.eri-test 使用图像可视化排序算法

在本周的博客中,我认为我会做一些事情来帮助我学习更多有关算法的知识,并为编写采访编程做准备。 但是,以我的典型方式,我认为我会尝试使其形象化以做一些不同的事情,以使此有趣的内容读起来并对其进行尝试。 因此,让我们开始吧。

Partitioning the image

起初,我认为使用P5将图像划分为不同的像素,然后将这些图像随机排列以放置在图像的不同区域会很有趣。 但是,事实证明,JavaScript中的P5库和Java中的处理是通过两种不同的方式处理像素的。 Java,当使用loadPixels()函数时,将返回一个数组,该数组包含所有像素作为该数组中的单个元素,其中每个像素在整个图像上从左到右逐行读取。

现在在javascript中不是这种情况。 P5实际上会给您一个像素数组,其中每个像素实际上会分成该数组中的三个不同元素。 数组的元素实际上是第一个图像像素中的红色量,第​​二个元素是绿色的量,第三个元素是蓝色的量。

因此,与其将一个像素精确地映射到像素阵列的单个元素中,不如将每个单个像素拆分为阵列上的三个元素,每个元素代表各自的rgb值。

这使得交换像素和遍历像素数组比处理对象复杂得多。 同样,考虑到像素数量通常在数百或数千中,对该数组进行排序实际上会增加计算负担,并且观看时没有那么大的指导意义。

因此,我决定根据图像的任意块进行分区更适合该任务。 这里的问题是图像必须在x和y方向上根据指定的量进行分割。 在P5中看起来像:

const sketch = (p) => {
    let img, loc, array = [], amountX = 30, amountY = 30;
    let blockWidth, blockHeight;

...

p.setup = () => {
        img.resize(img.width - img.width % amountX, img.height - img.height % amountY)
        blockWidth = img.width / amountX
        blockHeight = img.height / amountY

        ...

    } 

p.partition = (img) => {
        let array = []
        let newBlock
        for (let j = 0; j < amountY; j++) {
            for (let i = 0; i < amountX; i++) {
                newBlock = img.get(i * blockWidth, j * blockHeight, blockWidth, blockHeight)
                array.push({img: newBlock, index: i + j * amountX})
            }
        }
        return array
    }
}

如果标记有点令人困惑,那么它就是实例模式下的P5,因为我必须运行服务器才能将其用于图像处理. 不幸的是,除非代码在服务器上,否则P5无法使用图片. 而且,我想做一些可以部署的事情,并展示不同的排序算法如何相互竞争,从而使React得以使用。. I have a post about using React and P5 together if you're curious.

无论如何,上面的代码包含了我的素描函数中的所有内容,除了draw函数(几秒钟内即可完成)。 分区函数使用草图函数中的作用域变量获取img,并使用amountX和amountY将图像分解为网格。 将图像宽度除以数量X并将图像高度除以数量Y(分别称为blockWidth和blockHeight)也很有用,以了解每一列和每一行的宽度。

重要提示:我在设置功能中调整了图像的大小,因此没有百分比宽度或高度值,以后会引起问题。

因此,网格创建发生在嵌套的for循环中

p.partition = (img) => {
        let array = []
        let newBlock
        for (let j = 0; j < amountY; j++) {
            for (let i = 0; i < amountX; i++) {
                newBlock = img.get(i * blockWidth, j * blockHeight, blockWidth, blockHeight)
                array.push({img: newBlock, index: i + j * amountX})
            }
        }
        return array
    }

我们知道,在垂直方向上将有amountY块,在水平方向上将有数量X的块,因此在垂直和水平方向上计数两个变量是关键。 一旦在任意i和j的某个块处,我们在P5中使用get()将复制图像的某些指定部分。

get()函数仅获取所需图像的起始x和y值以及宽度和高度。 这会雕刻出一个很好的矩形或图像块。 起始的x和y将仅是块的宽度的i倍和块的高度的j倍。 然后,最终值将是我们的blockWidth和blockHeight。 然后,您只需将此值放入数组中并返回即可。 但是,我想到以后再进行排序,因此将每个图像及其在图像中的适当位置一起推入,以便以后对其进行正确排序。

Now Do the Shuffle

有了图像块阵列后,在进行排序之前,重要的事情是适当地对阵列进行洗牌。 为此,我读到了这段代码足以随机地对数组进行随机排序。

array.sort((a,b) => Math.random() - 0.5)

However I read an article by the creator of D3 that this algorithm is no good at all. 原因似乎是数学.random constantly returns a random value and thus doesn't obey transitivity for comparing things (a < b and b < c implies a < c). Mike Bostock, the author of the article, does a good job visualizing the bias in this algorithm when a random algorithm should have absolutely no bias for any point whatsoever. 相反,他建议使用此算法:

function shuffle(array) {
  var n = array.length, t, i;
  while (n) {
    i = Math.random() * n-- | 0; // 0 ≤ i < n
    t = array[n];
    array[n] = array[i];
    array[i] = t;
  }
  return array;
}

实际上,这确实做得更好,没有表现出对某些点的偏见,实现了我们真正随机的想法。 这只是循环遍历数组,并且在每个索引处随机选择一个元素与之交换。 当然,您可能会多次交换元素,但是它可以完成工作,如下所示:

Scrambled Puppy

Now the Sorting

这是对图像进行分区并添加正确的索引属性的第一步很重要的地方。 可视化正在执行的排序的棘手事情是,您将使用draw函数作为外部for循环迭代器。 由于此函数被反复调用,因此我们必须在草图范围中初始化iterator变量,以便其持久存在。 我的整个草图如下所示:

const sketch = (p) => {
    let img, loc, array = [], amountX = 50, amountY = 50;
    let i = 0;
    let blockWidth, blockHeight;
    const path = './puppy.jpg'

    p.preload = () => {
        img = p.loadImage(path)
    }

    p.setup = () => {
        img.resize(img.width - img.width % amountX, img.height - img.height % amountY)
        blockWidth = img.width / amountX
        blockHeight = img.height / amountY
        p.createCanvas(img.width * 2,img.height);
        array = p.partition(img);
        p.background(255);
        p.image(img, img.width, 0);
        shuffle(array)
    } 

    p.draw = () => {
        if (i < array.length) {
            for (let j = 0; j < array.length - i - 1; j++) {
                if (array[j + 1].index < array[j].index) {
                    let t = array[j];
                    array[j] = array[j + 1];
                    array[j + 1] = t;
                }
            }
            i++;
        } else {
            p.noLoop()
        }
        for (let i = 0; i < amountX; i++) {
            for (let j = 0; j < amountY; j++) {
                p.image(array[i + j * amountX].img, i * blockWidth, j * blockHeight)
            }
        }
    }

    p.partition = (img) => {
        ...
    }
}

重要的是在草图和draw函数的前几行中声明的i变量。 我只是我们的迭代器,然后一行:

if (i < array.length) {
            for (let j = 0; j < array.length - i - 1; j++) {
                if (array[j + 1].index < array[j].index) {
                    let t = array[j];
                    array[j] = array[j + 1];
                    array[j + 1] = t;
                }
            }
            i++;
        } else {
            p.noLoop()
        }

是冒泡排序的非常标准的实现。 我还没有实现其他排序算法,但是我绝对会计划在上面。 我在这里不打算讨论冒泡排序,因为我认为关于该算法的信息非常丰富,因为它最容易实现。

然后,对于每次我们进行的迭代,我都会循环遍历图像数组,并将其输出到画布上的正确位置,此处是这样:

for (let i = 0; i < amountX; i++) {
            for (let j = 0; j < amountY; j++) {
                p.image(array[i + j * amountX].img, i * blockWidth, j * blockHeight)
            }
        }

这是制作分区的相同的double for循环,但用于显示每个图像。

这几乎就是基础. 稍后再检查以添加可视化工具. You can see it in action here and the repository for the code can be found here.

from: https://dev.to//christiankastner/visualizing-sorting-algorithms-with-images-17co

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值