C语言_(13)_单向链表(通俗易懂,理解链表看这个就行了)

目录

链表的概念

0.链表的构成

1.创建一个头指针

2.头插法

3.打印链表

4.判断链表是否为空

5.计算链表有效结点个数

6.尾插法

7.头删法

8.尾删法

9.删除链表

10.源代码

运行结果


链表的概念

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

其中每个结点包含他的数据域和指针域,数据域是来存放这个结点的数据,而指针域存储的是这个结点链接下一个结点的地址。一个个结点互相链接就形成结点

那么怎么让一个个结点链接起来呢,怎么形成一个非连续,非顺序的存储结构呢。我们可以看下图理解

从一个空数据的结点开头,生成新的结点,把想存放的数据存进新结点的数据域,继续将下一个结点的地址存进当前结点的指针域,如果没有下一个,那就将最后这个结点的指针域置(NULL)。

0.链表的构成

链表由一个个结点构成,每一个结点我们可以用结构体来形成,那就可以创建结点数据类型。

//结点
typedef struct NodeName
{
          char *name;//数据域
          struct NodeName *next;//指针域
}namelist;

1.创建一个头指针

这里我们先创建一个头结点,并且令头结点的数据域为0(没有意义)和指针域为NULL(防止野指针)。

int main(int argc, char const *argv[])
{
         namelist head = {NULL,NULL};//头结点
         namelist *phead = &head;//头结点的指针
         return 0;
}

2.头插法

如果我要在链表的最前端插入一个新的结点,这种方法叫做头插法。那我们将怎样把这个结点插入在前面呢,假设后面有其他旧的结点,那么该怎么能插入这个新结点并且不让后面的旧的结点失去联系。

所以我们得让新的结点的指针域指向旧的结点(phead的指针域),插入新结点的数据域,再让phead的指针域指向新的结点。

//头插法
int CreatHeadNode(namelist *phead, char *pname)
{
          namelist *NewHeadTmp = NULL;//创建新节点
          NewHeadTmp = malloc(sizeof(namelist));//分配堆空间
          if(NULL == NewHeadTmp)//如果申请空间失败,结束!
          {
                    printf("malloc NewHeadTmp failed\n");
                    return -1;
          }
          NewHeadTmp->next = phead->next;//将旧结点的地址给到新插入结点的地址域
          NewHeadTmp->name = pname;//传入数据
          phead->next = NewHeadTmp;//将新节点的地址给到Phead的地址域

          return 0;
}

3.打印链表

我们用头插法插入了姓名数据,那么我们怎么可以看到我们插入的数据呢。

在这里我们给打印链表的函数传入phead,然后创建一个指针p指向phead的指针域,也就是下一个结点的地址,然后打印出该结点的数据域即可,然后让p一步步向后指向每一个结点,打印出每一个结点的数据域,直到最后一个结点的指针域为NULL,结束打印。我们可以用字符串数组遍历的类似的方法找\0,在这里我们找最后一个结点的指针域是否为NULL;

//打印链表
void PrintfNode(namelist *phead)
{
          namelist *p = NULL;
          p = phead->next;//让p指向第一个结点
          while (NULL != p)//在p为NULL的时候结束循环
          {
                    printf("%s\n", p->name);
                    p = p->next;//指向下一个结点
          }      
}

运行结果 

在这里我插入了三个结点,顺序非别为张三、李四、王五。因为是头插法,所以打印出来的顺序是倒着的。

int main(int argc, char const *argv[])
{
         namelist head = {NULL,NULL};//头结点
         namelist *phead = &head;//头结点的指针

         CreatHeadNode(phead,"张三");
         CreatHeadNode(phead,"李四");
         CreatHeadNode(phead,"王五");
         PrintfNode(phead);
         return 0;
}

4.判断链表是否为空

逻辑简单,不予分析

//判断链表是否为空
int JudgeNull(namelist *phead)
{
          return (NULL == phead->next);//为空返回1,不为空返回0
}

5.计算链表有效结点个数

这个计算结点的个数的思路,与我们上面打印链表结点的思路一样,我们只需要在函数中定义一个计数变量自加,直到遍历到NULL,就得到了个数。

//计算链表结点个数
int CountNode(namelist *phead)
{
          int count = 0;
          namelist *p = NULL;
          p = phead->next;

          while(NULL != p)//遍历
          {
                    count++;//计数
                    p = p->next;
          }
          return count;//返回结点个数
}

运行结果 

我用两次次头插法分别打印出结点的数据域和链表结点的个数。

        主函数代码

int main(int argc, char const *argv[])
{
         namelist head = {NULL,NULL};//头结点
         namelist *phead = &head;//头结点的指针

         CreatHeadNode(phead,"张三");
         CreatHeadNode(phead,"李四");
         CreatHeadNode(phead,"王五");
         PrintfNode(phead);//打印链表
         printf("==========结点个数count=%d==========\n",CountNode(phead));//打印此时结点个数

         CreatHeadNode(phead,"托马斯");
         PrintfNode(phead);//打印链表
         printf("==========结点个数count=%d==========\n",CountNode(phead));//打印此时结点个数

          return 0;
}

6.尾插法

我们上述分析了头插法和一系列的打印,计数,判断NULL。接下来说一说尾插法。

尾插法同样我们给函数参数传入的参数是Phead和数据域的值。

在尾插前提我们得判断是否链表为空链表,如果是空链表的话我们尾插给谁的尾巴插呀?

所以就使用我们前面提到的判断链表是否为空,如果为空的话我们是用头插法先创建新结点,然后下一次插入就不为空链表可用尾插法插入。

进入尾插法,我们先用一个指针遍历到尾部插入前的最后一个链表,然后将新链表的地址给到插入前尾部链表的指针域,然后装入数据,再把插入后尾部链表的指针域置NULL;

//尾插法
int CreatTailNode(namelist *phead, char *pname)
{

          if(JudgeNull(phead))//如果为空链表
          {
                    CreatHeadNode(phead,pname);
          }
          else
          {
                    namelist *NewTailTmp = NULL;
                    NewTailTmp = malloc(sizeof(namelist));
                    if(NULL == NewTailTmp)
                    {
                              printf("malloc NewTailTmp failed!\n");
                              return -1;
                    }
                    namelist *p = phead ->next;
                    while(NULL != p->next)//遍历
                    {
                              p = p->next;
                    }
                    p->next = NewTailTmp;//将新结点的地址,给到插入前最后一个结点的指针域
                    NewTailTmp->name = pname;//插入数据域
                    NewTailTmp->next = NULL;//将尾插结点的指针域置NULL
          }
          return 0;
}

运行结果 

 我又尾插了两个数据与没有尾插做对比

int main(int argc, char const *argv[])
{
         namelist head = {NULL,NULL};//头结点
         namelist *phead = &head;//头结点的指针
         CreatHeadNode(phead,"张三");
         CreatHeadNode(phead,"李四");
         CreatHeadNode(phead,"王五");
         PrintfNode(phead);//打印链表
         printf("==========结点个数count=%d==========\n",CountNode(phead));//打印此时结点个数
         CreatHeadNode(phead,"托马斯");
         CreatTailNode(phead,"詹姆斯");
         CreatTailNode(phead,"约翰逊");
         PrintfNode(phead);//打印链表
         printf("==========结点个数count=%d==========\n",CountNode(phead));//打印此时结点个数
          return 0;
}

7.头删法

我们说了怎么插入结点,下面说一说怎么删除结点。

我们只需要将需要删除结点的指针域给phead的指针域,然后用free()函数释放就行了。

//删除头结点
void DeleteHeadNode(namelist *phead)
{
          if(!JudgeNull(phead))//如果不为空链表
          {
                    namelist *p = NULL;
                    p = phead->next;//p为删除结点的地址
                    phead->next = p->next;//将删除结点的指针域给到phead的指针域
                    free(p);//删除释放头结点
          }
}

运行结果

主函数内容

int main(int argc, char const *argv[])
{
         namelist head = {NULL,NULL};//头结点
         namelist *phead = &head;//头结点的指针
         CreatHeadNode(phead,"张三");
         CreatHeadNode(phead,"李四");
         CreatHeadNode(phead,"王五");
         PrintfNode(phead);//打印链表
         printf("==========结点个数count=%d==========\n",CountNode(phead));//打印此时结点个数
         CreatHeadNode(phead,"托马斯");
         CreatTailNode(phead,"詹姆斯");
         CreatTailNode(phead,"约翰逊");
         PrintfNode(phead);//打印链表
         printf("==========结点个数count=%d==========\n",CountNode(phead));//打印此时结点个数
         DeleteHeadNode(phead);
         PrintfNode(phead);//打印链表
         printf("=======删除头结点后结点个数count=%d==========\n",CountNode(phead));//打印此时结点个数
          return 0;
}

8.尾删法

有头删法那肯定会有尾删法

思路是遍历到倒数第二个结点,释放删除倒数第二个结点的指针域(也就是倒数第一个结点的地址),然后将倒数第二个结点的指针域置NULL,完成删除,因为要用到倒数第二个结点,所以如果结点小于两个的话,尾插法将无法完成,我们用头插法完成删除,所以我们先判断有效结点的个数,再进行尾插法。

//删除尾结点
void DeleteTailNode(namelist *phead)
{
          if(2 <= CountNode(phead))//超过两个结点
          {
                    namelist *p = NULL;
                    p = phead->next;
                    while(NULL != p->next->next)//遍历到倒数第二个结点
                    {
                              p = p->next;
                    }
                    free(p->next);//释放删除尾结点
                    p->next = NULL;//将现在的最后结点的指针域置NULL
          }
          else//不超过两个结点
          {
                    DeleteHeadNode(phead);
          }
}

运行结果

主函数内容

int main(int argc, char const *argv[])
{
         namelist head = {NULL,NULL};//头结点
         namelist *phead = &head;//头结点的指针
         CreatHeadNode(phead,"张三");
         CreatHeadNode(phead,"李四");
         CreatHeadNode(phead,"王五");
         PrintfNode(phead);//打印链表
         printf("==========结点个数count=%d==========\n",CountNode(phead));//打印此时结点个数
         CreatHeadNode(phead,"托马斯");
         CreatTailNode(phead,"詹姆斯");
         CreatTailNode(phead,"约翰逊");
         PrintfNode(phead);//打印链表
         printf("==========结点个数count=%d==========\n",CountNode(phead));//打印此时结点个数
         DeleteHeadNode(phead);
         PrintfNode(phead);//打印链表
         printf("=======删除头结点后结点个数count=%d==========\n",CountNode(phead));//打印此时结点个数
         DeleteTailNode(phead);
         PrintfNode(phead);//打印链表
         printf("=======删除尾结点后结点个数count=%d==========\n",CountNode(phead));//打印此时结点个数

          return 0;
}

9.删除链表

那么我们怎样删除整个链表呢?学习了头删和尾删发,那么删除链表就很容易了,我们写一个循环判定,只要链表一直空,我们就一直进行头删或者尾删法,头删法的效率高与尾删法,所以运用头删即可。

//删除链表
void DeleteList(namelist *phead)
{
          while (!JudgeNull(phead))
          {
                    DeleteHeadNode(phead);
          }
          printf("已清空链表!\n");        
}

 运行结果

主函数

int main(int argc, char const *argv[])
{
          namelist head = {NULL,NULL};//头结点
          namelist *phead = &head;//头结点的指针
          CreatHeadNode(phead,"张三");
          CreatHeadNode(phead,"李四");
          CreatHeadNode(phead,"王五");
          PrintfNode(phead);//打印链表
          printf("==========结点个数count=%d==========\n",CountNode(phead));//打印此时结点个数
          CreatHeadNode(phead,"托马斯");
          CreatTailNode(phead,"詹姆斯");
          CreatTailNode(phead,"约翰逊");
          PrintfNode(phead);//打印链表
          printf("==========结点个数count=%d==========\n",CountNode(phead));//打印此时结点个数
          DeleteHeadNode(phead);
          PrintfNode(phead);//打印链表
          printf("=======删除头结点后结点个数count=%d==========\n",CountNode(phead));//打印此时结点个数
          DeleteTailNode(phead);
          PrintfNode(phead);//打印链表
          printf("=======删除尾结点后结点个数count=%d==========\n",CountNode(phead));//打印此时结点个数
          DeleteList(phead);
          PrintfNode(phead);//打印链表
          printf("=======删除链表后后结点个数count=%d==========\n",CountNode(phead));//打印此时结点个数
          return 0;
}

10.源代码

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

//结点
typedef struct NodeName
{
          char *name;//数据域
          struct NodeName *next;//指针域
}namelist;

//头插法
int CreatHeadNode(namelist *phead, char *pname)
{
          namelist *NewHeadTmp = NULL;//创建新节点
          NewHeadTmp = malloc(sizeof(namelist));//分配堆空间
          if(NULL == NewHeadTmp)//如果申请空间失败,结束!
          {
                    printf("malloc NewHeadTmp failed\n");
                    return -1;
          }
          NewHeadTmp->next = phead->next;//将旧结点的地址给到新插入结点的地址域
          NewHeadTmp->name = pname;//传入数据
          phead->next = NewHeadTmp;//将新节点的地址给到Phead的地址域

          return 0;
}
//打印链表
void PrintfNode(namelist *phead)
{
          namelist *p = NULL;
          p = phead->next;//让p指向第一个结点
          while (NULL != p)//在p为NULL的时候结束循环
          {
                    printf("%s\n", p->name);
                    p = p->next;//指向下一个结点
          }      
}
//判断链表是否为空
int JudgeNull(namelist *phead)
{
          return (NULL == phead->next);//为空返回1,不为空返回0
}
//计算链表结点个数
int CountNode(namelist *phead)
{
          int count = 0;
          namelist *p = NULL;
          p = phead->next;

          while(NULL != p)//遍历
          {
                    count++;//计数
                    p = p->next;
          }
          return count;//返回结点个数
}
//尾插法
int CreatTailNode(namelist *phead, char *pname)
{

          if(JudgeNull(phead))//如果为空链表
          {
                    CreatHeadNode(phead,pname);
          }
          else
          {
                    namelist *NewTailTmp = NULL;
                    NewTailTmp = malloc(sizeof(namelist));
                    if(NULL == NewTailTmp)
                    {
                              printf("malloc NewTailTmp failed!\n");
                              return -1;
                    }
                    namelist *p = phead ->next;
                    while(NULL != p->next)//遍历
                    {
                              p = p->next;
                    }
                    p->next = NewTailTmp;//将新结点的地址,给到插入前最后一个结点的指针域
                    NewTailTmp->name = pname;//插入数据域
                    NewTailTmp->next = NULL;//将尾插结点的指针域置NULL
          }
          return 0;
}
//删除头结点
void DeleteHeadNode(namelist *phead)
{
          if(!JudgeNull(phead))//如果不为空链表
          {
                    namelist *p = NULL;
                    p = phead->next;//p为删除结点的地址
                    phead->next = p->next;//将删除结点的指针域给到phead的指针域
                    free(p);//删除释放头结点
          }
}
//删除尾结点
void DeleteTailNode(namelist *phead)
{
          if(2 <= CountNode(phead))//超过两个结点
          {
                    namelist *p = NULL;
                    p = phead->next;
                    while(NULL != p->next->next)//遍历到倒数第二个结点
                    {
                              p = p->next;
                    }
                    free(p->next);//释放删除尾结点
                    p->next = NULL;//将现在的最后结点的指针域置NULL
          }
          else//不超过两个结点
          {
                    DeleteHeadNode(phead);
          }
}
//删除链表
void DeleteList(namelist *phead)
{
          while (!JudgeNull(phead))
          {
                    DeleteHeadNode(phead);
          }
          printf("已清空链表!\n");        
}
int main(int argc, char const *argv[])
{
          namelist head = {NULL,NULL};//头结点
          namelist *phead = &head;//头结点的指针
          CreatHeadNode(phead,"张三");
          CreatHeadNode(phead,"李四");
          CreatHeadNode(phead,"王五");
          PrintfNode(phead);//打印链表
          printf("==========结点个数count=%d==========\n",CountNode(phead));//打印此时结点个数
          CreatHeadNode(phead,"托马斯");
          CreatTailNode(phead,"詹姆斯");
          CreatTailNode(phead,"约翰逊");
          PrintfNode(phead);//打印链表
          printf("==========结点个数count=%d==========\n",CountNode(phead));//打印此时结点个数
          DeleteHeadNode(phead);
          PrintfNode(phead);//打印链表
          printf("=======删除头结点后结点个数count=%d==========\n",CountNode(phead));//打印此时结点个数
          DeleteTailNode(phead);
          PrintfNode(phead);//打印链表
          printf("=======删除尾结点后结点个数count=%d==========\n",CountNode(phead));//打印此时结点个数
          DeleteList(phead);
          PrintfNode(phead);//打印链表
          printf("=======删除链表后后结点个数count=%d==========\n",CountNode(phead));//打印此时结点个数
          return 0;
}

运行结果

猜你喜欢

转载自blog.csdn.net/m0_58193842/article/details/128398602