单向链表

单向链表

前言

个人觉得,一开始接触数据结构看一大堆的定义对初学者并不友好,那样会令人望而生畏,一开始应该是简单的定义加上对代码的详细解释。

单向链表

单向链表是链表的一种,其特点是链表的连接方向是单向的,对链表的访问要通过头部开始,依序往下读取。

一个单向链表由多个节点组成,一个节点被分成两部分:

  • 第一个部分是保存关于节点的信息,称作数据域。
  • 第二个部分是存储下一个节点的地址,称作指针域。

单向链表结构示意图

p1

其中head是链表的头指针,指向的是表头节点,该节点的数据域无内容,其指针域指向的是链表的第一个节点。

链表最后一个节点的指针域为空,用NULL表示。

既不是表头结点也不是尾节点的节点数据域和指针域都不为空。

p2


节点表示

struct node{
    int data;           // 数据域 
    struct node *next;  // 指针域 
}; 
typedef struct node Node;    
typedef struct node* PNode;  // 节点指针 

其中data为节点保存的数据,next是节点指针类型的数据,用来保存下一个节点的地址。


链表初始化

链表初始化的内容是生成一个链表表头并返回其节点地址。如果初始化失败,则返回NULL表示初始化失败。

PNode Init(){
    
    PNode head = (PNode )malloc(sizeof(struct Node));
    if(!head)           // 申请内存空间失败的情况 
        return NULL;
    else{               // 申请成功的情况 
        head->next = NULL;          
        return head;
    } 
} 

数据的插入、删除、查找

插入

数据的增加操作即往链表中插入数据元素,往链表里插入元素有两种形式:

  • 不要求次序的插入,使用头插法。
  • 要求次序的精准插入,找到位置后插入。

不要求次序的插入

不要求次序的插入只需要将数据元素插入链表即可,因为单向链表只能从表头往后遍历,所以采用头插法减少插入的时间复杂度。

头插法

往链表里边插入元素的方法,可能你第一想到的是往链表的尾部直接添加元素,但是因为单向链表的特性,我们获得链表尾部的节点地址的时间复杂度是不稳定的。链表越长,获取链表尾部节点的地址就越慢。所以为了插入数据操作的高效性,我们使用头插法。

头插法,顾名思义就是直接在链表的头部插入数据。

大致操作如下:

p3

  • 申请新节点

  • 将需要插入的节点数据赋值给新节点数据域。

    p4

  • 使新节点指向表头指向的节点。

    p5

    p6

  • 使表头指向新节点。

    p7

    p8


将该操作写成函数,该函数的作用是往链表插入元素,插入成功返回1,失败返回0。

代码为:

int Insert(PNode rt,int data){
    // 申请新节点的空间 
    PNode NewNode = (PNode )malloc(sizeof(struct Node));
    if(!NewNode)            // 申请失败的情况 
        return 0;
    else{                   // 申请成功的情况
        NewNode->data = data;       // 节点数据赋值 
        NewNode->next = rt->next;   // 令新节点的next指向表头指向的节点 
        rt->next = NewNode;         // 令表头指向新节点 
        return 1;
    } 
}

精准位置插入

将数据元素插入到第i个元素之后,如果i超过链表长度则将其插入到链表的最后位置。插入成功返回1,失败返回0。

int Insert_i(PNode rt,int i,int data){
    // 申请新节点空间
    PNode NewNode = (PNode )malloc(sizeof(struct Node));
    if(!NewNode)                    // 申请空间失败的情况 
        return false; 
    else{                           // 申请成功的情况 
        NewNode->data = data;       // 节点数据赋值 
        int index = 0;
        PNode pr = rt;              // pr的代表的是第i个节点的地址 
        while(pr->next != NULL){    //这个循环的作用是使pr获取第i个节点的地址 
            index++;
            pr = pr->next;
            if(index == i)break;
        }
        NewNode->next = pr->next;   //获取地址之后进行修改,类似于头插法的方式 
        pr->next = NewNode;
        return 1;
    }
} 

删除

删除操作是插入操作的逆操作,有两种形式:

  • 删除特定元素。
  • 删除特定位置的元素。

删除特定元素

找到该元素的地址和其前驱即可对该元素进行删除,删除成功返回1,失败返回0。

int Delete(PNode rt,int data){
    PNode pre = rt,add = rt;
    // 查找链表中是否含有此元素
    while(add->next != NULL){
        pre = add;
        add = add->next;
        if(add->data == data)break;
    }
    if(add->data != data || add == rt)return 0; // 未找到的情况
    else{                                       // 找到的情况
        pre->next = add->next;          // 直接让前驱指向第i个元素的后继
        free(add);                      // 释放该节点空间
        return 1;
    }
} 

删除特定位置的元素

删除第i个位置的元素,如果i大于链表长度则删除失败返回0,成功则返回1。

int Delete_i(PNode rt,int i){
    PNode pre = rt,add = rt;
    int index = 0;
    while(add->next != NULL){
        index++;
        pre = add;
        add = add->next;
        if(index == i)break;        // 找到第i个元素
    }
    if(index != i)return 0;         //i大于链表长度或者i<= 0的情况
    else{
        pre->next = add->next;      // 直接让前驱指向第i个元素的后继
        free(add);                  // 释放第i个节点
        return 1;                   // 删除成功
    }
}

查找

查找操作即在链表中查找是否存在某元素,若存在返回其节点地址,不存在返回NULL。

PNode find(PNode rt,int data){
    PNode pr = rt;
    while(pr->next != NULL){
        pr = pr->next;
        if(pr->data == data)break;  // 找到的情况
    }
    if(pr->data != data || pr == rt)return NULL;    // 未找到或者链表为空的情况
    else return pr;
}

这篇文章主要介绍了单向链表中最重要的三类操作,分别是数据插入、数据删除、数据查找。

实际上还有很多其他的操作,初学阶段能学会这几种足够了,贪多嚼不烂。

一下是测试函数正确性的源代码

#include<stdio.h>
#include<stdlib.h>

struct node{
    
    int data;           // 数据域 
    struct node *next;  // 指针域 
    
}; 

typedef struct node Node;    // 对结构体重命名,方便定义数据
typedef struct node* PNode;  // 节点指针 

PNode Init(){
    
    PNode head = (PNode )malloc(sizeof(Node));
    if(!head)           // 申请内存空间失败的情况 
        return NULL;
    else{               // 申请成功的情况 
        head->next = NULL;          
        return head;
    } 
} 

int Insert(PNode rt,int data){
    // 申请新节点的空间 
    PNode NewNode = (PNode )malloc(sizeof(Node));
    if(!NewNode)            // 申请失败的情况 
        return 0;
    else{                   // 申请成功的情况
        NewNode->data = data;       // 节点数据赋值 
        NewNode->next = rt->next;   // 令新节点的next指向表头指向的节点 
        rt->next = NewNode;         // 令表头指向新节点 
        return 1;
    } 
}

int Insert_i(PNode rt,int i,int data){
    // 申请新节点空间
    PNode NewNode = (PNode )malloc(sizeof(Node));
    if(!NewNode)                    // 申请空间失败的情况 
        return 0; 
    else{                           // 申请成功的情况 
        NewNode->data = data;       // 节点数据赋值 
        int index = 0;
        PNode pr = rt;              // pr的代表的是第i个节点的地址 
        while(pr->next != NULL){    //这个循环的作用是使pr获取第i个节点的地址 
            index++;
            pr = pr->next;
            if(index == i)break;
        }
        NewNode->next = pr->next;   //获取地址之后进行修改,类似于头插法的方式 
        pr->next = NewNode;
        return 1;
    }
} 

int Delete(PNode rt,int data){
    PNode pre = rt,add = rt;
    // 查找链表中是否含有此元素
    while(add->next != NULL){
        pre = add;
        add = add->next;
        if(add->data == data)break;
    }
    if(add->data != data || add == rt)return 0; // 未找到的情况
    else{                                       // 找到的情况
        pre->next = add->next;          // 直接让前驱指向第i个元素的后继
        free(add);                      // 释放该节点空间
        return 1;
    }
} 

int Delete_i(PNode rt,int i){
    PNode pre = rt,add = rt;
    int index = 0;
    while(add->next != NULL){
        index++;
        pre = add;
        add = add->next;
        if(index == i)break;        // 找到第i个元素
    }
    if(index != i)return 0;         //i大于链表长度或者i<= 0的情况
    else{
        pre->next = add->next;      // 直接让前驱指向第i个元素的后继
        free(add);                  // 释放第i个节点
        return 1;                   // 删除成功
    }
}

PNode find(PNode rt,int data){
    PNode pr = rt;
    while(pr->next != NULL){
        pr = pr->next;
        if(pr->data == data)break;  // 找到的情况
    }
    if(pr->data != data || pr == rt)return NULL;    // 未找到或者链表为空的情况
    else return pr;
}

// 遍历输出链表全部元素 
void Tra(PNode rt){
    PNode pr = rt;
    while(pr->next != NULL){
        pr = pr->next;
        printf("%d ",pr->data);
    }
    printf("\n");
}

int main(){
    
    PNode L1,L2;
    // 初始化两个链表 
    L1 = Init();
    L2 = Init();
    if(L1)printf("链表一创建成功。\n");
    if(L2)printf("链表二创建成功。\n");
    
    int i ;
    printf("L1使用头插法插入1~10:\n");
    for(i = 1; i <= 10; i++)
        Insert(L1,i);
    Tra(L1);
    printf("L2使用位置插入10~19:\n");
    for(i = 10; i <= 19; i++)
        Insert_i(L2,i-9,i);
    Tra(L2);
    
    printf("删除L1元素1、2、3、4、5:\n");
    for(i = 1; i <= 5; i++)
        Delete(L1,i);
    Tra(L1);
    printf("删除L2第10个元素:\n");
    Delete_i(L2,10);
    Tra(L2);
    return 0;
}

输出为:

链表一创建成功。
链表二创建成功。
L1使用头插法插入1~10:
10 9 8 7 6 5 4 3 2 1
L2使用位置插入10~19:
10 11 12 13 14 15 16 17 18 19
删除L1元素1、2、3、4、5:
10 9 8 7 6
删除L2第10个元素:
10 11 12 13 14 15 16 17 18

转载于:https://www.cnblogs.com/bingdada/p/11574855.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值