随机播放歌曲的算法,原来是这么做的,我一直都搞错了

本篇文章,我将以数组为基础,探索“在线洗牌”的原理。同时,我会以多种方式编写这个原理的代码。还等什么,继续往下看~

方法一:Fisher-Yates 算法

Fisher-Yates 算法的基本前提是遍历条目,将数组中的每个元素与从数组中剩余的未洗牌部分随机选择的元素进行交换。下面的代码中就依据这个算法进行实现的:

const shuffle = (array: string[]) => { 
  for (let i = array.length - 1; i > 0; i--) { 
    const j = Math.floor(Math.random() * (i + 1)); 
    [array[i], array[j]] = [array[j], array[i]]; 
  } 
  return array; 
}; 
  
const myArray = ["apple", "banana", "cherry", "date", "elderberry"]; 
const shuffledArray = shuffle(myArray); 

console.log(shuffledArray); 

上述代码中,我们首先创建了一个 for 循环。在这个循环中,我们将遍历数组中的每个元素,将其位置与数组中的另一个元素交换

接着,创建一个 i 变量,将 array.lenght-1 值赋给它。

通过从最后一个元素开始并向后操作,可以保证数组末尾的元素与任何其他元素交换的机会相等

如果你要从开头开始进行洗牌,那么数组开头的元素将有更高的机会被交换多次,从而导致有偏差或不均匀的洗牌

接着,创建一个 j 变量,它将用于交换索引指针。

然后将索引为 i 的数组赋值给索引为 j 的数组,反之亦然。这将交换数组中的每个项的值并将它们洗牌。

数组解构赋值

接着看到这句代码:[array[i], array[j]] = [array[j][array[i]] 称为数组解构赋值。它允许在两个变量或数组元素之间交换值,而不需要临时变量

下面我们解释一下,在使用 Fisher-Yates 算法对数组进行洗牌的情况下,数组解构赋值是如何工作的:

  • Array [i]Array [j] 表示数组中需要交换的两个元素。
  • [array[j][array[i]] 创建一个临时数组,其中包含 array[j]array[i] 的值,但顺序相反。
  • 通过将 [array[j]array[i]] 赋值给 [array[i]array[j]], array[j]array[i] 的值被交换。

如果我们一步一步地打印出函数的步骤,我们会得到以下结果:

["apple", "banana", "cherry", "date", "elderberry"]; 

//1. 交换 elderberry 和 date 
i = 4; // elderberry 
j = 3; // date 
[("apple", "banana", "cherry", "elderberry", "date")]; 

// 2. 交换 elderberry 和 apple 
i = 3; // 
elderberry j = 0; // apple 
[("elderberry", "banana", "cherry", "apple", "date")]; 

// 3. 交换 cherry 和 banana
i = 2; // cherry 
j = 1; // banana 
[("elderberry", "cherry", "banana", "apple", "date")]; 

// 4. 交换 cherry 自己
i = 1; // cherry 
j = 1; // cherry 
[("elderberry", "cherry", "banana", "apple", "date")]; 

最终,我们就完成了使用 Fisher-Yates 算法进行洗牌的工作。

方法二:使用 sort() 方法和 Random 函数

const shuffle = (array: string[]) => { 
    return array.sort(() => Math.random() - 0.5); 
}; 

const myArray = ["apple", "banana", "cherry", "date", "elderberry"]; 
const shuffledArray = shuffle(myArray); 
console.log(shuffledArray);

这是一个简单的 sort() 函数,它会返回一个随机数,该随机数可以是 负数、0 或 正数sort() 方法在内部比较数组中的元素对,并根据比较函数的返回值确定它们的相对顺序,返回值有三种结果:

  • 如果返回一个负值,则认为第一个元素较小,应该放在排序数组中第二个元素之前。
  • 如果返回一个正值,则认为第一个元素更大,应该放在排序数组中第二个元素之后
  • 如果返回0,则元素的相对顺序保持不变。

Math.random()

当调用 Math.random() 时,它会生成一个伪随机数。“伪随机” 意味着生成的数字看起来是随机的,但实际上是由确定性算法确定的它返回的数字总是一个介于0到1之间的浮点数。浮点数是可以是正的或负的,并且可以有小数部分的数字,例如 3.14、-0.5、1.0、2.71828 等等。

为什么要从 Math.random() 的结果中减去 0.5 ?

通过从 Math.random() 的结果中减去 0.5,将会引入一个介于 -0.50.5 之间的随机值。这个随机值**将导致比较函数以随机的方式为不同的元素对返回负、正或零值。**因此,sort() 方法随机打乱数组。

方法3:使用 Array.map() 函数

map() 函数允许迭代数组的每个元素,并根据提供的映射函数将它们转换为新值。map() 函数返回一个包含转换后的值的新数组,而原始数组保持不变。

const shuffle = (array: string[]) => { 
    return array.map((a) => ({ sort: Math.random(), value: a }))
        .sort((a, b) => a.sort - b.sort)
        .map((a) => a.value); 
}; 

const myArray = ["apple", "banana", "cherry", "date","elderberry"]; const shuffledArray = shuffle(myArray); 
console.log(shuffledArray); 

在这里,循环遍历数组,并在 map() 函数中使用与上面示例中相同的Math.random() 函数,返回具有排序编号和值的对象数组。然后,可以使用 sort() 函数根据这些值对数组进行排序,然后再次调用 map() 函数创建值数组。

const shuffle = (array: string[]) => { 
  const shuffled = array.slice(); 
  let currentIndex = shuffled.length; 
  let temporaryValue, randomIndex; 
  while (currentIndex !== 0) { 
    randomIndex = Math.floor(Math.random() * currentIndex); 
    currentIndex -= 1; 
    temporaryValue = shuffled[currentIndex]; 
    shuffled[currentIndex] = shuffled[randomIndex]; 
    shuffled[randomIndex] = temporaryValue; 
  }
  return shuffled; 
}; 

const myArray = ["apple", "banana", "cherry", "date", "elderberry"]; const shuffledArray = shuffle(myArray); 
console.log(shuffledArray);

不过,经过使用上面三种方法,我还是推荐 Fisher-Yates 算法,因为它的时间复杂度很低:O(n)。这意味着输入的数组的大小增加一倍将大致增加一倍的执行时间。类似地,如果输入的数组的大小减半,执行时间也将大约减半。所以数组越大,洗牌的复杂度和时间就越大。

因此,在对大型数组进行洗牌时,这一点值得注意。可能值得考虑其他方法,或者将数组分块并并行运行变换,然后再将其拼凑在一起。

该方法还允许更容易地对任何类型的数组进行洗牌,而不仅仅是 string[] 类型。同时,当使用 TypeScript 泛型时,它也能很好地工作。这允许将任何类型的数组可以传递给函数并进行洗牌。

const shuffle = <T>(array: T[]) => { 
  for (let i = array.length - 1; i > 0; i--) { 
    const j = Math.floor(Math.random() * (i + 1)); 
    [array[i], array[j]] = [array[j], array[i]]; 
  }
  return array; 
}; 

const strings = ["apple", "banana", "cherry", "date", "elderberry"]; 
const users = [ { name: "John", surname: "Doe" }, { name: "Jane", surname: "Doe" }];

const shuffledArray = shuffle(strings); 
const shuffledObjects = shuffle(users); 

console.log(shuffledArray); 
console.log(shuffledObjects); 

这个方法,在日常开发案例中有很多实际的应用。例如:

  • 随机播放歌曲列表
  • 麻将、斗地主游戏随机洗牌
  • 创建随机问题进行样本测验

希望对你有帮助。

这里是编程轨迹,下篇文章再见。

<think>嗯,用户想了解YOLOv1中k-means算法在anchor box生成中的应用。首先,我需要确认一下YOLOv1是否真的用了k-means,因为可能用户记混了后续版本。YOLOv1其实并没有使用anchor boxes,而是直接预测边界框的坐标。Anchor boxes是从YOLOv2开始引入的,而YOLOv2确实用了k-means聚类来生成先验框。用户可能把版本搞错了,但问题本身是关于YOLOv1的,所以需要澄清这一点。 接下来,我应该先解释YOLOv1的基本工作原理,然后指出anchor boxes是在后续版本中加入的,并说明k-means的作用。这样用户就能明白两者的区别,同时也能得到他们可能真正需要的信息。此外,用户提供的引用资料里提到了peakC和Hive的类型映射,这些可能与当前问题无关,但需要确保回答中的引用正确。不过用户给出的引用可能只是示例,实际回答中可能不需要用到这些,但根据系统指令,引用标识需要自然添加,可能需要检查是否有相关内容可以引用。 然后,我需要详细描述YOLOv2中使用k-means生成anchor boxes的步骤,包括如何计算距离度量,如何选择聚类中心,以及如何应用到模型中。同时,要比较YOLOv1和YOLOv2在边界框预测上的不同,强调k-means带来的改进。这样用户不仅能了解k-means的应用,还能区分不同版本的YOLO。 用户的问题可能源于对YOLO系列发展的不熟悉,因此生成的相关问题应该涵盖版本差异、k-means的具体步骤、选择k值的依据以及k-means的优势和局限性。这样可以帮助用户更全面地理解主题,并引导他们进一步学习。</think>### YOLOv1中k-means算法在anchor box生成中的应用解释 YOLOv1是首个将目标检测建模为单阶段回归问题的模型,但其原始版本**并未使用k-means生成anchor boxes**。事实上,**anchor box机制是从YOLOv2开始引入的**,而k-means聚类正是用于优化anchor box尺寸的关键方法[^1]。 #### 背景说明 1. **YOLOv1的边界框预测**: - 直接预测边界框的坐标$(x,y,w,h)$,其中$(w,h)$是相对于整张图像的绝对尺寸 - 每个网格单元仅预测1个边界框,导致对小目标和密集目标检测效果有限 2. **YOLOv2的改进**: - 引入anchor boxes作为先验知识 - 使用k-means聚类**分析训练集的标注框分布**,生成更符合数据特性的anchor尺寸 #### k-means在YOLOv2中的具体应用 **步骤说明**: 1. **数据准备**: - 提取训练集中所有标注框的宽度和高度$(w_i,h_i)$ - 进行归一化处理(相对于图像尺寸) 2. **距离度量设计**: - 定义特殊距离函数:$$d(box, centroid) = 1 - IOU(box, centroid)$$ - 相比欧氏距离,此度量更符合目标检测任务特性 3. **聚类过程**: - 随机初始化$k$个聚类中心 - 迭代执行分配和更新步骤,直至收敛 - 最终得到$k$个anchor box的基准尺寸 4. **结果应用**: - 将聚类结果作为网络先验知识 - 每个网格单元预测$k$个anchor boxes的偏移量 #### 关键公式示例 对于标注框与聚类中心的IOU计算: $$IOU = \frac{Area(box \cap centroid)}{Area(box \cup centroid)}$$ #### 代码示例(概念性实现) ```python def kmeans_anchors(boxes, k, max_iters=100): # boxes: 归一化的(w,h)数组 centroids = boxes[np.random.choice(len(boxes), k, replace=False)] for _ in range(max_iters): distances = 1 - calculate_iou(boxes, centroids) labels = np.argmin(distances, axis=1) new_centroids = np.array([boxes[labels==i].mean(0) for i in range(k)]) if np.all(centroids == new_centroids): break centroids = new_centroids return centroids ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蟹蟹蟹风流

期望和你分享一杯咖啡

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值