无头节点的单链表的简单、复杂操作。
声明
本文针对的是无头结点的单链表, 而下文所出现的头结点所表示的指的是单链表的第一个结点。文中可能出现语句不当的情况,但绝不会影响读者对本文的理解,请以宽容、乐观的心态放心地阅读本文。文中可能会出现一些错误,在此提前向读者致以歉意,并欢迎在评论区留言指正!
说在前面
单链表比起双链表,其复杂性较低,减少了从后向前的指针变量,只有一个从前往后的指针变量,导致单链表无法像双链表一样从后往前遍历。因此数据的存储的优势就会明显大于数据的访问,尤其是链表的头尾端。
首先介绍一下单链表的节点内部结构:
typedef struct node {
int data;
struct node * next;
}node, list_node;
我们创建少许节点,并将我们的数据存储在这些节点中:
node node_1;
node node_2;
node node_3;
node_1.data = 100;
node_2.data = 2000;
node_3.data = 30000;
这些个结构体对象里面存放了整型的数据,但还只是一个个孤零零的节点,当然你也可以认为它们是只含有一个节点的单链表。我们如何将这些节点串起来形成一个单链表呢?请往下看:
node_1.next = &(node_2);
node_2.next = &(node_3);
node_3.next = NULL;
我们每次将后一节点的地址赋给当前节点的next指针变量。如此一来,当前节点的信息除了存储的整型数据,在data中;还有下一节点的地址,在next中。通常情况下,我们会让尾节点,即链表的最后一个节点指向NULL,以防止尾节点随机指向不安全的区域。
链表基本操作
接口是否够完善,和对链表的操作是否周全有很大关系,增删改查自是不能少了,但还有一些其他的操作也是经常需要用到的。
0、链表之前
1、创建节点
2、链接节点
3、遍历链表
4、获取索引节点数据
5、头插法
6、尾插法
7、按索引插入
8、按索引删除
9、清空链表
10、销毁链表
从基本操作到复杂操作,极大的展现链表的功能。
0、链表之前
typedef struct list {
int data;
struct list * next;
}list, link_list;
1、创建结点
如前所述,我们将节点的创建分成了两步:声明结构体对象,对结构体对象的成员变量进行赋值。在这里我们将这种过程封装成函数,以方便使用。函数如下:
list * create_list(int value) {
list * node = NULL;
node = (list *)malloc(sizeof(list));
if (node == NULL) return NULL;
node->data = value;
node->next = NULL;
return node;
}
我们为节点申请了动态空间,并对节点进行了赋值,包括整形类型和指针类型。指针变量将用于指向其它节点以形成链表,而此处赋值为空是防止指针指向非法空间。最后将创建好的节点作为函数的返回值,而之所以返回为指针类型,是因为如前文所述,我们需要取下一节点的指针并赋给当前节点的指针成员变量,使用指针类型就省去了取地址这一过程。到此,节点创建完毕。
2、链接节点
取链表之链,链接节点,将创建好的节点连接起来形成链表。我们使用函数的方式实现下一节点的地址赋给当前节点的指针成员变量,如下:
int connect_list(list * cur_node, list * next_node) {
if (cur_node == NULL) return -1;
cur_node->next = next_node;
return 0;
}
该函数不能一次性地将两个以上的节点链接成链表,每次只能链接两个,故需要n-1次函数调用才能将所有的节点链接成链表,其中n为节点个数。
3、遍历链表
int print_list(list * link) {
if (link == NULL) return -1;
while (link) {
printf("%d\t", link->data);
link = link->next;
}
printf("\n");
return 0;
}
不用多做解释,就是从头到尾打印整个链表。
4、获取索引节点数据
int index_print_list(list * link, int index) {
if (link == NULL) return -1;
while (index && link) {
index--;
link = link->next;
}
if (index) return -3;
printf("%d\n", link->data);
return 0;
}
如果链表为空,则返回错误码-1;如果索引值超过链表长度,则返回错误码-3。
5、头插法
很重要的一点需要注意:C语言里面没有引用概念,故函数形参如果直接使用链表的第一个节点的指针变量,则该变量仅在函数作用域中有效,一旦函数调用完,形参即被销毁,对应的实参不会有任何变化。如下面这个函数:
int head_insert_list(list * link, int value) {
if (link == NULL) return -1;
list * node = NULL;
node = (list *)malloc(sizeof(list));
if (node == NULL) return -2;
node->data = value;
node->next = link;
return 0;
}
但是函数并没有起作用,与双向链表不同,我们无法得到当前节点的前一个节点的指针,