再次展示算法的力量!~~三分钟的程序优化到了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问题一样宽广。
浙公网安备 33010602011771号