再次展示算法的力量!~~三分钟的程序优化到了90毫秒,还是那句话,位运算神马的最给力了。

再次展示算法的力量!~~三分钟的程序优化到了90毫秒,还是那句话,位运算神马的最给力了。
淘宝卖家想知道,哪些商品的组合是最受欢迎的。
已知十万多订单项里,有几十种商品,有一万多相关用户,要求输出2-6种商品的全部组合,对应的订单数。
订单项,记录的是产品编号,用户编号,可以随机模拟。
这个程序,同事小张,在输出2种商品的情况下,就耗时3分多钟。
在他原有的代码上,我把他原来的复杂度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。
强大不?组合数学
这样就可以把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问题一样宽广。

 
 
 
posted @ 2011-06-29 17:29  CSDN大笨狼  阅读(1145)  评论(5编辑  收藏  举报