组合算法二-商品属性组合实现

516 阅读4分钟

组合算法(二)

源码地址

接上段组合算法一的后续,完成对属性分组的组合,之前是懒得写了所以就没写了

01. 回顾

场景应用

1. 属性组合

根据商品的属性规格计算可以组合的sku有多少种【什么是sku?这个不介绍了】如下

某某商品的属性规格(电脑)

颜色 = {银灰色,纯黑色,淡绿色}
CPU/运行内存 = {i7-8/16G, i7-8/8G, i5-8/16G, i5-8/8G}
存储空间 = {1T, 512G}

以上面三中规格属性组成具体的sku则就是;有如下24种

sku1 = {银灰色, i7-8/16G, 1T}
sku2 = {纯黑色, i7-8/16, 512G}
sku3 = {淡绿色, i5-8/16, 512G}
...

在某一些面试题中也有针对上面的情况进行处理

02. 分析

为了简化一些不必要的元素影响,我们假定给定的元素为如下元素

a := ["a", "b", "c"]
b := ["1", "2", "3"]
c := ["Q", "R", "B"]

目的嘛,就是简单看起来没有那么复杂,数据类型定位string类型;对于上面的数组组合元素,首先需要值得注意的是组合元素的排列顺序,是根据基于给定的数组顺序定;

比如a,b,c从这三个数组中分别获取一个元素进行组合,以x代表获取数组元素的下标,那么组合的结果应是[a[x], b[x], c[x]];其组合元素的排列顺序需要与a,b,c的数组顺序一致;而对于元素的组合要求是每个组合的元素是不能存在重复的元素。

对于组合分析可行思路【这里的思路是我目前能够想到的,暂时还没有相当更好的思路,如果有更好的欢迎指点】

思路1:

如下图先例举好对应的3个数组

markdown-img-paste-20220327142707583.png

从上而之下选择一个元素位置固定不变,然后与最后一组的每一个元素组合,当组合完之后往上一层选择下一个元素固定不变再重新与最后一层的元素进行组合,直到最上层的元素也组合完成为止;

markdown-img-paste-20220327143311983.png

如上图,选择a,b两组的第一个元素固定不变然后与最后c组的元素遍历组合,即可得到组合的元素;

[a, 1, R][a, 1, Q][a, 1, L]

当与c组元素组合完成之后,我们就让c组上层的b组往前选择下个元素位置固定不变

markdown-img-paste-20220327143630247.png

然后再进行重复的过程,当c组元素组合完之后b又往前移动选择下个元素不变;同样的b组的元素全部组合完成之后就选择a组的元素

markdown-img-paste-20220327143851900.png

思路2:

原以为这种思路会比第一种好,后来细想错了;..... ̄□ ̄||

基于第一种,实际上每次从上而之下的组合下层元素总是会存在重复中的情况,比如b,c这两组的组合会存在这重复多次的情况,因此思路上我们可以把b,c提前组合好然后再与a组合;

markdown-img-paste-2022032714462426.png

但是实际运算的时间复杂度与思路1相差无几...测试中结果一样;

思路3:

待发掘

03. 实现

具体实践实现:

思路1

思路在实现上采用递归的思路去实现的,在结构体中定义count可以用来记录算法运行次数


type Combinatiorial struct {
	count int
}

func (com *Combinatiorial) Recursion(a [][]string) [][]string {
	// 避免传入空的字符串
	if len(a) == 0 {
		return nil
	}

	return com.recursion(make([]string, 0, len(a)), 0, a)
}

func (com *Combinatiorial) recursion(record []string, tier int, a [][]string) [][]string {
	if tier > len(a) -1 {

		return nil
	}

	var ret [][]string

	for i := 0; i < len(a[tier]); i++ {
		tmp := make([]string, 0, len(record) + 1)
		tmp = append(tmp, record...)
		tmp = append(tmp, a[tier][i])

		com.count++ // 单纯记录次数

		if len(tmp) == len(a) {
			// 这是最后一轮
			ret = append(ret, tmp)
			continue
		}

		// 获取下一层的组合结果
		ret = append(ret, com.recursion(tmp, tier+1, a)...)
	}

	return ret
}

在循环类目tmp就是用来记录每次组合的结果,如果不是最下层元素则会记录当前上层的组合,然后再传递重复执行;从上而之下的组合过程

思路2:

思路2在实现上定义map[int][][]string记录组合的结果,而在实现的细节上也是从上而至下,是先组合前两层的结果,然后继续拿组合的结果往下继续对后续的元素进行组合;在时间复杂度上与思路1一样

func (com *Combinatiorial) RecursionMemo(a [][]string) [][]string {
	// 避免传入空的字符串
	if len(a) == 0 {
		return nil
	}

	memo := make(map[int][][]string)
	for i := 0; i < len(a[0]); i++ {
		com.count++

		memo[0] = append(memo[0], []string{a[0][i]})
	}
	return com.recursionMemo(1, a, memo)
}

func (com *Combinatiorial) recursionMemo(tier int, a [][]string, memo map[int][][]string) [][]string {

	for _, v := range memo[tier - 1] {
		for i := 0; i < len(a[tier]); i++ {
			com.count++
			tmp := make([]string, 0,tier)
			tmp = append(tmp, v...)
			tmp = append(tmp, a[tier][i])
			memo[tier] = append(memo[tier], tmp)
		}
	}

	if tier+1 < len(a){
		com.recursionMemo(tier+1, a, memo)
	}
	return memo[len(a) - 1]
}

对于数组组合的实现运用在商品属性的组合上可以用到

在实现的思路上目前想到的就是上面两种方案两者相差不是太大,如果建议的话可以推荐思路1,如果有更好的方案欢迎指点