单链表的创建及相关操作——入门级

本文详细介绍了如何使用C语言创建和操作单链表,包括初始化链表、判断链表是否为空、返回链表长度、头插法和尾插法创建链表、输出链表、获取指定位置数据、插入和删除节点以及查找元素等操作。强调了带头结点在链表操作中的重要性,并提供了完整的代码示例,帮助初学者理解单链表的概念和操作技巧。
摘要由CSDN通过智能技术生成

本文我们将学习单链表的创建以及相关操作。使用c语言实现,结尾有全部代码。

(仅供正在学习的朋友参考,如有错误的地方,欢迎评论指出。)

很多朋友在初次学习时常常会搞不清楚头指针、头结点、结点以及链表关系或存在,本文章中我会一一为各位指出,至少是便于理解的。

单链表想象图(带头结点):

注:不带头结点单链表想象图即把该图中“head”去掉即可,正常创建单链表都是带头结点的,原因是大部分情况下,带头结点的单链表可以轻松的完成很多操作。

在本文章的示例中我们将用到两个头文件,分别是:

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

下面我们做一些准备工作:

const int TRUE = 1;
const int FALSE = 0;
const int OK = 1;
const int ERROR = 0;
typedef int ELEMTYPE
void Test_Point(int k){ //排错测试
    printf("\n测试点%d\n",k);
}

在这段代码中我们定义了TRUE、OK、FALSE、ERROR和ELEMTYPE,这些都是为了方便接下来介绍操作代码而定义的,其最关键的用处就是增加我们阅读代码时对自定义函数的理解。第6~8行为测试代码,用于排查一些错误。

定义结点:

typedef struct NODE{
    ELEMTYPE data;//数据域
    struct NODE *next;//指针域,指向下一结点
}NODE,*linked;

一个结点由一个数据域和一个指针域组成,定义结点的时候要注意有效数据域占单个结点比例大小,尽量大密度。此段代码解析:

typedef + 关键字 + 自定义名称,给关键字定义别称。这里typedef struct NODE{}NODE,*linked;

这里NODE是这个结构体的别称,使用方法:NODE p;此时定义了一个名为p的NODE结构体。

这里*linked是这个结构体的指针别称,使用方法:linked p;此时定义了一个指向NODE结构体的指针。等价于NODE *p;

ELEMTYPE data:数据域部分,可以定义多个数据,这里ELEMTYPE是int别称。

struct NODE *next:指针域部分,可以定义多个指针(双向链表、树等),这里next指向下一个结点,一个一个下一个结点连接起来就是一根链,每个结点带有数据组起来就是表,二者结合起来就是链表(仅供理解使用)。

初始化操作(带头结点单链表定义):

int InitList_Head(linked &L){
    L = (linked)malloc(sizeof(NODE));//申请头结点空间
    L->next = NULL;/*让头结点next指向NULL。NULL是非常实用的,定义函数时要非常注意NULL的使用*/

    if(L){//判断是否申请头结点空间成功
        printf("初始化成功!\n");
        return OK;
    }
    else{
        printf("初始化失败!\n");
        return ERROR;
    }
}

本段代码运行结果:

判断链表是否为空操作:

int EmptyList(linked L){
    if(L->next){//判断条件,如果头结点下一结点即首元结点为空,则链表为空
        printf("链表为非空!\n");
        return FALSE;
    }
    else{
        printf("链表为空!\n");
        return TRUE;
    }
}

运行代码:

运行结果:

返回链表长度操作:

int Length_Linklist(linked L){
    linked p;//定义一个能够指向结构体NODE的指针
    p = L->next;//让p指向链表L的首元结点。注:此处L可以理解为头结点
    int counter = 0;//计数器
    while(p){//遍历链表计数
        counter++;
        p = p->next;//每一次循环计数结束后让本结点指针指向下一结点
    }
    return counter;//返回计数器数字
}

运行代码:

运行结果:

头插法创建单链表操作:

int Createlist_Head(linked &L){
    linked p;//定义一个能够指向NODE结构体的指针p
    int k;//要输入的链表长度k(可以根据具体情况不需要,本文章为了便于刚接触的朋友理解故加一个长度)
    printf("请输入要创建的链表长度:\n");
    scanf("%d",&k);
    printf("请输入要存储的数据:\n");
    for(int i = 0;i < k;i++){//依次录入数据并且将其接入链表
        p = (linked)malloc(sizeof(NODE));//向内存申请结点空间
        scanf("%d",&p->data);//录入数据进入指针p指向的结点。注:此处的结点可以理解为漂浮在某个空间中不接触任何物体的物体,此时,它拥有了数据
        p->next = L->next;//让拥有了数据的p指针指向的结点的指针域指向头结点的指针域指向的方向,即指向NULL。
        L->next = p;//让头结点的next指向p指向的结点。
    }
    return OK;
}

尾插法创建单链表操作:

int Createlist_Tail(linked &L){
    linked p,q;
    int k;
    p = L;//让p指向L即让p指向头结点
    printf("请输入要创建的链表长度:\n");
    scanf("%d",&k);
    printf("请输入要存储的数据:\n");
    for(int i = 0;i < k;i++){
        q = (linked)malloc(sizeof(NODE));
        scanf("%d",&q->data);
        q->next = p->next;
        p->next = q;
        p = q;
    }
    return OK;
}

逻辑想象及各自对应代码(尾插法,因为理解尾插法就能够理解头插法):

需要注意的是很多初学的朋友不能够理解p,q,L之间的交互,其实它们也都只是指针而已,要自己细细品读才能深刻理解。

运行代码:

运行结果:

注意:头插法输出为正序输入,反向输出;尾插法输出为正序输入,正序输出。

输出链表操作:

void Output(linked L){
    linked p;
    p = L->next;
    while(p){
        printf("%3d",p->data);
        p = p->next;
    }
    printf("\n");
}

运行代码以及结果在上一个例子中。

获取链表中某位置的数据:

int GetData(linked L,int i){//i为要查找的位置
    linked p;
    p = L;
    if(i <= 0 || i > Length_Linklist(L)){//查找位置是否在链表长度范围内
        printf("非法的获取位置!!!\n");
        return FALSE;
    }
    if(p){//判断链表是否为空
        for(int h = 0;h < i;h++){//遍历寻找到第i个位置
            p = p->next;
        }
        printf("链表中第%d个元素为%d\n",i,p->data);
    }
    else{
        printf("无该位置数据!\n");
        return ERROR;
    }
}

调用GetData:GerData(L2,1);——查找L2中第一个元素

运行结果:

单链表链表插入操作:

int InsList(linked &L,int i,ELEMTYPE e){//i为要插入的位置,e为要插入的数据
    int k;
    linked p,q;
    p = L;
    k = 1;
    while(p && k < i){//找到要插入的位置
        p = p->next;
        ++k;
    }
     if(!p || k > i)//判断插入位置是否合法
        return ERROR;

    /*以下代码同头插法操作思路相同*/
    q = (linked)malloc(sizeof(NODE));
    q->data = e;
    q->next = p->next;
    p->next = q;
    return OK;
}

调用函数:InsList(L2,1,11);——在L2表中第一个位置插入数据11

运行结果:

单链表删除结点操作:

int DelList(linked &L,int i,ELEMTYPE &e){//e用来返回要删除的数据
    int k;
    linked p,q;
    p = L;
    k = 1;
    while(p->next && k < i){
        p = p->next;
        ++k;
    }
    if(!(p->next) || k > i)
        return ERROR;
    q = p->next;
    p->next = q->next;
    e = q->data;
    free(q);
    return OK;
}

调用函数:DelList(L2,1,e);——删除L2中第一个元素,并将删除的元素赋给e。

运行结果:

判断链表中是否有某元素操作:

int LocateElem(linked L,ELEMTYPE e){
    int i = 0;
    linked p;
    p = L ->next;
    while(p){
        i++;
        if(p->data == e)
            return i;
        p = p->next;
    }
    return NULL;
}

调用函数:LocateElem(L2,2);在L2表中查询是否有数据2

运行代码:

运行结果:

全部代码:

#include<stdlib.h>
#include<stdio.h>
const int TRUE = 1;
const int FALSE = 0;
const int OK = 1;
const int ERROR = 0;
typedef int ELEMTYPE
/*测试点*/
void Test_Point(int k){ //排错测试
    printf("\n测试点%d\n",k);
}
/*定义结点*/
typedef struct NODE{
    ELEMTYPE data;//数据域
    struct NODE *next;//指针域,指向下一结点
}NODE,*linked;
/*初始化链表(带头结点)*/
int InitList_Head(linked &L){
    L = (linked)malloc(sizeof(NODE));//申请头结点空间
    L->next = NULL;/*让头结点next指向NULL。NULL是非常实用的,定义函数时要非常注意NULL的使用*/

    if(L){//判断是否申请头结点空间成功
        printf("初始化成功!\n");
        return OK;
    }
    else{
        printf("初始化失败!\n");
        return ERROR;
    }
}
/*判断链表是否为空*/
int EmptyList(linked L){
    if(L->next){//判断条件,如果头结点下一结点即首元结点为空,则链表为空
        printf("链表为非空!\n");
        return FALSE;
    }
    else{
        printf("链表为空!\n");
        return TRUE;
    }
}
/*返回链表长度*/
int Length_Linklist(linked L){
    linked p;//定义一个能够指向结构体NODE的指针
    p = L->next;//让p指向链表L的首元结点。注:此处L可以理解为头结点
    int counter = 0;//计数器
    while(p){//遍历链表计数
        counter++;
        p = p->next;//每一次循环计数结束后让本结点指针指向下一结点
    }
    return counter;//返回计数器数字
}
/*头插法创建链表*/
int Createlist_Head(linked &L){
    linked p;//定义一个能够指向NODE结构体的指针p
    int k;//要输入的链表长度k(可以根据具体情况不需要,本文章为了便于刚接触的朋友理解故加一个长度)
    printf("请输入要创建的链表长度:\n");
    scanf("%d",&k);
    printf("请输入要存储的数据:\n");
    for(int i = 0;i < k;i++){//依次录入数据并且将其接入链表
        p = (linked)malloc(sizeof(NODE));//向内存申请结点空间
        scanf("%d",&p->data);//录入数据进入指针p指向的结点。注:此处的结点可以理解为漂浮在某个空间中不接触任何物体的物体,此时,它拥有了数据
        p->next = L->next;//让拥有了数据的p指针指向的结点的指针域指向头结点的指针域指向的方向,即指向NULL。
        L->next = p;//让头结点的next指向p指向的结点。
    }
    return OK;
}
/*尾插法创建链表*/
int Createlist_Tail(linked &L){
    linked p,q;
    int k;
    p = L;//让p指向L即让p指向头结点
    printf("请输入要创建的链表长度:\n");
    scanf("%d",&k);
    printf("请输入要存储的数据:\n");
    for(int i = 0;i < k;i++){
        q = (linked)malloc(sizeof(NODE));
        scanf("%d",&q->data);
        q->next = p->next;
        p->next = q;
        p = q;
    }
    return OK;
}
/*链表输出*/
void Output(linked L){
    linked p;
    p = L->next;
    while(p){
        printf("%3d",p->data);
        p = p->next;
    }
    printf("\n");
}
/*查找链表中i位置的数据*/
int GetData(linked L,int i){//i为要查找的位置
    linked p;
    p = L;
    if(i <= 0 || i > Length_Linklist(L)){//查找位置是否在链表长度范围内
        printf("非法的获取位置!!!\n");
        return FALSE;
    }
    if(p){//判断链表是否为空
        for(int h = 0;h < i;h++){//遍历寻找到第i个位置
            p = p->next;
        }
        printf("链表中第%d个元素为%d\n",i,p->data);
    }
    else{
        printf("无该位置数据!\n");
        return ERROR;
    }
}
/*插入结点*/
int InsList(linked &L,int i,ELEMTYPE e){//i为要插入的位置,e为要插入的数据
    int k;
    linked p,q;
    p = L;
    k = 1;
    while(p && k < i){//找到要插入的位置
        p = p->next;
        ++k;
    }
     if(!p || k > i)//判断插入位置是否合法
        return ERROR;

    /*以下代码同头插法操作思路相同*/
    q = (linked)malloc(sizeof(NODE));
    q->data = e;
    q->next = p->next;
    p->next = q;
    return OK;
}
/*删除结点*/
int DelList(linked &L,int i,ELEMTYPE &e){//e用来返回要删除的数据
    int k;
    linked p,q;
    p = L;
    k = 1;
    while(p->next && k < i){
        p = p->next;
        ++k;
    }
    if(!(p->next) || k > i)
        return ERROR;
    q = p->next;
    p->next = q->next;
    e = q->data;
    free(q);
    return OK;
}
/*查询链表中是否有某数据*/
int LocateElem(linked L,ELEMTYPE e){
    int i = 0;
    linked p;
    p = L ->next;
    while(p){
        i++;
        if(p->data == e)
            return i;
        p = p->next;
    }
    return NULL;
}
void main(){
    linked L1,L2;//定义链表
    int e;
    Initlist_head(L1);//初始化带头结点链表L1
    Initlist_head(L2);//初始化带头结点链表L2
    EmptyList(L2);
    printf("\nL2表长为:%d\n",Length_Linklist(L2));
    Test_Point(2);//测试点2
    printf("头插法L1相关操作:\n");
    Createlist_Head(L1);//创建头插链表L1
    printf("输出L1:");
    Output(L1);//输出头插法链表L1
    printf("尾插法L2相关操作:\n");
    Createlist_Tail(L2);//创建尾插链表L2
    printf("输出L2:");
    Output(L2);//输出尾插法链表L2
    printf("L2表长为:%d\n",Length_Linklist(L2));//输出L2表长
    Test_Point(3);//测试点3
    printf("L2");
    GetData(L2,1);
    Test_Point(5);
    InsList(L2,1,11);
    Output(L2);
    DelList(L2,1,e);
    printf("删除的结点数据为:%d\n",e);
    printf("删除第一位元素后的L2链表为:");
    Output(L2);
    if(LocateElem(L2,1)){
        printf("该数字为第%d位\n",LocateElem(L2,2));
    }
    else printf("无该数字\n");
}

运行结果:

博主也正在学习,偶尔会分享学习进程,文章肯定有不妥的地方,欢迎各位朋友评论斧正。

关于数据结构,一定一定要亲手操作一遍,其中抽象思想一定一定要慢慢细品才能有所感悟。

  • 2
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值