查找嵌套和排序

时间:2017-03-10 13:41:32

标签: c# algorithm sorting recursion

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

示例:
A - 链接到 - B& C& d
B - 链接到 - C& ˚F
C - 链接到 - E& ˚F
D - 链接到 - B& ç
电子链接 - 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&lt; 20和你有相同的输出(6 - 5 - 3 - 2 - 4 - 1)你已经改进了它

1 个答案:

答案 0 :(得分: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<>:一个用于我已经完全解析的页面(包含所有链接),另一个用于我正在解析的页面(用于检查递归)。此时我在递归的情况下使用Exception(),但是应该可以在任何地方return false并且在递归的情况下在每个地方检查此false值。

对于O(N)的调用,这段代码为GetLinksFromKey(),因此,如果总共有6页,则应该正好加载N次。

请注意,此代码是递归的...递归是一把双刃剑...通常情况下,StackOverflowException不应该出现这样的问题(你不会有10000深链接)