数据结构完全攻略
分类 | 数据结构 | 说明 | 主要作用 |
---|---|---|---|
线性结构 | 数组(Array) | 同类型元素的固定长度线性集合 | 提供基于索引的快速访问 |
链表(LinkedList) | 通过指针连在一起的元素序列 | 提供快速插入删除操作 | |
列表(List) | 可变长度的元素序列,允许重复 | 提供动态顺序访问元素 | |
栈(Stack) | 后进先出(LIFO)的元素集合 | 实现LIFO的访问限制 撤销,重做 | |
队列(Queue) | 先进先出(FIFO)的元素集合 | 实现FIFO的访问限制 安排任务 | |
非线性结构 | 树(Tree) | 具有根节点和子节点的分层数据结构 | 实现分层、递归的数据表示 |
二叉树(Binary Tree) | 每个节点最多有两个子节点的树结构 | 实现快速的插入查找删除 | |
图(Graph) | 由节点和边组成的非线性数据结构 | 表示网络、关系的数学模型 搜索查找路径 | |
哈希表(Hash Table) | 通过散列函数映射到数组下标存储的 字典/键值对结构 | 实现快速键值对查询 | |
键值对集合 | 字典(Dictionary) | 以键值对形式存储数据的集合 | 提供根据键快速查询值 |
集合(HashSet) | 无序不重复元素的集合 | 判断元素是否在集合内 | |
排序结构 | 堆(Heap) | 具有堆排序性质的二叉树 | 高效实现基于比较的排序 任务调度 |
字符串匹配 | 后缀树(Suffix Tree) | 通过子字符串共同前缀快速匹配字符串 | 高效匹配字符串子串 |
多维空间索引 | R树(R-tree) | 通过空间切分索引多维数据 | 快速索引多维空间数据 快速查找临近点 |
特殊数据结构 | 元组(Tuple) | 存储一个对象集合的数据结构 | 将多个对象打包为一个整体 |
数据表(DataTable) | 表示内存中数据表的类 | 在内存中模拟数据库表 |
1.线性结构
数据结构 | 描述 | 声明及基本操作 | 与其他结构比较 | 优缺点 |
---|---|---|---|---|
数组(Array) | 同类型数据的连续静态线性集合 | 声明 - type[] arr; 访问 - arr[index] 插入 - arr[index] = value 删除 - 改为默认值或移位 | vs链表:访问快,插入删除慢 vs队列/栈:访问更灵活 | 优点:快速访问;占用连续内存 缺点:插入删除慢;长度固定 |
链表(LinkedList) | 通过指针链接的动态线性集合 | 声明 - LinkedList list = new LinkedList(); 插入 - list.add(value) 删除 - list.remove(index) 访问 - list.get(index) | vs数组:插入删除快,访问慢 vs树:无分层结构 | 优点:插入删除快速;长度动态 缺点:访问需遍历;占用随机内存 |
列表(List) | 可变长度的有序元素序列 | 声明 - List list = new ArrayList(); 插入 - list.add(value) 删除 - list.remove(index) 访问 - list.get(index) | vs数组:长度可变 vs链表:有更多接口 | 优点:长度动态;功能强大 缺点:访问慢于数组 |
栈(Stack) | 后进先出(LIFO)的元素集合 | 声明 - Stack stack = new Stack(); 入栈 - stack.push(value) 出栈 - stack.pop() 访问 - stack.peek() | vs队列:先进先出 vs数组:只允许一端操作 | 优点:符合LIFO原则 缺点:只允许限定操作 |
队列(Queue) | 先进先出(FIFO)的元素集合 | 声明 - Queue queue = new LinkedList(); 入队 - queue.offer(value) 出队 - queue.poll() 访问 - queue.peek() | vs栈:先进先出 vs数组:只允许两端操作 | 优点:符合FIFO原则 缺点:只允许限定操作 |
数组(Array)
数组是一种由同类型数据元素组成的固定大小的顺序集合。数组中的每个数据元素可以通过索引来直接访问,使得访问速度非常快。但数组的长度是固定的,插入和删除操作需要移动大量元素,效率较低。数组的主要优点是可以快速随机访问任意位置元素,缺点是插入删除元素效率较差。
数据结构 | 声明及基本操作 |
---|---|
数组(Array) | 声明 - type[] arr; 访问 - arr[index] 插入 - arr[index] = value 删除 - 改为默认值或移位 |
数组(Array):
int[] arr = new int[10]; // 创建数组
arr[0] = 1; // 访问和赋值
列表(List)
数据结构 | 声明及基本操作 |
---|---|
列表(List) | 声明 - List list = new ArrayList(); 插入 - list.add(value) 删除 - list.remove(index) 访问 - list.get(index) |
// 可变长度的有序元素序列
List<T> list = new List<T>(); // 声明列表
list.Add(value); // 插入元素
list.RemoveAt(index); // 删除元素
list.Get(index); // 访问元素
链表(Linked List)
链表是一种通过指针链接数据元素的线性结构,每个元素都保存着下一个元素的地址。链表长度可变,插入和删除操作快速,但需要遍历链表来访问特定位置的元素。链表的主要优点是插入删除快速,存储空间动态管理;缺点是访问任意位置元素效率较低。
数据结构 | 声明及基本操作 |
---|---|
链表(LinkedList) | 声明 - LinkedList list = new LinkedList(); 插入 - list.add(value) 删除 - list.remove(index) 访问 - list.get(index) |
// 通过指针链接的动态线性集合
LinkedList list = new LinkedList(); // 声明链表
list.Add(value); // 插入元素
list.RemoveAt(index); // 删除元素
list.Get(index); // 访问元素
栈(Stack)
栈是一种后进先出(LIFO)的线性结构,只允许在一端进行插入和删除操作。栈非常适合实现类似盘符撤销的功能。栈的主要优点是符合LIFO原则,实现限制性访问;缺点是只能在栈顶进行操作。
数据结构 | 声明及基本操作 |
---|---|
栈(Stack) | 声明 - Stack stack = new Stack(); 入栈 - stack.push(value) 出栈 - stack.pop() 访问 - stack.peek() |
// 后进先出(LIFO)的元素集合
Stack<T> stack = new Stack<T>(); // 声明栈
stack.Push(value); // 入栈
stack.Pop(); // 出栈
stack.Peek(); // 访问栈顶元素
队列(Queue)
队列是一种先进先出(FIFO)的线性结构,允许从一端插入,从另一端删除。队列可以在线程池和消息队列中实现任务调度。队列的主要优点是符合FIFO原则,实现限制性访问;缺点是只能在两端进行操作。
以上涵盖了几种常见和重要的线性数据结构,每个结构都有其独特优点,在适当场合可以发挥重要作用,希望这些概括有助于对线性数据结构有一个初步了解。请让我知道如果需要对其进行添加或改进。
数据结构 | 声明及基本操作 |
---|---|
队列(Queue) | 声明 - Queue queue = new LinkedList(); 入队 - queue.offer(value) 出队 - queue.poll() 访问 - queue.peek() |
// 先进先出(FIFO)的元素集合
Queue<T> queue = new Queue<T>(); // 声明队列
queue.Enqueue(value); // 入队
queue.Dequeue(); // 出队
queue.Peek(); // 访问队首元素
2.非线性结构
树(Tree)具有根节点和子节点的分层数据结构实现分层、递归的数据表示
图(Graph)由节点和边组成的非线性数据结构表示网络、关系的数学模型
搜索查找路径二叉树(Binary Tree)每个节点最多有两个子节点的树结构实现快速的插入查找删除
哈希表(Hash Table)通过散列函数映射到数组下标存储的 字典/键值对结构实现快速键值对查询
数据结构 | 描述 | 声明及基本操作 | 与其他结构比较 | 优缺点 |
---|---|---|---|---|
树(Tree) | 具有根节点和子节点的分层数据表示 | 声明TreeNode类; 插入、删除、查找节点 | 与图相比更适合表示层级关系 | 查找快,插入删除中等;消耗内存 |
图(Graph) | 由节点和边组成,表示网络关系 | 声明Node和Edge类; 添加、删除节点和边;搜索算法 | 与树相比更适合表示任意关系 | 查找复杂;消耗内存 |
二叉树(Binary Tree) | 每个节点最多两个子节点 | 声明BinaryTreeNode类; 插入、删除、查找节点 | 查找速度快于普通树 | 查找快,插入删除中等;消耗内存 |
哈希表(Hash Table) | 通过散列函数映射,实现快速查询 | 定义散列函数; 添加、删除、获取键值对 | 查找速度极快 | 查找极快,插入删除快;消耗内存 |
树(Tree)
表示图:
节点关系表单:
节点编号 | 节点名称 | 父节点编号 | 子节点编号列表 |
---|---|---|---|
1 | 根节点 | -1 | [2,3] |
2 | 子节点1 | 1 | [4] |
3 | 子节点2 | 1 | [5,6,7,8] |
4 | 子节点2.子节点1 | 2 | [] |
5 | 子节点2.子节点2 | 3 | [6,7,8] |
6 | 子节点2.子节点2.子节点1 | 5 | [] |
7 | 子节点2.子节点2.子节点2 | 5 | [] |
8 | 子节点2.子节点2.子节点3 | 5 | [] |
由节点和边组成的层次结构,每个节点可以有零个或多个子节点。
常见操作:查找、插入和删除。
树的应用非常广泛,文件系统、搜索引擎索引、数据库索引等。
二叉树(Binary Tree)
根节点:在图的顶部是这棵二叉树的根节点。
第一层子节点:从根节点A,我们引出了一条箭头线,这条线指向了两个节点,分别是B1和B2。在二叉树中,B1和B2被称为根节点的第一层子节点。
第二层子节点:在第一层的两个子节点B1和B2的下方,我们又看到了四个节点,分别是C1、C2、D1和D2。这四个节点是第一层子节点的第二层子节点。在二叉树中,我们通常会按照层次顺序来描述节点。
第三层子节点:最后,在第二层的两个子节点C1、C2、D1和D2的下方,我们看到了四个节点,分别是E1、E2、F1和F2。这四个节点是第三层子节点。
二叉树的一个重要特点是每个节点最多只有两个子节点,,每个节点还可以是叶子节点(即没有子节点的节点),如E1、E2、F1和F2。
组成
属性 | 描述 |
---|---|
节点数(N) | 树中所有节点的数量。 |
叶节点数(N0) | 没有子节点的节点数量。 |
度为2的节点数(N2) | 有两个子节点的节点数量。 |
深度(k) | 二叉树的层数。 |
第i层节点数(N_i) | 在第i层的节点数量,计算方式为2^(i-1)。 |
总节点数(N_total) | 所有层节点数的总和,计算方式为2^k - 1。 |
每个节点最多有2个子节点,分别为左子节点和右子节点。 除了根节点,每个节点都有且只有一个父节点。
二叉树的第i层最多有2^(i-1)个节点(i>=1)。 深度为k的二叉树最多有2^k - 1个节点(k>=1)。
对于任何一棵二叉树,如果其叶节点数为N0,度为2的节点数为N2,则N0=N2+1。
二叉树:其中每个节点最多有两个子节点,通常称为左子节点和右子节点。二叉树具有层次性和有序性,从根节点开始,其他节点按照层次顺序逐层向下。
平衡二叉树(AVL树):它的特点是任意节点的两个子树的高度差不超过1。这种平衡状态使得在平衡二叉树中进行查找、插入和删除等操作的时间复杂度相对较低,可以达到O(log n)。
红黑树:是一种自平衡的二叉搜索树,它的特点是每个节点要么是红色,要么是黑色。红黑树的平衡性保证了在红黑树中进行查找、插入和删除等操作的时间复杂度为O(log n)。红黑树还具有一个额外的属性,即任意两个相邻的节点颜色不同(交替出现)。
二叉树的应用场景
排序:二叉搜索树(Binary Search Tree)是一种特殊的二叉树,其特点是对于每个节点,其左子树中所有节点的值都小于该节点的值,右子树中所有节点的值都大于该节点的值。这使得在二叉搜索树中进行查找、插入和删除等操作的时间复杂度可以达到
O(log n),适用于快速排序等算法。
搜索:通过构建二叉搜索树,可以高效地搜索目标数据。当目标数据存在于树中时,只需通过搜索路径即可找到目标数据,时间复杂度为O(log n)。
决策树:决策树是一种以二叉树为基础的机器学习算法,用于分类和回归问题。决策树的每个节点表示一个特征或属性,而每个分支则代表一个决策结果。通过训练样本数据生成的决策树可以对新数据进行预测和分析。
图像处理:二叉树被广泛应用于图像处理中的各种算法,如霍夫变换、轮廓检测等。通过对图像进行二值化处理,将图像转换为黑白二值图像,再利用二叉树进行形态学操作(如腐蚀、膨胀等),可以实现图像的分割、去噪等功能。
图(Graph)
用于表示物体与物体之间存在某种关系的结构。
它表明了物件与物件之间的“多对多”的一种复杂关系。图包含了两个基
本元素:顶点(vertex, 简称V)和边(edge,简称E)。数学抽象后的“物
体”称作节点或顶点
有向图和无向图
在有向图中,如果从一个顶点出发的边数和指向该顶点的边数相等,那么这个顶
点被称为自环。如果一个有向图中没有自环,那么这个图被称为强连通图。
有向图和无向图的主要区别在于边的方向性:在无向图中,边是双向的,而在有向
图中,边是单向的。这意味着,如果你从顶点A到顶点B有一条边,那么从
点B到顶点A也必须有一条边(除非它是自环)。
有向图和无向图在许多方面都有不同的特性和应用:
例如:
在社交网络分析中,有向图可以用来表示关注关系(如A关注B)
关系表达(A,B)和(B,A)
而无向图可以用来表示友谊关系(如A和B是朋友)。
关系表达(A,B)
有权图和无权图
图论中的两种基本形式,它们的主要区别在于是否赋予边权值。
不画图了,就是在线上表示一个友谊值,距离,时间等等,在人工智能领域可以说为权重
有权图(Weighted Graph):在有权图中,每条边都有一个权值,这个权值可以代表连接两个顶点之间的物理距离、时间、成本等。边的权值可以是实数,也可以是复数。在有权图中,边的权值可以不同,这使得有权图能够表示出各种复杂的实际关系。例如,在物流网络中,两个城市之间的运输费用或距离可以通过边的权值来表示。
有权图通常用G=(V,E)来表示,其中G是图,V是顶点集,E是边集。在有权图中,边E中的每一条边e都可以被赋予一个权值w(e),这个权值表示了连接两个顶点的代价或距离。
无权图(Unweighted Graph):在无权图中,每条边的权值都是相同的,或者根本没有权值。这种图通常用于表示简单的连接关系,例如社交网络中的朋友关系、交通网络中的路线连接等。无权图通常用G=(V,E)来表示,其中G是图,V是顶点集,E是边集。在无权图中,边E中的每一条边e的权值都是相同的,通常表示为无穷大或者1。
连通图:基于连通的概念。在一个无向图G中,若从顶点i到顶点j有路径相连(当然从j到i也一定有路径),则称i和j是连通的。如果图中任意两点都是连通的,那么图被称作连通图。
图的存储
常用的存储方式有两种:邻接矩阵和邻接表。
邻接矩阵表:
图:
邻接矩阵表 | A | B | C |
---|---|---|---|
A | 0 | 1 | 1 |
B | 1 | 0 | 1 |
C | 0 | 1 | 1 |
邻接表:
邻接表 | |
---|---|
A | B C |
B | - A C |
C | A B |
A: B C
B: - A C
C: A B
其中,每个顶点后面的部分表示与该顶点相邻接的顶点。例如,顶点A后面跟着B和C,表示A与B和C相连。对于无向图而言,由于边是双向的,因此邻接表中每条边都会被记录两次。在这个例子中,顶点B后面跟着"-“和"A C”,表示B没有自环(即不与自己相连),并且与A和C相连;同样地,顶点C后面跟着"A B",表示C与A和B相连。
图(Graph)的用途
图(Graph)是数据结构中的一种,主要用于表示对象之间的关系。图的应用非常广泛,主要包括以下几个方面:
-
路径搜索:图搜索算法(Pathfinding and Search Algorithms)用于在一个图中探索路径,可以用于一般发现或显式搜索。
-
中心性计算:中心性计算(Centrality Computation)是一种衡量图中节点重要性的方法,可用于社交网络分析、推荐系统等。
-
社群发现:社群发现(Community Detection)是一种用于检测图中密集连接区域的方法,可用于社交网络分析、生物信息学等领域。
-
最小生成树:在无向网络中,最小生成树是指各边权数之和最小的生成树,也称为最小代价生成树。
-
图计算:图计算(Graph Computing)是一种研究客观世界中事物与事物之间关系的技术,通过点和边来构建图并进行计算和分析。
哈希表(Hash Table) 和 字典(Dictionary)
哈希表和字典都是基于键值对的数据结构
哈希表通过哈希函数将键映射到内存中的位置,
字典更注重的是提供类型安全的键值对存储。
键(key) | 值(value) |
---|---|
哈希表/ 字典的键(key) | 数据 |
集合(HashSet)
存储的是唯一元素的无序集合。HashSet 底层由哈希表(HashMap)实现,它不允许存储重复元素,并且元素无序。
堆(Heap)
通过维护元素的堆积性质,可以快速地获取最大值和最小值,从而服务于诸如排序、查找极值、优化搜索等算法,是一种非常有用和高效的数据结构。
堆作为一种动态内存分配方式,使得程序可以灵活地申请和释放内存,有效地管理和优化内存的使用。
以下是一些Heap的常见应用案例:
堆排序(Heap Sort):堆排序是使用Heap数据结构实现的排序算法,具有快速排序和归并排序的时间复杂度,并且不需要额外存储空间。
using System;
class HeapSort {
// 主要的堆排序函数
public static void Sort(int[] arr) {
int n = arr.Length; // 获取数组长度
// 从最后一个非叶子节点开始,逐步向上构建最大堆
// 非叶子节点的索引为 n/2-1 到 0
for (int i = n / 2 - 1; i >= 0; i--) {
Heapify(arr, n, i); // 构建最大堆,对每个非叶子节点执行下沉操作
}
// 交换堆顶元素与末尾元素,减少待排序元素个数,并重建最大堆
for (int i = n - 1; i > 0; i--) {
Swap(arr, 0, i); // 将当前最大值交换到最后
Heapify(arr, i, 0); // 重新构建最大堆,继续排序
}
}
// 下沉操作,构建最大堆
static void Heapify(int[] arr, int n, int i) {
int largest = i; // 假设当前节点为最大
int left = 2 * i + 1; // 左子节点索引
int right = 2 * i + 2; // 右子节点索引
// 如果左子节点值大于父节点值,则将largest指向左子节点
if (left < n && arr[left] > arr[largest]) {
largest = left;
}
// 如果右子节点值大于父节点值,则将largest指向右子节点
if (right < n && arr[right] > arr[largest]) {
largest = right;
}
// 如果父节点不是最大值,交换父子节点
if (largest != i) {
Swap(arr, i, largest); // 交换
Heapify(arr, n, largest); // 递归下沉,继续构建最大堆
}
}
// 交换数组中的两个元素
static void Swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
public static void Main() {
int[] arr = {4, 1, 3, 9, 7}; // 初始化示例数组
Sort(arr); // 调用堆排序函数
Console.WriteLine(string.Join(" ", arr)); // 打印排序结果
}
}
观察和描述堆排序的整个运行过程:
假设数组为:arr = {4, 1, 3, 9, 7}
- 初始化,数组长度n=5。先构建最大堆,从最后一个非叶子节点3开始:
- 将9和3交换位置,得到{4, 1, 9, 3, 7}
- 将9和7交换位置,得到{4, 1, 7, 3, 9}
现在数组构建成了一个最大堆。
- 第一轮排序,将最大元素9交换到末尾:
- 交换首尾元素,得到{4, 1, 7, 3, 9}
- 重新调整最大堆,先交换4和7,得到{7, 1, 4, 3, 9},然后继续调整,数组变为{7, 3, 4, 1, 9}
- 第二轮排序,继续交换首尾元素,得到{4, 3, 1, 7, 9}
- 重新构建最大堆,{7, 3, 1, 4, 9}
- 第三轮,{3, 1, 4, 7, 9}
- 重新构建最大堆,{4, 1, 3, 7, 9}
- 第四轮,{1, 3, 4, 7, 9}
至此排序完成,数组有序。
通过观察每次构建最大堆和交换首尾元素的过程,可以更直观地理解堆排序的运行机制。
优先队列(Priority Queue):优先队列是一种特殊的队列,其中每个元素都有一个关联的“优先级”。Heap可以用来快速实现优先队列,优先级越高的元素越先出队列。
using System;
using System.Collections.Generic;
public class PriorityQueue<T> {
// 声明一个List来存储元素和优先级
private List<Tuple<int, T>> heap;
public PriorityQueue() {
// 构造函数,初始化一个空的List
heap = new List<Tuple<int, T>>(); // []
}
public void Enqueue(T item, int priority) {
// 向堆中插入一个新元素和优先级
heap.Add(new Tuple<int, T>(priority, item)); // [(1, "study")]
// 对列表排序,按Tuple的第一个元素(优先级)排序
heap.Sort((x, y) => x.Item1.CompareTo(y.Item1));
// [(1, "study")]
}
public T Dequeue() {
// 获取第一个元素(堆顶,优先级最高)
T item = heap[0].Item2; // "study"
// 删除第一个元素
heap.RemoveAt(0); // []
// 返回得到的元素
return item; // "study"
}
public T Peek() {
// 返回堆顶元素,不会删除
return heap[0].Item2;
}
public int Count() {
// 返回堆中的元素个数
return heap.Count;
}
}
// 使用示例
var pq = new PriorityQueue<string>();
// 第1次插入
pq.Enqueue("study", 1); // [(1, "study")]
// 第2次插入
pq.Enqueue("eat", 2);
// [(1, "study"), (2, "eat")]
// 排序后变成[(1, "study"), (2, "eat")]
// 弹出堆顶元素
Console.WriteLine(pq.Dequeue()); // "study"
// 队列变为[(2, "eat")]
Console.WriteLine(pq.Peek()); // "study" 堆顶元素
Console.WriteLine(pq.Count()); // 2 堆中元素个数##
- 初始化空堆列表
- push时传入元素和优先级
- pop时只返回元素,不要优先级
- peek查看堆顶元素
- empty检查堆是否为空
图形算法(Graph Algorithms):在图论中,Heap通常用于计算最短路径或最小生成树。使用Heap可以快速找到与已经访问的节点相邻的最短路径节点。
// 创建图的数据结构,字典存储每个节点及相邻节点
Dictionary<string, Node> graph = new Dictionary<string, Node>();
// 初始化图中每个节点
graph["A"] = new Node("A");
graph["B"] = new Node("B");
//...
// Dijkstra算法
public static void Dijkstra(Dictionary<string, Node> graph, string source) {
// 创建最小堆,用于按dist排序节点
var queue = new PriorityQueue<Node, int>();
// 源节点dist设为0
graph[source].dist = 0;
// 将源节点进入队列
queue.Enqueue(graph[source], 0);
// 主循环,当堆不空时
while (queue.Count > 0) {
// 取堆顶节点u,这是dist最小的节点
Node u = queue.Dequeue();
// 遍历u的所有相邻节点
foreach (Node v in graph[u.name].neighbors) {
// 如果以u为中介点路径更短,则更新dist
if (graph[v.name].dist > u.dist + weight(u, v)) {
// 更新dist
graph[v.name].dist = u.dist + weight(u, v);
// 将更新后的节点重新入堆,堆会重新调整
queue.Enqueue(graph[v.name], graph[v.name].dist);
}
}
}
}
运行过程:
- 初始化图数据
- 将源节点入堆,dist=0
- 循环取堆顶dist最小节点u
- 遍历u的相邻节点v,如果路径更短则更新v.dist
- 将v重新入堆,使堆重新调整
- 重复3-5直到堆空,最后每个节点dist即最短路径长度
通过最小堆始终取出dist最小的节点,快速更新相邻节点dist,使算法时间复杂度降为O(ElogV)。
K路归并(K-way Merge):K路归并是一种将多个已排序的序列合并成一个大的已排序序列的算法。Heap可以用来维护K个输入序列的当前最小元素,以便可以选择最小元素并将其添加到输出序列中。
using System;
using System.Collections.Generic;
class KWayMerge {
public static void Main() {
// 输入数组
int[][] arrays = {
new int[] {1, 5, 8, 9},
new int[] {2, 6, 10},
new int[] {3, 7, 11, 15}
};
// K = 3路归并
int k = 3;
// 调用归并方法
int[] result = Merge(arrays, k);
// 输出结果
Console.WriteLine("Result: " + string.Join(",", result));
}
public static int[] Merge(int[][] arrays, int k) {
// 初始化最小堆
PriorityQueue<Tuple<int,int>> minHeap = new PriorityQueue<Tuple<int,int>>();
// 将每个数组的首元素放入最小堆
for(int i=0; i<k; i++) {
if(arrays[i].Length > 0) {
minHeap.Enqueue(new Tuple<int,int>(arrays[i][0], i));
}
}
// 结果数组
int[] result = new int[arrays.Sum(x => x.Length)];
int index = 0;
// 当堆不空时循环
while(minHeap.Count > 0) {
// 获取最小值和所在数组索引
Tuple<int,int> tuple = minHeap.Dequeue();
int minValue = tuple.Item1;
int arrayIndex = tuple.Item2;
// 将最小值放入结果数组
result[index++] = minValue;
// 如果对应数组还有元素,则将下一个元素放入堆
if(arrays[arrayIndex].Length > 1) {
minHeap.Enqueue(new Tuple<int,int>(arrays[arrayIndex][1], arrayIndex));
arrays[arrayIndex] = arrays[arrayIndex].Skip(1).ToArray();
}
}
return result;
}
}
// 运行结果:
// Result: 1,2,3,5,6,7,8,9,10,11,15
- 定义了3个数组作为输入:
int[][] arrays = {
{1, 5, 8, 9},
{2, 6, 10},
{3, 7, 11, 15}
}; - 初始化最小堆minHeap。
- 遍历输入的3个数组,将每个数组的第一个元素作为一个Tuple放入minHeap。
这时minHeap中的元素为:
(1, 0)
(2, 1)
(3, 2) - 开始while循环,当minHeap不为空时:
(1) 从minHeap中取出Tuple最小值(1, 0)
(2) 将min值1放入结果数组
(3) 从arrays[0]取出下一个元素5,与其数组索引0封装为元组放入minHeap
(4) minHeap现在为:(2, 1),(3, 2),(5, 0) - while循环继续,进行上述操作,每次从堆顶取最小值放入结果数组,并将对应数组的下一个元素放入minHeap。
- 当最小堆为空时,所有元素都已归并到结果数组,循环结束。
- 返回最终有序的结果数组。
所以通过维护一个最小堆,就可以每次快速获取下一个最小值,实现K路归并排序。
Heap是一种非常有用的数据结构,它在许多应用程序中都有广泛的应用。
后缀树(Suffix Tree)
using System;
using System.Collections.Generic;
// 后缀树节点类
public class SuffixTreeNode {
public char value; // 节点的值
public Dictionary<char, SuffixTreeNode> children = new Dictionary<char, SuffixTreeNode>(); // 子节点
public SuffixTreeNode suffixLink; // 后缀链接
public int start; // 节点表示的字符串在原字符串中的起始索引
public int end; // 节点表示的字符串在原字符串中的结束索引
public int suffixIndex; // 后缀在原字符串中的起始索引
}
// 构建后缀树
public SuffixTree BuildSuffixTree(string text) {
SuffixTreeNode root = new SuffixTreeNode(); // 创建树的根节点
root.start = -1; // 根节点没有表示具体的字符串
root.end = -1;
root.suffixLink = root; // 根节点的后缀链接指向自己
// 遍历所有后缀
for (int i = 0; i < text.Length; i++) {
SuffixTreeNode curNode = root; // 从根节点开始
for (int j = i; j < text.Length; j++) {
char c = text[j]; // 获取当前字符
SuffixTreeNode nextNode;
// 检查子节点是否存在
if (curNode.children.ContainsKey(c)) {
nextNode = curNode.children[c]; // 存在则直接获取
} else {
// 不存在就新建节点
nextNode = new SuffixTreeNode();
nextNode.value = c;
curNode.children[c] = nextNode; // 添加到children字典
}
// 更新节点start和end
if (nextNode.start == -1) {
// 若是新节点,更新start和end
nextNode.start = i;
nextNode.end = j;
nextNode.suffixIndex = i;
} else {
nextNode.end++; // 存在的节点end后移
}
curNode = nextNode; // 指针下移
}
// 设置后缀链接
if (root.suffixLink == root) {
root.suffixLink = prevNode;
} else {
SuffixTreeNode suffLink = GetSuffixLink(prevNode);
prevNode.suffixLink = suffLink;
}
prevNode = root; // prev指向根,以便下一次设置后缀链接
}
return new SuffixTree(root); // 返回构建的后缀树
}
1. 字符串搜索
public bool Exists(SuffixTree tree, string pattern) {
SuffixTreeNode node = tree.root; // 从根节点开始
for (int i = 0; i < pattern.Length; i++) {
char c = pattern[i]; // 取得模式串的当前字符
// 检查是否存在对应字符的子节点
if (!node.children.ContainsKey(c)) {
return false; // 不存在匹配的子节点,直接返回不存在
}
node = node.children[c]; // 存在则移动到子节点
}
return true; // 遍历完整个模式串,说明存在匹配
}
注释关键点:
- 从根节点开始匹配
- 每一层检查字符是否存在对应的子节点
- 如果任一字符不存在对应的子节点,直接返回false
- 如果所有字符都存在对应子节点,则说明模式串在树中存在,返回true
以上逐行注释详细解释了后缀树模式匹配存在性检查的实现逻辑。
2. 最长公共子串
public string LongestCommonSubstring(SuffixTree tree, string s1, string s2) {
// 计算s1和s2组合字符串的后缀树
SuffixTree sufTree = BuildSuffixTree(s1 + "$" + s2);
int maxLen = 0; // 最大长度初始化为0
SuffixTreeNode node = sufTree.root; // 从根节点开始
// 遍历根节点的所有子节点
foreach (SuffixTreeNode n in node.children.Values) {
// 如果当前节点表示的字符串长度更长
if (n.end - n.start > maxLen) {
node = n; // 保存该节点
maxLen = n.end - n.start; // 更新最大长度
}
}
// 返回该节点表示的最长公共子串
return s1.Substring(node.start, node.end - node.start + 1);
}```
注释关键点:
1. 构建组合字符串的后缀树
2. 找到路径最长的叶子节点
3. 这个叶子节点表示的字符串就是最长公共子串
4. 通过start和end索引从原字符串中截取出来
这样通过后缀树的遍历可以高效地找到最长公共子串。
#### 3. 字符串压缩
```csharp
public string Compress(SuffixTree tree, string text) {
List<string> fragments = new List<string>(); // 存储所有子串
SuffixTreeNode node = tree.root;
GetFragments(node, fragments); // 从根节点递归获取所有子串
// 对子串排序,长的排前面
fragments.Sort((a, b) => b.Length.CompareTo(a.Length));
StringBuilder sb = new StringBuilder();
Dictionary<string, int> dictionary = new Dictionary<string, int>();
foreach (string frag in fragments) {
if (!dictionary.ContainsKey(frag)) {
dictionary.Add(frag, dictionary.Count); // 子串映射到编号
}
sb.Append(dictionary[frag]); // 将编号加入到结果
}
return sb.ToString();
}
// 递归获取所有子串
void GetFragments(SuffixTreeNode node, List<string> fragments) {
if (IsLeaf(node)) {
// 到达叶子节点,添加子串
fragments.Add(text.Substring(node.start, node.end - node.start + 1));
}
foreach (SuffixTreeNode child in node.children.Values) {
GetFragments(child, fragments); // 对子节点递归
}
}
注释关键点:
- 递归遍历后缀树,获取所有子串
- 对子串排序,长的排前面
- 子串映射到编号,组成压缩结果
- 递归获取子串,叶子节点代表一个子串
这样通过后缀树可以有效地进行字符串压缩。
4. 重复子串统计
public Dictionary<string, int> GetRepeatedSubstrings(SuffixTree tree) {
Dictionary<string, int> result = new Dictionary<string, int>(); // 存储结果
SuffixTreeNode node = tree.root;
CountRepeated(node, result); // 从根节点递归统计
return result;
}
// 递归统计每个子串出现次数
void CountRepeated(SuffixTreeNode node, Dictionary<string,int> result) {
if (IsLeaf(node)) {
// 到达叶子节点,获取对应的子串
string sub = text.Substring(node.start, node.end - node.start + 1);
if (!result.ContainsKey(sub)) {
result[sub] = 0; // 不存在则添加到字典
}
result[sub]++; // 对应子串出现次数加 1
}
foreach(SuffixTreeNode child in node.children.Values) {
CountRepeated(child, result); // 递归处理子节点
}
}
注释关键点:
- 递归遍历后缀树,统计每个子串
- 叶子节点表示一个子串
- 使用字典存储每个子串和出现次数
- 递归处理每个子节点
- 最终字典包含所有重复子串和对应的次数
这样可以通过后缀树高效获取重复子串。
5. 字符串比较
public int LongestCommonPrefix(SuffixTree tree, string s1, string s2) {
SuffixTree sufTree = BuildSuffixTree(s1 + "$" + s2); // 构建合并后缀树
SuffixTreeNode node = sufTree.root;
int maxDepth = 0; // 最大深度初始化为0
GetCommonPrefix(node, 0, ref maxDepth); // 从根节点递归搜索
return maxDepth;
}
void GetCommonPrefix(SuffixTreeNode node, int depth, ref int maxDepth) {
if (depth > maxDepth) {
maxDepth = depth; // 更新最大深度
}
foreach (SuffixTreeNode child in node.children.Values) {
GetCommonPrefix(child, depth + 1, ref maxDepth); // 递归处理子节点
}
}
注释关键点:
- 构建合并后缀树
- 从根节点递归搜索最大深度
- 当前深度比最大深度大时更新最大深度
- 递归处理每个子节点
- 最大深度即为最长公共前缀的长度
这样通过后缀树可以高效地求出两个字符串的最长公共前缀。
6. 序列分析
public List<string> FindRepeatedPatterns(string sequence) {
SuffixTree tree = BuildSuffixTree(sequence);
List<string> patterns = new List<string>();
SuffixTreeNode node = tree.root;
FindPatterns(node, sequence, patterns);
return patterns;
}
void FindPatterns(SuffixTreeNode node, string sequence, List<string> patterns) {
if (IsLeaf(node) && node.end - node.start > 1) {
patterns.Add(sequence.Substring(node.start, node.end - node.start + 1));
}
foreach (SuffixTreeNode child in node.children.Values) {
FindPatterns(child, sequence, patterns);
}
}
7. 词频统计
public List<string> FindRepeatedPatterns(string sequence) {
SuffixTree tree = BuildSuffixTree(sequence); // 构建后缀树
List<string> patterns = new List<string>(); // 存储重复模式
SuffixTreeNode node = tree.root;
FindPatterns(node, sequence, patterns); // 从根节点递归查找
return patterns;
}
// 递归查找
void FindPatterns(SuffixTreeNode node, string sequence, List<string> patterns) {
if (IsLeaf(node) && node.end - node.start > 1) {
// 到达叶子并且长度大于1的节点代表一个重复模式
patterns.Add(sequence.Substring(node.start, node.end - node.start + 1));
}
foreach (SuffixTreeNode child in node.children.Values) {
FindPatterns(child, sequence, patterns); // 递归处理子节点
}
}
注释关键点:
- 构建字符串的后缀树
- 从根节点递归查找
- 到达叶子节点判断是否代表重复模式
- 重复模式存储到列表
- 递归处理每个子节点
- 返回列表包含所有重复模式
这样可以通过后缀树有效地找出字符串中的重复模式。
8. 文本过滤
public string FilterText(string text, string[] blacklist) {
SuffixTree tree = BuildSuffixTree(string.Join("#", blacklist));
// 构建后缀树,以#作为分隔符
StringBuilder sb = new StringBuilder();
for (int i = 0; i < text.Length; i++) {
if (!Exists(tree, text.Substring(i, blacklist.Max(w => w.Length)))) {
// 判断子串是否在后缀树中
sb.Append(text[i]); // 不在则添加到结果中
}
}
return sb.ToString();
}
注释关键点:
- 构建黑名单词的后缀树
- 遍历文本的每个字符
- 判断以该字符开头的子串是否在后缀树中
- 不在则添加该字符到结果
- 最终结果过滤掉了黑名单词
这样可以通过后缀树有效地过滤文本。
R树(R-tree)
R树(R-tree)是一种用于空间数据索引的树形数据结构。它将二维或更高维空间划分成一系列的最小边界矩形(MBR)区域,在其上构建一棵树来表示空间对象之间的包含关系,用于加速空间数据的搜索、插入和删除操作。
R树的主要特征包括:
- R树是一个层次数据结构,每个节点可以有多个子节点。
- 每个节点对应一个最小边界矩形(MBR),表示该节点所包含的所有子节点的MBR的并集。
- 叶子节点包含实际的空间数据对象,非叶子节点存储MBR信息。
- 通过MBR的包含关系构建树结构,使得搜索时可以快速过滤掉不相关的节点。
- 插入和删除可能引起节点的拆分和合并,以维持R树的平衡。
- 搜索算法一般采用自顶向下的递归方式,利用MBR间的包含和相交关系进行过滤。
R树常用于地理信息系统、CAD等空间应用中,可以加速区域查询、最近邻查询等空间操作。缺点是增加、删除节点时可能需要重新构建,实现较为复杂。
// RTree节点类
public class RTreeNode {
public Rectangle mbr; // 定义mbr成员变量,用于存储该节点的最小边界矩形
public List<Rectangle> rectangles; // 定义rectangles成员变量,用于存储叶节点包含的矩形对象
public List<RTreeNode> children; // 定义children成员变量,用于存储非叶节点的子节点对象
public RTreeNode() { // RTreeNode类的构造函数
rectangles = new List<Rectangle>(); // 初始化rectangles列表
children = new List<RTreeNode>(); // 初始化children列表
}
}
// RTree类
public class RTree {
RTreeNode root; // 定义RTree的根节点对象
// RTree构造函数
public RTree() {
root = new RTreeNode(); // 初始化根节点
}
// 插入新矩形方法
public void Insert(Rectangle r) {
Insert(root, r); // 调用递归插入方法,以根节点和新矩形为参数
}
// 递归插入方法
void Insert(RTreeNode node, Rectangle r) {
// 1. 更新node的mbr,包含新插入的矩形
node.mbr = Include(node.mbr, r);
// 2. 如果是叶节点,直接插入
if (node.children.Count == 0) {
node.rectangles.Add(r);
return;
}
// 3. 否则遍历子节点,选择一个子节点插入
RTreeNode childNode = ChooseSubtree(node, r);
Insert(childNode, r);
}
// 更新mbr包含指定矩形方法
Rectangle Include(Rectangle r1, Rectangle r2) {
// 计算包含r1和r2的最小外接矩形并返回
}
// 选择子节点插入方法
RTreeNode ChooseSubtree(RTreeNode node, Rectangle r) {
// 计算插入到各子节点的面积增量,选择面积增量最小的节点
}
// 搜索方法
List<Rectangle> Search(Rectangle range) {
List<Rectangle> result = new List<Rectangle>();
Search(root, range, result); // 递归搜索
return result; // 返回结果
}
// 递归搜索方法
void Search(RTreeNode node, Rectangle range, List<Rectangle> result) {
// 1. 判断node的mbr与搜索范围是否相交
if (!node.mbr.Intersects(range)) {
return; // 不相交则直接返回
}
// 2. 如果是叶节点,检查包含的矩形
if(node.children.Count == 0) {
foreach(var r in node.rectangles) {
if (range.Contains(r)) {
result.Add(r); // 添加符合的矩形到结果
}
}
return;
}
// 3. 否则,递归搜索子节点
foreach (var child in node.children) {
Search(child, range, result);
}
}
}
R树C#代码的运行过程:
- 创建RTree对象:
RTree rTree = new RTree();
这将初始化一个RTree,其根节点root为一个空的RTreeNode。
2. 插入矩形
Rectangle r1 = new Rectangle(0, 0, 10, 10);
rTree.Insert(r1);
调用Insert将矩形r1插入R树。该方法会递归的找到一个叶节点插入该矩形,并通过Include方法更新 ancestors 节点的mbr。
3. 重复插入矩形,继续更新R树。
///
4. 搜索矩形:
Rectangle searchRange = new Rectangle(5, 5, 15, 15);
List<Rectangle> result = rTree.Search(searchRange);
调用Search方法以指定的搜索范围进行矩形搜索。该方法通过递归遍历R树,利用节点mbr比较以及叶节点矩形检查进行过滤,找出匹配的矩形。
6. 最终result将包含所有在searchRange内的矩形对象。
通过这样递归构建R树并利用其空间索引特性进行搜索,可以实现高效的空间数据查询。每个步骤中的关键点是利用mbr比较进行过滤,以减少不必要的节点访问。
整体上,该示例代码实现了R树核心功能,可用于理解R树运作过程。可以继续完善Include、ChooseSubtree等细节算法来使R树更加高效。
元组(Tuple)
元组(Tuple)代表一个包含多个元素的数据结构,可以很方便地把多个值组合成一个复合值。
元组的基本语法如下:
// 声明元组
Tuple<int, string, bool> t1;
// 初始化元组
t1 = new Tuple<int, string, bool>(1, "hello", true);
// 使用元组元素
int num = t1.Item1;
string str = t1.Item2;
bool flag = t1.Item3;
// 也可以通过下标访问
int num = t1[0];
string str = t1[1];
bool flag = t1[2];
元组支持任意数量和类型的元素,语法为:
Tuple<T1, T2, ..., Tn>
其中T1到Tn是元组包含的n个元素类型。
元组的一些主要特性:
- 元组元素可以是不同的数据类型
- 元组元素可以通过名称(Item1, Item2等)或索引访问
- 元组是值类型,且不可变(immutable)
- 支持比较运算符来比较两个元组
- 可以通过Deconstruct方法解构元组
元组常用于需要返回多个值的函数,或封装临时数据等场景。相比传统方式,元组可以更简洁地表达多值集合。
数据表(DataTable)
1.DataTable代表内存中的一个表结构数据,包含行(DataRow)和列(DataColumn)
// 创建DataTable对象,指定表名为"Products"
DataTable table = new DataTable("Products");
// 添加列 - 产品编号,整型
table.Columns.Add("ProductId", typeof(int));
// 添加列 - 产品名称,字符串型
table.Columns.Add("Name", typeof(string));
// 添加列 - 产品价格,十进制浮点型
table.Columns.Add("Price", typeof(decimal));
// 设置主键 - 根据ProductId列设置主键
table.PrimaryKey = new [] { table.Columns["ProductId"] };
// 添加行 - 产品编号1,名称Apple,价格2.99
table.Rows.Add(1, "Apple", 2.99m);
// 添加行 - 产品编号2,名称Orange,价格3.49
table.Rows.Add(2, "Orange", 3.49m);
// 查询名称为Apple的行
var apple = table.Select("Name = 'Apple'");
// 修改Apple价格为1.99
apple[0]["Price"] = 1.99m;
// 删除查询得到的Apple行
apple[0].Delete();
// 提交删除更改
table.AcceptChanges();
// 按Price降序排序查询
var sortedTable = table.Select("", "Price DESC");
// 绑定表到DataGridView控件显示
dataGridView1.DataSource = table;
// 将表的数据导出到XML文件
table.WriteXml("Products.xml");
数据表创建、增删改查、绑定和导出
2.绑定UI控件:
- 可以用DataTable作为数据源,绑定到DataGridView、ListBox等控件
1.绑定DataGridView:
DataGridView是一个功能强大且使用灵活的表格控件,适合在 WINDOWS forms 应用程序中处理和呈现表格数据
// 创建DataTable,添加列和数据
DataTable table = new DataTable("Products");
// 添加代码省略
// 创建DataGridView控件
DataGridView dataGridView1 = new DataGridView();
// 将DataTable对象绑定到DataGridView控件
// 设置DataSource属性/数据为刚才创建的DataTable
dataGridView1.DataSource = table;
// 设置AutoGenerateColumns属性为true
// 使DataGridView自动根据DataTable生成列
dataGridView1.AutoGenerateColumns = true;
// 调整列标题
dataGridView1.Columns[0].HeaderText = "产品编号";
dataGridView1.Columns[1].HeaderText = "产品名称";
dataGridView1.Columns[2].HeaderText = "产品价格";
// 遍历列,修改header text,显示列标题 for的方式 2
/*
for(int i=0; i<dataGridView1.Columns.Count; i++)
{
dataGridView1.Columns[i].HeaderText =
table.Columns[i].ColumnName;
}
*/
// 隐藏ProductId列,不在页面显示
dataGridView1.Columns["ProductId"].Visible = false;
//dataGridView1.Columns[0].Visible = false; //方式2
// 设置DataGridView的边框,行背景色等属性
// 使显示更美观
// ...你其他的代码关于DataGridView的
//自定义列,实现分页和筛选等功能
// 将DataGridView控件添加到Windows Form上
// 进行显示
this.Controls.Add(dataGridView1);
2.绑定ListBox
,ListBox 通过其简单、实用、灵活的列表显示和选择功能,可以大大简化开发流程,是 Windows 表单开发中最常用的控件之一
- // 创建DataTable作为数据源
DataTable dataTable = new DataTable();
// 添加列、行等到dataTable
// ...
// 创建ListBox控件
ListBox listBox1 = new ListBox();
// 将DataTable对象绑定到ListBox控件
// 设置ListBox属性/数据为刚才创建的DataTable
listBox1.DataSource = table;
// 设置DisplayMember为需要显示的列名
listBox1.DisplayMember = "Name";
// 设置ValueMember为主键列名
listBox1.ValueMember = "ProductId";
// 设置ListBox的大小、字体等属性
listBox1.Size = new Size(200, 150);
listBox1.Font = new Font("Arial", 12);
// 在Form上显示ListBox
this.Controls.Add(listBox1);
3. BindingSource组件作二级绑定
BindingSource 是 .NET Windows 窗体和 WPF 应用中最常用的数据绑定工具之一,可以大大简化数据绑定相关的工作
- // 创建DataTable作为数据源
DataTable dataTable = new DataTable();
// 添加列、行等到dataTable
// ...
// 创建BindingSource实例
BindingSource bindingSource1 = new BindingSource();
// 将DataTable绑定到BindingSource
// BindingSource作为第一层绑定源
bindingSource1.DataSource = dataTable;
// 创建DataGridView控件
DataGridView dataGridView1 = new DataGridView();
// 将BindingSource绑定到DataGridView
// BindingSource作为二级绑定源
dataGridView1.DataSource = bindingSource1;
// 在BindingSource上设置筛选条件
// 只显示名称以A开头的行
bindingSource1.Filter = "Name LIKE 'A%'";
// 在BindingSource上设置排序
// 根据Name降序排序
bindingSource1.Sort = "Name DESC";
// DataGridView会显示经过筛选和排序的结果
// 但原始DataTable的数据不会变化
// 修改BindingSource或DataTable的数据
// 会自动同步到绑定的控件
3.与数据库交互:
1.DataAdapter可用来填充DataTable,并处理修改
- // 创建连接字符串
string connString = "...";
// 创建连接对象
SqlConnection conn = new SqlConnection(connString);
// 创建DataAdapter,指定SELECT命令
SqlDataAdapter adapter = new SqlDataAdapter(
"SELECT * FROM Products", conn);
// 创建UPDATE、DELETE命令
SqlCommand updateCmd = new SqlCommand(
"UPDATE Products SET Price = @Price WHERE Id = @Id", conn);
adapter.UpdateCommand = updateCmd;
SqlCommand deleteCmd = new SqlCommand(
"DELETE FROM Products WHERE Id = @Id", conn);
adapter.DeleteCommand = deleteCmd;
// 创建INSERT命令
SqlCommand insertCmd = new SqlCommand(
"INSERT INTO Products VALUES (@Id, @Name, @Price)", conn);
adapter.InsertCommand = insertCmd;
// 创建DataTable
DataTable table = new DataTable();
// 填充表结构
adapter.FillSchema(table, SchemaType.Source);
// 填充表数据
adapter.Fill(table);
// 显示填充的数据
dataGridView1.DataSource = table;
// 修改价格
table.Rows[0]["Price"] = 1.99;
// 删除行
table.Rows[1].Delete();
// 提交更新
adapter.Update(table);
运行过程:
1. 创建连接对象
2. 创建适配器,指定Select命令
3. 设置Update、Delete、Insert命令
4. 填充DataTable结构
5. 填充DataTable数据
6. 显示数据
7. 修改和删除数据
8. 提交更新
直接装载到DataSet,或从DataSet访问
// 创建DataTable对象,指定表名为"Products"
DataTable table = new DataTable("Products");
// 添加列,指定列名和类型
table.Columns.Add("ProductId", typeof(int));
table.Columns.Add("Name", typeof(string));
table.Columns.Add("Price", typeof(decimal));
// 设置ProductId列为主键
table.PrimaryKey = new [] { table.Columns["ProductId"] };
// 添加行,传入列值
table.Rows.Add(1, "Apple", 2.99m);
table.Rows.Add(2, "Orange", 3.49m);
// 查询指定条件的行
var apple = table.Select("Name = 'Apple'");
// 修改指定行的列值
apple[0]["Price"] = 1.99m;
// 删除行
apple[0].Delete();
// 提交删除更改
table.AcceptChanges();
// 排序查询
var sortedTable = table.Select("", "Price DESC");
// 绑定到DataGridView显示
dataGridView1.DataSource = table;
// 导出表数据到XML文件
table.WriteXml("Products.xml");
// 加载到DataSet
DataSet ds = new DataSet();
ds.Tables.Add(table);
// 从DataSet访问
DataTable productTable = ds.Tables["Products"];
1. 创建表,添加列和行
2. 查询/修改/删除数据
3. 排序和显示
4. 导入导出
5. 加载到DataSet,或从中访问表
DataTable可以独立使用,也可以集成到DataSet中,与数据库交互。
- 线程安全:
- DataTable不是线程安全的
- 需要用线程同步或只读方式访问
- 性能:
- 仅在内存中,通常快于磁盘和数据库
- 但大数据量时需要注意内存占用
- 使用场景:
- 只需要在内存中暂存和操作数据
- 作为前端UI的临时数据源
DataTable提供了方便的表结构数据操作,适用于对内存中数据的快速查询、计算和绑定UI。