【问题标题】:Find nesting and sort查找嵌套和排序
【发布时间】:2017-07-31 21:10:08
【问题描述】:

情况:
我有链接到其他页面的“页面”,这些页面将存储在数据库中,因此要存储 1 我需要先存储其链接页面(对于外键)。
所以我需要做的是找到从所选页面链接到根目录的所有页面。

示例:
A - 链接到 - B & C & D
B - 链接到 - C & F
C - 链接到 - E & F
D - 链接到 - B & C
E - 链接到 - F
F - 链接到 - 没有链接

本例中正确的嵌套顺序是:
F - E - C - B - D - A

请注意,我通常有近 30 个页面,链接遍布各处

问题:
我已经有了可以工作的代码,但是从每个页面获取链接需要一段时间(平均 800 毫秒),所以我想尽可能少地检查页面的链接。

代码示例:(非生产代码)

static class Program
{
    public static Dictionary<int, int[]> Dict = new Dictionary<int, int[]>();
    private static int hitcount = 0;
    static void Main(string[] args)
    {
        //Example data
        Stopwatch sw = new Stopwatch();
        sw.Start();
        Dict.Add( 1, new int[]{2, 3, 4});
        Dict.Add(2, new int[] {3, 6 });
        Dict.Add(3, new int[] { 5, 6 });
        Dict.Add(4, new int[] { 2, 3 });
        Dict.Add(5, new int[] { 6 });
        Dict.Add(6, new int[] {});

        var links = GetAllLinks( 1 );
        foreach ( var link in links )
        {
            Console.WriteLine(link.ToString());
        }
        sw.Stop();
        Console.WriteLine("MS:" + sw.ElapsedMilliseconds + " - " + hitcount);
        Console.ReadKey();
    }


    private static List<int> GetLinksFromKey(int key)
    {
        //This usually takes avg 800ms so sleep here
        //This is the BottleNeck, the more often its called longer it will take
        Thread.Sleep( 800 );
        hitcount++;
        return Dict[key].ToList();
    }

    private static List<int> GetAllLinks(int key)
    {
        var allPages = new List<int>();
        var pages = new List<int>();
        pages.Add(key);
        while (true)
        {
            var i = 0;
            var newP = new List<int>();
            newP.AddRange(pages);
            foreach (var page in pages.Distinct())
            {
                if (allPages.Contains(page))
                {
                    continue;
                }
                newP.AddRange(GetLinksFromKey(page));
                i++;
                allPages.Add(page);
            }
            pages = new List<int>(newP);
            if (i == 0)
            {
                break;
            }
        }
        return SortLinks(new List<int>(allPages.Distinct()));
    }

    private static List<int> SortLinks(List<int> pagesToSort)
    {
        var sortedPages = new List<int>();
        var hasReference = new List<int>();
        while (sortedPages.Count != pagesToSort.Count)
        {
            foreach (var page in pagesToSort)
            {
                if (sortedPages.Contains(page))
                {
                    continue;
                }
                var links = GetLinksFromKey(page);
                if (new List<int>(links.Distinct()).RemoveListFromList(sortedPages).Count == 0)
                {
                    sortedPages.Add(page);
                }
                else
                {
                    hasReference.Add(page);
                }
                if (hasReference.Distinct().Count() == pagesToSort.Distinct().Count())
                {
                    Console.WriteLine("There are circular references, can't find the root.");
                    return sortedPages;
                }
            }
        }
        return sortedPages;
    }
    private static List<int> RemoveListFromList(this List<int> mainList, List<int> removeList)
    {
        foreach (var item in removeList)
        {
            if (mainList.Contains(item))
            {
                mainList.Remove(item);
            }
        }
        return mainList;
    }
}

我将此代码作为我的嵌套情况的示例,我这样做是为了使它与字典而不是页面一起使用。为此使用字典非常快,但我知道瓶颈在哪种方法中,所以如果有人有我使用它的解决方案,那就太好了。

问题:
反正有没有让这更有效?我觉得我做错了。
如果没有更快的方法来做到这一点,我也很高兴听到。

如果你可以让 hitcount

【问题讨论】:

  • 有一种方法可以使它成为 O(N) - 以任何顺序处理页面。每次您找到指向不存在页面的链接时,请在数据库中创建一个占位符条目。然后,当您最终到达链接页面时,只需更新占位符即可。
  • @RB 为什么不能是 O(N)?使用递归似乎很简单......
  • 我需要在存储之前找到嵌套顺序等,因为可能存在我们不允许的循环引用:)
  • @xanatos 是的,对不起,你是对的。我的方式内存效率更高,但不一定性能更高。

标签: c# algorithm sorting recursion


【解决方案1】:

使用递归应该是:

public static Dictionary<int, int[]> Dict = new Dictionary<int, int[]>();
private static int hitcount = 0;

static void Main(string[] args)
{
    //Example data
    Stopwatch sw = new Stopwatch();
    sw.Start();

    Dict.Add(1, new int[] { 2, 3, 4 });
    Dict.Add(2, new int[] { 3, 6 });
    Dict.Add(3, new int[] { 5, 6 });
    Dict.Add(4, new int[] { 2, 3 });
    Dict.Add(5, new int[] { 6 });
    Dict.Add(6, new int[] { });

    var links = GetAllLinks(1);

    foreach (var link in links)
    {
        Console.WriteLine(link.ToString());
    }

    sw.Stop();
    Console.WriteLine("MS:" + sw.ElapsedMilliseconds + " - " + hitcount);
    Console.ReadKey();
}


private static List<int> GetLinksFromKey(int key)
{
    //This usually takes avg 800ms so sleep here
    //This is the BottleNeck, the more often its called longer it will take
    Thread.Sleep(800);
    hitcount++;
    return Dict[key].ToList();
}

private static List<int> GetAllLinks(int key)
{
    var alreadyDone = new HashSet<int>();
    var workingOn = new HashSet<int>();

    var pages = new List<int>();

    RecursiveGetAllLinks(pages, alreadyDone, workingOn, key);

    return pages;
}

private static void RecursiveGetAllLinks(List<int> pages, HashSet<int> alreadyDone, HashSet<int> workingOn, int key)
{
    if (!workingOn.Add(key))
    {
        throw new Exception("Cyclic recursion for " + key);
    }

    var links = GetLinksFromKey(key);

    foreach (int link in links)
    {
        if (alreadyDone.Contains(link))
        {
            continue;
        }

        RecursiveGetAllLinks(pages, alreadyDone, workingOn, link);
    }

    alreadyDone.Add(key);
    pages.Add(key);

    workingOn.Remove(key);
}

我有两个HashSet&lt;&gt;:一个用于我已经完全解析的页面(及其所有链接),一个用于我正在解析的页面(用于检查递归)。目前我在递归的情况下使用Exception(),但应该可以在任何地方使用return false,并在递归的情况下检查任何地方的false值。

这段代码对于GetLinksFromKey()的调用是O(N),所以如果总共有6个页面,它应该加载恰好N次。

请注意,此代码是递归的...递归是一把双刃剑...通常不会有StackOverflowException 这样的问题(您不会有 10000 个深层链接)

【讨论】:

  • 我确实不会有那么多链接。我会去尝试实现这个,如果它像预期的那样工作,我会接受你的回答。感谢您的宝贵时间!
  • 整个操作从 38950ms 到 14730ms。非常感谢!
猜你喜欢
  • 2017-06-29
  • 1970-01-01
  • 1970-01-01
  • 2019-08-01
  • 2020-11-28
  • 2018-01-16
  • 1970-01-01
  • 1970-01-01
  • 2020-10-04
相关资源
最近更新 更多