数据结构——C语言实现链表篇 稳扎稳打,步步为营!

一 链表的定义

链表是一种物理存储单元上,非连续·非线性的存储结构,数据元素的逻辑顺序是通过链表中的指针链接依次实现的

一条链表可以看作是由若干个节点连接而成的 对于每个节点我们可以把它划分为数据区和指针区 其中数据区用来表示此节点存放的数据,而指针区用来存放下一个节点的地址

在这里插入图片描述
二 链表的管理节点

类似于线性表中的管理节点,为了对整个结构进行管理,我们又在链表中引入了管理节点的概念。其中管理节点的功能包括记录第一个节点的位置,以及目前我们总共有多少个节点

三 链表的插入操作

在这里插入图片描述

当我们需要对链表进行插入操作时只需要改变链表地址区的指针指向关系即可,因为物理地址的不连续性,我们并不需要类似像线性表一般对后面的元素进行后移操作 具体操作为图示 例如我们需要在b元素和c元素之间插入data 此时我们的操作步骤为
①找到一个指针就插入位置的上一个节点,将2号节点“5”地址给予新节点的指针区
②1号节点的地址区由原来记录的下一个节点地址5改为新节点的地址

注意 两者的顺序不可改变,若是先执行②则会导致一号节点后的节点找不到的情况

此外 我们可以发现,在进行插入操作时,最重要的不是插入位置的该节点,而是插入位置节点的上一个节点,正是由于上一个节点的存在,我们才能理清节点之间的指向关系,并且可以找到其后的所有节点。 但是,当我们要在0号位置插入节点,由于0号位置前没有新节点了该怎么办呢? 这里我们便引入了一个虚拟头节点下标为-1,为了保持插入操作的一致性以及方便于头部节点的插入

四 链表的删除操作

删除节点,亦是抹去节点之间的指向关系
①利用指针指向删除节点的前一个节点
②指针由原来的指向下一个节点改变为跳过该节点,指向要删除节点的下一个节点即可
③释放掉删除节点的内存

链表的删除对于头节点和仅剩一个节点的链表同样适用。

五 循环链表

循环链表逻辑可以看成一个环形结构,实现方式是由链表中最后一个节点指向头节点所致

特别注意的是,为了方便起见在循环链表中 我们的虚拟头节点首先要记录尾节点的位置

在这里插入图片描述
如图 当虚拟头节点地址指向我们的第一个元素时
头插法:利用虚拟头节点,将地址3赋予新节点的指针区,接着遍历整个链表找到尾节点,将尾节点的指针区指针指向新节点的地址
尾插法:遍历整个链表,将尾节点指针区的地址3转变为记录新节点的地址,利用虚拟头节点获得头节点的地址将其赋予新节点的指针区,由此新节点成为链表的尾节点。

当虚拟头节点地址指向我们的最后一个元素时
头插法:由于已知尾节点的具体内容 先把尾节点记录头节点的地址信息赋予新节点的地址区,再让尾节点的地址区记录新节点的地址
尾插法
尾插法:同头插法,最后把虚拟尾节点的地址由12改为新节点的地址

时间复杂度由此降低。

五 代码部分
5.1 链表的初始化

typedef struct node {//节点包含指针区与数据区
    struct node *next;
    int val;
}node;
typedef struct list {//管理节点包含虚拟头节点与链表长度
    node head;//注意此处不需要用指针 把虚拟头节点可看成真正的节点
    int len;
}list;
node *initnode(int val) {
    node *n = (node *)malloc(sizeof(node));
    n->next = NULL;
    n->val = val;
    return n;
}
void freenode(node *p) {
    if (!p)
       return;
    free(p);
    return;
}
list* initlist() {
    list *l = (list *)malloc(sizeof(list));
    l->head.next = NULL;//虚拟头节点的指针区先设置为空
    l->len = 0;
    return l;
}
void freelist(list *l) {//循环整个链表一个个释放节点空间,最后再释放链表空间
    if(!l)
      return;
    node *p = l->head.next;//记录头节点位置
    node *k;//为了避免释放某个节点时而导致找不到后续节点
    while (p) {
        k = p;//顺序不可颠倒
        p = p->next;
        freenode(k);
    }
    free(l);
    return;
}

> 链表时基于节点而存在的,对链表进行操作时,必须先封装对单个节点进行操作。

5.2 链表的插入

int insert_node (list *l, int idx, node *n) {//各个参数对应,对什么进行操作,插入位置在哪?插入节点是什么
    if (!n)
       return 0;
    if (idx < 0 || idx > l->len) //首先判断失败情况
       return 0;
    node *p = &(l->head);//插入时就利用到了虚拟头节点,定义节点p记录虚拟头节点内容,此处注意必须要有取地址符,因为我们当时定义的是整个虚拟头节点而不是地址
    while (idx--) {//找到插入位置,注意是先idx再-- 确保位置一致 我们是从虚拟头节点开始向后的
        p = p->next;
    }
    n->next = p->next;
    p->next = n;
    l->len++;
    return 1;
}
int insert_list(list *l, int idx, int val) {
    if(!l)
       return 0;
    node *n = initnode(val);//封装节点
    if (!insert_node) {//插入失败,则释放我们创建的新节点空间
        freenode(n);
        return 0;
    }
    return 1;
}

> 注意 链表节点下标的计算是和普遍存储规则一样都是从下标0开始的。

5.3 链表的删除

int erase_node(list *l, int idx) {
    if (!l)
       return 0;
    if (idx < 0 || idx >= l->len) //注意“=”
       return 0;
    node *p =&(l->head);//获取虚拟头节点地址
    while (idx--) //找到前一个节点位置
        p = p->next;
    node *k = p->next;//记录将要删除的节点
    p->next = p->next->next;
    freenode(k);
    l->len--;
    return 1;
    
}   

5.4 链表的翻转

所谓链表的翻转即让0->1->2->3->4的形式转变为4->3->2->1->0形式 方法采用头插法:利用两个指针A,B 指针A遍历从前往后遍历第一个链表,指针B随着A的遍历利用头插法不断在新链表头部插入元素

int reverse (list *l) {
    if (!l)
        return 0;
    node *p = l->head.next;//利用虚拟头节点获得第一个节点位置
    node *k;//获得临时节点
    l->head.next = NULL;//改变虚拟头节点的指针区内容由此在逻辑上新创一个链表
    l->len = 0;
    while (p){
        k = p;
        p = p->next;
        insert_node(l, 0, k);//头插法
    }
    return 1;
}

5.5 实现双向链表
双向链表即在原来链表节点的基础上补充一个*prev区,用来记录该节点的前一个节点 改变操作如下


> typedef struct node {
    struct node *next;
    int val;
    struct node *prev;//新增
}node;
node *initnode(int val) {
    node *n = (node *)malloc(sizeof(node));
    n->next = NULL;
    n->prev = NULL;//新增
    n->val = val;
    return n;
}
int insert_node (list *l, int idx, node *n) {
    if (!n)
       return 0;
    if (idx < 0 || idx > l->len) 
       return 0;
    node *p = &(l->head);
    while (idx--) {
        p = p->next;
    }
    n->next = p->next;
    p->next = n;
        n->prev = p;//我们对该节点和该节点的下一个节点进行新增prev操作
    if (n->next) //当要插入的下一个节点不为空时
        n->next->prev = n;
    
    l->len++;
    return 1;
}
int erase_node(list *l, int idx) {
    if (!l)
       return 0;
    if (idx < 0 || idx >= l->len) 
       return 0;
    node *p =&(l->head);
    while (idx--) 
        p = p->next;
    node *k = p->next;
    p->next = p->next->next;
    if (k->next) //当该节点的下一个节点不为空,我们仅对删除节点的下一个节点进行prev操作
        k->next->prev = p;
    freenode(k);
    l->len--;
    return 1;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值