2.2.18

2.2.18 #

解答 #

可以在用归并排序的方法做。

将归并时取两边较小的元素改为随机取一侧的值,即可实现打乱的效果。

算法的分析和普通归并排序一致,满足题目要求。

代码 #

分治法打乱链表的实现。

/// <summary>
/// 分治法打乱链表。
/// </summary>
public class MergeShuffle
{
    /// <summary>
    /// 利用分治法打乱链表。
    /// </summary>
    /// <typeparam name="T">链表元素类型。</typeparam>
    /// <param name="a">等待打乱的链表。</param>
    public void Shuffle<T>(LinkedList<T> a)
    {
        var blockLen = 1;
        var random = new Random();
        while (blockLen <= a.Size())
        {
            // 找到第一个块
            var lo = a.GetFirst();
            var mid = FindBlock(lo, blockLen);

            if (mid.Next == null)
                break;

            while (mid.Next != null)
            {
                var hi = FindBlock(mid.Next, blockLen);
                Node<T>[] result;
                if (lo == a.GetFirst())
                {
                    result = Merge(lo, mid, hi, random);
                    a.SetFirst(result[0]);
                }
                else
                {
                    result = Merge(lo.Next, mid, hi, random);
                    lo.Next = result[0];
                }


                // 跳到表尾
                lo = result[1];

                if (lo.Next != null)
                    mid = FindBlock(lo.Next, blockLen);
                else
                    mid = lo;
            }
            blockLen *= 2;
        }
    }

    /// <summary>
    /// 将两个有序链表块随机归并,返回新的表头。
    /// </summary>
    /// <typeparam name="T">链表元素类型。</typeparam>
    /// <param name="lo">第一个块起点。</param>
    /// <param name="mid">第一个块终点(第二个块起点的前驱)。</param>
    /// <param name="hi">第二个块的终点。</param>
    /// <param name="random">随机数生成器。</param>
    /// <returns>新的表头。</returns>
    private Node<T>[] Merge<T>(Node<T> lo, Node<T> mid, Node<T> hi, Random random)
    {
        var after = hi.Next; // 要合并的两个块之后的元素
        var result = new Node<T>[2];
        var i = lo;          // 链表1
        var j = mid.Next;    // 链表2

        // 切割链表
        mid.Next = null;
        hi.Next = null;

        Node<T> current;
        // 决定新的表头
        if (random.NextDouble() >= 0.5)
        {
            current = i;
            i = i.Next;
        }
        else
        {
            current = j;
            j = j.Next;
        }

        var first = current;

        // 归并表
        while (i != null && j != null)
        {
            if (random.NextDouble() >= 0.5)
            {
                current.Next = i;
                i = i.Next;
                current = current.Next;
            }
            else
            {
                current.Next = j;
                j = j.Next;
                current = current.Next;
            }
        }

        if (i == null)
            current.Next = j;
        else
            current.Next = i;

        // 连接表尾(链表 1 的尾部或者链表 2 的尾部)
        if (mid.Next == null)
        {
            mid.Next = after;
            result[1] = mid;
        }
        else
        {
            hi.Next = after;
            result[1] = hi;
        }
        result[0] = first;
            
        return result;
    }

    /// <summary>
    /// 获取从指定位置开始定长的链表。
    /// </summary>
    /// <typeparam name="T">链表的元素类型。</typeparam>
    /// <param name="lo">链表的起始结点。</param>
    /// <param name="length">需要获取的链表长度。</param>
    /// <returns>结果链表的最后一个元素结点。</returns>
    private Node<T> FindBlock<T>(Node<T> lo, int length)
    {
        var hi = lo;
        for (var i = 0; i < length - 1 && hi.Next != null; i++)
        {
            hi = hi.Next;
        }
            
        return hi;
    }
}

另请参阅 #

Merge 库