目录
- 如何构造链表
- 如何遍历链表
- 插入链表节点
- 删除链表节点
一、如何构造链表
1、基本概念
- 链:各结点之间以指针(指出元素地址)相连的数据结构;
- 两个域是广义的,数据域可以有多个数据项,就职能、功能而言,仅此2个。;
- 存储单元可是连续的,也可是不连续的,由指针负责“联系”;
- 线性链表 链表的每个结点中只包含一个指针域,又称单链表 ;
- 头指针:第一个数据元素的存储地址作为线性表的基地址;
- 链头结点:无data值,指向表首元;
- 链尾结点:无next值,链为空
2、链表节点的结构
在C++中可以利用结构体表示数据域和指针域,具体代码如下所示:
struct ListNode {
int data; //代表数据
struct ListNode *next; //代表指针
};
3、创建链表——尾插法
尾插法顾名思义,即在链表尾部进行节点的创建,为最常用的一种方法。具体而言,每次创建一个新的结构体,为其赋值后,由上一节点的指针指向新的节点即可完成创建,具体代码如下所示:
struct ListNode* initLink() {
int i;
//创建头指针
struct ListNode* p = NULL;
//创建头节点
struct ListNode* head = (struct ListNode* )malloc(sizeof(struct ListNode));
//创建中间节点
struct ListNode* temp = NULL;
head -> val = 0;
head -> next = NULL;
p = head;
temp = head;
for (i = 1; i < 10; i++){
//创建新节点
struct ListNode* newNode = (struct ListNode* )malloc(sizeof(struct ListNode));
newNode -> val = i;
newNode -> next = NULL;
temp -> next = newNode; //利用中间节点完成链表的逐步更新创建
temp = temp -> next;
}
return p;
}
注意一些易错点:
- 不要直接用head进行操作,而应用其他的指针代替完成遍历链表的任务,防止链表丢失。
- 尾插法新节点的指针域注意为NULL。
4、创建链表——头插法
头插法与尾插法相似,即在链表头部(每次在头节点后插入)进行节点的创建,在特殊的题目条件下有奇效。具体而言,每次创建一个新的结构体,为其赋值后,由新节点的指针指向head的next所指向的节点,然后记得将新节点赋值给head的next即可,从而确保head始终指向头节点,即可完成创建,具体代码大部分与上述尾插法相同,核心代码如下所示:
for (i = 1; i < 10; i++){
struct ListNode* newNode = (struct ListNode* )malloc(sizeof(struct ListNode));
newNode -> val = i;
newNode -> next = head -> next;
head -> next = newNode;
}
此时运行代码可以发现,头插法与尾插法生成的列表数字序列正好相反。
二、如何遍历链表
注意使用中间节点进行遍历,不要直接对head进行操作。
同时,注意以NULL为循环的终止条件,具体代码如下:
void printList(struct ListNode* p) {
struct ListNode* temp = p;
while (temp){
printf("%d ", temp -> val);
temp = temp -> next;
}
printf("\n");
}
int32_t getLength(struct ListNode* p) {
struct ListNode* temp = p;
int len = 0;
while (temp){
len++;
temp = temp -> next;
}
return len;
}
需要注意的是getLength方法在之后的插入和删除中是必须的
三、插入链表节点
1、在第一个位置插入节点
此情况较为简单,将新节点的next指向head节点即可,记得最后要将head指向新节点,从而保证head节点始终指向的是链表的第一个节点。
2、在最后一个位置插入节点
同样,让原链表中最后一个节点的next指向新节点即可,记得新节点的next设置NULL,这是遍历链接循环中止的重要条件。
3、在链表中间插入节点
此情况与上述的头插法类似,需要找到插入位置的前一个链表节点,同时注意代码执行顺序不可轻易调换,如图所示:
此上图演示为正确的插入代码执行顺序
此上图演示为错误的插入代码执行顺序,将导致链表断裂;
整体的实现代码如下所示:
struct ListNode* insertNode(struct ListNode* head, struct ListNode* nodeInsert, int position) {
if (head == NULL)
{
return nodeInsert;
}
if (position == 1)
{
nodeInsert -> next = head;
head = nodeInsert;
}
int len = getLength(head);
int count = 1;
if (position > len + 1 || position < 1)
{
printf("位置参数越界");
return head;
}
struct ListNode* temp;
temp = head;
while (count < position - 1)
{
temp = temp -> next;
count++;
}
nodeInsert -> next = temp -> next;
temp -> next = nodeInsert;
return head;
}
需要注意的是位置参数越界的情况,插入是可以在当前链表长度加一的位置插入的,而删除则不行。还要注意对于空链表的插入情况,可以报错,也可以插入。
四、删除链表节点
1、删除第一个位置的节点
删除第一个位置的节点,只需要让head指向其下一个节点即可。
2、删除最后一个位置的节点
找到最后一个位置节点的前一个节点,让其next赋值为NULL即可。
3、删除中间位置的节点
找到需要删除位置的前一个节点,让这前一个节点的next指向删除节点的后一个节点即可,如图所示:
整体实现代码如下:
struct ListNode* deleteNode(struct ListNode*head, int position) {
if (head == NULL)
{
return head;
}
int len = getLength(head);
if (position > len || position < 1)
{
printf("位置参数错误");
return head;
}
if (position == 1)
{
struct ListNode* temp = head;
head = head -> next;
free(temp);
return head;
}
int count = 1;
struct ListNode* cur = head;
while (count < position - 1)
{
cur = cur -> next;
count++;
}
struct ListNode* pNode = cur -> next;
cur -> next = pNode -> next;
free(pNode);
return head;
}
需要注意的依然是位置参数错误的判断,此处的边界条件为链表长度,而非插入时的链表加一。还需要注意利用free()系统调用函数,释放内存,C++没有垃圾回收机制,需要手动释放,即要考虑到用新的指针代替完成free()操作。
至此,链表入门从创建到增删改查,结束。
学号:15 昵称:秋容何暮