链表实现增删改查等基本操作

链表实现增删改查等基本操作

当数据结构是线性结构时,我们可以用顺序表或者是链表实现,上一篇我们用顺序表实现,这一次我们用链表实现。

首先为了便于程序的演示以及说明,我们先定义相应的数据结构如下:

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所指向的结点

一 链表的初始化以及删除

  1. 初始化链表时,仅需初始化链表list->length = 0, 指向链表第0个结点的指针为NULL
  2. 删除时,逐个删除链表的每一个结点,最后删除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
}

二 链表的插入操作

在插入元素时需要提前进行两方面的预判

  1. 当前链表是否为空?
  2. 当前插入的位置是否合法?
    链表中已有的结点编号范围为: [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;
}

在这里我 们来说明引入虚拟头结点的好处:

  1. 若我们没有一个虚拟的头结点,假设我拥有的只是一个指向链表第0号结点的current_node指针,此时若我要在index = 3处插入结点,那么current_node指针需要向后移动2步,才会指向代插入结点的前一个结点,也就是说在index处插入结点,则current_node指针需要向后移动index - 1次;而当我拥有头结点时,current_node指针指向头结点,若要在index处插入结点,此时current_node指针需要向后移动index次,不需要减一,操作方便。
  2. 更重要的是,若插入结点的位置index = 0, 若我们没有头结点,此时需要将这种情况单独拿出来判断,将该结点插入到原第0号结点的前方,并更新head指针;但有头结点后,此时不需要单独判断,同其他情况一样进行操作即可。

三 链表的删除操作

在删除元素时同样需要提前进行两方面的预判

  1. 当前链表是否为空?
  2. 当前插入的位置是否合法?
    链表中已有的结点编号范围为: [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已经解决了,大家可以放心使用,代码重新敲了一遍,可能和说明有些出入,但不影响)

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值