数据结构概述
随着计算机科学的发展,人们在计算机的各个领域创造了许多前所唯有的东西,在软件领域,在追求算法效率的同时与其紧密相连的就是数据结构。好的数据结构不仅可以有效的管理计算机中的所有数据,同时还能为为算法的设计上提供了许多意想不到的好处,因此许多高效的算法都是依赖其特定的数据结构对所操纵的数据进行组织的。
即使现如今计算机硬件仍然遵循摩尔定律在高速发展着,但人类的需求总是大于硬件发展所能提供的性能。因此算法与数据结构的重要性不但没有随着硬件的发展而逐渐降低,反而是日益重要,并且随着机器学习领域的不断发展,这一需求也将会日益增加。
在这儿将主要讲述linux的queue.h文件中的链表和队列的实现,同时它们与一些通常的设计方法进行比较,对其实现的优劣,性能,适用场景等进行综合描述。
因为主要对Linux平台中的queue.h中实现的数据结构进行描述,所以文中使用的语言为C语言,包括对其它相关数据结构的描述都将以C语言的形式呈现。因此这儿要求读者最好能具备一定的C或数据结构的基础。
链表
链表是一种非常基础的数据结构,几乎每本讲数据结构与算法的书籍都绝对避免不了对链表的描述,因为链表也是实现许多更复杂的数据结构的基础。linux中提供了两种链表的实现,分别是Singly-Linked List和List。
List
数据结构定义:
#define LIST_HEAD(name, type) \
struct name { \
struct type *lh_first; /* first element */ \
}
#define LIST_ENTRY(type) \
struct { \
struct type *le_next; /* next element */ \
struct type **le_prev; /* address of previous next element */ \
}
结构图:
这儿实现的链表实现了以下的原语操作:
链表初始化:LIST_INIT(head)
将对象插入指定对象之后:LIST_INSERT_AFTER(listelm, elm, field)
将对象插入指定对象之前:LIST_INSERT_BEFORE(listelm, elm, field)
将对象插入链表头:LIST_INSERT_HEAD(head, elm, field)
从链表中删除指定对象:LIST_REMOVE(elm, field)
遍历链表:LIST_FOREACH(var, head, field)
链表为空判断:LIST_EMPTY(head)
获取链表第一个对象:LIST_FIRST(head)
获取指定对象的下一个对象:LIST_NEXT(elm, field)
这些原语操作中每一个都比较简单,但是对于这种链表结构的设计却能够难倒许多数据结构初学者。在讲这种设计的优势之前我们先来看看通常数据结构初学者是怎样来设计链表的。
常见的双向链表形式:
常见的单向链表形式:
这拿双向链表举个例子,数据结构书籍上的双向链表画图都是比较抽象的,其重点在于说明双向链表的重要性质及基础用法,在我们知道双向链表是个什么东西后,我们首先能够想到的双向链表结构如下:
struct element {
struct element *dl_next;
struct element *dl_prev;
};
基于上面的定义,我们自然而然就能够轻松实现插入、删除等原语操作。但一个单纯的链表实际上是没有任何意义的,我们需要给它附加意义,所以产生了如下的形式:
struct element {
int value;
struct element *dl_next;
struct element *dl_prev;
};
看到这里不知道有多少人有似曾相识的感受,至少曾经我在很长一段时间对链表应用的理解上是这样的。这种直接将链表与我们所要表示的数据想融合的形式也是我们从数据结构与算法相关书籍上能够学到的最直观的一种应用了。
上面那种应用没有什么大的问题,如果你的代码规模不大的话,我认为这种设计也不是不可以接受的,但是我们应该知道,也是必须知道还有更好的设计的。我们现在可以来看看List的一个定义实例:
struct elelist_head {
struct element *lh_first;
} head;
struct element {
int value;
struct {
struct element *le_next;
struct element **le_prev;
};
};
从上面的代码来看貌似与我们的定义区别不大,但我们再看看其用宏定义的版本: