List<T>的各种排序方法

近日,在工作的时候遇到要对一个大的List<T>集合进行排序,于是就了解下各种List<T>的排序方法。

首先,排序自然就会想到用Sort方法,看看List<T>的Sort方法各个重载版本:

public void Sort();
public void Sort(Comparison<T> comparison);
public void Sort(IComparer<T> comparer);
public void Sort(int index, int count, IComparer<T> comparer);

1:Sort()方法,摘要:使用默认比较器对整个 System.Collections.Generic.List<T> 中的元素进行排序,这里的默认比较器就是指Comparer<T>.Default。要使用默认比较器来排序,则类必须实现IComparable<T>接口,排序的时候会调用接口的CompareTo方法。

接下来,就定义一个测试类Article,实现IComparable<Article>接口,排序先按SortIndex排序,再按Comments排序。类的定义如下:

复制代码
public class Article : IComparable<Article>
    {
        public string Title { get; set; }
        public int Comments { get; set; }
        public int SortIndex { get; set; }

        public override string ToString()
        {
            return string.Format("文章:{0},评论次数:{1}", this.Title, this.Comments);
        }
        
        public int CompareTo(Article other)
        {
            if (other == null)
                return 1;
            int value = this.SortIndex - other.SortIndex;
            if (value == 0)
                value = this.Comments - other.Comments;
            return value;
        }
    }
复制代码

定义一个获取100万的Article集合的方法,Article的SortIndex,Comments都是随机生成的。实现代码如下:

复制代码
 private static List<Article> GetArticleList()
        {
            List<Article> source = new List<Article>();
            Article article = null;
            var random = new Random(DateTime.Now.Millisecond);
            for (int i = 1000000; i > 0; i--)
            {
                article = new Article()
                {
                    Title = "文章" + i.ToString(),
                    Comments = random.Next(),
                    SortIndex = random.Next()
                };
                source.Add(article);
            }
            return source;
        }
复制代码

执行Sort方法排序:

private static void SortByDefaultComparer()
        {
            List<Article> list = GetArticleList();
            list.Sort();
        }

2:使用Comparison委托

Comparison委托的定义如下:public delegate int Comparison<in T>(T x, T y);

使用委托,可以传递一个与委托签名相同的函数,可以使用匿名委托,还可以用Lambda表达式:

首先使用第一种方法:定义一个ArticleComparison类,在里面定义一个Compare方法,函数签名与Comparison委托保持一致。实现代码如下:

复制代码
public class ArticleComparison
    {
        public int Compare(Article x, Article y)
        {
            if (x == null)
            {
                if (y == null)
                    return 0;
                else
                    return -1;
            }
            else
            {
                if (y == null)
                {
                    return 1;
                }
                else
                {
                    int value = x.SortIndex.CompareTo(y.SortIndex);
                    if (value == 0)
                        value = x.Comments.CompareTo(y.Comments);
                    return value;
                }
            }
        }
    }

//方法调用
private static void SortByComparison()
        {
            List<Article> list = GetArticleList();
            list.Sort(new Comparison<Article>(new ArticleComparison().Compare));
        }
复制代码

接下来,使用Lambda表达式,实现代码如下:

复制代码
 private static void SortByLambda()
        {
            List<Article> list = GetArticleList();
            list.Sort((x, y) =>
            {
                int value = x.SortIndex.CompareTo(y.SortIndex);
                if (value == 0)
                    value = x.Comments.CompareTo(y.Comments);
                return value;
            });
        }
复制代码

3:使用自定义的IComparer来排序,这里自定义一个ArticleCompare类:实现代码如下:

复制代码
 public class ArticleCompare : IComparer<Article>
    {
        public int Compare(Article x, Article y)
        {
            if (x == null)
            {
                if (y == null)
                    return 0;
                else
                    return -1;
            }
            else
            {
                if (y == null)
                {
                    return 1;
                }
                else
                {
                    int value = x.SortIndex.CompareTo(y.SortIndex);
                    if (value == 0)
                        value = x.Comments.CompareTo(y.Comments);
                    return value;
                }
            }
        }
    }
//方法调用
private static void SortByCustomerComparer()
        {
            List<Article> list = GetArticleList();
            list.Sort(new ArticleCompare());
        }
复制代码

4:最后一个函数重载,就是指定对那些元素进行排序。

小结:

Sort<T>方法的各个重载版本,最终调用的都是Array.Sort<T>(T[] array, int index, int length, IComparer<T> comparer)方法。

使用Comparison委托的时候,会执行IComparer<T> comparer = new Array.FunctorComparer<T>(comparison),最终转换为IComparer,因此在性能上会打点折扣。

5:使用LINQ排序,实现代码如下:

复制代码
private static void SortByLinq()
        {
            List<Article> list = GetArticleList();
            var sortedList =
               (from a in list
                orderby a.SortIndex, a.Comments
                select a).ToList();
        }
复制代码

6:使用扩展方法OrderBy,OrderByDescending,实现代码如下:

private static void SortByExtensionMethod()
        {
            List<Article> list = GetArticleList();
            var sortedList = list.OrderBy(a => a.SortIndex).ThenBy(a => a.Comments);
 sortedList.ToList(); //这个时候会排序
}

 

疑惑:让我觉得奇怪的是,用扩展方法,排序的效率竟然会快的惊人。我看了扩展方法的实现如下:

public static IOrderedEnumerable<TSource> OrderBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
    return new OrderedEnumerable<TSource, TKey>(source, keySelector, null, false);
}

返回的是一个新的OrderedEnumerable<TSource, TKey>对象,OrderedEnumerable<>的构造方法如下:

OrderedEnumerable(IEnumerable<TElement> source, Func<TElement, TKey> keySelector, IComparer<TKey> comparer, bool descending);

看了实现代码,从常理来说是不可能快过Array.Sort<T>方法。只能留下疑惑。希望高人解答!



转自:http://www.cnblogs.com/supperwu/archive/2012/06/13/2548122.html

<think>我们被要求比较LinkedList<T>和List<T>在C#中的区别。根据之前的引用和知识,我们可以从以下几个方面进行对比: 1. **内部数据结构**: - `List<T>`:基于动态数组实现。这意味着它在内存中是连续存储的,可以通过索引直接访问元素。 - `LinkedList<T>`:基于双向链表实现。每个元素(节点)包含数据以及指向前一个节点和后一个节点的引用(如引用[1]所述:包含item、next和prev)。 2. **插入和删除操作的性能**: - `List<T>`: - 在末尾添加元素(`Add`)效率高,平均$O(1)$(虽然偶尔需要扩容,但分摊时间复杂度仍是$O(1)$)。 - 在开头或中间插入/删除元素(`Insert`/`RemoveAt`)效率较低,因为需要移动元素,时间复杂度为$O(n)$。 - `LinkedList<T>`: - 在任意位置插入/删除节点(只要知道该位置的节点引用)效率高,为$O(1)$。因为只需要修改相邻节点的引用。 - 但是,如果要插入/删除的位置需要先查找(比如按索引或按值),则查找过程是$O(n)$,所以整体还是$O(n)$(除非已经持有节点引用)。 3. **访问元素的性能**: - `List<T>`:支持索引访问,时间复杂度为$O(1)$。 - `LinkedList<T>`:不支持索引访问,必须从头或尾开始遍历,访问第k个元素需要$O(k)$时间。 4. **内存占用**: - `List<T>`:内存连续,每个元素只占存储数据的空间,额外开销小(但可能会有预留空间,即容量大于元素个数)。 - `LinkedList<T>`:每个节点需要额外的空间存储前驱和后继引用(每个引用在32位系统占4字节,64位占8字节),所以内存开销更大。 5. **其他特性**: - `List<T>`:支持快速排序、二分查找等基于索引的操作。可以使用`List<T>.BinarySearch`方法。 - `LinkedList<T>`:提供了在链表头部/尾部快速添加/移除节点的方法(`AddFirst`,`AddLast`,`RemoveFirst`,`RemoveLast`),这些操作都是$O(1)$。适合实现队列(FIFO)或栈(LIFO)等数据结构。 6. **适用场景**: - `List<T>`:适用于需要频繁随机访问元素,大部分添加操作在末尾,或者元素数量变化不大的情况。 - `LinkedList<T>`:适用于需要频繁在头部或中间插入/删除元素,且不需要随机访问(即不需要按索引访问)的情况。例如,实现一个需要频繁在头部和尾部操作的队列(可以用`LinkedList<T>`,但通常`Queue<T>`(基于数组)在大多数情况下更优,因为缓存友好)。 7. **枚举性能**: - 两者枚举都是$O(n)$,但`List<T>`由于内存连续,缓存友好,所以枚举速度通常比`LinkedList<T>`快。 8. **接口实现**: - 两者都实现了`IList<T>`接口?实际上,`List<T>`实现了`IList<T>`(支持索引),而`LinkedList<T>`没有实现`IList<T>`(因为不支持按索引访问),它实现了`ICollection<T>`和`IEnumerable<T>`等(引用[3]提到它实现了一系列接口,但不包括`IList<T>`)。 根据以上分析,我们可以总结如下: | 特性 | List<T> | LinkedList<T> | |----------------------|----------------------------|-----------------------------| | **内部结构** | 动态数组 | 双向链表 | | **索引访问** | $O(1)$ | $O(n)$ | | **末尾添加** | $O(1)$(分摊) | $O(1)$(通过`AddLast`) | | **开头插入/删除** | $O(n)$ | $O(1)$(通过`AddFirst`/`RemoveFirst`) | | **中间插入/删除** | $O(n)$(移动元素) | $O(1)$(如果已知节点引用)<br/>但查找位置需要$O(n)$ | | **内存占用** | 较少(连续,无额外指针) | 较多(每个节点两个指针) | | **缓存友好** | 是 | 否 | | **适用场景** | 随机访问多,尾部操作多 | 频繁在头/中插入删除,不需要随机访问 | **示例代码说明**: ```csharp // List<T> 示例 List<int> list = new List<int>(); list.Add(1); // 末尾添加 list.Insert(0, 2); // 开头插入,效率较低 // LinkedList<T> 示例 LinkedList<int> linkedList = new LinkedList<int>(); linkedList.AddLast(1); // 末尾添加 linkedList.AddFirst(2); // 开头添加,高效 // 在第一个节点后插入 if (linkedList.First != null) linkedList.AddAfter(linkedList.First, 3); // 高效 ``` **建议**: - 如果需要频繁在开头或中间插入/删除,且不需要随机访问(即不需要通过索引访问元素),则选择`LinkedList<T>`。 - 如果需要通过索引快速访问元素,或者大部分操作在末尾,则选择`List<T>`。 注意:在C#中,`Queue<T>`和`Stack<T>`通常使用数组实现,因为它们的操作都在两端,数组实现更高效(缓存友好)。而`LinkedList<T>`可以用于实现更复杂的链表结构。 最后,生成相关问题。</think>### LinkedList<T> 与 List<T> 的核心区别 #### 1. **底层数据结构** - **`List<T>`** 基于**动态数组**实现,元素在内存中**连续存储** ```csharp List<int> list = new() { 10, 20, 30 }; // 内存布局: [10][20][30] (连续地址) ``` - **`LinkedList<T>`** 基于**双向链表**实现,元素通过节点分散存储 ```csharp LinkedList<int> linkedList = new(); linkedList.AddLast(10); // 节点1: [prev=null|data=10|next→节点2] linkedList.AddLast(20); // 节点2: [prev→节点1|data=20|next→节点3] linkedList.AddLast(30); // 节点3: [prev→节点2|data=30|next=null] ``` 每个节点包含:`T item` + `prev`指针 + `next`指针[^1] #### 2. **插入/删除性能对比** | 操作位置 | List<T> | LinkedList<T> | |------------------|-------------------------|------------------------| | **开头插入** | $O(n)$ (需移动所有元素) | $O(1)$ (仅修改指针)[^1]| | **中间插入** | $O(n)$ | $O(1)$ (已知节点位置) | | **末尾插入** | $O(1)$ (分摊) | $O(1)$ | | **随机删除** | $O(n)$ | $O(1)$ (已知节点位置) | > 示例:在开头插入10,000个元素 > - `List<T>`:需移动约50,000,000次元素($n^2/2$) > - `LinkedList<T>`:仅修改20,000次指针 #### 3. **访问性能对比** | 操作 | List<T> | LinkedList<T> | |------------------|---------|---------------| | **索引访问** | $O(1)$ | $O(n)$ | | **顺序遍历** | $O(n)$ | $O(n)$ | | **二分查找** | 支持 | 不支持 | ```csharp // List<T> 随机访问高效 int val = list[999]; // 直接计算内存地址访问 // LinkedList<T> 需遍历 LinkedListNode<int> node = linkedList.First; for (int i = 0; i < 999; i++) node = node.Next; int val = node.Value; ``` #### 4. **内存占用** - **`List<T>`** 内存连续,预留容量(减少扩容开销) 额外开销:约4-8字节/列表(容量指针) - **`LinkedList<T>`** 每个节点额外16-24字节开销(prev+next指针) 内存碎片化,缓存不友好[^3] #### 5. **典型应用场景** - **优先选 `List<T>` 当**: ✅ 需要频繁随机访问元素 ✅ 主要进行末尾插入/删除 ✅ 需要排序/二分查找 ✅ 内存敏感场景 - **优先选 `LinkedList<T>` 当**: ✅ 频繁在开头/中间插入删除 ✅ 实现队列/栈(需操作两端) ✅ 元素数量极大且插入位置已知 ✅ 实现LRU缓存等需要快速节点操作的场景 #### 6. **代码示例对比 ```csharp // List<T> 在开头插入 var list = new List<int>(100000); for (int i = 0; i < 100000; i++) list.Insert(0, i); // 性能灾难! // LinkedList<T> 在开头插入 var linkedList = new LinkedList<int>(); for (int i = 0; i < 100000; i++) linkedList.AddFirst(i); // 高效稳定 ``` #### 7. **特殊功能对比** | 功能 | List<T> | LinkedList<T> | |-------------------------|-----------------|-----------------------------| | **容量预分配** | 支持(`Capacity`)| 不支持 | | **批量插入** | `InsertRange()` | 需手动遍历添加 | | **节点引用操作** | 不支持 | 支持(`AddAfter(node, value)`)| | **内存连续性** | 连续 | 非连续 | > 💡 经验法则: > - 90%场景选 `List<T>`(现代CPU缓存优化使其在遍历时更快) > - 仅当需要**极高频非尾部插入/删除**时才用 `LinkedList<T>`
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值