“知止而后有定,定而后能静,静而后能安,安而后能虑,虑而后能得。”
昨天我们学习了C#属性访问器、方法参数和C#字符串,StringBuilder的一些内容,今天我们学习C#常见数据结构。
以下是本篇文章正文内容:
数据结构和算法一直是学习编程语言绕不开的部分,今天我们就一起来学习一些基本的数据结构,将来可以通过力扣,牛客,cf 等网站来学习我们数据结构和算法部分。
好的,让我们更详细地探讨 C# 中的集合、栈、堆、队列和字典。我们将通过更深入的解释和具体的示例代码来扩展这些内容。
C# 中的集合提供了处理一组对象的灵活方式。集合通常被封装在 System.Collections.Generic
命名空间中。每种集合类型都有其独特的特性,适用于不同的需求。
List
是一种动态数组,它允许按照索引快速访问元素,并且在添加或删除元素时可以自动调整大小。它是最常用的集合之一,适用于需要频繁添加、删除或按索引访问元素的场景。
示例代码:
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
numbers.Add(6);
numbers.Remove(3);
int number = numbers[2]; // 获取第三个元素
HashSet
是一种无序的集合,不允许包含重复元素。它使用哈希表来存储数据,提供了快速的查找、添加和删除操作,非常适合需要确保唯一性的场景。
示例代码:
HashSet<string> fruits = new HashSet<string> { "Apple", "Banana", "Cherry" };
fruits.Add("Banana"); // 无效操作,因为 "Banana" 已存在
bool containsApple = fruits.Contains("Apple"); // 返回 true
LinkedList
是一个双向链表,允许快速的插入和删除操作,尤其适用于需要在集合中间频繁插入或删除元素的场景。
示例代码:
LinkedList<string> linkedList = new LinkedList<string>();
linkedList.AddLast("First");
linkedList.AddLast("Second");
linkedList.AddFirst("Zero");
linkedList.Remove("Second");
ObservableCollection
是一个支持通知机制的集合,当集合的内容发生变化时,它会通知绑定的用户界面组件。这个特性使得它非常适用于数据绑定(Data Binding)场景,尤其是在 WPF 或 UWP 应用中。
示例代码:
ObservableCollection<string> observableList = new ObservableCollection<string>();
observableList.CollectionChanged += (sender, args) =>
{
Console.WriteLine("Collection changed");
};
observableList.Add("New Item"); // 触发 CollectionChanged 事件
Stack
是一种后进先出(LIFO, Last In First Out)的数据结构。它类似于一堆盘子,你只能从顶部取下盘子(弹出 Pop
操作),也只能在顶部放置新的盘子(推入 Push
操作)。栈非常适合处理需要反向顺序处理数据的场景,比如撤销操作或深度优先搜索(DFS)算法。
示例代码:
Stack<string> stack = new Stack<string>();
stack.Push("First");
stack.Push("Second");
stack.Push("Third");
string top = stack.Pop(); // "Third" 被移除并返回
string peek = stack.Peek(); // 返回 "Second",但不移除
在 C# 中,广度优先搜索(BFS)和深度优先搜索(DFS)是两种常用的图遍历算法。我们可以记住以下模板加快我们的刷题速度:
深度优先搜索是一种递归算法,沿着图的深度遍历节点,直到无法继续为止,然后回溯。
using System;
using System.Collections.Generic;
class Node {
public List<Node> Neighbors { get; set; }
public Node() {
Neighbors = new List<Node>();
}
}
class GraphTraversal {
public bool DFS(Node cur, Node target, HashSet<Node> visited) {
if (cur == target) {
return true;
}
visited.Add(cur);
foreach (Node next in cur.Neighbors) {
if (!visited.Contains(next)) {
if (DFS(next, target, visited)) {
return true;
}
}
}
return false;
}
}
class Program {
static void Main() {
// 创建节点和图的结构
Node start = new Node();
Node target = new Node();
// 添加邻居节点以构建图...
HashSet<Node> visited = new HashSet<Node>();
GraphTraversal traversal = new GraphTraversal();
bool pathExists = traversal.DFS(start, target, visited);
Console.WriteLine(pathExists ? "Path exists!" : "No path found.");
}
}
广度优先搜索是一种非递归算法,使用队列逐层遍历图的节点。
using System;
using System.Collections.Generic;
class Node {
public List<Node> Neighbors { get; set; }
public Node() {
Neighbors = new List<Node>();
}
}
class GraphTraversal {
public bool BFS(Node start, Node target) {
Queue<Node> queue = new Queue<Node>();
HashSet<Node> visited = new HashSet<Node>();
queue.Enqueue(start);
visited.Add(start);
while (queue.Count > 0) {
Node cur = queue.Dequeue();
if (cur == target) {
return true;
}
foreach (Node next in cur.Neighbors) {
if (!visited.Contains(next)) {
queue.Enqueue(next);
visited.Add(next);
}
}
}
return false;
}
}
class Program {
static void Main() {
// 创建节点和图的结构
Node start = new Node();
Node target = new Node();
// 添加邻居节点以构建图...
GraphTraversal traversal = new GraphTraversal();
bool pathExists = traversal.BFS(start, target);
Console.WriteLine(pathExists ? "Path exists!" : "No path found.");
}
}
DFS:
true
。visited
集合用于跟踪已经访问过的节点,以防止循环。BFS:
visited
集合跟踪已访问的节点,避免重复访问。这两种算法在不同的图结构和问题中有不同的表现。选择使用哪种算法应根据具体问题的要求来决定。
在 C# 中,堆(Heap)主要是指内存管理中的概念,而不是特定的数据结构。在托管堆(Managed Heap)中,动态分配的对象被存储,并由垃圾回收器(Garbage Collector)自动管理它们的生命周期。
堆用于存储应用程序运行时创建的动态对象,例如在 new
操作符下创建的实例。堆的大小是动态的,内存管理器根据需要分配和释放内存空间。C# 的垃圾回收器会自动清理不再使用的对象,减少内存泄漏的风险。
示例代码:
class MyClass
{
public int Value;
}
MyClass myObject = new MyClass();
myObject.Value = 10;
// 对象 myObject 存储在堆中,直到不再使用,垃圾回收器会自动清理
尽管 C# 语言本身没有直接的堆数据结构实现,但可以通过 PriorityQueue
类(在 .NET 6+ 中引入)来模拟优先级队列(Priority Queue),这实际上使用堆结构来管理数据。
示例代码:
PriorityQueue<string, int> priorityQueue = new PriorityQueue<string, int>();
priorityQueue.Enqueue("Low priority", 3);
priorityQueue.Enqueue("High priority", 1);
priorityQueue.Enqueue("Medium priority", 2);
while (priorityQueue.Count > 0)
{
string item = priorityQueue.Dequeue();
Console.WriteLine(item);
}
// 输出顺序为 "High priority", "Medium priority", "Low priority"
Queue
是一种先进先出(FIFO, First In First Out)的数据结构。队列的典型应用场景包括任务调度、请求处理等,其中需要以进入的顺序处理项目。
示例代码:
Queue<string> queue = new Queue<string>();
queue.Enqueue("First");
queue.Enqueue("Second");
queue.Enqueue("Third");
string firstItem = queue.Dequeue(); // "First" 被移除并返回
string nextItem = queue.Peek(); // 返回 "Second",但不移除
Dictionary
是一种键值对(Key-Value Pair)的数据结构,允许根据键快速查找对应的值。键必须是唯一的,这意味着字典非常适合处理需要快速查找或存储与唯一标识符相关的数据的场景。
示例代码:
Dictionary<int, string> dictionary = new Dictionary<int, string>();
dictionary.Add(1, "One");
dictionary.Add(2, "Two");
dictionary.Add(3, "Three");
if (dictionary.ContainsKey(2))
{
string value = dictionary[2]; // 获取键 2 对应的值 "Two"
}
dictionary.Remove(3); // 移除键为 3 的键值对
C# 提供了多种数据结构来帮助开发者高效管理和处理数据。通过了解和正确使用这些数据结构,可以大大提高代码的性能和可维护性。根据具体的需求选择合适的集合类型、栈、队列或字典,是编写高质量代码的关键。
呜呼!今天我们C#语言的基础部分就算结束了,后面还有更加深奥的知识等着我们。以后我们还会更新关于其他语言的一些知识。C#更加复杂的部分还在后面我们今后可以通过一些实战的案例来学习。如果您学到了一些有点用的知识,不妨点赞,分享关注一波。