文章目录
数据结构学习——单链表的创建及其基本操作
什么是链表?
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。
——百度百科
链表就是一种链式存储的线性表,它由一系列节点组成,每个节点包含数据部分和指向下一个节点的指针。
单链表的创建
节点定义
在C语言中,链表节点通常通过结构体来定义,结构体包含至少两个部分:数据域和指针域。数据域用于存储数据,而指针域用于链接到下一个节点。节点的内存空间通常通过
malloc
函数动态分配。typedef int ElemType;//定义数据类型 Element Type 元素 类型 typedef struct node { ElemType data;//数据域 struct node* next;//指针域 }Node;//Node 节点
初始化单链表
初始化单链表通常涉及创建一个头节点,并将其指针域设置为NULL,表示链表为空。
Node* initList() { Node* head = (Node*)malloc(sizeof(Node));//分配头节点内存 head->data = 0;//将头节点数据域设置为0 head->next = NULL;//指针域设置为NULL return head;//返回创建的头节点 }//初始化单链表,返回头节点 Initialize 初始化
代码如下,现在一个单链表就创建好了,但它现在只是一个空表,没有存储任何数据
#include <stdlib.h> typedef int ElemType; typedef struct Node { ElemType data; struct Node* next; }Node; Node* initList() { Node* head = (Node*)malloc(sizeof(Node)); head->data = 0; head->next = NULL; return head; } int main() { Node* head = initList(); return 0; }
单链表的基本操作
遍历单链表
方便检查链表存储情况,该操作将链表存储数据依次输出
void listNode(Node* head) { Node* p = head->next; while (p != NULL) { printf("%d ", p->data); p = p->next; } printf("\n"); }
单链表的增加操作
链表的增加操作可以在链表的头部、尾部或指定位置插入新节点。头插法会在链表的头部插入新节点,尾插法会在链表的尾部插入新节点,而指定位置插入则需要先找到目标位置的前一个节点,然后插入新节点。
头插法
头插法是将新节点插入到链表的前端,即头节点之后。这种方法创建的单链表中节点的顺序与输入的顺序相反。具体操作是先让新节点指向头节点的下一个节点,再让头节点指向新节点,**顺序很重要!!!**如果先让头节点指向新节点,链表就断了。
void insertHead(Node* head, ElemType e) { Node* p = (Node*)malloc(sizeof(Node));//创建新节点 p->data = e;//赋值 p->next = head->next;//新节点指向头节点的下一个节点 head->next = p;//头节点指向新节点 }
代码如下:
#include <stdio.h> #include <stdlib.h> typedef int ElemType; typedef struct Node { ElemType data; struct Node* next; }Node; Node* initList() { Node* head = (Node*)malloc(sizeof(Node)); head->data = 0; head->next = NULL; return head; } void listNode(Node* head) { Node* p = head->next; while (p != NULL) { printf("%d ", p->data); p = p->next; } printf("\n"); } void insertHead(Node* head, ElemType e) { Node* newNode = (Node*)malloc(sizeof(Node)); newNode->data = e; newNode->next = head->next; head->next = p; } int main() { Node* head = initList(); insertHead(head, 1); insertHead(head, 2); insertHead(head, 3); insertHead(head, 4); insertHead(head, 5); listNode(head); return 0; }
输出结果:
5 4 3 2 1
尾插法
尾插法是将新节点插入到链表的末尾。这种方法创建的单链表中节点的顺序与输入的顺序一致。具体操作是先找到尾节点的地址,让尾节点指向新节点,新节点指向NULL,现在新节点就变成了尾节点,返回新的尾节点。
Node* getTail(Node* head) { Node* p = head; while (p->next != NULL) { p = p->next; }//遍历链表,直到最后一个节点 return p;//返回尾节点 } Node* insertTail(Node* tail, ElemType e) { Node* newNode = (Node*)malloc(sizeof(Node));//创建新节点 newNode->data = e;//赋值 tail->next = newNode;//尾节点指向新节点 newNode->next = NULL;//新节点指向空 return newNode;//返回新节点 }
代码如下:
#include <stdio.h> #include <stdlib.h> typedef int ElemType; typedef struct Node { ElemType data; struct Node* next; }Node; Node* initList() { Node* head = (Node*)malloc(sizeof(Node)); head->data = 0; head->next = NULL; return head; } void listNode(Node* head) { Node* p = head->next; while (p != NULL) { printf("%d ", p->data); p = p->next; } printf("\n"); } Node* getTail(Node* head) { Node* p = head; while (p->next != NULL) { p = p->next; } return p; } Node* insertTail(Node* tail, ElemType e) { Node* newNode = (Node*)malloc(sizeof(Node)); newNode->data = e; tail->next = newNode; newNode->next = NULL; return newNode; } int main() { Node* head = initList(); Node* tail = getTail(head); tail = insertTail(tail, 1);//尾插法并更新尾节点 tail = insertTail(tail, 2); tail = insertTail(tail, 3); tail = insertTail(tail, 4); tail = insertTail(tail, 5); listNode(head); return 0; }
输出结果:
1 2 3 4 5
向指定位置插入
要向指定位置插入,首先要找到指定位置的前驱节点,即指定位置的前一个节点,先让新节点指向前驱节点的下一个节点,再让前驱节点指向新节点,顺序很重要!!!
void insertPosition(Node* head, int pos, ElemType e) { Node* p = head;//用来保存插入位置的前驱节点 //遍历链表,找到插入位置的前驱节点 for (int i = 0; i < pos - 1; i++) { p = p->next; if (p == NULL) { printf("插入位置错误\n"); return; } } Node* newNode = (Node*)malloc(sizeof(Node));//创建新节点 newNode->data = e;//给新节点赋值 newNode->next = p->next;//让新节点指向插入位置的节点 p->next = newNode;//让插入位置的前驱节点指向新节点 }
代码如下:
#include <stdio.h> #include <stdlib.h> typedef int ElemType; typedef struct Node { ElemType data; struct Node* next; }Node; Node* initList() { Node* head = (Node*)malloc(sizeof(Node)); head->data = 0; head->next = NULL; return head; } void listNode(Node* head) { Node* p = head->next; while (p != NULL) { printf("%d ", p->data); p = p->next; } printf("\n"); } Node* getTail(Node* head) { Node* p = head; while (p->next != NULL) { p = p->next; } return p; } Node* insertTail(Node* tail, ElemType e) { Node* newNode = (Node*)malloc(sizeof(Node)); newNode->data = e; tail->next = newNode; newNode->next = NULL; return newNode; } void insertPosition(Node* head, int pos, ElemType e) { Node* p = head; for (int i = 0; i < pos - 1; i++) { p = p->next; if (p == NULL) { printf("插入位置错误\n"); return; } } Node* newNode = (Node*)malloc(sizeof(Node)); newNode->data = e; newNode->next = p->next; p->next = newNode; } int main() { Node* head = initList(); Node* tail = getTail(head); tail = insertTail(tail, 1); tail = insertTail(tail, 2); tail = insertTail(tail, 3); tail = insertTail(tail, 4); tail = insertTail(tail, 5); insertPosition(head, 3, 30);//把30插入在链表的第三个位置 listNode(head); return 0; }
输出结果:
1 2 30 3 4 5
单链表的删除操作
删除节点时,需要先找到待删除节点的前驱节点,然后调整指针,最后释放节点内存。
具体操作如下:
- 找到要删除节点的前驱节点p
- 用指针q记录要删除的节点
- 通过改变p的后继节点实现删除
- 释放删除节点的空间
void deleteNode(Node* head, int pos) { Node* p = head;//用来保存删除位置的前驱节点 //遍历链表,找到删除位置的前驱节点 for (int i = 0; i < pos - 1; i++) { p = p->next; if (p == NULL) { printf("删除位置错误\n"); return; } } //判断删除位置是否为空 if (p->next == NULL) { printf("删除位置错误\n"); return; } Node* q = p->next;//q是要删除的节点 p->next = q->next;//让要删除节点的前驱指向要删除节点的后继 free(q);//释放删除节点的内存空间 }
代码如下:
#include <stdio.h> #include <stdlib.h> typedef int ElemType; typedef struct Node { ElemType data; struct Node* next; }Node; Node* initList() { Node* head = (Node*)malloc(sizeof(Node)); head->data = 0; head->next = NULL; return head; } void listNode(Node* head) { Node* p = head->next; while (p != NULL) { printf("%d ", p->data); p = p->next; } printf("\n"); } Node* getTail(Node* head) { Node* p = head; while (p->next != NULL) { p = p->next; } return p; } Node* insertTail(Node* tail, ElemType e) { Node* newNode = (Node*)malloc(sizeof(Node)); newNode->data = e; tail->next = newNode; newNode->next = NULL; return newNode; } void insertPosition(Node* head, int pos, ElemType e) { Node* p = head; for (int i = 0; i < pos - 1; i++) { p = p->next; if (p == NULL) { printf("插入位置错误\n"); return; } } Node* newNode = (Node*)malloc(sizeof(Node)); newNode->data = e; newNode->next = p->next; p->next = newNode; } void deleteNode(Node* head, int pos) { Node* p = head; for (int i = 0; i < pos - 1; i++) { p = p->next; if (p == NULL) { printf("删除位置错误\n"); return; } } if (p->next == NULL) { printf("删除位置错误\n"); return; } Node* q = p->next; p->next = q->next; free(q); } int main() { Node* head = initList(); Node* tail = getTail(head); tail = insertTail(tail, 1); tail = insertTail(tail, 2); tail = insertTail(tail, 3); tail = insertTail(tail, 4); tail = insertTail(tail, 5); insertPosition(head, 3, 30); deleteNode(head, 3);//删除链表第三个节点 listNode(head); return 0; }
输出结果:
1 2 3 4 5
单链表的修改操作
修改节点数据需要先找到该节点,然后更新其数据部分。
void modifyNode(Node* head, int pos, ElemType new_data) { Node* p = head; //遍历链表,找到要修改的节点 for (int i = 0; i < pos; i++) { p = p->next; if (p == NULL) { printf("修改位置错误\n"); return; } } p->data = new_data; }
代码如下:
#include <stdio.h> #include <stdlib.h> typedef int ElemType; typedef struct Node { ElemType data; struct Node* next; }Node; Node* initList() { Node* head = (Node*)malloc(sizeof(Node)); head->data = 0; head->next = NULL; return head; } void listNode(Node* head) { Node* p = head->next; while (p != NULL) { printf("%d ", p->data); p = p->next; } printf("\n"); } Node* getTail(Node* head) { Node* p = head; while (p->next != NULL) { p = p->next; } return p; } Node* insertTail(Node* tail, ElemType e) { Node* newNode = (Node*)malloc(sizeof(Node)); newNode->data = e; tail->next = newNode; newNode->next = NULL; return newNode; } void modifyNode(Node* head, int pos, ElemType new_data) { Node* p = head; for (int i = 0; i < pos; i++) { p = p->next; if (p == NULL) { printf("修改位置错误\n"); return; } } p->data = new_data; } int main() { Node* head = initList(); Node* tail = getTail(head); tail = insertTail(tail, 1); tail = insertTail(tail, 1); tail = insertTail(tail, 1); tail = insertTail(tail, 1); tail = insertTail(tail, 1); modifyNode(head, 3, 3);//将第三个节点的数据修改为3 listNode(head); return 0; }
输出结果:
1 1 3 1 1
单链表的查找操作
查找节点涉及到遍历链表并比较节点数据,找到匹配的节点后返回该节点。
Node* findNode(Node* head, ElemType data) { Node* p = head->next; //遍历链表,找到包含指定数据的节点 while (p != NULL) { if (p->data == data) { return p;//返回找到的节点 } p = p->next; } printf("链表中不存在该值\n"); return NULL;//未找到,返回NULL }
获取链表长度
遍历链表,计算链表长度
int listLenth(Node* head) { Node* p = head->next; int len = 0; while (p != NULL) { len++; p = p->next; } return len; }
单链表的释放
单链表的释放是将头节点以外的节点释放掉,具体操作如下:
- 指针p指向头节点后的第一个节点
- 判断指针p是否为空
- 如果不为空,用指针q记录指针p的后继节点
- 释放指针p指向的空间
- 指针p和指针q指向同一个节点,循环上面的操作
void freeList(Node* head) { Node* p = head->next; Node* q; while (p != NULL) { q = p->next; free(p); p = q; } head->next = NULL; }