参考:
Microsoft Learning - System Collections
C#高级--常用数据结构、使用C#实现数据结构堆、平衡二叉树、硬核图解面试最怕的红黑树
从B树、B+树、B*树谈到R 树、【数据结构】B树(B-树)和B+树
图论入门及基础概念(图篇)、图论(一)基本概念、C#实现图(Graph)、排序算法——拓扑排序
1、基本概念
数据结构分类:
集合 |
无序、一对一 |
线性 |
有序、一对一 |
树 |
有序、一对多 |
图 |
有序、多对多 |
常见的数据结构:
数组 |
有序地存储同一类型的多个变量,内存地址也是连续的。 |
链表 |
有序地存储同一类型的多个变量,内存地址不必连续。 |
队列 |
特殊的线性表,只允许在表的一端进行插入操作,而在另一端进行删除操作。 |
栈 |
特殊的线性表,只能在一个表的一个固定端进行数据结点的插入和删除操作。 |
树 |
一种递归的数据结构,n个结点的有限集,有一个特定的根结点。 |
堆 |
可以看作是一个完全二叉树的数组对象,子节点总不大于/不小于其父节点。 |
哈希表 |
又称散列表,对存储的数据进行散列函数计算后分类,便于查找。 |
图 |
一系列结点以及连接这些节点的边组成。 |
注:C#运行不安全代码,在项目->XXX属性->生成->勾选“允许使用unsafe{}关键字编译的代码”。在unsafe{ ... }中可是使用指针。
int[] arr = new int[10];
for(int i=0; i<10; i++) { arr[i] = i; }
foreach(int i in arr) {
unsafe {
int* ptr = &i;
IntPtr add = (IntPtr)ptr;
Console.WriteLine(add.ToString("x")); // 打印内存地址
}
}
2、数组
优点:连续的内存地址使之可以随机访问。
缺点:插入数据效率低,每次插入需要分配新的内存。
2.1、普通数组、Array
int[] arr = new int[5] {0, 1, 2, 3, 4};
foreach(int i in arr) {
unsafe {
int* ptr = &i;
IntPtr add = (IntPtr)ptr;
Console.WriteLine("{0}: {1}", i, add.ToString("x")); // 打印内存地址
}
}
Array类提供了数组的创建、处理、搜索数组并对数组进行排序的方法,是所有数组的基类。
Array实现ICollection、IEnumerable、IList、IStructuralComparable、IStructuralEquatable、ICloneable。
using System.Collections;
Array array = Array.CreateInstance(typeof(int), 3, 4); // 创建一个3行4列int的数组
for (int i = array.GetLowerBound(0); i <= array.GetUpperBound(0); i++) // 下边界索引、上边界索引
{
for (int j = array.GetLowerBound(1); j <= array.GetUpperBound(1); j++)
{
array.SetValue((int)Math.Pow(10, i) + j, i, j);
}
}
Console.WriteLine("Dimension: {0}", array.Rank);
Console.WriteLine("Length: {0}", array.Length);
void Print(Array myArray) { // 按行输出数组
IEnumerator myEnumerator = myArray.GetEnumerator(); // 返回一个枚举
int i = 0;
int cols = myArray.GetLength(myArray.Rank - 1);
while (myEnumerator.MoveNext()) {
if (i < cols) { i++; }
else { Console.WriteLine(); i = 1; }
Console.Write("\t{0}", myEnumerator.Current);
}
Console.WriteLine();
}
Print(array);
2.2、ArrayList
优点:大小根据需求会动态增加。
不建议使用 ArrayList 类进行新类的开发,建议改用泛型 List<T> 类。类 ArrayList 旨在保存对象的异类集合,任何对象都是object类型,值类型会进行装箱。
ArrayList实现ICollection、IEnumerable、IList、ICloneable。
using System.Collections;
ArrayList arrayList = new ArrayList(); // 实例化
for(int i = 0; i < 5; i++) { arrayList.Add(i); }
Console.Write("长度:{0}\t容量: {1}\t", arrayList.Count, arrayList.Capacity);
void Print(IEnumerable arrList)
{
foreach (object obj in arrList)
{
Console.Write("{0} ", obj);
}
}
Print(arrayList);
2.3、List<T>
是类 ArrayList 的泛型等效项,可通过索引访问对象的强类型列表,提供对列表进行搜索、排序和操作的方法。List<T>在大多数情况下性能更好,并且类型安全。但是,如果值类型用于类型 T,则需要考虑实现和装箱问题。
实现ICollection<T>、IEnumerable<T>、IList<T> IReadOnlyCollection<T>、IReadOnlyList<T>、ICollection、IEnumerable、IList。
using System.Collections;
public class Person {
public string Name { set; get; }
public int Age { set; get; }
public override string ToString()
{ // 用于WriteLine,输出前调用对象的ToString()
return "Name: " + Name + "; Age: " + Age;
}
public override bool Equals(object obj)
{ // 用于Remove时比较对象是否相等
if (obj == null) return false; // object对象为空
Person objAsPerson = obj as Person; // 转换为Person为空,即非Person对象
if (objAsPerson == null) return false;
else return Equals(objAsPerson);
}
public bool Equals(Person other) {
if (other == null) return false;
return (this.Name.Equals(other.Name)); // 只比较Name属性
}
}
public class Pragram
{
public static void Main()
{
IList<Person> persons = new List<Person>(); // List
persons.Add(new Person() { Name = "Tom", Age = 19 });
persons.Add(new Person() { Name = "Jerry", Age = 18 });
persons.Add(new Person() { Name = "Buly", Age = 20 });
persons.Insert(2, new Person() { Name = "aaa", Age= 21 });
persons.Remove(new Person() { Name = "aaa" });
Console.WriteLine(persons.Contains(new Person() { Name = "aaa" }));
foreach (Person person in persons) { Console.WriteLine(person); }
}
}
3、链表、队列、栈
3.1、LinkedList<T>
优点:插入、删除效率高,复杂度O(1)。
缺点:随机访问效率低,需要遍历链表,复杂度O(n)。
双重链表,增删节点时不会在堆上分配分配其他对象。
对象中的每个 LinkedList<T> 节点的类型为 LinkedListNode<T>。 由于LinkedList<T>是双向的,因此每个节点都指向前一个节点,Next向后指向后一个节点。
同时创建节点及其值时,包含引用类型的列表性能更好。 LinkedList<T> 接受 null 作为引用类型的有效 Value 属性,并允许重复值。如果 LinkedList<T>为空,则 First 和 Last 属性包含 null。
LinkedList<T>仅支持多线程读。
LinkedList实现ICollection<T>、IEnumerable<T>、IReadOnlyCollection<T>、ICollection、IEnumerable、IDeserializationCallback、ISerializable。
using System.Collections;
public class Pragram
{
public static void Main()
{
string[] words = { "Hello", "World", "!", "C", "sharp", "!"};
LinkedList<string> sentence = new LinkedList<string>(words); // 双链表
sentence.AddLast("Four-lettle Words!");
sentence.AddFirst("Welcome!");
sentence.RemoveLast();
foreach (string word in sentence) { Console.Write("{0} ", word); }
Console.WriteLine(sentence.Last); // 最后一个节点
LinkedListNode<string> currentNode = sentence.Find("Welcome!");
Console.WriteLine(currentNode.Value); // 节点的值
}
}
3.2、Queue(队列)
对象先进先出(FIFO)的集合,容量动态扩展。
一般用于存放临时信息,在检索元素值后,可能需要放弃该元素。按照存储顺序访问时,使用Queue。与存储顺序相反时,使用Stack。
Queue 接受 null 为有效值,并允许重复元素。
如果需要同时从多个线程访问集合,使用 ConcurrentQueue<T>。
实现ICollection、IEnumerable、ICloneable。
using System.Collections;
public class Pragram
{
public static void Main()
{
Queue queue = new Queue(); // 队列
queue.Enqueue("Hello");
queue.Enqueue("World");
queue.Enqueue("!");
queue.Dequeue(); // 移除队首,并返回
Console.WriteLine(queue.Contains("Hello"));
foreach (var item in queue) { Console.Write($"{item} "); }
Console.WriteLine(queue.Peek()); // 查询队首
}
}
3.3、Stack(栈)
简单的对象后进先出 (LIFO) 的非泛型集合,容量动态扩展。
Stack 接受 null 为有效值并允许重复元素。
实现ICollection、IEnumerable、ICloneable。
using System.Collections;
public class Pragram
{
public static void Main()
{
Stack stack = new(); // 堆栈
stack.Push("Hello");
stack.Push("World");
stack.Push("!");
stack.Pop(); // 移除堆顶,并返回
Console.WriteLine(stack.Contains("Hello"));
foreach (var item in stack) { Console.Write($"{item} "); }
Console.WriteLine(stack.Peek()); // 查询堆顶
}
}
4、集合
4.1、HashSet
集是不包含重复元素且其元素没有特定顺序的集合。
HashSet<T> 类提供高性能集合操作。动态容量扩展。
HashSet<T> 提供了集合运算,例如UnionWith(并集)、IntersectWith(交集)、ExceptWith(差集)、SymmetricExceptWith(补集)。
实现ICollection<T>、IEnumerable<T>、IReadOnlyCollection<T>、ISet<T>、IEnumerable、IReadOnlySet<T>、IDeserializationCallback、ISerializable。
using System.Collections;
public class Pragram
{
public static void Main()
{
HashSet<int> nums = new HashSet<int> { 1, 2, 3, 4, 5, 6 };
HashSet<int> evens = new HashSet<int>();
for (int i = 1; i <= 6; i++) { evens.Add( 2 * i ); }
// 并集
HashSet<int> unions = new HashSet<int>(nums);
unions.UnionWith(evens);
// 交集
HashSet<int> intersects = new HashSet<int>(nums);
intersects.IntersectWith(evens);
// 差集
HashSet<int> excepts = new HashSet<int>(nums);
excepts.ExceptWith(evens);
// 补集
HashSet<int> symmExcepts = new HashSet<int>(nums);
symmExcepts.SymmetricExceptWith(evens);
HashSet<int>[] result = { nums, evens, unions, intersects, excepts, symmExcepts };
foreach(var i in result) { Display(i); }
void Display(HashSet<int> hs) {
Console.Write($"{
{");
foreach (int i in hs) { Console.Write($" {i}"); }
Console.WriteLine(" }");
}
}
}
4.2、SortedSet(有序集合)
不包含重复元素且其元素有一定顺序的集合。
SortedSet<T> 对象在插入和删除元素时维护排序顺序,而不会影响性能。同样可以使用HashSet中的并交叉补的集合运算方法。
通过实现IComparer<T>中的Compare(T x, T y)方法,完成比较器的定义。
实现ICollection<T>、IEnumerable<T>、IReadOnlyCollection<T>、ISet<T>、ICollection、IEnumerable、IReadOnlySet<T>、IDeserializationCallback、ISerializable。
public class Pragram
{
public static void Main()
{
SortedSet<int> defaultOrder = new SortedSet<int>();
SortedSet<int> descendOrder = new SortedSet<int>(new ByDescend());
Random random = new Random();
for (int i = 0; i < 10; i++) {
int t = random.Next(0, 1000);
defaultOrder.Add(t);
descendOrder.Add(t);
}
foreach (int s in defaultOrder) { Console.Write($"{s} "); }
Console.WriteLine();
foreach (int s in descendOrder) { Console.Write($"{s} "); }
}
public class ByDescend: IComparer<int> { // 比较器,定义类型为比较两个对象而实现的方法
public int Compare(int x, int y)
{ // 比较两个对象,并返回一个int来表明相对大小;<0表示x小于y,0表示相等,>0表示x大于y
if (x > y) { return -1; } // 降序
else if (x < y) { return 1; }
else { return 0; }
}
}
}
自定义类型的比较器。
using System.Collections;
public class Pragram
{
public static void Main()
{
SortedSet<Person> persons = new SortedSet<Person>(new ByNameAge()) {
new Person("Tom", 21),
new Person("Tom's Master", 55),
new Person("Jerry", 18),
new Person("Jerry's Cousin", 35),
new Person("Jerry's Cousin", 32),
new Person("Buly", 20) };
foreach (Person person in persons) {
Console.WriteLine($"name: {person.Name}; age: {person.Age}");
}
}
public class Person {
private string name;
private int age;
public string Name { get { return name; } }
public int Age { get { return age; } }
public Person(string name, int age) { this.name = name; this.age = age; }
}
public class ByNameAge: IComparer<Person>
{ // 比较器
public int Compare(Person x, Person y)
{ // 比较两个对象,并返回一个int来表明相对大小;<0表示x小于y,0表示相等,>0表示x大于y
if (x.Name.CompareTo(y.Name) !=0 ) { return -x.Name.CompareTo(y.Name); } // 降序
else if (x.Age.CompareTo(y.Age) != 0 ) { return x.Age.CompareTo(y.Age); } // 默认升序
else { return 0; }
}
}
}
5、哈希表
5.1、Dictionary(字典)
表示键和值的集合,值是无序的,键是唯一的。动态容量扩展。
使用键检索值的速度非常快,接近O(1) ,因为 Dictionary<TKey,TValue> 类是作为哈希表实现的。
如果键的类型TValue为引用类型,则键不能为null,但值可以为null 。
实现ICollection<KeyValuePair<TKey,TValue>>、IDictionary<TKey,TValue>、IEnumerable<KeyValuePair<TKey,TValue>>、IEnumerable<T>、IReadOnlyCollection<KeyValuePair<TKey,TValue>>、IReadOnlyDictionary<TKey,TValue>、ICollection、IDictionary、IEnumerable、IDeserializationCallback、ISerializable。
using System.Collections;
public class Pragram
{
public static void Main()
{
Dictionary<string, int> dic = new Dictionary<string, int>();
dic.Add("Tom", 19);
dic.Add("Jerry", 18);
dic.Add("Buly", 20);
// 增加相同key的异常处理
try { dic.Add("Tom", 20); }
catch (ArgumentException) { Console.WriteLine("Key Alreadly Exists!"); }
// 赋值不存在的key的异常处理
try { dic["aaa"] = 20; }
catch (KeyNotFoundException) { Console.WriteLine("Key is not found"); }
// 若经常获取/访问不存在的键,使用TryGetValue()方法
int valueAge;
if (dic.TryGetValue("Tom", out valueAge)) {
Console.WriteLine($"Key[Tom]'s value is {valueAge}");
} else {
Console.WriteLine("Key[Tom]'s value is not found");
}
// ContainsKey()方法来检查Key
if (!dic.ContainsKey("bbb")) {
dic.Add("bbb", 222);
}
// 在foreach中字典的枚举元素类型是KeyValuePair键值对
foreach(KeyValuePair<string, int> kvp in dic) {
Console.WriteLine($"Key is {kvp.Key}; Value is {kvp.Value}");
}
// Values属性可以只获取值,是强类型的
Dictionary<string, int>.ValueCollection dicValue = dic.Values;
}
}
5.2、HashTable(哈希表)
根据键的哈希代码进行组织的键/值对的集合,动态容量扩展。
Hashtable 中任何元素都被当做object处理,存在装修、拆箱。
重写方法Object.Equals(或IHashCodeProvider接口)和方法Object.GetHashCode (或IComparer接口) 需要Hashtable用作键的对象。 方法和接口的实现必须以相同的方式处理区分大小写。
Hashtable 中的每个键对象都必须提供自己的哈希函数,可通过调用 GetHash来访问该函数。
将元素添加到 Hashtable时,该元素将基于键的哈希代码放入存储桶中。随后的键查找使用键的哈希代码在一个特定存储桶中搜索,从而大大减少了查找元素所需的键的比较数。元素与存储桶的最大比率决定了Hashtable 的负载因子。 较小的负载因子的查找更快,但是内存消耗更多。 默认负载系数 1.0 通常提供速度和内存大小之间的最佳平衡。 创建 Hashtable 时还可以指定不同的负载因子。
实现ICollection、IDictionary、IEnumerable、ICloneable、IDeserializationCallback、ISerializable。
using System.Collections;
public class Pragram
{
public static void Main()
{
Hashtable ht = new Hashtable();
ht.Add("Tom", 19);
ht.Add("Jerry", 18);
ht.Add("Buly", 20);
try { ht.Add("Tom", 20); }
catch { ht["Tom"] = 20; }
// 默认的Item属性
ht["aaa"] = 30;
// 判断是否包含某个键
if (!ht.ContainsKey("bbb")) { ht.Add("bbb", 40); }
// 遍历哈希表的键值对
foreach (DictionaryEntry kv in ht) { Console.WriteLine($"Key is {kv.Key}; Value is {kv.Value}"); }
// 删除键
ht.Remove("bbb");
// 遍历哈希表的键
ICollection htKeys = ht.Keys;
foreach (string key in htKeys) { Console.WriteLine($"Key is {key}"); }
}
}
5.3、SortedDictionary
根据键进行排序的键/值对的集合。
SortedDictionary<TKey,TValue> 泛型类是具有 O(logn) 检索的二进制搜索树,与 SortedList<TKey,TValue> 泛型类类似,但是,SortedList使用的内存少于 SortedDictionary,SortedDictionary具有更快的未排序数据的插入和删除操作。如果一次性从已排序的数据填充列表, SortedList 比 SortedDictionary快。
每个键/值对都可以作为