0%

链表的部分函数实现(二)

接上文,依旧是链表的实现。

按照前面说的,链表的整表创建可以当作链表长度为0的时候将数组转化为链表的特殊情况,即这个可以分为两步:

  1. 插入n个节点。
  2. 遍历链表赋值。

所以我们需要先完成插入指定数量的节点这个函数。但是前文也说了,插入一串链表,本质上是将一串链表拼接到另一串链表上。所以插入n个节点也可以分解成两步:

  1. 创建一个长度为n的链表。
  2. 将它拼接到另一个链表上。

这样就要思考了,如果要用最省的步骤来写,是应该先写创建链表还是应该先写插入链表


创建一段空链表

整表创建

好像以前在书上看见过有这么个东西,但是从来没有用过,我也不记得具体是个什么了,也懒得再翻书了,要是再对着书上抄就没意思了。

我从字面意思上理解,应该是用数组创建整个链表,所以原型应该是这样:

1
void LinkListCreateFromArray(LinkList *l, void *array, size_t num, size_t size);

num为数组的元素个数,size为每个元素的大小。

既然叫做整表创建,要是使用插入函数,感觉就没有突出一个整字,而且我们每次插入时都会让长度加一,并且让两个节点的前继后继重复赋值,即每插入一个节点就会重复2次赋值和一次自增,所以整表创建可能节省了3n次操作。这个有点麻烦,就直接贴代码了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void LinkListCreateFromArray(LinkList *l, void *array, size_t num, size_t size)
{
LinkList a = (LinkList)malloc(sizeof(Node));
Node **A = (Node **)malloc(num * sizeof(Node *));
char *p = (char *)array;
A[0] = (Node *)malloc(sizeof(Node));
A[0]->pre = a;
A[0]->data = malloc(size);
memcpy(A[0]->data, p, size);
p += size;
for (size_t i = 1, j = 0; i < num; i++, j++)
{
A[i] = (Node *)malloc(sizeof(Node));
A[j]->next = A[i];
A[i]->pre = A[j];
A[i]->data = malloc(size);
memcpy(A[i]->data, p, size);
p += size;
}
A[num - 1]->next = a;
a->next = A[0];
a->pre = A[num - 1];
a->data = malloc(sizeof(LinkListHeader));
memset(a->data, 0, sizeof(LinkListHeader));
((LinkListHeader *)a->data)->length = num;
*l = a;
}

p是一个char类型的指针,因为char的大小为1,p指向数组的首地址,通过对p加上size可以跳到下一个数组元素的位置。

通过for循环来连接前后两个节点,就能避免前继后继重复赋值,然后对第一个数据节点和尾节点进行特殊处理,连接头节点,并在最后直接将元素个数赋与长度,即可完成整表创建。

这里之前还犯了一个错误,之前我是用malloc一次创建了整个A数组,然后后面无法用free释放单个节点,找了好久才知道是这个问题,于是就改成了在for循环里面一个个malloc,这样删除的时候也可以一个个free。


链表的清空

清空整个链表也算是一个常用操作,虽然我从来没用过,但是有句话叫可以不用,不能没有。跟整表创建相似,如果一个个erase那也会重复2n次赋值和n次自减,也可以优化一下,那就是直接遍历来删除节点,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
void LinkListClear(LinkList l)
{
Node *p = l->next;
while(p!=l)
{
free(p->data);
p = p->next;
free(p->pre);
}
l->next = l;
l-pre = l;
((LinkListHeader *)l->data)->length = 0;
}

链表的销毁

清空链表之后链表就只剩下一个头节点了,只需要再释放头节点,那就是链表的销毁了,释放头节点之后,根据规范应该将指针指向NULL,所以参数用LinkList *,释放后将l置为NULL,代码如下:

1
2
3
4
5
6
7
8
void LinkListDestroy(LinkList *l)
{
LinkListClear(*l);
free(LinkListGetHeaderInfo(*l));
free((*l)->data);
free(*l);
*l=NULL;
}

将链表转化为数组

接上一节,我们还可以写个用链表生成数组的函数,大概思路就是,返回一个void *的数组,通过参数返回数组长度,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void **LinkListToArray(LinkList l, size_t *n)
{
size_t len = LinkListGetLength(l);
size_t size = LinkListGetSize(l);
void **data = (void **)malloc(len*size);
char *p = *data;
LinkListFor(q, LinkListGetFront(l))
{
memcpy(p, NodeGetData(q), size);
p += size;
}
*n = len;
return data;
}

这个是拷贝链表生成的数组,而且不管怎么样,对数组的修改也不会影响到原本的链表的数据,所以我们或许还会需要一个函数来将数组转换回链表。


将数组转化为链表

上面才写有一个整表创建,这里又写个将数组转化为链表,可能你们会觉得我想水字数,或者说为什么不直接用上面的。首先说明,我也是个懒人,如果上面的可以用我也不想重新写一个新的东西,上面那个只适用于空链表来转化,我们这个链表本身带有节点,如果这样用的话会直接让一圈的节点从世界上消失,然后又出现一圈节点取代了它们的位置,让人无法察觉到它们已经不是之前的那些节点了(出现了,传说中的沼泽人问题,只不过内存泄漏)。

首先,我们的链表本身带有节点,本身有长度,不需要全部创建,所以只需要比较链表的长度和数组的长度,会有3种情况,大于小于和等于,如果长度刚好相等,我们就只需要遍历节点然后赋值,如果长度不一样,那就是多的删除,少的补齐,然后赋值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
void LinkListCreateFromArray(LinkList *l, void *array, size_t num, size_t size)
{
if(l == null)
{
LinkListCreateFromArray(l, array, num, size);
return;
}
if(size != LinkListGetSize(l))
{
return;
}
size_t len = LinkListGetLength(l);
if(num > len)
{
size_t n = num -len;
Node **A = (Node **)malloc(n * sizeof(Node *));
A[0] = (Node *)malloc(sizeof(Node));
A[0]->pre = LinkListGetTail(l);
A[0]->data = malloc(size);
for (size_t i = 1, j = 0; i < n; i++, j++)
{
A[i] = (Node *)malloc(sizeof(Node));
A[j]->next = A[i];
A[i]->pre = A[j];
A[i]->data = malloc(size);
}
A[num - 1]->next = l;
l->pre->next = A[0];
l->pre = A[num - 1];
((LinkListHeader *)a->data)->length = num;
}

LinkList a = (LinkList)malloc(sizeof(Node));

char *p = (char *)array;
A[0] = (Node *)malloc(sizeof(Node));
A[0]->pre = a;
A[0]->data = malloc(size);
memcpy(A[0]->data, p, size);
p += size;
for (size_t i = 1, j = 0; i < num; i++, j++)
{
A[i] = (Node *)malloc(sizeof(Node));
A[j]->next = A[i];
A[i]->pre = A[j];
A[i]->data = malloc(size);
memcpy(A[i]->data, p, size);
p += size;
}
A[num - 1]->next = a;
a->next = A[0];
a->pre = A[num - 1];
a->data = malloc(sizeof(LinkListHeader));
memset(a->data, 0, sizeof(LinkListHeader));

*l = a;
}

文章写到这,我才发现我漏了两个重要的东西,删除某一区间的节点和指定位置插入一串节点,链表的整表创建应该也只是将数组转化为链表的一个特殊情况,即链表长度为0的情况。以及再一想,还需要链表的复制和链表的拼接,插入一串节点本质也是链表的拼接。