再次展示算法的力量!~~三分钟的程序优化到了90毫秒,还是那句话,位运算神马的最给力了。
再次展示算法的力量!~~三分钟的程序优化到了90毫秒,还是那句话,位运算神马的最给力了。
淘宝卖家想知道,哪些商品的组合是最受欢迎的。
已知十万多订单项里,有几十种商品,有一万多相关用户,要求输出2-6种商品的全部组合,对应的订单数。
订单项,记录的是产品编号,用户编号,可以随机模拟。
已知十万多订单项里,有几十种商品,有一万多相关用户,要求输出2-6种商品的全部组合,对应的订单数。
订单项,记录的是产品编号,用户编号,可以随机模拟。
这个程序,同事小张,在输出2种商品的情况下,就耗时3分多钟。
在他原有的代码上,我把他原来的复杂度O(n^2*m^2),利用哈希表,优化到了O(n^2*m),还需要59秒。
今天决定得瑟一下,突破性能极限,显露一下位运算的威武。
在他原有的代码上,我把他原来的复杂度O(n^2*m^2),利用哈希表,优化到了O(n^2*m),还需要59秒。
今天决定得瑟一下,突破性能极限,显露一下位运算的威武。
原理如下:
63个数以内的排列组合,不管几选几,都可以用一个Ulong整数来表示。
比如:【1,3,5】组合可以表示成:2^1+2^3+2^5=(1<<1)+(1<<3)+(1<<5)=42;
那么42就包含了【1,3,5】组合的信息,如果判断一个整数x是否在这个组合里
只需要与运算,42 & (1<<x)!=0 即可。
而且,还可以O(1)时间内判断出两个集合的交集。
例如:【1,3,4】可以表示成(1<<1)+(1<<3)+(1<<4)=26;
那么【1,3,5】和【1,3,4】的交集,显然是【1,3】
而【1,3】可以表示成(1<<1)+(1<<3)=10;
见证奇迹的时间到了
与运算42 & 26结果,恰恰就是10。
63个数以内的排列组合,不管几选几,都可以用一个Ulong整数来表示。
比如:【1,3,5】组合可以表示成:2^1+2^3+2^5=(1<<1)+(1<<3)+(1<<5)=42;
那么42就包含了【1,3,5】组合的信息,如果判断一个整数x是否在这个组合里
只需要与运算,42 & (1<<x)!=0 即可。
而且,还可以O(1)时间内判断出两个集合的交集。
例如:【1,3,4】可以表示成(1<<1)+(1<<3)+(1<<4)=26;
那么【1,3,5】和【1,3,4】的交集,显然是【1,3】
而【1,3】可以表示成(1<<1)+(1<<3)=10;
见证奇迹的时间到了
与运算42 & 26结果,恰恰就是10。
强大不?组合数学
这样就可以把O(n^2*m^2)问题转化为O(C(n,2)),震撼吧?
请看我如何实现,翠花,上代码!~~
这样就可以把O(n^2*m^2)问题转化为O(C(n,2)),震撼吧?
请看我如何实现,翠花,上代码!~~
public class User { public string UserName; public User(int UserID) { UserName = "用户" + UserID; } }
public class Prdouct { public string PrdouctName; public Prdouct(int PrdouctID) { PrdouctName = "货" + PrdouctID; } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication2 { //测试用例类 public class TestCase { //模拟商品编号索引,不超过63种,注意计算过程中完全用的是下标,到最后结果才输出值。 public List<Prdouct> PrdouctList = new List<Prdouct>(); //模拟不重复用户编号,随意数量,这些用户必须和这些产品有关系。 计算过程中完全用的是下标. public List<User> UserList = new List<User>(); //会员-商品关系表,核心索引。Key是用户编号,值是购物信息。 //公式:购物信息=∑(1ul<<PrdouctIndex) 。其中PrdouctIndex是在本程序内的索引,不超过63种商品。 //实现见:InitUserProduct()模拟用户随机购买商品的函数 public Dictionary<int, ulong> UserBuyProduct = new Dictionary<int, ulong>(); //商品-会员数量关系表,第二个核心索引。Key是商品组合信息,值是会员数量。 //递归产生C(n,m)组合。这个复杂度是避免不了的。 //实现见:Cross()函数 public Dictionary<ulong, int> ProductUserBuy = new Dictionary<ulong, int>(); public int ProductDimension; private Random rand = new Random(); //产生测试用例的构造函数 public TestCase(int Prdouct_Count, int User_Count) { //Prdouct_Count不要超过63种。 //模拟产生随机商品编号 InitPrdouctList(Prdouct_Count); //模拟产生随机用户编号 InitUserList(User_Count); } //模拟产生随机商品 private void InitPrdouctList(int prdouctCount) { int lastPrdouctID = 0; while (PrdouctList.Count < prdouctCount) { lastPrdouctID += rand.Next(1, 3); PrdouctList.Add(new Prdouct(lastPrdouctID)); } } //模拟产生随机用户 private void InitUserList(int userCount) { int lastUserID = 0; while (UserList.Count < userCount) { lastUserID += rand.Next(1, 3); UserList.Add(new User(lastUserID)); } } //模拟用户随机购买商品订单,核心函数,重点 public int InitUserProduct() { int count = 0; for (int userIndex = 0; userIndex < UserList.Count; userIndex++) { UserBuyProduct.Add(userIndex, 0ul); //每个用户随机次数购买,随机产品种类。 for (int userProducNum = 0; userProducNum < rand.Next(1, 99); userProducNum++) { int productIndex = rand.Next(PrdouctList.Count); UserBuyProduct[userIndex] += (1ul << productIndex);//这行是重点 count++; } } return count; } //根据产品纬度,输出完全交叉报表 //例如CrossReport(2)表示二维报表,两种产品维度 public string CrossReport(int Product_Dimension) { ProductDimension = Product_Dimension; StringBuilder SB = new StringBuilder(); Cross(0ul, 0, -1, 0); foreach (ulong BuyInfo in ProductUserBuy.Keys) { SB.AppendLine(Report(BuyInfo, Product_Dimension) + "数量" + ProductUserBuy[BuyInfo]); } return SB.ToString(); } //递归产生C(n,m)组合。 private void Cross(ulong lastBuyInfo, int lastProductDimension, int lastPrdouctIndex, int lastUserCount) { if (lastProductDimension >= ProductDimension) { ProductUserBuy.Add(lastBuyInfo, lastUserCount); return; } for (int PrdouctIndex = lastPrdouctIndex + 1; PrdouctIndex < PrdouctList.Count; PrdouctIndex++) { ulong currentBuyInfo = lastBuyInfo + (1ul << PrdouctIndex); int currentUserCount = lastUserCount; foreach (int UserID in UserBuyProduct.Keys) { if ((currentBuyInfo & UserBuyProduct[UserID]) != 0ul)//买过此商品。 { currentUserCount++; } } Cross(currentBuyInfo, lastProductDimension + 1, PrdouctIndex, currentUserCount); } } //解读二进制购买信息为可读的,复杂度小于O(63)。 private string Report(ulong BuyInfo, int Product_Dimension) { StringBuilder SB = new StringBuilder(); int D = 0; for (int PrdouctIndex = 0; PrdouctIndex < PrdouctList.Count; PrdouctIndex++) { if ((BuyInfo & (1ul << PrdouctIndex)) != 0ul) { SB.Append(PrdouctList[PrdouctIndex].PrdouctName + ","); D++; } if (D >= Product_Dimension) { break; } } return SB.ToString(); } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Diagnostics; namespace ConsoleApplication2 { class Program { static void Main(string[] args) { Stopwatch SW1 = new Stopwatch();//计时器 Stopwatch SW = new Stopwatch();//计时器 int productCount = 10;//商品种类数,不要超过63。 int userCount = 10000;//和这些商品有关系的用户数。 int productDimention = 2;//报表维度,不要超过productCount。 TestCase TC = new TestCase(productCount, userCount); int total = TC.InitUserProduct(); //模拟用户随机购买商品订单,核心函数,重点. SW1.Start(); for (int i = 2; i < 6; i++) { SW.Start(); productDimention = i; Console.Write(TC.CrossReport(productDimention)); SW.Stop(); Console.WriteLine("" + productDimention + "维报表耗时" + SW.ElapsedMilliseconds + "毫秒"); } SW1.Stop(); Console.WriteLine("总共耗时" + SW1.ElapsedMilliseconds + "毫秒"); Console.WriteLine("商品数:" + productCount + "用户数" + userCount + "交易量:" + total); Console.Read(); } } }
现在输出2维报表,只需要90毫秒,而2到6维报表一起,总共只需要1秒钟!~~
再次见证了算法的力量,本人威武!~~我爱算法皮肤好好,欧嗷噢哦。。。
英雄,总以豪情相伴,现改编《套马杆》歌词如下,玩算法的人你伤不起啊!~~
给我一个算法,让我一算到天亮。
给我一个难题,永远记心上。
给我一段代码,一个不变的思想。
给我一个结构体,一劳永逸。
会写代码的汉子你威武雄壮,
飞驰地程序,象疾风一样。
一望无际的网络,
随你去流浪,
你的胸怀象NP问题一样宽广。