数据结构之带头节点的单链表增删改查操作实现

 

单链表的定义
什么是单链表
      单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。

      单链表的各个数据元素在物理上可以是离散存放的,每个结点除了存放数据元素外,还要存储指向下一个节点的指针。而顺序表是连续存放的,每个结点中只存放数据元素。

      单链表的优点:不要求大片连续空间,改变容量方便,只需在内存单元中随便找个位置作为新节点的区域;缺点:不可随机存储,要耗费一定空间存放指针。

      顺序表的优点:可随机存储,存储密度高;缺点:要求大片连续空间,改变容量不方便,要在内存中再申请一片连续空间。

先理解并记住专业术语 :

        首节点:第一个有效节点。

      尾节点:最后一个有效节点。

      头节点:头节点的数据类型和首节点的类型一样,没有存放有效数据,最最前面的,是在首节点之前的,主要是为了方便对链表的操作(不理解的话,先记住)。

      头指针:指向头节点的指针变量。

      尾指针:指向尾节点的指针。

 确定一个链表需要几个参数:(或者说如果期望一个函数对链表进行操作,我们至少需要接收链表的那些信息???)

        只需要一个参数:头指针,因为通过它我们可以推出链表的所有信息。

用代码定义一个单链表 

          大概思路:由一个个节点组成一个单链表 ,所以从定义节点出发,来创建单链表。那节点里都有什么呢?每个节点里不光有存放的数据元素,还要有一个指向下一个节点的指针。这两个也称为数据域和指针域。为什么要有指针域呢?因为单链表的每个节点在物理上是离散存放的,有了一个节点后,那下一个节点就需要通过这个节点找到,这个节点的指针域可以理解为起着连接的作用。需要注意的是指针域的类型是我们此时定义的节点类型。之后为了方便写代码和区分,用typedef 起了节点和节点指针的别名。单链表的英文单词是LinkList,LNode是简写了单链表节点的英文单词 LinkNode。

typedef struct LNode{ //定义单链表结点类型    //这种方式代码可读性更强
    int data;//每个节点存放一个数据元素
    struct LNode *next;//指针指向下一个节点
}LNode, *LinkList; //LNode 是struct LNode 的别名,LinkList是struct LNode *的别名,指针指向整个结构体

         在后续代码中 ,使用LinkList,强调这是一个单链表; 使用LNode,强调这是一个节点。

void test()
{
    LinkList L; //声明一个指向单链表的指针,头指针一定会有,若单链表没有头结点,则头指针指向第一个结点
    InitList(L);
    // .......后续代码......
}

初始化单链表

        大概思路:在初始化的过程中,给单链表分配好一个头节点,让头节点的指向下一个节点的指针域指向空,因为在整个单链表中,此时还是空表,也就是没有有效节点,头节点虽然起着节点的名字,并且在写专业术语的时候说过,它只是为了便于操作,注意不要混淆了。

bool InitList(LinkList &L) //初始化一个单链表(带头结点)
{
    L = (LNode *)malloc(sizeof(LNode)); //分配一个头节点,malloc函数强调返回是一个节点(LNode *),如果单链表有头节点,则头指针指向头节点,即 LinkList L = (LNode *)malloc(sizeof(LNode)); 等号有指向作用
    if(L == NULL) //内存不足,分配失败,就没有创建出单链表,因为已经分配了一个节点,所以只能内存不足的原因,空表是没有节点
        return false;
    L->next = NULL;//目前只有一个头节点,头节点之后暂时还没有节点
    return true;
}
void test()
{
    LinkList L; //声明一个指向单链表的指针
    InitList(L); //初始化一个空表
    // .......后续代码......
}

如果要判断带头节点的单链表是否为空,代码如下:

bool Empty(LinkList L) //判断单链表是否为空(带头节点) 头节点不存储数据,只有指针域,且表内没有有效节点
{
    if(L->next == NULL)
        return true;
    else
        return false;
}

带头结点的单链表的插入操作

     InsertList(&L,i,e):插入操作。在表 L 中的第 i 个位置上插入指定元素 e,刚开始建议看b站的动画,再来理解代码 。

     利用while循环找到第 i - 1 个结点,将新结点插入其后,头结点可以看作 “ 第0个” 结点。

    

bool InsertList(LinkList &L, int i, ElemType e) //在第 i 个位置插入元素 e (带头结点)
{
    if(i < 1) //只能在头结点之后插入,头结点是第0个,之后是第1个
        return false;
    LNode *p; //LNode * 强调这是一个结点,声明一个结点,表示指针p指向当前找到的结点
    p = L; //L是指向头结点的头指针,再把刚开始的指针p指向L,表示目前的指针p指向头结点
    int j = 0;//记录当前p指向的是第几个结点,从头结点0开始
    while(p!= NULL && j < i - 1) //循环找到第 i-1 个结点,j = i-1,不进入循环,内存满了,不进入循环
    {
        p = p->next; //p指向下一个结点
        j++;
    }
    if(p==NULL) //i值不合法,如果有4个结点,那i不能等于6,因为 p = p->next,在第5个位置之后满了,p指向NULL,下一步p->next出错了,根本找不到表的某个位置
        return false;
    LNode *s = (LNode *)malloc(sizeof(LNode));//创建一个节点,这个节点的数据域就是e
    s->data = e;
    s->next = p->next;  //自己画图理解,一开始p后的节点不是s,这里让p的下一个节点变成了在s的下一个节点
    p->next = s; //将结点s连到p之后
    return true; //插入成功
}

带头结点的单链表的删除操作

     DeleteList(&L,i,&e):删除操作。删除表L中第 i 个位置的元素,并用e返回删除元素的值。

     利用while循环找到第 i - 1 个结点,将其指针指向 第 i + 1个结点,并释放第 i 个结点。

bool DeleteList(LinkList &L, int i, ElemType &e)
{
    if(i < 1)
        return false;
    LNode *p;
    p = L;
    int j = 0;
    while(p != NULL && j < i - 1) //循环找第 i-1 个节点
    {
        p = p->next;
        j++;
    }
    if(p == NULL) //i值不合法,导致第 i-1 个节点根本在表中找不到
        return false;
    if(p->next == NULL) //第 i-1 个节点能找到,但是刚好第 i-1 个节点之后已无其他节点(第i个结点和第i+1个节点没有了),那就没有办法把第 i-1 个节点的指向下一个节点的指针指向第 i+1 个节点
        return false;
    LNode *q = p->next; //p是第 i-1 个节点,p->next是之后的节点,也是第 i 个节点,令q指向被删除节点
    e = q->data; //用e返回元素的值
    p->next = q->next; //将*q 节点从链中断开,重新更改指针p的指向,指向第 i + 1个节点
    free(q); //释放节s点的存储空间
    return true;
}

带头结点的单链表的修改操作

  UpdateList(&L, i, num):修改操作,把第i个位置的数修改为num

int UpdateList(LinkList &L, int i, int num)//把第i个位置的数修改为num
{
   if(i < 0)//有头节点,头节点是第0个节点,所以查找元素i不能小于0
        return 0;
    LNode *p;
    int j = 0;
    p = L;
    while(p != NULL && j < i) //找的是第i个节点,插入那里找的是第i-1 个节点
    {  //如果i大于表的长度,那循环找到的p会指向NULL,不会进入循环体,最后返回NULL
        p = p->next;
        j++;
    }
    p->data = num;
    return p->data;
}

带头结点的单链表的查找操作

     GetList(L,i):按位查找操作。获取表L中第 i 个位置的元素的值。

int GetList(LinkList L, int i)//按位查找,返回第 i 个元素(带头节点)
{
    if(i < 0)//有头节点,头节点是第0个节点,所以查找元素i不能小于0
        return NULL;
    LNode *p;
    int j = 0;
    p = L;
    while(p != NULL && j < i) //找的是第i个节点,插入那里找的是第i-1 个节点
    {  //如果i大于表的长度,那循环找到的p会指向NULL,不会进入循环体,最后返回NULL
        p = p->next;
        j++;
    }
    return p->data;
} //平均时间复杂度为O(n)

带头结点的单链表的销毁操作

        利用while循环把头节点之后的有效节点free掉。

void Destroy(LinkList &L)
{
    LNode *p;
    while(L != NULL)
    {
        p = L;
        L = L->next;
        free(p);
    }
}

完整的增删改查操作的代码,我自己一开始全写在main函数里,只能调用成功一个函数,我也不知道为啥,所以就用了switch选择结构来分别实现这些功能。这些代码理解了,就不难,建议多敲几遍!

#include <stdio.h>
#include <malloc.h>
typedef struct LNode{ //定义单链表结点类型    //这种方式代码可读性更强
    int data;//每个节点存放一个数据元素
    struct LNode *next;//指针指向下一个节点
}LNode, *LinkList; //LNode 是struct LNode 的别名,LinkList是struct LNode *的别名,指针指向整个结构体
bool InitList(LinkList &L) //初始化一个单链表(带头结点)
{
    L = (LNode *)malloc(sizeof(LNode)); //分配一个头节点,malloc函数强调返回是一个节点(LNode *),如果单链表有头节点,则头指针指向头节点,即 LinkList L = (LNode *)malloc(sizeof(LNode)); 等号有指向作用
    if(L == NULL) //内存不足,分配失败,就没有创建出单链表,因为已经分配了一个节点,所以只能内存不足的原因,空表是没有节点
        return false;
    L->next = NULL;//目前只有一个头节点,头节点之后暂时还没有节点
    return true;
}
bool InsertList(LinkList &L, int i, int e) //在第 i 个位置插入元素 e (带头结点)
{
    if(i < 1) //只能在头结点之后插入,头结点是第0个,之后是第1个
        return false;
    LNode *p; //LNode * 强调这是一个结点,声明一个结点,表示指针p指向当前找到的结点
    p = L; //L是指向头结点的头指针,再把刚开始的指针p指向L,表示目前的指针p指向头结点
    int j = 0;//记录当前p指向的是第几个结点,从头结点0开始
    while(p!= NULL && j < i - 1) //循环找到第 i-1 个结点,j = i-1,不进入循环,内存满了,不进入循环
    {
        p = p->next; //p指向下一个结点
        j++;
    }
    if(p==NULL) //i值不合法,如果有4个结点,那i不能等于6,因为 p = p->next,在第5个位置之后满了,p指向NULL,下一步p->next出错了,根本找不到表的某个位置
        return false;
    LNode *s = (LNode *)malloc(sizeof(LNode));//创建一个节点,这个节点的数据域就是e
    s->data = e;
    s->next = p->next;  //自己画图理解,一开始p后的节点不是s,这里让p的下一个节点变成了在s的下一个节点
    p->next = s; //将结点s连到p之后
    return true; //插入成功
}
void ShowList(LinkList L) //显示
{
    if(L->next == NULL)
       printf("这是一个空表\n");
    while(L != NULL)
    {
        L = L->next;
        printf("%d ",L->data);
    }

}
bool DeleteList(LinkList &L, int i, int &e)//删除表L中第 i 个位置的元素,并用e返回删除元素的值。
{
    if(i < 1)
        return false;
    LNode *p;
    p = L;
    int j = 0;
    while(p != NULL && j < i - 1) //循环找第 i-1 个节点
    {
        p = p->next;
        j++;
    }
    if(p == NULL) //i值不合法,导致第 i-1 个节点根本在表中找不到
        return false;
    if(p->next == NULL) //第 i-1 个节点能找到,但是刚好第 i-1 个节点之后已无其他节点(第i个结点和第i+1个节点没有了),那就没有办法把第 i-1 个节点的指向下一个节点的指针指向第 i+1 个节点
        return false;
    LNode *q = p->next; //p是第 i-1 个节点,p->next是之后的节点,也是第 i 个节点,令q指向被删除节点
    e = q->data; //用e返回元素的值
    p->next = q->next; //将*q 节点从链中断开,重新更改指针p的指向,指向第 i + 1个节点
    free(q); //释放节s点的存储空间
    return true;
}
int GetList(LinkList &L, int i)//按位查找,返回第 i 个元素(带头节点)
{
    if(i < 0)//有头节点,头节点是第0个节点,所以查找元素i不能小于0
        return 0;
    LNode *p;
    int j = 0;
    p = L;
    while(p != NULL && j < i) //找的是第i个节点,插入那里找的是第i-1 个节点
    {  //如果i大于表的长度,那循环找到的p会指向NULL,不会进入循环体,最后返回NULL
        p = p->next;
        j++;
    }
    return p->data;
} //平均时间复杂度为O(n)
int UpdateList(LinkList &L, int i, int num)
{
   if(i < 0)//有头节点,头节点是第0个节点,所以查找元素i不能小于0
        return 0;
    LNode *p;
    int j = 0;
    p = L;
    while(p != NULL && j < i) //找的是第i个节点,插入那里找的是第i-1 个节点
    {  //如果i大于表的长度,那循环找到的p会指向NULL,不会进入循环体,最后返回NULL
        p = p->next;
        j++;
    }
    p->data = num;
    return p->data;
}
void Destroy(LinkList &L) //销毁单链表
{
    LNode *p;
    while(L != NULL)
    {
        p = L;
        L = L->next;
        free(p);
    }
}
void ShowMenu()
{
    printf("************************\n");
    printf("*****  1,添加数据  *****\n");
    printf("*****  2,删除数据  *****\n");
    printf("*****  3,查找数据  *****\n");
    printf("*****  4,修改数据  *****\n");
    printf("*****  5,显示数据  *****\n");
    printf("*****  0,销毁并退出  ***\n");
}
int main()
{
    LinkList L;//声明一个单链表
    InitList(L);//初始化函数调用

    int select = 0; //创建选择输入的变量
    while (true)
    {
        //菜单调用
        ShowMenu();
        printf("请输入你的选择\n");
        scanf("%d", &select);
        switch (select)
        {
        case 1: //1,添加数据
            InsertList(L,1,1);
            InsertList(L,2,2);
            InsertList(L,3,3);
            InsertList(L,4,4);
            InsertList(L,5,5);
            printf("数据添加成功!\n");
            getchar();
            break;
        case 2: //2,删除数据
            int e;
            if(DeleteList(L,4,e))
            {
                printf("删除的数是:%d\n",e);
            }
            getchar();
            break;
        case 3: //3,查找数据
            printf("查找的元素是:%d\n",GetList(L,1));
            getchar();
            break;
        case 4: //4,修改数据
            UpdateList(L,1,0);
            printf("修改成功!\n");
            getchar();
            break;
        case 5: //4,显示数据
            printf("单链表的数据有:");
            ShowList(L);
            getchar();
            break;
        case 0: //0,退出
            Destroy(L);
            printf("单链表已销毁!");
            printf("欢迎下次使用");
            return 0;
            break;

        default:
            break;
        }
    }
    return 0;
}

运行结果如下:

       在单链表中添加数据并显示数据成功:

     

     在单链表中删除数据并显示数据成功:

 在单链表中查找数据并显示数据成功:

 在单链表中修改数据并显示数据成功:

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

银河小船儿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值