您的数据结构如下:
// 节点结构体
struct node_
{
int data;
struct node_* next_node;
};
// 链表结构体
struct linked_list_
{
struct node_* first_node;
};
// 链表集合结构体
struct set_of_linked_lists_
{
struct linked_list_* list_1;
struct linked_list_* list_2;
struct linked_list_* list_3;
};
在main
函数中,set_of_linked_lists_
声明为指向该结构体的指针:
struct set_of_linked_lists_* set_of_lists =
malloc(sizeof(struct set_of_linked_lists_));
当您执行以下语句时:
set_of_lists->list_1->first_node = NULL; // 这里会失败
之所以会失败,是因为在set_of_linked_lists_
结构体中,三个指针并未指向任何有效对象。如同您写的那样:
struct set_of_linked_lists_* set_of_lists =
malloc(sizeof(struct set_of_linked_lists_));
struct linked_list_* list =
malloc(sizeof(struct linked_list_));
list->first_node = NULL; // 这里有效
set_of_lists->list_1->first_node = NULL; // 这里无效
为了使其有效,您可以先将list
赋值给set_of_linked_lists_
的列表指针,如下所示:
list->first_node = NULL; // 这里有效
set_of_lists->list_1 = list; // 这样也有效
set_of_lists->list_2 = list; // 这样也有效
set_of_lists->list_3 = list; // 这样也有效
set_of_lists->list_1->first_node = NULL;
这样就有效了,因为所有三个指针都指向list
,而list
是一个struct linked_list_
类型的指针。
关于代码优化的建议:
- 如名称所示——
list_1
、list_2
和list_3
——链表集合可以用数组形式表示,这样更便于使用和理解,例如:
struct linked_list_ my_list[3];
my_list[0].first_node = NULL;
// 或者
struct linked_list_* p_list[3] = {NULL, NULL, NULL};
for (size_t i = 0; i < 3; i += 1)
{
p_list[i] =
malloc(sizeof(struct linked_list_));
p_list[i]->first_node = NULL;
}
- 可以考虑将集合设计为一个链表的链表,只需改变节点的概念即可。
- 对于每个链表保存一个
size
大小作为元数据,在所有函数中仅使用指针是方便的做法。
- 如果可以选择的话,双链表比单链表更容易编程处理,因为单链表只有一侧指针,遍历起来较为复杂。
接下来提供一个简化的例子,展示如何使用一个链表集合容器,其中包含多个链表,每个链表由多个节点组成。这个例子采用了封装和面向对象编程原则。
首先,定义一个Node
节点:
// Node
typedef struct node_
{
int data;
struct node_* next;
} Node; // 一个节点
int so_print_n(Node*, const char* msg); // 打印节点的辅助函数
然后,定义一个链表List
结构:
// List
typedef struct
{
size_t size;
Node* tail;
Node* head;
} List; // 包含多个节点的链表(可能为空)
List* so_create_l(); // 创建链表
List* so_delete_l(List*); // 删除链表
int so_insert_back(const int data, List*); // 在链表尾部插入元素
int so_insert_front(const int data, List*); // 在链表头部插入元素
int so_insert_multi_back(List*, const unsigned N, ...); // 一次性在链表尾部插入多个元素
int so_insert_multi_front(List*, const unsigned N, ...); // 一次性在链表头部插入多个元素
int so_insert_pos(const int data, const size_t N, List*); // 在链表指定位置插入元素
int so_print_l(List*, const char* msg); // 打印链表
这里,链表具有元数据head
、tail
和size
属性,并提供了用于整体操作链表的各种函数。
接着,定义一个链表集合Set
结构:
// Set
typedef struct
{
size_t size;
size_t limit;
List** L;
} Set; // 链表集合(可能为空)
Set* so_create_s(size_t size); // 创建链表集合
Set* so_delete_s(Set*); // 删除链表集合
int so_insert_s(List*, Set*); // 向集合中插入链表
在这个例子中,Set
只是一个指向List
指针的数组,可以替代原始代码中的list_1
、list_2
和list_3
。
测试用main
函数如下:
int main(void)
{
List* x3 = so_create_l();
so_insert_multi_back(x3, 6, 3, 6, 9, 12, 15, 18);
List* x5 = so_create_l();
so_insert_multi_back(x5, 4, 5, 10, 15, 20);
List* x7 = so_create_l();
so_insert_multi_back(x7, 6, 7, 14, 21, 28, 35, 42);
Set* S = so_create_s(20);
so_insert_s(x3, S);
so_insert_s(x5, S);
so_insert_s(x7, S);
so_insert_s(x7, S); // 尝试再次插入x7
printf("\t%llu/%llu lists in set\n\n", S->size, S->limit);
so_print_l(x7, "[Last list] ");
so_print_l(S->L[S->size-1], "[Last list, from set] ");
so_print_l(x5, "[x5] ");
so_print_l(x3, "[x3] ");
S = so_delete_s(S);
return 0;
}
此程序:
- 构建三个链表
- 将它们插入到集合中
- 使用集合打印这些链表
- 最后释放所有资源
通过这种方式构建链表,可以简化为:
List* x7 = so_create_l();
so_insert_multi_back(x7, 6, 7, 14, 21, 28, 35, 42);
将链表x7
插入集合的操作如下:
Set* S = so_create_s(20);
so_insert_s(x7, S);
打印链表则调用:
so_print_l(x7, "[Last list] ");
so_print_l(S->L[S->size-1], "[Last list, from set] ");
此处,我们看到可选的msg
参数带来的便利性。
示例输出如下:
[Last list] #6 elements in list.
head element: 7
tail element: 42
7 14 21 28 35 42
[-----]
[Last list, from set] #6 elements in list.
head element: 7
tail element: 42
7 14 21 28 35 42
[-----]
以下是完整输出结果:
创建包含20个链表的集合
3/20个链表在集合中
[Last list] #6个元素的链表
头元素: 7
尾元素: 42
7 14 21 28 35 42
[-----]
[从集合获取的Last list] #6个元素的链表
头元素: 7
尾元素: 42
7 14 21 28 35 42
[-----]
[x5] #4个元素的链表
头元素: 5
尾元素: 20
5 10 15 20
[-----]
[x3] #6个元素的链表
头元素: 3
尾元素: 18
3 6 9 12 15 18
[-----]
销毁集合(3/20个链表)
下面是示例的完整代码:
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
// 节点结构体
typedef struct node_
{
int data;
struct node_* next;
} Node; // 一个节点
int so_print_n(Node*, const char* msg);
// 链表结构体
typedef struct
{
size_t size;
Node* tail;
Node* head;
} List; // 一个可能为空的节点链表
List* so_create_l();
List* so_delete_l(List*);
int so_insert_back(const int data, List*);
int so_insert_front(const int data, List*);
int so_insert_multi_back(List*, const unsigned N, ...);
int so_insert_multi_front(List*, const unsigned N, ...);
int so_insert_pos(const int data, const size_t N, List*);
int so_print_l(List*, const char* msg);
// 链表集合结构体
typedef struct
{
size_t size;
size_t limit;
List** L;
} Set; // 一个可能为空的链表集合
Set* so_create_s(size_t size);
Set* so_delete_s(Set*);
int so_insert_s(List*, Set*);
int main(void)
{
// ... 主函数代码 ...
}
// ... 节点相关函数代码 ...
// ... 链表相关函数代码 ...
// ... 链表集合相关函数代码 ...
// 注意:Set实际上可以通过修改Node结构体实现为另一种链表
// 例如:
// 节点结构体
// typedef struct node_
// {
// void* data; // 指向某种数据
// struct node_* next;
// } Node; // 一个节点
//
// 然后利用C语言中void*的强大功能编写针对Node的操作函数。
// 在Java、C++或Python等语言中,通常将链表用作容器,存储某种类型的数据。
以上代码实现了链表、链表集合以及相关的增删查等功能。注意,在某些高级语言中,链表通常作为某种数据的容器,类似于这里的实现方式。而在本示例中,通过适当修改Node结构体和相关函数,可以将Set实现为另一个包含不同数据类型的链表。