获取数字的所有约数的最有效方法

发布于 2024-11-04 01:24:02 字数 3445 浏览 0 评论 0原文

可能的重复:
高效查找数字的所有约数

这更多的是一种效率问题而不是通用的“找到一种方法来做到这一点”,但在得到一些奇怪的结果后,我想看看是否有人可以告诉我为什么最后一种方法如此低效:

方法1:暴力,没有优化

    public static List<int> proper_divisors(int x)
    {
        List<int> toreturn = new List<int>();
        for (int i = 1; i <= Math.Floor(Math.Sqrt(x)); i++)
        {
            if (x % i == 0)
            {
                toreturn.Add(i);
                toreturn.Add(x / i);
            }
        }
        if (toreturn.ElementAt(toreturn.Count() / 2) == toreturn.ElementAt(toreturn.Count() / 2 - 1))
        {
            toreturn.Remove(toreturn.ElementAt(toreturn.Count() / 2));
        }

        return toreturn;
    }

方法2:与之前,但这一次,首先检查它是否是素数(因为这些情况占用了最多的时间,使用 miller-rabin 进行素数检查)

        public static List<int> proper_divisors(int x)
    {
        List<int> toreturn = new List<int>();
        if (!isprime(x))
        {
            for (int i = 1; i <= Math.Floor(Math.Sqrt(x)); i++)
            {
                if (x % i == 0)
                {
                    toreturn.Add(i);
                    toreturn.Add(x / i);
                }
            }
            if (toreturn.ElementAt(toreturn.Count() / 2) == toreturn.ElementAt(toreturn.Count() / 2 - 1))
            {
                toreturn.Remove(toreturn.ElementAt(toreturn.Count() / 2));
            }
        }
        else
        {
            toreturn.Add(1);
            toreturn.Add(x);

        }
        return toreturn;
    }

它认为迄今为止最快的方法是方法 3,因为它减少了它的数量每次找到素数因子时都会进行处理,并且它只尝试素数(这些素数是在运行时由筛子生成的,需要大约 34 毫秒才能获得小于一百万的所有素数),这种方式要做的最后一件事就是采用主要因素及其力量,并列出所有因素。

方式 3:

                public static HashSet<int> prime_factors(int x)
    {
        if (!isprime(x))
        {
            List<int> toreturn = new List<int>();
            int i = 0;
            while (primes[i] <= x)
            {
                if (x % primes[i] == 0)
                {
                    toreturn.Add(primes[i]);
                    x = x / primes[i];
                }
                else
                {
                    i++;
                }
            }
            var power_set_primes = GetPowerSet(toreturn);
            var factors = new HashSet<int>();
            foreach (var p in power_set_primes)
            {
                var factor = p.Select(z => z).Aggregate(1, (z, y) => z * y);
                factors.Add(factor);
            }
            return factors;
        }
        else
        {
            HashSet<int> toreturn = new HashSet<int>();
            toreturn.Add(1);
            toreturn.Add(x);
            return toreturn;
        }
        public static IEnumerable<IEnumerable<T>> GetPowerSet<T>(List<T> list)
    {
        return from m in Enumerable.Range(0, 1 << list.Count)
               select
                   from i in Enumerable.Range(0, list.Count)
                   where (m & (1 << i)) != 0
                   select list[i];
    }

分解前一百万个数字所需的时间: 方式 1:7223 毫秒 方式2:8985 ms(我猜对于小数字来说素数检查不值得) 方式3:49423毫秒

所以我的问题是双重的: 1)为什么方式3这么慢??? 2)有什么东西可以让它更快吗? 顺便说一句,素数被计算为列表,然后转换为数组,因为我认为这样会更快。不好的举动?

Possible Duplicate:
Efficiently finding all divisors of a number

This is much more of an efficiency question than a generic "find a way to do it", but after getting some odd results, I want to see if someone can tell me why the last way is so inefficient:

way 1: brute force, no optimization

    public static List<int> proper_divisors(int x)
    {
        List<int> toreturn = new List<int>();
        for (int i = 1; i <= Math.Floor(Math.Sqrt(x)); i++)
        {
            if (x % i == 0)
            {
                toreturn.Add(i);
                toreturn.Add(x / i);
            }
        }
        if (toreturn.ElementAt(toreturn.Count() / 2) == toreturn.ElementAt(toreturn.Count() / 2 - 1))
        {
            toreturn.Remove(toreturn.ElementAt(toreturn.Count() / 2));
        }

        return toreturn;
    }

way 2: same as before, but this time, check if its prime first (as those cases take up the most time, using miller-rabin for prime checking)

        public static List<int> proper_divisors(int x)
    {
        List<int> toreturn = new List<int>();
        if (!isprime(x))
        {
            for (int i = 1; i <= Math.Floor(Math.Sqrt(x)); i++)
            {
                if (x % i == 0)
                {
                    toreturn.Add(i);
                    toreturn.Add(x / i);
                }
            }
            if (toreturn.ElementAt(toreturn.Count() / 2) == toreturn.ElementAt(toreturn.Count() / 2 - 1))
            {
                toreturn.Remove(toreturn.ElementAt(toreturn.Count() / 2));
            }
        }
        else
        {
            toreturn.Add(1);
            toreturn.Add(x);

        }
        return toreturn;
    }

what it thought would be the fastest way by far was way 3, because it reduced the number that it was working with every time it found a prime factor, and it only tried primes (these were generated by a sieve at runtime, takes about 34 ms to get all primes less than a million) the last thing this way had to do was take the prime factors and their powers, and make a list of all the factors.

way 3:

                public static HashSet<int> prime_factors(int x)
    {
        if (!isprime(x))
        {
            List<int> toreturn = new List<int>();
            int i = 0;
            while (primes[i] <= x)
            {
                if (x % primes[i] == 0)
                {
                    toreturn.Add(primes[i]);
                    x = x / primes[i];
                }
                else
                {
                    i++;
                }
            }
            var power_set_primes = GetPowerSet(toreturn);
            var factors = new HashSet<int>();
            foreach (var p in power_set_primes)
            {
                var factor = p.Select(z => z).Aggregate(1, (z, y) => z * y);
                factors.Add(factor);
            }
            return factors;
        }
        else
        {
            HashSet<int> toreturn = new HashSet<int>();
            toreturn.Add(1);
            toreturn.Add(x);
            return toreturn;
        }
        public static IEnumerable<IEnumerable<T>> GetPowerSet<T>(List<T> list)
    {
        return from m in Enumerable.Range(0, 1 << list.Count)
               select
                   from i in Enumerable.Range(0, list.Count)
                   where (m & (1 << i)) != 0
                   select list[i];
    }

Time it took to factor the first million numbers:
way 1: 7223 ms
way 2: 8985 ms (prime checking is not worth it for small numbers i guess)
way 3: 49423 ms

so my question is twofold:
1) why is way 3 so slow???
2) is there something that can make it faster?
as an aside, primes was computed as a list, then converted to an array, as I thought it would be faster that way. bad move?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

掩耳倾听 2024-11-11 01:24:02

这是整数分解的问题域。这里有许多众所周知的算法:

http://en.wikipedia.org/wiki/Integer_factorization# Factoring_algorithms

我建议您选择最佳匹配+配置文件。


我的原始评论:

个人资料个人资料。另外,如果您关心效率,请不要使用枚举器或 LINQ。用 C 语言编写并使用 P/Invoke。一般来说,不要问是否可以测量的问题

This is the problem domain of integer factorization. There are a number of wellknown algorithms here:

http://en.wikipedia.org/wiki/Integer_factorization#Factoring_algorithms

I suggest you pick the best match + profile.


my original comment:

Profile profile profile. Also, don't use enumerators or LINQ if you care about efficiency there. Write it in C and use P/Invoke. In general, don't ask the question if you can measure it

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文