基本数据结构 -- 链表的遍历、查找、插入和删除
本文将使用 C 语言来实现一个单链表,并实现遍历、查找、插入、删除等操作。
一、创建一个单链表
首先,定义一个存放结点相关信息的结构体,结构体有两个元素,分别是键值和一个指向下一节点的指针。
/* 用于存放结点信息的结构体 */ struct node { int key; struct node *next; }; typedef struct node Node; typedef struct node *PtrToNode;
想要创建一个单链表,可以先创建一个表头结点(哑结点),然后在表头结点后不断插入新的结点即可,需要注意的是,每新建一个结点都要为该结点分配一段内存空间。
/* 创建一个链表 */ PtrToNode CreateList(int listLen) { int i, keyValue; /* 创建一个表头结点,并为其分配内存空间 */ PtrToNode headPtr = (PtrToNode)malloc(sizeof(Node)); if (headPtr == NULL) { perror("malloc failed!\n"); exit(EXIT_FAILURE); } PtrToNode tailNode = headPtr; // 创建一个表尾结点,并将表头结点赋给尾结点 tailNode->next = NULL; for (i = 0; i < listLen; i++) { /* 创建一个新结点,并为其分配内存空间 */ PtrToNode newNode = (PtrToNode)malloc(sizeof(Node)); if (newNode == NULL) { perror("malloc failed!\n"); exit(EXIT_FAILURE); } printf("请输入第 %d 个结点的键值:", i + 1); scanf_s("%d",&keyValue); // 将 newNode 插入链表尾部 newNode->key = keyValue; // 赋键值 newNode->next = NULL; // next 指针指向 NULL tailNode->next = newNode; // 这里的 tailNode 存放的是上一次循环中创建的 newNode,也就是现在新建结点的前驱结点 // 故而这里是将前驱结点的 next 指针指向当前结点 tailNode = newNode; // 将当前结点赋给 tailNode(实际上,tailNode 就起着一个临时结点的作用) } return headPtr; }
二、遍历一个单链表
/* 遍历链表 */ void TraverseList(PtrToNode List) { PtrToNode ptr = List->next; if (ptr == NULL) { printf("链表为空\n"); } while (ptr != NULL) { printf("%d ", ptr->key); ptr = ptr->next; } }
这段代码根据链表表尾结点的 next 指针指向 NULL 来遍历整个链表。
三、查找一个元素
/* 查找一个元素 */ PtrToNode FindElement(PtrToNode List,int val) { PtrToNode ptr = List->next; if (ptr == NULL) { printf("链表为空\n"); return NULL; } while (ptr != NULL && ptr->key != val) { ptr = ptr->next; } if (ptr != NULL) { printf("找到 %d 了\n", val); } else { printf("没有找到 %d\n", val); } return ptr; }
这段代码查找元素 val 是否在链表中,如果在,则打印元素已找到的信息,并返回该元素在链表中所在的结点;如果不在链表中,则打印没找到的信息,并返回一个空指针。
四、插入一个元素
/* 插入一个元素 */ void InsertElement(PtrToNode List,PtrToNode Position,int val) { PtrToNode tmpNode = (PtrToNode)malloc(sizeof(Node)); if (tmpNode == NULL) { perror("malloc failed!\n"); exit(EXIT_FAILURE); } tmpNode->key = val; tmpNode->next = Position->next; Position->next = tmpNode; }
这段代码将元素 val 插入到链表中指定结点的后面。
五、删除操作
5.1 删除整个链表
/* 删除整个链表 */ void DeleteList(PtrToNode List) { PtrToNode position, tmpNode; position = List->next; List->next = NULL; while (position != NULL) { tmpNode = position->next; // 先将当前结点的 next 指针赋给临时结点保存 free(position); // 然后释放当前结点 position = tmpNode; // 再将以保存的 next 指针赋给 position,即为下一个要删除的结点 } }
删除整个链表时,需要注意一点,要提前将要删除结点的 next 指针保存下来,再释放该结点。而不能在释放了一个结点后再去利用已释放结点的 next 指针去释放下一个结点,因为此时上一个结点已经被释放了,故而找不到 next 指针。此外,由于在创建链表时,每插入一个新的结点都会用 malloc 来给结点分配一块内存,故而在删除链表时,每释放一个结点也应该使用 free 来释放一次内存。
5.2 删除一个元素
/* 删除一个元素 */ void DeleteElement(PtrToNode List,int val) { PtrToNode tmpNode; PtrToNode prev_position = FindPrevNode(List, val); if (prev_position->next == NULL) { printf("要删除的元素不存在!\n"); } else { tmpNode = prev_position->next; prev_position->next = tmpNode->next; free(tmpNode); } } /* 获取元素的前驱结点 */ PtrToNode FindPrevNode(PtrToNode List, int val) { PtrToNode prev_position = List; while (prev_position->next != NULL && prev_position->next->key != val) { prev_position = prev_position->next; } return prev_position; }
删除一个元素时,需要先找到该元素的前驱结点。
参考资料:
《算法导论 第三版》
《数据结构与算法分析——C语言描述》