目录
链表的概念
链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针连接次序来实现的。
其中每个结点包含他的数据域和指针域,数据域是来存放这个结点的数据,而指针域存储的是这个结点链接下一个结点的地址。一个个结点互相链接就形成结点
那么怎么让一个个结点链接起来呢,怎么形成一个非连续,非顺序的存储结构呢。我们可以看下图理解
从一个空数据的结点开头,生成新的结点,把想存放的数据存进新结点的数据域,继续将下一个结点的地址存进当前结点的指针域,如果没有下一个,那就将最后这个结点的指针域置(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;
}