如何随机播放或随机化链表

时间:2016-09-02 06:32:45

标签: c# random linked-list

在这种情况下,我试图对图像的LinkedList进行随机播放或随机化,但是我设置它的方式似乎是无条件的

洗牌很简单,你有列表,记下最后一个条目,然后取第一个条目并把它放在列表中的一个随机位置,然后是下一个条目,等等,直到最上面的条目是您注意到的最后一个条目,您将该条目放在列表中的随机位置,并且列表被洗牌。

这是我的代码:

class ShuffleClass
{
    private LinkedList<Image> library;
    private Image lastCard;
    private Image topCard;
    private Random rng;
    private int place;
    private LinkedListNode<Image> node;

    public LinkedList<Image> shuffle(LinkedList<Image> library)
    {
        this.library = library;
        lastCard = library.Last.Value;
        rng = new Random();

        while (library.First.Value != lastCard)
        {
            topCard = library.First.Value;
            library.RemoveFirst();
            place = rng.Next(1,library.Count+1);

            if (place == library.Count)
            {
                library.AddBefore(library.Last, topCard);
            }
            else
            { 
                node = library.Find(library.ElementAt(place));
                library.AddBefore(node, topCard);
            }

        }
        topCard = library.First.Value;
        library.RemoveFirst();
        place = rng.Next(0,library.Count+1);
        if(place == library.Count)
        {
            library.AddBefore(library.Last, topCard);
        }
        else
        {
            node = library.Find(library.ElementAt(place));
            library.AddBefore(node, topCard);
        }
        return library;
    }
}

2 个答案:

答案 0 :(得分:1)

您可以使用随机类来随机播放列表:

public static void Shuffle()
{
    Random Rand = new Random();
    LinkedList<int> list = new LinkedList<int>(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 });

    foreach (int i in list)
        Console.Write("{0} ", i);

    Console.WriteLine();
    int size = list.Count;

    //Shuffle the list
    list =  new LinkedList<int>(list.OrderBy((o) =>
    {
        return (Rand.Next() % size);
    }));

    foreach (int i in list)
        Console.Write("{0} ", i);

    Console.WriteLine();
}

输出可能是这样的:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
10 2 17 7 9 15 8 14 1 12 13 16 4 18 3 5 11 20 19 6

答案 1 :(得分:1)

您拥有的代码的基本问题是,正如您所发现的那样,它不会终止。你只调用AddBefore(),它无法在链接列表的最后一个元素之后添加元素,因此链表的最后一个元素不可能改变,永远不会大脑将其值移到列表的开头。

即使你修复了这个问题,循环仍然是不对的,因为没有理由期望列表中的最后一个值只有在你完成洗牌后才会移动到第一个。

如果你真的想要在适当的位置对列表进行洗牌,你仍然需要从一个基本正确的shuffle算法开始,即Fisher-Yates,但是交换函数知道如何交换链表中的元素而不是只是交换数组元素(如通常的Fisher-Yates实现)。

但是,请注意,这样做是相当昂贵的,特别是当列表变大时,因为最终需要扫描列表很多次(每次交换两次)。

如果您将一系列索引重新排列到列表中,然后根据该列表构建新列表,则可以将该成本减半。例如:

class Program
{
    static void Main(string[] args)
    {
        LinkedList<int> test = new LinkedList<int>(Enumerable.Range(0, 10)),
            shuffled = Shuffle(test);

        Console.WriteLine(string.Join(", ", shuffled));
    }

    static LinkedList<T> Shuffle<T>(LinkedList<T> source)
    {
        LinkedList<T> result = new LinkedList<T>();
        int[] choices = Enumerable.Range(0, source.Count).ToArray();

        ShuffleArray(choices);
        foreach (int choice in choices)
        {
            result.AddLast(ElementAt(source, choice));
        }

        return result;
    }

    static void ShuffleArray<T>(T[] array)
    {
        // Naturally, in real code you'd want to reuse the same Random object
        // across multiple calls, by making it static readonly
        Random random = new Random();

        for (int i = array.Length; i > 1; i--)
        {
            int j = random.Next(i);

            if (i - 1 != j)
            {
                T t = array[i - 1];

                array[i - 1] = array[j];
                array[j] = t;
            }
        }
    }

    static T ElementAt<T>(LinkedList<T> source, int index)
    {
        LinkedListNode<T> current = source.First;

        while (index-- > 0)
        {
            current = current.Next;
        }

        return current.Value;
    }
}

如果你真的想要和/或需要在适当的位置随机播放列表,那么你可以做更多这样的事情:

class Program
{
    static void Main(string[] args)
    {
        LinkedList<int> test = new LinkedList<int>(Enumerable.Range(0, 10));

        ShuffleLinkedList(test);
        Console.WriteLine(string.Join(", ", test));
    }

    static void ShuffleLinkedList<T>(LinkedList<T> list)
    {
        // Naturally, in real code you'd want to reuse the same Random object
        // across multiple calls, by making it static readonly
        Random random = new Random();

        for (int i = list.Count; i > 1; i--)
        {
            SwapNodes(list, i - 1, random.Next(i));
        }
    }

    static void SwapNodes<T>(LinkedList<T> list, int i, int j)
    {
        if (i != j)
        {
            LinkedListNode<T> node1 = NodeAt(list, i), node2 = NodeAt(list, j),
                nodeBefore1 = node1.Previous, nodeBefore2 = node2.Previous;

            if (nodeBefore1 == node2)
            {
                list.Remove(node1);
                AddAfter(list, nodeBefore2, node1);
            }
            else if (nodeBefore2 == node1)
            {
                list.Remove(node2);
                AddAfter(list, nodeBefore1, node2);
            }
            else
            {
                list.Remove(node1);
                list.Remove(node2);
                AddAfter(list, nodeBefore2, node1);
                AddAfter(list, nodeBefore1, node2);
            }
        }
    }

    static void AddAfter<T>(LinkedList<T> list, LinkedListNode<T> after, LinkedListNode<T> add)
    {
        if (after != null)
        {
            list.AddAfter(after, add);
        }
        else
        {
            list.AddFirst(add);
        }
    }

    static LinkedListNode<T> NodeAt<T>(LinkedList<T> source, int index)
    {
        LinkedListNode<T> current = source.First;

        while (index-- > 0)
        {
            current = current.Next;
        }

        return current;
    }
}

请注意,在这两个示例中,逻辑分解为更小的方法。当您尝试将所有内容放入单个方法时,可能很难获得正确的结果,并且在执行此操作时甚至更难调试不正确的结果,因为很难检查并验证代码中的每一小块逻辑隔离。

通过利用数据结构的链表性质,可以改进就地方法。即虽然数组通过交换进行混洗有益,因此我们不必在数组中移动元素,我们可以通过选择随机元素,移除它并将其移动到最后来混洗列表。只要我们确保从列表中较短和较短的子集中进行选择,我们就会得到均匀分布的随机数。

例如:

class Program
{
    static void Main(string[] args)
    {
        LinkedList<int> test = new LinkedList<int>(Enumerable.Range(0, 10));

        ShuffleLinkedList(test);
        Console.WriteLine(string.Join(", ", test));
    }

    static void ShuffleLinkedList<T>(LinkedList<T> list)
    {
        // Naturally, in real code you'd want to reuse the same Random object
        // across multiple calls, by making it static readonly
        Random random = new Random();

        for (int i = list.Count; i > 1; i--)
        {
            LinkedListNode<T> node = NodeAt(list, random.Next(i));

            if (list.Last != node)
            {
                list.Remove(node);
                list.AddLast(node);
            }
        }
    }

    static LinkedListNode<T> NodeAt<T>(LinkedList<T> source, int index)
    {
        LinkedListNode<T> current = source.First;

        while (index-- > 0)
        {
            current = current.Next;
        }

        return current;
    }
}

不需要杂乱的节点交换。

最后,大多数高效时间(O(n)而不是O(n ^ 2)),以一些额外的中间内存消耗为代价,简单地复制所有的从列表到数组的元素,随机播放,并将它们添加回原始列表:

class Program
{
    static void Main(string[] args)
    {
        LinkedList<int> test = new LinkedList<int>(Enumerable.Range(0, 10));

        Shuffle(test);
        Console.WriteLine(string.Join(", ", test));
    }

    static void Shuffle<T>(LinkedList<T> source)
    {
        T[] choices = new T[source.Count];

        for (int i = 0; i < choices.Length; i++)
        {
            choices[i] = source.First.Value;
            source.RemoveFirst();
        }

        ShuffleArray(choices);

        foreach (T choice in choices)
        {
            source.AddLast(choice);
        }
    }

    static void ShuffleArray<T>(T[] array)
    {
        // Naturally, in real code you'd want to reuse the same Random object
        // across multiple calls, by making it static readonly
        Random random = new Random();

        for (int i = array.Length; i > 1; i--)
        {
            int j = random.Next(i);

            if (i - 1 != j)
            {
                T t = array[i - 1];

                array[i - 1] = array[j];
                array[j] = t;
            }
        }
    }
}