“数据结构+算法=程序”,这一句话深入人心,如果是程序员,那肯定对此话有深刻的理解。确实如此,整个应用程序的组成,无外乎两个元素:数据结构,算法。
算法是计算方式,是函数,是方法,是解决问题的步骤。算法在程序中,以函数为基本表现形式。
数据结构是存储数据的形式。在编码过程中,基本上就是寻找合理的数据结构,并编写对其操作的函数。
在不同的编程语言中,有些把常用的数据结构已经内置在语言中,有些则是以标准库的形式提供。
研究数据结构,需要从数据的逻辑结构和物理结构两方面着手。
逻辑结构:反应数据元素之间的逻辑关系,即前后元素之间的关系。数据的逻辑结构有以下几种:
- 线性结构:即元素存在一对一的相互关系
- 树形结构:即元素存在一对多的相互关系
- 集合:即元素同属一个集合,它们之间没有互相联系
- 图形结构:即元素存在多对多的相互关系
物理结构:指在计算机中存储的形式。数据的物理结构有以下几种:
- 顺序存储:数据顺序存储,元素与元素为邻接关系。
- 链式存储:元素与元素通过链相连。
- 索引存储:元素通过索引表一一对应。这会在数据元素之外,新建索引表。
数据结构的算法,往往涉及元素的这样几个操作:增、删、改、查。
- 增:往数据结构中添加元素。添加的位置可是:头、中、尾。
- 删:从数据结构中删除元素。删除的位置可是:头、中、尾。
- 查:从数据结构中查找满足条件的元素。查找的方式有这样几种:顺序、随机。
- 改:改变数据结构中某个或某些元素,改的前提是查,查到才能改。
常用结构:
- 数组:顺序数据结构。通常内置于编程语言中,是线性结构,常采用顺序存储方式。
- 元组:是非同种类型的数组。
- 栈:是只能一端插入和删除的结构,先进后出。是线性结构,可采用顺序和链式存储方式,常采用顺序存储。常用于undo类似的任务中。
- 队列:从前面出,后面进的结构,先进先出。是线性结构,可采用顺序和链式存储,但常采用链式存储。
- 循环队列:将队列头和尾相连
- 列表:有节点概念。是线性结构,常采用链式存储方式。
- 双向链表:每个节点都存储了前、后两个节点信息。
- 集合:是集合结构,可采用顺序和索引存储。
- 树:有根、叶概念。是非线性结构,常采用链式存储。
- 红黑树:是自平衡二叉树。
- 二/四/八叉树:每个节点对应有不同数量的子节点。常用于空间分割。
- 图:有节点集合、边集合概念。是图形结构,可采用链式和索引存储。
- 映射表:具映射关系的表。可是线性和树形结构,可采用链式和索引存储。
- 散列表:索引是由数据的散列值而来,当要查找数据时,直接计算散列值即可获取到数据。
- 多重映射表:一个索引有多个对应值
在C#中的实现
C#中的数据结构,都在System.Collections命名空间下。在C#中存在这样几个接口:
- ICollection:定义了方法
Add
,Clear
,Contains
,Remove
和属性Count
。Contains
,Remove
内部使用IEquatable
相等比较器。 - IEnumerator:枚举器。里面定义了一个属性Current,表示当前枚举迭代至某一元素位置。
- IList:定义了方法
[]
,IndexOf
,Insert
,RemoveAt
。可以索引访问、取索引、插入元素、删除指定索引处元素等。IndexOf
内部会进行相等比较。
在System.Collections.Generic下,有以下数据结构:
- List
:列表。使用它的类型,需要实现`IEquatable`接口。如果要进行排序、查找,则要实现`IComparable`接口。 - Stack
:栈。 - LinkedList
:双向链表。 - Directionary<K,V>:键值对表,哈希表。
- HashSet
:散列集合。 - Queue
:队列。
在C#中,常见的容器都是继承于ICollection<T>
,而它本身是可枚举的IEnumerable
,可通过foreach进行迭代。foreach内部工作使用枚举器,枚举器的使用前提是:集合保持不变。如果集合发生更改,枚举将会失效且不可恢复,并下个迭代中MoveNext
使会引发无效操作
异常。在对集合进行枚举的过程中,枚举器没有对集合的独占访问权。
一个很客观的例子,删除集合中所有重复数据。代码可能很快就来:
foreach(var a in collection) {
if (a.Equals(b)) {
collection.Remove(a)
}
}
但这个代码是有问题的,首先,Remove自身内部有遍历,会进行相等判断,移除第一个相等的元素;其次,foreach使用,枚举器,如果集合发生改变,那么foreach所依赖的枚举器将会不稳定,内部的MoveNext
会访问一个已被移除的节点,迭代会失效。
所以,可以如下:
for (var i = 0; i < collection.Count; i++) {
if (a.Equals(b)) {
collection.RemoveAt(i);
}
}
在C++中的实现
C++中的常用数据结构,放在std命名空间,包括:
- vector
:动态数组 - stack
:栈 - queue
:队列 - priority_queue
:优先级队列 - map<K,V>:键值对映射表
- multi_map<K,V>:多重映射表,一个键可有多个值
- unordered_map<K,V>:哈希表。键为值的哈希值
- unordered_multimap<K,V>:多重哈希表
- set
:集合 - multiset
:多重集合 - unordered_set
:哈希集合 - unordered_multiset
:多重哈希集合