c#数据结构学习总结

原文链接https://blog.csdn.net/bcbobo21cn/article/details/61614576

c#常用数据结构解析

http://blog.csdn.net/suifcd/article/details/42869341

谈谈在平时使用U3D时经常用到的数据结构和各种数据结构的应用场景吧。
1.几种常见的数据结构 
这里主要总结下小匹夫在工作中常碰到的几种数据结构:Array,ArrayList,List<T>,LinkedList<T>,Queue<T>,Stack<T>,Dictionary<K,T>
数组Array:  
数组是最简单的数据结构。其具有如下特点:
数组存储在连续的内存上。
数组的内容都是相同类型。
数组可以直接通过下标访问。
  数组Array的创建:

int size = 5;
int[] test = new int[size];

  创建一个新的数组时将在 CLR 托管堆中分配一块连续的内存空间,来盛放数量为size,类型为所声明类型的数组元素。如果类型为值类型,则将会有size个未装箱的该类型的值被创建。如果类型为引用类型,则将会有size个相应类型的引用被创建。
  由于是在连续内存上存储的,所以它的索引速度非常快,访问一个元素的时间是恒定的也就是说与数组的元素数量无关,而且赋值与修改元素也很简单。

string[] test2 = new string[3];

//赋值
test2[0] = "chen";
test2[1] = "j";
test2[2] = "d";

//修改
test2[0] = "chenjd";

  但是有优点,那么就一定会伴随着缺点。由于是连续存储,所以在两个元素之间插入新的元素就变得不方便。而且就像上面的代码所显示的那样,声明一个新的数组时,必须指定其长度,这就会存在一个潜在的问题,那就是当我们声明的长度过长时,显然会浪费内存,当我们声明长度过短的时候,则面临这溢出的风险。这就使得写代码像是投机,小匹夫很厌恶这样的行为!针对这种缺点,下面隆重推出ArrayList。
ArrayList:  
为了解决数组创建时必须指定长度以及只能存放相同类型的缺点而推出的数据结构。ArrayList是System.Collections命名空间下的一部分,所以若要使用则必须引入System.Collections。正如上文所说,ArrayList解决了数组的一些缺点。
不必在声明ArrayList时指定它的长度,这是由于ArrayList对象的长度是按照其中存储的数据来动态增长与缩减的。
ArrayList可以存储不同类型的元素。这是由于ArrayList会把它的元素都当做Object来处理。因而,加入不同类型的元素是允许的。
  ArrayList的操作:

ArrayList test3 = new ArrayList();

//新增数据
test3.Add("chen");
test3.Add("j");
test3.Add("d");
test3.Add("is");
test3.Add(25);

//修改数据
test3[4] = 26;

//删除数据
test3.RemoveAt(4);

 

  说了那么一堆”优点“,也该说说缺点了吧。为什么要给”优点”打上引号呢?那是因为ArrayList可以存储不同类型数据的原因是由于把所有的类型都当做Object来做处理,也就是说ArrayList的元素其实都是Object类型的,辣么问题就来了。

ArrayList不是类型安全的。因为把不同的类型都当做Object来做处理,很有可能会在使用ArrayList时发生类型不匹配的情况。
如上文所诉,数组存储值类型时并未发生装箱,但是ArrayList由于把所有类型都当做了Object,所以不可避免的当插入值类型时会发生装箱操作,在索引取值时会发生拆箱操作。这能忍吗?
注:为何说频繁的没有必要的装箱和拆箱不能忍呢?且听小匹夫慢慢道来:所谓装箱 (boxing):就是值类型实例到对象的转换(百度百科)。那么拆箱:就是将引用类型转换为值类型咯(还是来自百度百科)。下面举个栗子~

//装箱,将String类型的值FanyoyChenjd赋值给对象。

String  info = ”FanyoyChenjd”;  
object obj=(object)info; 
 
//拆箱,从Obj中提取值给info
object obj = "FanyoyChenjd";
String info = (String)obj;

那么结论呢?显然,从原理上可以看出,装箱时,生成的是全新的引用对象,这会有时间损耗,也就是造成效率降低。

List<T>泛型List  
为了解决ArrayList不安全类型与装箱拆箱的缺点,所以出现了泛型的概念,作为一种新的数组类型引入。也是工作中经常用到的数组类型。和ArrayList很相似,长度都可以灵活的改变,最大的不同在于在声明List集合时,我们同时需要为其声明List集合内数据的对象类型,这点又和Array很相似,其实List<T>内部使用了Array来实现。

List<string> test4 = new List<string>(); 
 
//新增数据 
test4.Add(“Fanyoy”); 
test4.Add(“Chenjd”); 

//修改数据 
test4[1] = “murongxiaopifu”;  
   
//移除数据 
test4.RemoveAt(0);

 这么做最大的好处就是即确保了类型安全。也取消了装箱和拆箱的操作。
它融合了Array可以快速访问的优点以及ArrayList长度可以灵活变化的优点。
假设各位和小匹夫一样,在工作中最常使用的一种数据结构就是它。那么我们是否能再多一点好奇心呢?那就是探究一下,如果我们自己实现一个类似的数据结构,该从何处下手呢?

刚才说过了,List<T>的内部其实也是一个Array,且是强类型的,所以我们的简单实现(暂且称之为EggArray<T>)也秉承这个特点,内部通过一个Array来实现,且需要声明类型。但是同时我们也看到List<T>继承和实现了很多接口,比如IEnumerable接口等,而且值类型和引用类型通吃。这里为了EggArray<T>实现起来轻装简行,我们不继承List<T>继承的各种接口,同时我们的EggArray只服务于引用类型。
那么首先明确了,它是一个处理引用类型,且实现了泛型的。那么定义就出来了:

//EggArray类
//定义

public
class 
EggArray<T> where T : class
{
}

那么下一步呢?该确定它的内部成员了,就先从字段和属性开始吧。
属性&变量
属性
说明
Capacity EggArray的容量
Count EggArray中的元素个数
items T[],一个Array,因为上一篇文章说过List<T>的内部其实还是Array,所以内部我们也使用Array

//EggArray<T>的属性&&变量

private int capacity;
private int count;
private T[] items;
public int Count
{
    get
    {
        return this.count;
    }
}
 
public int Capacity
{
    get
    {
        return this.capacity;
    }
}

之后呢?好像是需要一个构造函数了。上文也说了,貌似new的时候不需要指定容量呀。那么我们就把构造函数做成这样吧。
构造函数:
构造函数 说明
EggArray() 初始化 EggArray<T> 类的新实例,该实例为空并且具有默认初始容量。
EggArray(int32) 初始化 EggArray<T> 类的新实例,该实例为空并且具有指定的初始容量。

//EggArray的构造函数,默认容量为8

public
EggArray() : this(8)
{

}

 
public
EggArray(int
capacity)
{

    this.capacity
 = capacity;

    this.items
 = new

T[capacity];

}

好了,构造函数也说完了,那么就介绍一下私有方法,因为运行机制全部是有私有方法来运筹的,公共方法只不过是开放给我们的使用的罢了。小匹夫对公共方法的实现没有兴趣,这里就不做演示了。
刚刚也说了,List<T>是无所谓初始长度的,可以用Add()方法往里面添加元素,同时也不可能是有一个无限大的空间让它来存储,那么究竟它究竟为何能做到这一点呢?因为有一个能动态调整内部数组大小的方法存在,且调整大小是按照原有长度成倍增长的。我们姑且称之为Resize。
那么在进行下面的内容之前,小匹夫还想先问各位一个问题:

List<int>
 test = new

List<int>(){0,1,2,3,4,5,6,7,8,9};

                int
count = 0;

                for(int
i = 0; i < test.Count; i++)
                {
                        if(i == 1)
                                test.Remove(test[i]);
                        count++;
                }
                Debug.Log (count);

上面这段代码会输出什么呢?答案是9。可能有的盆油会感到奇怪,test进去时长度明明是10啊。就算你中间Remove了一个元素,可为什么会影响后面的元素呢?(比如把index为1的元素remove掉,原来index为2的元素现在的index就成1了。)感觉乱套有木有?其实这里List<T>在执行remove的同时,也把内部的数组压缩了。所以也肯定有一个方法用来压缩咯。我们姑且称为Compact。
私有方法
私有方法
说明
Resize 当数组元素个数大于或等于数组的容量时,调用该方法进行扩容,会创建一个新的Array存放数据,“增长因子”为2
Compact 压缩数组,在Remove时候默认调用

//当数组元素个[/size][/backcolor][/color][i][color=White][backcolor=DarkGreen][size=2]数不小于数组容量时,需要扩容,增长因子growthFactor为2

private

void 
Resize()

{

    int

capacity = this.capacity
 * growthFactor;

    if

(this.count
 > capacity)

    {

        this.count
 = capacity;

    }

    T[]
 destinationArray = new

T[capacity];

    Array.Copy(this.items,
 destinationArray, this.count);

    this.items
 = destinationArray;

    this.capacity
 = capacity;

}



 private

void 
Compact()

        {

            int

num = 0;

            for

(int

i = 0; i < this.count;
 i++)

            {

                if

(this.items[i]
 == null)

                {

                    num++;

                }

                else

if 
(num > 0)

                {

                    this.items[i
 - num] = this.items[i];

                    this.items[i]
 = null;

                }

            }

            this.count
 -= num;

        }[i][i][i]


LinkedList<T>  
也就是链表了。和上述的数组最大的不同之处就是在于链表在内存存储的排序上可能是不连续的。这是由于链表是通过上一个元素指向下一个元素来排列的,所以可能不能通过下标来访问。如图

  既然链表最大的特点就是存储在内存的空间不一定连续,那么链表相对于数组最大优势和劣势就显而易见了。
向链表中插入或删除节点无需调整结构的容量。因为本身不是连续存储而是靠各对象的指针所决定,所以添加元素和删除元素都要比数组要有优势。
链表适合在需要有序的排序的情境下增加新的元素,这里还拿数组做对比,例如要在数组中间某个位置增加新的元素,则可能需要移动移动很多元素,而对于链表而言可能只是若干元素的指向发生变化而已。
有优点就有缺点,由于其在内存空间中不一定是连续排列,所以访问时候无法利用下标,而是必须从头结点开始,逐次遍历下一个节点直到寻找到目标。所以当需要快速访问对象时,数组无疑更有优势。
  综上,链表适合元素数量不固定,需要两端存取且经常增减节点的情况。
  关于链表的使用,MSDN上有详细的例子。
Queue<T>  
在Queue<T>这种数据结构中,最先插入在元素将是最先被删除;反之最后插入的元素将最后被删除,因此队列又称为“先进先出”(FIFO—first in first out)的线性表。通过使用Enqueue和Dequeue这两个方法来实现对 Queue<T> 的存取。

  一些需要注意的地方:
先进先出的情景。
默认情况下,Queue<T>的初始容量为32, 增长因子为2.0。
当使用Enqueue时,会判断队列的长度是否足够,若不足,则依据增长因子来增加容量,例如当为初始的2.0时,则队列容量增长2倍。
乏善可陈。
  关于Queue<T>的使用方法,MSDN上也有相应的例子。
Stack<T>
  
  与Queue<T>相对,当需要使用后进先出顺序(LIFO)的数据结构时,我们就需要用到Stack<T>了。
  一些需要注意的地方:
后进先出的情景。
默认容量为10。
使用pop和push来操作。
乏善可陈。
  同样,在MSDN你也可以看到大量Stack<T>的例子。
Dictionary<K,T>  
字典这东西,小匹夫可是喜欢的不得了。看官们自己也可以想想字典是不是很招人喜欢,创建一个字典之后就可以往里面扔东西,增加、删除、访问那叫一个快字了得。但是直到小匹夫日前看了一个大神的文章,才又想起了那句话“啥好事咋能让你都占了呢”。那么字典背后到底隐藏着什么迷雾,拨开重重迷雾之后,是否才是真相?且听下回分。。。等等,应该是下面就让我们来分析一下字典吧。
  提到字典就不得不说Hashtable哈希表以及Hashing(哈希,也有叫散列的),因为字典的实现方式就是哈希表的实现方式,只不过字典是类型安全的,也就是说当创建字典时,必须声明key和item的类型,这是第一条字典与哈希表的区别。关于哈希表的内容推荐看下这篇博客哈希表。关于哈希,简单的说就是一种将任意长度的消息压缩到某一固定长度,比如某学校的学生学号范围从00000~99999,总共5位数字,若每个数字都对应一个索引的话,那么就是100000个索引,但是如果我们使用后3位作为索引,那么索引的范围就变成了000~999了,当然会冲突的情况,这种情况就是哈希冲突(Hash Collisions)了。扯远了,关于具体的实现原理还是去看小匹夫推荐的那篇博客吧,当然那篇博客上面那个大大的转字也是蛮刺眼的。。。
  回到Dictionary<K,T>,我们在对字典的操作中各种时间上的优势都享受到了,那么它的劣势到底在哪呢?对嘞,就是空间。以空间换时间,通过更多的内存开销来满足我们对速度的追求。在创建字典时,我们可以传入一个容量值,但实际使用的容量并非该值。而是使用“不小于该值的最小质数来作为它使用的实际容量,最小是3。”(老赵),当有了实际容量之后,并非直接实现索引,而是通过创建额外的2个数组来实现间接的索引,即int[] buckets和Entry[] entries两个数组(即buckets中保存的其实是entries数组的下标),这里就是第二条字典与哈希表的区别,还记得哈希冲突吗?对,第二个区别就是处理哈希冲突的策略是不同的!字典会采用额外的数据结构来处理哈希冲突,这就是刚才提到的数组之一buckets桶了,buckets的长度就是字典的真实长度,因为buckets就是字典每个位置的映射,然后buckets中的每个元素都是一个链表,用来存储相同哈希的元素,然后再分配存储空间。

因此,我们面临的情况就是,即便我们新建了一个空的字典,那么伴随而来的是2个长度为3的数组。所以当处理的数据不多时,还是慎重使用字典为好,很多情况下使用数组也是可以接受的。


2.几种常见数据结构的使用情景
Array 需要处理的元素数量确定并且需要使用下标时可以考虑,不过建议使用List<T>
ArrayList 不推荐使用,建议用List<T>
List<T>泛型List 需要处理的元素数量不确定时 通常建议使用
LinkedList<T> 链表适合元素数量不固定,需要经常增减节点的情况,2端都可以增减
Queue<T> 先进先出的情况
Stack<T> 后进先出的情况
Dictionary<K,T> 需要键值对,快速操作
========

C#数据结构一:基础知识

 http://www.cnblogs.com/walkingp/archive/2010/04/25/1720823.html


在学习数据结构之前先要学习几个相关的概念及术语1、数据(Data):数据是外部世界信息的载体,它能被计算机识别、存储和加工处理,是计算机程序加工的原料。2、数据元素(Data Element)和数据项:数据元素是数据的基本单位,有时也被称为元素、结点、顶点、记录等。一个数据元素可由若干个数据项组成;数据项是不可分割的、含有独立意义的最小数据单位,数据项有时也称为字段(Field)或域(Domain).之间关系为数据项组成数据元素,数据元素组成数据(,数据组成文件)。使用数据库模型来举例说明:3、数据对象(Data Object):性质相同的数据元素的集合,是数据的一个子集,例如字母表对象{a,b,c,…x,y,z}4、数据类型(Data Type):数据的取值范围和对数据进行操作的总和。数据类型规定了程序中对象的特性;程序中每个变量、常量或表达式的结果都应该属于某种确定的数据类型。数据类型可分可两类:一类是非结构的原子类型,如C#的基本类型;另一类是结构类型,其成分由多个结构类型组成,可以分解;如C#的数组类型。5、数据结构(Data Struct):相互之间存在一种或多种关系 的数据元素的集合。通常有4类基本数据结构:1)集合(Set)2)线性结构(Linear Structure)3)树形结构(True Structure)4)图状结构(Graphic Structure)数据结构(Data Structrue)简记为DS,是一个二元组,DS=(D,S),其中D为数据元素的有限集合,R是数据元素之间关系的有限集合。6、算法(Algorithm):是对某一特定类型的问题的求解步骤的一种描述,是指令的有限序列。它具有有穷性(Finity)、确定性(Unambiguousness)、输入(Input)、输出(Output)和有效性(Realizability)。针对算法优劣的评价标准包括正确性(Correctness)、可读性(Readability)、健壮性(Robustness鲁棒性)、运行时间(Running Time)和占用空间(Storage Space)。7、算法的时间复杂度(Time Complexity):指算法的运行时间与问题规模的对应关系。通常把算法中基本操作重复执行的次数作为算法的时间复杂度。它是与问题规模n相关的函数。记作T(n)=O(f(n)),例如T(n)=n(n+1),推荐一篇好文http://www.matrix67.com/blog/archives/5298、高等数学相关基础知识计量单位(Unit):字节为B,位缩写为b,兆字节为MB,千字节缩写为KB阶乘函数(Factorial Function):5!=5*4*3*2*1=120,特别地,0!=1取下整和取上整(Floor and Ceiling):⌊3.4⌋=3(下整) ,⌈3.4⌉=4(上整)取模操作符(Modulus):n=q*m+r ⇒m=n/q对数(Logarithm):若ab=N,那么数b叫做以a为底N的对数,记作logaN=b,其中a叫做对数的底数,N叫做真数。递归(Recursive):算法调用自己或间接调用自己。
在学习数据结构之前先要学习几个相关的概念及术语


1、数据(Data):数据是外部世界信息的载体,它能被计算机识别、存储和加工处理,是计算机程序加工的原料。


2、数据元素(Data Element)和数据项:数据元素是数据的基本单位,有时也被称为元素、结点、顶点、记录等。一个数据元素可由若干个数据项组成;数据项是不可分割的、含有独立意义的最小数据单位,数据项有时也称为字段(Field)或域(Domain).之间关系为数据项组成数据元素,数据元素组成数据(,数据组成文件)。使用数据库模型来举例说明:

3、数据对象(Data Object):性质相同的数据元素的集合,是数据的一个子集,例如字母表对象{a,b,c,…x,y,z}

4、数据类型(Data Type):数据的取值范围和对数据进行操作的总和。数据类型规定了程序中对象的特性;程序中每个变量、常量或表达式的结果都应该属于某种确定的数据类型。数据类型可分可两类:一类是非结构的原子类型,如C#的基本类型;另一类是结构类型,其成分由多个结构类型组成,可以分解;如C#的数组类型

。5、数据结构(Data Struct):相互之间存在一种或多种关系 的数据元素的集合。通常有4类基本数据结构:

1)集合(Set)

2)线性结构(Linear Structure)

3)树形结构(True Structure)

4)图状结构(Graphic Structure)


数据结构(Data Structrue)简记为DS,是一个二元组,DS=(D,S),其中D为数据元素的有限集合,R是数据元素之间关系的有限集合。

6、算法(Algorithm):是对某一特定类型的问题的求解步骤的一种描述,是指令的有限序列。它具有有穷性(Finity)、确定性(Unambiguousness)、输入(Input)、输出(Output)和有效性(Realizability)。针对算法优劣的评价标准包括正确性(Correctness)、可读性(Readability)、健壮性(Robustness鲁棒性)、运行时间(Running Time)和占用空间(Storage Space)。

7、算法的时间复杂度(Time Complexity):指算法的运行时间与问题规模的对应关系。通常把算法中基本操作重复执行的次数作为算法的时间复杂度。它是与问题规模n相关的函数。记作T(n)=O(f(n)),例如T(n)=n(n+1)。

常见时间复杂度举例:

1)、O(n) 

x=n;
y=0;
while(y<x){
 y=y+1;
}
 2)、O(n2) 

for(int i=1;i<n;++i){
  for(int j=0;j<n;++j){
    A[i][j]=i*j;
  }
}
 
3)、O(\sqrt{n})
 
x=n;
y=0;
while(x>=(y+1)*(y+1)){//即x=y2+1
 y=y+1;
}
 
关于算法复杂度,推荐一篇好文http://www.matrix67.com/blog/archives/529

8、高等数学相关基础知识

计量单位(Unit):字节为B,位缩写为b,兆字节为MB,千字节缩写为KB

阶乘函数(Factorial Function):5!=5*4*3*2*1=120,特别地,0!=1

取下整和取上整(Floor and Ceiling):⌊3.4⌋=3(下整) ,⌈3.4⌉=4(上整)

取模操作符(Modulus):n=q*m+r ⇒m=n/q

对数(Logarithm):若ab=N,那么数b叫做以a为底N的对数,记作logaN=b,其中a叫做对数的底数,N叫做真数。

递归(Recursive):算法调用自己或间接调用自己。


C#数据结构系列文章:
1、基础知识
2、顺序表Sequence List
3、单链表Singly Linked List
4、双向链表Double Linked List
5、循环链表Circular Linked List
6、栈Stack
7、队列Queue
8、串
9、数组Array
10、树Tree
========

C# list使用方法

http://www.cnblogs.com/vhtt/archive/2009/12/05/1617835.html


集合是OOP中的一个重要概念,C#中对集合的全面支持更是该语言的精华之一。

    为什么要用泛型集合?

    在C# 2.0之前,主要可以通过两种方式实现集合:

    a.使用ArrayList

    直接将对象放入ArrayList,操作直观,但由于集合中的项是Object类型,因此每次使用都必须进行繁琐的类型转换。

    b.使用自定义集合类

    比较常见的做法是从CollectionBase抽象类继承一个自定义类,通过对IList对象进行封装实现强类型集合。这种方式要求为每种集合类型写一个相应的自定义类,工作量较大。泛型集合的出现较好的解决了上述问题,只需一行代码便能创建指定类型的集合。

    什么是泛型?

    泛型是C# 2.0中的新增元素(C++中称为模板),主要用于解决一系列类似的问题。这种机制允许将类名作为参数传递给泛型类型,并生成相应的对象。将泛型(包括类、接口、方法、委托等)看作模板可能更好理解,模板中的变体部分将被作为参数传进来的类名称所代替,从而得到一个新的类型定义。泛型是一个比较大的话题,在此不作详细解析,有兴趣者可以查阅相关资料。

    怎样创建泛型集合?

    主要利用System.Collections.Generic命名空间下面的List<T>泛型类创建集合,语法如下:

定义Person类如下:

   可以看到,泛型集合大大简化了集合的实现代码,通过它,可以轻松创建指定类型的集合。非但如此,泛型集合还提供了更加强大的功能,下面看看其中的排序及搜索。

List<T> ListOfT = new List<T>();

其中的"T"就是所要使用的类型,既可以是简单类型,如string、int,也可以是用户自定义类型。下面看一个具体例子。

class Person

{

    private string _name; //姓名

    private int _age; //年龄

    //创建Person对象

    public Person(string Name, int Age)

    {

        this._name= Name;

        this._age = Age;

    }

    //姓名

    public string Name

    {

        get { return _name; }

    }

    //年龄

    public int Age

    {

        get { return _age; }

    }

}

//创建Person对象

Person p1 = new Person("张三", 30);

Person p2 = new Person("李四", 20);

Person p3 = new Person("王五", 50);

//创建类型为Person的对象集合

List<Person> persons = new List<Person>();

//将Person对象放入集合

persons.Add(p1);

persons.Add(p2);

persons.Add(p3);

//输出第2个人的姓名

Console.Write(persons[1].Name);

    泛型集合的排序

    排序基于比较,要排序,首先要比较。比如有两个数1、2,要对他们排序,首先就要比较这两个数,根据比较结果来排序。如果要比较的是对象,情况就要复杂一点,比如对Person对象进行比较,则既可以按姓名进行比较,也可以按年龄进行比较,这就需要确定比较规则。一个对象可以有多个比较规则,但只能有一个默认规则,默认规则放在定义该对象的类中。默认比较规则在CompareTo方法中定义,该方法属于IComparable<T>泛型接口。请看下面的代码:

class Person :IComparable<Person>

{

    //按年龄比较

    public int CompareTo(Person p)

    {

        return this.Age - p.Age;

    }

}

    CompareTo方法的参数为要与之进行比较的另一个同类型对象,返回值为int类型,如果返回值大于0,表示第一个对象大于第二个对象,如果返回值小于0,表示第一个对象小于第二个对象,如果返回0,则两个对象相等。

定义好默认比较规则后,就可以通过不带参数的Sort方法对集合进行排序,如下所示:

//按照默认规则对集合进行排序

persons.Sort();

//输出所有人姓名

foreach (Person p in persons)

{

    Console.WriteLine(p.Name); //输出次序为"李四"、"张三"、"王五"

}

    实际使用中,经常需要对集合按照多种不同规则进行排序,这就需要定义其他比较规则,可以在Compare方法中定义,该方法属于IComparer<T>泛型接口,请看下面的代码:

class NameComparer : IComparer<Person>

{

    //存放排序器实例

    public static NameComparer Default = new NameComparer();

    //按姓名比较


    public int Compare(Person p1, Person p2)

    {

        return System.Collections.Comparer.Default.Compare(p1.Name, p2.Name);

    }

}

    Compare方法的参数为要进行比较的两个同类型对象,返回值为int类型,返回值处理规则与CompareTo方法相同。其中的Comparer.Default返回一个内置的Comparer对象,用于比较两个同类型对象。

    下面用新定义的这个比较器对集合进行排序:

    还可以通过委托来进行集合排序,首先要定义一个供委托调用的方法,用于存放比较规则,可以用静态方法。请看下面的代码:然后通过内置的泛型委托System.Comparison<T>对集合进行排序:

    可以看到,后两种方式都可以对集合按照指定规则进行排序,但笔者更偏向于使用委托方式,可以考虑把各种比较规则放在一个类中,然后进行灵活调用。

//按照姓名对集合进行排序

persons.Sort(NameComparer.Default);

//输出所有人姓名

foreach (Person p in persons)

{

    Console.WriteLine(p.Name); //输出次序为"李四"、"王五"、"张三"

}class PersonComparison

{

    //按姓名比较

    public static int Name(Person p1, Person p2)

    {

        return System.Collections.Comparer.Default.Compare(p1.Name, p2.Name);

    }


}

    方法的参数为要进行比较的两个同类型对象,返回值为int类型,返回值处理规则与CompareTo方法相同。

System.Comparison<Person> NameComparison = new System.Comparison<Person>(PersonComparison.Name);

persons.Sort(NameComparison);

//输出所有人姓名

foreach (Person p in persons)

{

    Console.WriteLine(p.Name); //输出次序为"李四"、"王五"、"张三"

}

可以看到,后两种方式都可以对集合按照指定规则进行排序,但笔者更偏向于使用委托方式,可以考虑把各种比较规则放在一个类中,然后进行灵活调用。


    泛型集合的搜索


    搜索就是从集合中找出满足特定条件的项,可以定义多个搜索条件,并根据需要进行调用。首先,定义搜索条件,如下所示:


class PersonPredicate

{

    //找出中年人(40岁以上)

    public static bool MidAge(Person p)

    {

        if (p.Age >= 40)

            return true;

        else

            return false;

    }


}


    上面的搜索条件放在一个静态方法中,方法的返回类型为布尔型,集合中满足特定条件的项返回true,否则返回false。


System.Predicate<Person> MidAgePredicate = new System.Predicate<Person>(PersonPredicate.MidAge);


List<Person> MidAgePersons = persons.FindAll(MidAgePredicate);


//输出所有的中年人姓名


foreach (Person p in MidAgePersons)


{


    Console.WriteLine(p.Name); //输出"王五"


}然后通过内置的泛型委托System.Predicate<T>对集合进行搜索:


    


    泛型集合的扩展


    如果要得到集合中所有人的姓名,中间以逗号隔开,那该怎么处理?


    考虑到单个类可以提供的功能是有限的,很自然会想到对List<T>类进行扩展,泛型类也是类,因此可以通过继承来进行扩展。请看下面的代码:


//定义Persons集合类


class Persons : List<Person>


{


    //取得集合中所有人姓名


    public string GetAllNames()


    {


        if (this.Count == 0)

            return "";

        string val = "";

        foreach (Person p in this)

        {

            val += p.Name + ",";

        }

        return val.Substring(0, val.Length - 1);

    }

}

//创建并填充Persons集合

Persons PersonCol = new Persons();

PersonCol.Add(p1);

PersonCol.Add(p2);


PersonCol.Add(p3);

//输出所有人姓名

Console.Write(PersonCol.GetAllNames()); //输出“张三,李四,王五”

List的方法和属性 方法或属性 作用

Capacity 用于获取或设置List可容纳元素的数量。当数量超过容量时,这个值会自动增长。您可以设置这个值以减少容量,也可以调用trin()方法来减少容量以适合实际的元素数目。

Count 属性,用于获取数组中当前元素数量

Item( ) 通过指定索引获取或设置元素。对于List类来说,它是一个索引器。

Add( ) 在List中添加一个对象的公有方法

AddRange( ) 公有方法,在List尾部添加实现了ICollection接口的多个元素

BinarySearch( ) 重载的公有方法,用于在排序的List内使用二分查找来定位指定元素.

Clear( ) 在List内移除所有元素

Contains( ) 测试一个元素是否在List内


CopyTo( ) 重载的公有方法,把一个List拷贝到一维数组内

Exists( ) 测试一个元素是否在List内

Find( ) 查找并返回List内的出现的第一个匹配元素

FindAll( ) 查找并返回List内的所有匹配元素

GetEnumerator( ) 重载的公有方法,返回一个用于迭代List的枚举器

Getrange( ) 拷贝指定范围的元素到新的List内

IndexOf( ) 重载的公有方法,查找并返回每一个匹配元素的索引

Insert( ) 在List内插入一个元素

InsertRange( ) 在List内插入一组元素

LastIndexOf( ) 重载的公有方法,,查找并返回最后一个匹配元素的索引

Remove( ) 移除与指定元素匹配的第一个元素

RemoveAt( ) 移除指定索引的元素

RemoveRange( ) 移除指定范围的元素

Reverse( ) 反转List内元素的顺序

Sort( ) 对List内的元素进行排序

ToArray( ) 把List内的元素拷贝到一个新的数组内

trimToSize( ) 将容量设置为List中元素的实际数目

小结:

    本文着重于介绍运用C# 2.0中的泛型来实现集合,以及对集合功能进行扩展,恰当的运用泛型集合,可以减少很多重复工作,极大的提高开发效率。实际上,集合只不过是泛型的一个典型应用,如果想了解更多关于泛型的知识,可以查阅其他相关资料。希望本文对你有用:
========

C# List 用法

http://www.blogjava.net/ebecket/articles/301842.html
C# List Examples
by Sam Allen - Updated September 6, 2009
Problem. You have questions about the List collection in the .NET Framework, which is located in the System.Collections.Generic namespace. You want to see examples of using List and also explore some of the many useful methods it provides, making it an ideal type for dynamically adding data. Solution. This document has lots of tips and resources on the List constructed type, with examples using the C# programming language.


--- Key points: ---                                           
    Lists are dynamic arrays in the C# language.              
    They can grow as needed when you add elements.            
    They are called generic collections and constructed types.
    You need to use < and > in the List declaration.          
1. Adding values


Here we see how to declare a new List of int values and add integers to it. This example shows how you can create a new List of unspecified size, and add four prime numbers to it. Importantly, the angle brackets are part of the declaration type, not conditional operators that mean less or more than. They are treated differently in the language.


~~~ Program that adds elements to List (C#) ~~~
using System.Collections.Generic;
class Program
{
static void Main()
{
List<int> list = new List<int>();
list.Add(2);
list.Add(3);
list.Add(5);
list.Add(7);
}
}
Adding objects. The above example shows how you can add a primitive type such as integer to a List collection, but the List collection can receive reference types and object instances. There is more information on adding objects with the Add method on this site. [C# List Add Method - dotnetperls.com]


2. Loops


Here we see how you can loop through your List with for and foreach loops. This is a very common operation when using List. The syntax is the same as that for an array, except your use Count, not Length for the upper bound. You can also loop backwards through your List by reversing the for loop iteration variables. Start with list.Count - 1, and proceed decrementing to >= 0.


~~~ Program that loops through List (C#) ~~~
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
List<int> list = new List<int>();
list.Add(2);
list.Add(3);
list.Add(7);
foreach (int prime in list) // Loop through List with foreach
{
Console.WriteLine(prime);
}
for (int i = 0; i < list.Count; i++) // Loop through List with for
{
Console.WriteLine(list[i]);
}
}
}
~~~ Output of the program ~~~
(Repeated twice)
2
3
7
3. Counting elements


To get the number of elements in your List, access the Count property. This is fast to access, if you avoid the Count() extension method. Count is equal to Length on arrays. See the section "Clearing List" for an example on using the Count property.


4. Clearing List—setting to null


Here we see how to use the Clear method, along with the Count property, to erase all the elements in your List. Before Clear is called, this List has 3 elements; after Clear is called, it has 0 elements. Alternatively, you can assign the List to null instead of calling Clear, with similar performance. However, after assigning to null, you must call the constructor again.


=== Program that counts List (C#) ===
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
List<bool> list = new List<bool>();
list.Add(true);
list.Add(false);
list.Add(true);
Console.WriteLine(list.Count); // 3
list.Clear();
Console.WriteLine(list.Count); // 0
}
}
=== Output of the program ===
3
0
5. Copying array to List


Here we see an easy way to create a new List with the elements in an array that already exists. You can use the List constructor and pass it the array as the parameter. List receives this parameter, and fills its values from it.


--- Program that copies array to List (C#) ---
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
int[] arr = new int[3]; // New array with 3 elements
arr[0] = 2;
arr[1] = 3;
arr[2] = 5;
List<int> list = new List<int>(arr); // Copy to List
Console.WriteLine(list.Count);       // 3 elements in List
}
}
--- Output of the program ---
Indicates number of elements.
3
Notes on the example. It is useful to use the List constructor code here to create a new List from Dictionary keys. This will give you a List of the Dictionary keys. The array element type must match the type of the List elements, or the compiler will refuse to compile your code.


6. Finding elements


Here we an example of how you can test each element in your List for a certain value. This shows the foreach loop, which tests to see if 3 is in the List of prime numbers. Note that more advanced List methods are available to find matches in the List, but they often aren't any better than this loop. They can sometimes result in shorter code. [C# List Find Methods for Searching List - dotnetperls.com]


~~~ Program that uses foreach on List (C#) ~~~
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
// New list for example
List<int> primes = new List<int>(new int[] { 2, 3, 5 });
// See if List contains 3
foreach (int number in primes)
{
if (number == 3) // Will match once
{
Console.WriteLine("Contains 3");
}
}
}
}
~~~ Output of the program ~~~
Contains 3
7. Using capacity


You can use the Capacity property on List, or pass an integer into the constructor, to improve allocation performance when using List. The author's research shows that capacity can improve performance by nearly 2x for adding elements. Note however that this is not usually a performance bottleneck in programs that access data. [C# Capacity Property - dotnetperls.com]


TrimExcess method. There is the TrimExcess method on List as well, but its usage is very limited and I have never needed to use it. It reduces the memory used. Note: "The TrimExcess method does nothing if the list is at more than 90 percent of capacity". [List(T).TrimExcess Method - MSDN]


8. Using BinarySearch


You can use the binary search algorithm on List with the instance BinarySearch method. Binary search uses guesses to find the correct element much faster than linear searching. It is often much slower than Dictionary. [C# BinarySearch List - dotnetperls.com]


9. Using AddRange and InsertRange


You can use AddRange and InsertRange to add or insert collections of elements into your existing List. This can make your code simpler. See an example of these methods on this site. [C# List AddRange Use - dotnetperls.com]


10. Using ForEach method


Sometimes you may not want to write a regular foreach loop, which makes ForEach useful. This accepts an Action, which is a void delegate method. Be very cautious when you use Predicates and Actions, because they can decrease the readability of your code.


Another useful method. There is a TrueForAll method that accepts a Predicate. If the Predicate returns true for each element in your List, the TrueForAll method will return true also. Else, it will return false.


11. Using Join—string List


Here we see how you can use string.Join on a List of strings. This is useful when you need to turn several strings into one comma-delimited string. It requires the ToArray instance method on List. The biggest advantage of Join here is that no trailing comma is present on the resulting string, which would be present in a loop where each string is appended.


=== Program that joins List (C#) ===
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
// List of cities we need to join
List<string> cities = new List<string>();
cities.Add("New York");
cities.Add("Mumbai");
cities.Add("Berlin");
cities.Add("Istanbul");
// Join strings into one CSV line
string line = string.Join(",", cities.ToArray());
Console.WriteLine(line);
}
}
=== Output of the program ===
New York,Mumbai,Berlin,Istanbul
12. Getting List from Keys in Dictionary


Here we see how you can use the List constructor to get a List of keys in your Dictionary collection. This gives you a simple way to iterate over Dictionary keys, or store them elsewhere. The Keys instance property accessor on Dictionary returns an enumerable collection of keys, which can be passed to the List constructor as a parameter.


::: Program that converts Keys (C#) :::
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
// Populate example Dictionary
var dict = new Dictionary<int, bool>();
dict.Add(3, true);
dict.Add(5, false);
// Get a List of all the Keys
List<int> keys = new List<int>(dict.Keys);
foreach (int key in keys)
{
Console.WriteLine(key);
}
}
}
::: Output of the program :::
3, 5
13. Inserting elements


Here we see how you can insert an element into your List at any position. The string "dalmation" is inserted into index 1, which makes it become the second element in the List. Note that if you have to Insert elements extensively, you should consider the Queue and LinkedList collections for better performance. Additionally, a Queue may provide clearer usage of the collection in your code.


~~~ Program that inserts into List (C#) ~~~
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
List<string> dogs = new List<string>(); // Example List
dogs.Add("spaniel");         // Contains: spaniel
dogs.Add("beagle");          // Contains: spaniel, beagle
dogs.Insert(1, "dalmation"); // Contains: spaniel, dalmation, beagle
foreach (string dog in dogs) // Display for verification
{
Console.WriteLine(dog);
}
}
}
~~~ Output of the program ~~~
spaniel
dalmation
beagle
14. Removing elements


The removal methods on List are covered in depth in another article on this site. It contains examples for Remove, RemoveAt, RemoveAll, and RemoveRange, along with the author's notes. [C# List Remove Methods - dotnetperls.com]


15. Sorting and reversing


You can use the powerful Sort and Reverse methods in your List collection. These allow you to order your List in ascending or descending order. Additionally, you can use Reverse even when your List is not presorted. There is more information on these topics, as well as sorting your List with LINQ on a property on this site. [C# Sort List Method, Sorting and Reversing Lists - dotnetperls.com]


16. Converting List to array


You can convert your List to an array of the same type using the instance method ToArray. There are examples of this conversion, and the opposite, on this site. [C# Convert List to Array - dotnetperls.com]


17. Getting range of elements


Here we see how you can get a range of elements in your List collection using the GetRange instance method. This is similar to the Take and Skip methods from LINQ, but has different syntax.


--- Program that gets ranges from List (C#) ---
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
List<string> rivers = new List<string>(new string[]
{
"nile",
"amazon",     // River 2
"yangtze",    // River 3
"mississippi",
"yellow"
});
// Get rivers 2 through 3
List<string> range = rivers.GetRange(1, 2);
foreach (string river in range)
{
Console.WriteLine(river);
}
}
}
--- Output of the program ---
amazon
yangtze
18. Testing Lists for equality


Sometimes you may need to test two Lists for equality, even when their elements are unordered. You can do this by sorting both of them and then comparing, or by using a custom List equality method. This site contains an example of a method that tests lists for equality in an unordered way. [C# List Element Equality - dotnetperls.com]


19. Using List with structs


When using List, you can improve performance and reduce memory usage with structs instead of classes. A List of structs is allocated in contiguous memory, unlike a List of classes. This is an advanced optimization. Note that in many cases using structs will actually decrease the performance when they are used as parameters in methods such as those on the List type.


20. Using var keyword


Here we see how you can use List collections with the var keyword. This can greatly shorten your lines of code, which sometimes improves readability. The var keyword has no effect on performance, only readability for programmers.


~~~ Program that uses var with List (C#) ~~~
using System.Collections.Generic;
class Program
{
static void Main()
{
var list1 = new List<int>();       // <- var keyword used
List<int> list2 = new List<int>(); // <- Is equivalent to
}
}
21. Summary


Here we saw lots of examples with the List constructed type. You will find that List is powerful and performs well. It provides flexible allocation and growth, making it much easier to use than arrays. In most programs that do not have memory or performance constraints and must add elements dynamically, the List constructed type in the C# programming language is ideal.


List 类是 ArrayList 类的泛型等效类,某些情况下,用它比用数组和 ArrayList 都方便。


我们假设有一组数据,其中每一项数据都是一个结构。


public struct Item
{
    public int Id;
    public string DisplayText;
}
注意结构是不能给实例字段赋值的,即 public int Id = 1 是错误的。


using System.Collections.Generic;


List<Item> items = new List<Item>();


//添加
Item item1 = new Item();
item1.Id = 0;
item1.DisplayText = "水星";
items.Add(item1);


//添加
Item item2 = new Item();
item2.Id = 1;
item2.DisplayText = "地球";
items.Add(item2);


//修改
//这里使用的是结构,故不能直接用 items[1].DisplayText = "金星";,如果 Item 是类,则可以直接用。为什么呢?因为结构是按值传递的。
Item item = items[1];
item.DisplayText = "金星";
items[1] = item;
========

C#实现图(Graph)的算法

http://www.bianceng.cn/Programming/csharp/201311/38092.htm


简介
图表示点之间的关系,在C#中通过节点对象的集合来表示点(Vertex),用邻接矩阵(adjacency matrix)来表示点之间的关系。下面来看C#实现。
PS:本片文章是我复习的笔记,代码注释很全。勿吐槽。
表示点的对象
下面实现代码:
class Vertex
    {
        publicstring Data;
        publicbool IsVisited;
        public Vertex(string Vertexdata)
        {
            Data = Vertexdata;
        }
    }
每个节点包含两个字段,分别为节点数据以及表示是否被访问过的一个布尔类型。
表示图的对象
图中除了需要点的集合和邻接矩阵之外,还需要一些基本的向图中添加或删除元素的方法,以及一个构造方法来对图进行初始化。
publicclass Graph
    {
        //图中所能包含的点上限privateconstint Number = 10;
        //顶点数组private Vertex[] vertiexes;
        //邻接矩阵publicint[,] adjmatrix;
        //统计当前图中有几个点int numVerts = 0;
        //初始化图public Graph()
        {
            //初始化邻接矩阵和顶点数组
            adjmatrix = new Int32[Number, Number];
            vertiexes = new Vertex[Number];
            //将代表邻接矩阵的表全初始化为0for (int i = 0; i < Number; i++)
            {
                for (int j = 0; j < Number; j++)
                {
                    adjmatrix[i, j] = 0;
                }
            }
        }
 
        //向图中添加节点publicvoid AddVertex(String v)
        {
            vertiexes[numVerts] = new Vertex(v);
            numVerts++;
        }
        //向图中添加有向边publicvoid AddEdge(int vertex1, int vertex2)
        {
            adjmatrix[vertex1, vertex2] = 1;
            //adjmatrix[vertex2, vertex1] = 1;
        }
        //显示点publicvoid DisplayVert(int vertexPosition)
        {
            Console.WriteLine(vertiexes[vertexPosition]+"");
        }
}
拓扑排序(TopSort)
拓扑排序是对一个有向的,并且不是环路的图中所有的顶点线性化。需要如下几个步骤
1.首先找到没有后继的节点。
2.将这个节点加入线性栈中
3.在图中删除这个节点
4.重复步骤1,2,3
因此,首先需要找到后继节点的方法:
        //寻找图中没有后继节点的点
        //具体表现为邻接矩阵中某一列全为0//此时返回行号,如果找不到返回-1privateint FindNoSuccessor()
        {
            bool isEdge;
            //循环行for (int i = 0; i < numVerts; i++)
            {
                isEdge = false;
                //循环列,有一个1就跳出循环for (int j = 0; j < numVerts; j++)
                {
                    if (adjmatrix[i, j] == 1)
                    {
                        isEdge = true;
                        break;
                    }
                }
                if (!isEdge)
                {
                    return i;
                }
            }
            return -1;
 
        }
========

C#实现二叉查找树的算法

http://www.bianceng.cn/Programming/csharp/201311/38091.htm


简介
树是一种非线性结构。树的本质是将一些节点由边连接起来,形成层级的结构。而二叉树是一种特殊的树,使得树每个子节点必须小于等于2.而二叉查找树又是一类特殊的二叉树。使得每一个节点的左节点或左子树的所有节点必须小于这个节点,右节点必须大于这个节点。从而方便高效搜索。
下面来看如何使用C#实现二叉查找树。
实现节点
二叉查找树是节点的集合。因此首先要构建节点,如代码1所示。
//二叉查找树的节点定义publicclass Node
    {
        //节点本身的数据publicint data;
        //左孩子public Node left;
        //右孩子public Node right;
        publicvoid DisplayData()
        {
            Console.Write(data+"");
        }
    }
代码1.节点的定义
构建二叉树
构建二叉树是通过向二叉树插入元素得以实现的,所有小于根节点的节点插入根节点的左子树,大于根节点的,插入右子树。依此类推进行递归。直到找到位置进行插入。二叉查找树的构建过程其实就是节点的插入过程。C#实现代码如代码2所示。
publicvoid Insert(int data)
        {
            Node Parent;
            //将所需插入的数据包装进节点
            Node newNode=new Node();
            newNode.data=data;
 
            //如果为空树,则插入根节点if(rootNode==null)
            {
                rootNode=newNode;
            }
            //否则找到合适叶子节点位置插入else
            {
                Node Current = rootNode;
                while(true)
                {
                    Parent=Current;
                    if(newNode.data<Current.data)
                    {
                        Current=Current.left;
                        if(Current==null)
                        {
                            Parent.left=newNode;
                            //插入叶子后跳出循环break;
                        }
                    }
                    else
                    {
                        Current = Current.right;
                        if (Current == null)
                        {
                            Parent.right = newNode;
                            //插入叶子后跳出循环break;
                        }
                    }
                }
            }
        }
代码2.实现二叉树的插入

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值