跳过第一章, 直奔主题--线性表
我觉得线性表应该是数组和链表的统称。 因为他们的结构都是线性的, 只是从内存地址(逻辑地址, 不是物理地址)角度来看一个是连续的, 另一个是不连续的。因此我觉得本书的第2章到第5章都在讲线性表。
第2章和第3章讲的链表(list)以及链表(list)的配接器stack和queue。
第4章和第5章讲的是数据(array)的配接器。
STL容器所实现的也就这么几种, 最常用的是array和list, 然后衍生出vector, string, queue, deque, stack, heap等. 这些统称为顺序容器. 他们具有类似的结构, 类似的操作接口, 只是实现上略有差异.
1. 什么是链表:
链表(list)是常用的数据结构, 基本上有点规模的程序都离不开链表。
那什么是链表, 最直观的描述(data域 + 指针域):
template <typename T>
struct node {
T data;
struct node* next;
};
+----------+ +----------+ +----------+
|data|next | ----->|data|next | ----->|data|next | -----> NULL
+----------+ +----------+ +----------+
最简洁的描述要数linux 内核的链表结构了
struct list_head {
struct list_head *prev, *next;
};
# +----------+ +----------+ +----------+
+-->|prev|next | ----->|prev|next | ----->|prev|next | ---+
| +----------+ +----------+ +----------+ |
+---------------------------------------------------------+
list_head通常作为某个数据结构的list成员.
2. 链表的基本操作:
在书上用伪码给链表定义了一个抽象的数据结构, 其中包含了一些链表的基本操作
ADT List {
InitList(&L);
Destory(&L);
ClearList(&L);
ListEmpty(L);
ListLength(L);
GetElem(L, i, &e);
LocateElem(L, e, compare());
PriorElem(L, cur_e, &pre_e);
NextElem(L, cur_e, &next_e);
ListInsert(&L, i, e);
ListDelete(&L, i, &e);
ListTraverse(L, visit());
};
为什么要定义这些基本操作?
我的理解是利用这些基本操作, 可以组合出一些稍微高级点的操作如List union
ListUnion(&La, Lb) {
La_len = ListLength(La);
Lb_len = ListLength(Lb);
for ( i = 1; i <= Lb_len; i++ ) {
GetElem(Lb, i, e);
if ( !LocateElem(La, e, equal) )
ListInsert(La, ++La_len, e);
}
}
List union函数分解为ListLength(), GetElem(), LocateElem()和ListInsert()四个子函数, 这四个子函数加上适当的控制流, 就变成了List Merge:
ListMerge(La, Lb, &Lc) {
InitList(Lc);
i = j = 1;
k = 0;
La_len = ListLength(La);
Lb_len = ListLenght(Lb);
while ( (i <= La_len) && (j <= Lb_len) ) {
GetElem(La, i, ai);
GetElem(Lb, j, bj);
if ( ai <= bj ) {
ListInsert(Lc, ++k, ai);
++i;
} else {
ListInsert(Lc, ++k, bj);
++j;
}
}
while ( i <= La_len ) {
GetElem(La, i++, ai);
ListInsert(Lc, ++k, ai);
}
while ( j <= Lb_len ) {
GetElem(Lb, j++, bj);
ListInsert(Lc, ++k, bj);
}
}
这符合unix设计原则, unix中类似的设计就是通过cat, ifconfig, netstate, cut, paste, grep等命令, 配合管道可以组合出不同的功能, 例如想查看/etc/passwd中的所有用户
$ cat /etc/passwd | cut -d':' -f1
3. 链表的特点
链表最大的特点就是地址(逻辑地址)不连续, 但逻辑上是连续的. 由于通过指针相互联系, 链表插入, 删除操作的效率要比连续地址结构的数组高.
菜鸟的实现(持续修改中):
https://github.com/lancerex/dailyAlgorithms/blob/master/DataStructure/yanweimin/list.c牛人的实现:
http://www.sgi.com/tech/stl/stl_list.h