链表实现增删改查等基本操作
当数据结构是线性结构时,我们可以用顺序表或者是链表实现,上一篇我们用顺序表实现,这一次我们用链表实现。
首先为了便于程序的演示以及说明,我们先定义相应的数据结构如下:
typedef struct ListNode { //链表结点对应的结构体,包含数据域和指向下一个结点的指针域
int data;
struct ListNode *next;
} ListNode;
typedef struct List { //链表结构体,包含链表的虚拟头结点和长度
struct ListNode head; //注意!就是头结点,不是指向头结点的指针
int length;
} List;
注意!结构体List中的头结点head,是为了后续insert和erase操作的方便而引入的,链表中真正的第0号结点是头结点head的下一个结点,也就是指针head.next所指向的结点
一 链表的初始化以及删除
- 初始化链表时,仅需初始化链表list->length = 0, 指向链表第0个结点的指针为NULL
- 删除时,逐个删除链表的每一个结点,最后删除List *list结构体变量
对应代码如下:
List *getLinkList() { //初始化链表
List *list = (List *)malloc(sizeof(List));
list->length = 0;
list->head.next = NULL;
return list;
}
void clear_list(List *list) { //清空链表
ListNode *current_node = list->head.next, *next_node;
while (current_node) {
next_node = current_node->next;
free(current_node); //从第0号结点开始,逐个释放链表结点
current_node = next_node;
}
free(list); //释放链表list
}
二 链表的插入操作
在插入元素时需要提前进行两方面的预判
- 当前链表是否为空?
- 当前插入的位置是否合法?
链表中已有的结点编号范围为: [0,list->length - 1],因此插入结点的下标位置index在 [0, list->length]之间
对应代码如下:
ListNode *getNewNode(int val) { //传入代插入结点数据域的数据,生成待插入结点,返回指向待插入结点的指针
ListNode *node = (ListNode *)malloc(sizeof(ListNode));
node->data = val;
node->next = NULL;
return node;
}
bool insert(List *list, int index, int val) { //链表list第index号位置 插入值为val的结点,成功返回true,否则返回false
if (!list) return false; //若链表为空,即之前初始化失败,返回false
if (index < 0 || index > list->length) return false; //若插入位置非法,返回false
ListNode *current_node = &(list->head); //current_node结点指向虚拟头结点
while (index--) current_node = current_node->next; //current_node后移,直到current_node指向待插入结点的前一个结点
ListNode *node = getNewNode(val); //生成待插入结点,并插入链表
node->next = current_node->next;
current_node->next = node;
list->length += 1; //链表长度加一,插入结点成功
return true;
}
在这里我 们来说明引入虚拟头结点的好处:
- 若我们没有一个虚拟的头结点,假设我拥有的只是一个指向链表第0号结点的current_node指针,此时若我要在index = 3处插入结点,那么current_node指针需要向后移动2步,才会指向代插入结点的前一个结点,也就是说在index处插入结点,则current_node指针需要向后移动index - 1次;而当我拥有头结点时,current_node指针指向头结点,若要在index处插入结点,此时current_node指针需要向后移动index次,不需要减一,操作方便。
- 更重要的是,若插入结点的位置index = 0, 若我们没有头结点,此时需要将这种情况单独拿出来判断,将该结点插入到原第0号结点的前方,并更新head指针;但有头结点后,此时不需要单独判断,同其他情况一样进行操作即可。
三 链表的删除操作
在删除元素时同样需要提前进行两方面的预判
- 当前链表是否为空?
- 当前插入的位置是否合法?
链表中已有的结点编号范围为: [0,list->length - 1],因此删除结点的下标位置也在[0, list->length]之间
对应代码如下
bool erase(List *list, int index) { //删除链表中第index个结点,成功返回true,失败返回false
if (!list) return false; //链表为空,或者插入位置非法,插入失败,返回false
if (index < 0 || index >= list->length) return false;
ListNode *current_node = &(list->head);
while (index--) current_node = current_node->next; //找到待删除结点的前一个结点,并删除待删除结点
ListNode *delete_node = current_node->next;
current_node->next = delete_node->next;
free(delete_node);
list->length -= 1; //链表长度减一,返回true
return true;
}
四 链表的翻转操作
链表的翻转,采用的是头部插入的方法,将原第0个结点取出,之后依次取出当前链表的头结点,并将其next指向翻转链表的头结点,之后翻转链表的头结点前移,如此进行下去,最后修改虚拟头结点,使其head指针指向翻转链表的第0号结点。
对应代码如下:
void reverse_list(List *list) {
if (!list) return ;
ListNode *p = list->head.next, *q;
list->head.next = NULL;
while (p) {
q = p->next;
p->next = list->head.next;
list->head.next = p;
p = q;
}
}
代码原理解释 (之前代码出问题就是翻转这里,下面的描述和代码有些出入,但总体思想是一致的):
五 链表的遍历以及修改操作
遍历和修改操作十分简单,前者直接遍历输出,后者找到对应结点修改数据域的值即可,故此处省略。
六 代码汇总
#include <iostream>
using namespace std;
typedef struct ListNode {
int data;
struct ListNode *next;
} ListNode;
typedef struct List {
struct ListNode head;
int length;
} List;
ListNode *getNewNode(int val) {
ListNode *node = (ListNode *)malloc(sizeof(ListNode));
node->data = val;
node->next = NULL;
return node;
}
List *getList() {
List *list = (List *)malloc(sizeof(List));
list->head.next = NULL;
list->length = 0;
return list;
}
bool insert(List *list, int ind, int val) {
if (!list) return false;
if (ind < 0 || ind > list->length) return false;
ListNode *p = &(list->head), *q = getNewNode(val);
while (ind--) p = p->next;
q->next = p->next;
p->next = q;
list->length++;
return true;
}
bool erase(List *list, int ind) {
if (!list) return false;
if (ind < 0 || ind >= list->length) return false;
ListNode *p = &(list->head);
while (ind--) p = p->next;
ListNode *delete_node = p->next;
p->next = delete_node->next;
free(delete_node);
list->length--;
return true;
}
void reverse_list(List *list) {
if (!list) return ;
ListNode *p = list->head.next, *q;
list->head.next = NULL;
while (p) {
q = p->next;
p->next = list->head.next;
list->head.next = p;
p = q;
}
}
void output(List *list) {
if (!list) return ;
printf("List(%d) = [", list->length);
for (ListNode *p = list->head.next; p; p = p->next) {
if (p - list->head.next) printf("->");
printf("%d", p->data);
}
printf("]\n");
}
void clear_list(List *list) {
if (!list) return ;
ListNode *p = (list->head).next, *delete_node = NULL;
while (p) {
delete_node = p;
p = p->next;
free(delete_node);
}
free(list);
}
int main() {
#define MAX_OP 20
srand(time(0));
List *list = getList();
int val, ind, op;
for (int i = 0; i < MAX_OP; i++) {
val = rand() % 100;
ind = rand() % (list->length + 3) - 1;
op = rand() % 5;
switch (op) {
case 0:
case 1:
case 2: {
printf("insert %d at %d into list = %d\n", val, ind, insert(list, ind, val));
break;
}
case 3: {
printf("reverse_list!\n");
reverse_list(list); break;
}
case 4: {
printf("erase item at %d from list = %d\n", ind, erase(list, ind));
break;
}
default: {
printf("it's impossible\n");
}
}
output(list);
printf("\n");
}
clear_list(list);
return 0;
}
/*代码结果输出:
reverse_list!
List(0) = []
insert 86 at 0 into list = 1
List(1) = [86]
erase item at 2 from list = 0
List(1) = [86]
insert 5 at 2 into list = 0
List(1) = [86]
erase item at 1 from list = 0
List(1) = [86]
reverse_list!
List(1) = [86]
insert 87 at 2 into list = 0
List(1) = [86]
insert 97 at 1 into list = 1
List(2) = [86->97]
erase item at 1 from list = 1
List(1) = [86]
insert 32 at -1 into list = 0
List(1) = [86]
insert 3 at -1 into list = 0
List(1) = [86]
insert 81 at 1 into list = 1
List(2) = [86->81]
insert 94 at 2 into list = 1
List(3) = [86->81->94]
insert 5 at 2 into list = 1
List(4) = [86->81->5->94]
insert 97 at 4 into list = 1
List(5) = [86->81->5->94->97]
insert 99 at 5 into list = 1
List(6) = [86->81->5->94->97->99]
insert 79 at 3 into list = 1
List(7) = [86->81->5->79->94->97->99]
insert 7 at 8 into list = 0
List(7) = [86->81->5->79->94->97->99]
reverse_list!
List(7) = [99->97->94->79->5->81->86]
insert 92 at 0 into list = 1
List(8) = [92->99->97->94->79->5->81->86]
*/
很奇怪,有时候代码运行会出段错误,有时候又没有,代码应该哪里还是有缺陷,自己还需要在改改!!(代码已修改,bug已经解决了,大家可以放心使用,代码重新敲了一遍,可能和说明有些出入,但不影响)