Vico Bill< 刘 利 波 > 的个人网站

记录关于学习、工作中的技术点滴

C,C++,Rust,Ruby爱好者;热衷于游戏开发、任务自动化与跨平台;沉迷于游戏引擎与图形表现;深信'简单、多元'哲学的力量。


访问主页

【编码】-数据结构剪影

“数据结构+算法=程序”,这一句话深入人心,如果是程序员,那肯定对此话有深刻的理解。确实如此,整个应用程序的组成,无外乎两个元素:数据结构,算法。

算法是计算方式,是函数,是方法,是解决问题的步骤。算法在程序中,以函数为基本表现形式。

数据结构是存储数据的形式。在编码过程中,基本上就是寻找合理的数据结构,并编写对其操作的函数。

在不同的编程语言中,有些把常用的数据结构已经内置在语言中,有些则是以标准库的形式提供。

研究数据结构,需要从数据的逻辑结构和物理结构两方面着手。

逻辑结构:反应数据元素之间的逻辑关系,即前后元素之间的关系。数据的逻辑结构有以下几种:

  • 线性结构:即元素存在一对一的相互关系
  • 树形结构:即元素存在一对多的相互关系
  • 集合:即元素同属一个集合,它们之间没有互相联系
  • 图形结构:即元素存在多对多的相互关系

物理结构:指在计算机中存储的形式。数据的物理结构有以下几种:

  • 顺序存储:数据顺序存储,元素与元素为邻接关系。
  • 链式存储:元素与元素通过链相连。
  • 索引存储:元素通过索引表一一对应。这会在数据元素之外,新建索引表。

数据结构的算法,往往涉及元素的这样几个操作:增、删、改、查。

  • 增:往数据结构中添加元素。添加的位置可是:头、中、尾。
  • 删:从数据结构中删除元素。删除的位置可是:头、中、尾。
  • 查:从数据结构中查找满足条件的元素。查找的方式有这样几种:顺序、随机。
  • 改:改变数据结构中某个或某些元素,改的前提是查,查到才能改。

常用结构:

  • 数组:顺序数据结构。通常内置于编程语言中,是线性结构,常采用顺序存储方式。
    • 元组:是非同种类型的数组。
  • 栈:是只能一端插入和删除的结构,先进后出。是线性结构,可采用顺序和链式存储方式,常采用顺序存储。常用于undo类似的任务中。
  • 队列:从前面出,后面进的结构,先进先出。是线性结构,可采用顺序和链式存储,但常采用链式存储。
    • 循环队列:将队列头和尾相连
  • 列表:有节点概念。是线性结构,常采用链式存储方式。
    • 双向链表:每个节点都存储了前、后两个节点信息。
  • 集合:是集合结构,可采用顺序和索引存储。
  • 树:有根、叶概念。是非线性结构,常采用链式存储。
    • 红黑树:是自平衡二叉树。
    • 二/四/八叉树:每个节点对应有不同数量的子节点。常用于空间分割。
  • 图:有节点集合、边集合概念。是图形结构,可采用链式和索引存储。
  • 映射表:具映射关系的表。可是线性和树形结构,可采用链式和索引存储。
    • 散列表:索引是由数据的散列值而来,当要查找数据时,直接计算散列值即可获取到数据。
    • 多重映射表:一个索引有多个对应值

在C#中的实现

C#中的数据结构,都在System.Collections命名空间下。在C#中存在这样几个接口:

  • ICollection:定义了方法Add,Clear,Contains,Remove和属性CountContainsRemove内部使用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:多重哈希集合
最近的文章

【编码】-编程语言剪影

编程语言是整个程序活动中最重要的部分,如同人说话一样,说话是用于表达思想,与人沟通。类似,编程语言是程序员表达思想,与计算机沟通的。对于计算机而言,能理解的只是电流高低,对应即二进制10。这称为机器语言。计算机内部由集成电路铺就,会有一些处理器元件存在,通过算术逻辑单元(ALU),能进行简单的逻辑运算:与、或、非、移位、比较以及简单的加、减、乘运算。如今的计算机软件层出不穷,百花齐放,规模也逐年增大,但计算机底层硬件仍然是没有变化。那么如何让计算机按照我们的意愿去工作?我们这时需要指令。指...…

继续阅读
更早的文章

【Game】-美术资源管理办法

Unity 项目的目录结构推荐:以下为 Unity 默认资源目录,存放在项目 Assets 目录下,即根目录: Plugins:Unity 默认的插件目录 x86 x86_64 Android iOS Editor Default Resources:编辑器用到的资源 StreamingAssets : Unity 默认的流式资源目录。 Gizmos:Unity 默认 icon 目录。 ImportedAss...…

继续阅读