单链表操作的C++/C实现详解
在数据结构中,单链表是一种非常基础且重要的数据结构。它由一系列节点组成,每个节点包含数据和指向下一个节点的指针。今天我们就来深入探讨用C/C++实现的单链表及其各种操作。
一、单链表的定义
const int N = 1e5;
//单链表
typedef struct node {int val; //当前链表值struct node* next; //链表后面的值域(地址)
}lst;
这里定义了一个单链表节点的结构体lst
。val
用于存储节点的数据,next
是一个指针,指向链表中的下一个节点。通过这种方式,节点之间可以串联起来形成链表。const int N = 1e5;
定义了一个常量N
,虽然在当前代码中没有直接使用,但可以作为链表最大长度的一个限制,在实际应用中可能会用于内存分配或边界检查等。
二、链表初始化
//链表初始化
lst* initlst()
{lst* head;head = (lst*)malloc(sizeof (lst));//开辟空间head->next = NULL; //指向空return head;
}
initlst
函数用于初始化一个单链表。首先使用malloc
函数为头节点分配内存空间,然后将头节点的next
指针设置为NULL
,表示这是一个空链表。最后返回头节点指针,后续对链表的操作都可以通过这个头节点来进行。
三、打印链表
//打印链表
void printlst(lst*l)
{lst* p = l; //找到头指针while (p!= NULL){if (p->val!= NULL)cout << p->val << endl; //头指针不空p = p->next; //一直取出next}
}
printlst
函数用于打印链表中的所有节点值。通过一个临时指针p
从链表的头节点开始遍历,只要p
不为NULL
,就表示还没有到达链表末尾。在循环中,检查当前节点的val
值是否有效(这里if (p->val!= NULL)
判断有误,val
是int
类型,不能与NULL
比较,应直接输出p->val
),然后输出当前节点的值,并将p
移动到下一个节点。
四、尾插法相关操作
(一)单个节点尾插
void insert2(lst* l, int x) //尾插法
{lst* s, * last;last = l;while (last->next!= NULL) //找到最后的值{last = last->next;}s = (lst*)malloc(sizeof(lst));s->val = x;s->next = NULL;last->next = s;last = s;puts("完成插入");
}
insert2
函数实现了将一个新节点尾插到链表中。首先找到链表的最后一个节点(通过遍历到last->next
为NULL
),然后为新节点分配内存,设置新节点的值和next
指针(next
为NULL
表示这是链表的最后一个节点),最后将新节点连接到链表的末尾,并更新last
指针。
(二)多个节点尾插初始化
void initinsert2(lst *l, int n) {//尾插法の初始化lst* s, * last;last = l;puts("请输入");for (int i = 1; i <= n; i++) {int x;cin >> x;s = (lst*)malloc(sizeof(lst));s->val = x;s->next = NULL;last->next = s;last = s;}puts("完成插入");
}
initinsert2
函数用于通过尾插法初始化链表,一次性插入n
个节点。它与insert2
函数的原理类似,通过循环多次执行尾插操作,每次从用户输入中获取一个值,创建新节点并插入到链表末尾。
五、头插法相关操作
(一)单个节点头插
void insert1(lst* &l, int x) //头插法[记得要取地址]
{//1.c++:lst *&l//2.c:lst** llst* s;s = (lst*)malloc(sizeof(lst));s->val = x;s->next = l;l = s;
}
insert1
函数实现了头插法,将一个新节点插入到链表的头部。这里lst* &l
表示传递的是头指针的引用,这样在函数内部对l
的修改会直接影响到函数外部的头指针。首先为新节点分配内存,设置新节点的值,然后将新节点的next
指针指向原来的头节点,最后更新头指针l
,使其指向新插入的节点。需要注意的是这里要用地址,方便更新头结点
(二)多个节点头插初始化
void initinsert1(lst* l, int n) {//头插法の初始化lst* s;puts("请输入");for (int i = 1; i <= n; i++) {int x;cin >> x;s = (lst*)malloc(sizeof(lst));s->val = x;s->next = l->next;l->next = s;//l = s;}puts("完成插入");
}
六、求链表长度
//求长度
int get_len(lst* l)
{lst* p = l->next;int len = 0;while (p!= NULL){p = p->next;len++;}return len;
}
get_len
函数用于计算链表的长度(不包括头节点)。通过一个临时指针p
从链表头节点的下一个节点开始遍历,每遍历一个节点,长度计数器len
就加1,直到遍历完整个链表(p
为NULL
),最后返回链表的长度。
七、查找操作
(一)按值查找位置
//查找
void find_idex(lst* l, int x) //找x的位置
{lst* p = l->next;int id = 1;while (p!= NULL && p->val!= x){p = p->next;id++;}if (p!= NULL) cout << "位置=" << id << endl;
}
find_idex
函数用于查找值为x
的节点在链表中的位置(从1开始计数)。从链表的第一个数据节点开始遍历,同时记录当前节点的位置id
,如果找到值为x
的节点,就输出其位置;如果遍历完链表都没有找到,则不输出任何信息。
(二)按位置查找值
void find_val(lst* l, int x) //找x的位置上的值
{lst* p = l->next;int j = 1;while (p!= NULL && j < x){p = p->next;j++;}if (x == j) cout << "值=" << p->val << endl;
}
find_val
函数用于查找链表中第x
个位置的节点的值。同样从链表的第一个数据节点开始遍历,用j
记录当前遍历到的节点位置,当遍历到第x
个节点时,输出该节点的值。
八、插入操作
//插入
void insert(lst* l, int i, int x) //找i插入x
{lst* p, * s;int j = 0;while (p->next!= NULL && j < i - 1){p = p->next;j++;}if (p!= NULL){s = (lst*)malloc(sizeof(lst));s->val = x;s->next = p->next;p->next = s;}
}
insert
函数用于在链表的第i
个位置插入值为x
的节点。首先找到第i - 1
个节点(通过遍历并计数j
),然后为新节点分配内存,设置新节点的值和next
指针,使其插入到第i - 1
个节点和第i
个节点之间。需要注意的是,这里代码存在问题,while (p->next!= NULL && j < i - 1)
中p
未初始化,应在循环前将p
初始化为l
。
九、删除操作
//删除
void insert(lst* l, int i)
{lst* p, * s;int j = 0;while (p->next!= NULL && j < i - 1){p = p->next;j++;}if (p!= NULL&&j == i - 1) //删除的位置要在i之前{s = p->next; //这个是要删的地址p->next = s->next;free(s); //释放空间}
}
这里insert
函数名错误,应该改为deleteNode
之类的名字。此函数用于删除链表中第i
个节点。先找到第i - 1
个节点,然后将第i
个节点从链表中移除(通过修改指针),并释放第i
个节点的内存空间。同样,这里也存在p
未初始化的问题,应在循环前将p
初始化为l
。
十、总结
通过以上对单链表各种操作的实现,我们可以看到单链表在数据存储和操作上的灵活性。虽然代码中存在一些小问题,如类型判断错误、指针未初始化等,但通过理解这些操作的原理和逻辑,我们可以更好地掌握单链表这一数据结构,并且能够根据实际需求进行优化和改进。在实际应用中,单链表常用于实现栈、队列等数据结构,以及在需要动态分配内存和频繁插入删除操作的场景中发挥重要作用。