程序代码优化--位运算优化方式
再次展示算法的力量!~~三分钟的程序优化到了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秒钟!~~
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步