单链表的解读
其实定义这种东西在书上,或者百度上都有很多,在这里不多作介绍仅用自己的理解来表达一下我对单链表的理解。
我的理解中单链表分为两个含义,第一个就是链表,第二个时单一。这个单一不是指内容单一,而是指方向单一。
即你拿到一个单链表的某个节点,你只能选择向后寻找,而不能说向前。所以在查询层面上,单链表的友好性不足。
因为你每查找一个节点内容你都时从开始去遍历的。当这个单链表很长的时候效率也会很低
在谈谈链表吧,其实可以理解为一串不连续的箱子,箱子的里面呢放着我们想要存放的物品,同时还有一个很关键
的东西,那就是下一个箱子的位置,根据这个很详细的位置就可以找到下一个箱子的所在位置去拿到下一个箱子里面
的东西。
代码详解
单链表的节点
相信很多人都见过下面这段代码:
那么下面这段代码中,这个a就是上面我们说的放在箱子里的东西,next 就是指向下一个节点的指针,在c语言里
称之为指针。通俗的说就是下一个箱子的地址。
typedef struct ListNode
{
int a;
struct ListNode *next;
}list;
创建节点
在单链表中节点的创建,需要动态申请空间:
其中有两点肯定能够会导致错误,第一点就是很多人写完声明以后发现报错:
error C2065: 'STUDENT' : undeclared identifier
如上是,基本上多出现在Vc++6.0编译器中,造成的原因主要是在声明节点的时候代码类似下面:
ListNode * node = (ListNode*) malloc(sizeof(ListNode));
主要原因就是编译器不认识,解决的方案如下:
struct ListNode * node = (struct ListNode*) malloc(sizeof(struct ListNode));
或者
list* node = (list*) malloc(sizeof(list));
// An highlighted block
int CreateHead(list*head)
{
head = (list*) malloc(sizeof(list));
head->next = NULL;//这一步很关键,如果不把下一节点指针置空,很能在释放的时候越界导致崩溃
return 0;
}
list* CreateNode(int a)//这里面传的值就是我们要传输的数据,如果单链表里面的data是数组或者结构体
//对应的传过来的值也应该是数组或者结构体
{
list *node = (list*)malloc(sizeof(list));
node->a = a;
node->next = NULL;//同样要把下一节点指针置空
return node;
}
插入节点
在我们初始化了头节点和创建了很多节点以后我们怎么把这些数据联系到一起:
在本文中只考虑无序的状态,后面如果有人想知道有序节点怎么创建在单独讲一章排序的内容
在单链表使用中,必须有一个指针指向头节点的地址,一般我们把这个称为链表头。所有的插入、遍历等操做
一般都是从头节点一点一点操做下去
下面展示一些 `内联代码片`。
// An highlighted block
/*
head 链表头
node 需要插入的链表节点
这里面有一个入参需要注意,就是head 这个head可以是链表中的任何一个位置,我写head只是因为我习惯在表头操做
如果你需要在其他位置插入,需要把那个位置的节点地址传进来
int型的出参为了方便扩展,比如 当要求数据唯一的时候我们不清楚这个数据是否会被插入进去,
那么插入的结果可以返回一个不同的值
*/
int AddListNode(list *head,list *node)
{
node->next = head->next;
head->next = node;
/*
我相信很多人跟我一样,当初刚开始学习链表的时候都有过这样的疑问,为啥要这样写,我们还用箱子作比喻
我现在知道head箱子里放着的下一个箱子的地址,但是我现在需要在这个箱子里放上node的地址
同时我不想丢了下个箱子的地址,那我是知道node的箱子里还没放别的箱子的地址,
所以我先把地址在node箱子里也放上一个。
**当然或者比喻不太恰当,以为这里如果是箱子存物品的话。
地址是会被复制的,但实际上在链表里不会。**
同样的,那我head里面也就可以放上我想要放地node的地址了
*/
}
遍历节点
遍历节点这个比较好动,其实就是找到每个箱子把每个箱子里的物体看一遍;
下面展示一些 内联代码片
。
// An highlighted block
//这里做一个假设,head是我们一直要使用的链表头指针,所以我们先从新声明个指针指向他,防止他的位置发生改变
void PrintfListNode(list *head)
{
list * temp = head;
//这里就可以看出来当初我们为啥把每一个新创建出来的指针的next置空的原因
while(temp->next!=NULL)
{
printf("%d",temp->next->a);
temp = temp->next;
}
}
释放节点
释放某一节点和释放所有节点类似也用到了上面遍历的知识:
// An highlighted block
//释放值为x的节点
void FreeListNodeIsA(list *head,int a)
{
//跟遍历同一个道理,我们保存头指针
list *temp;
list *node = NULL;
while(temp->next!=NULL)
{
if(temp->next->a == a)
{
node = temp->next;
temp->next = node->next;//这里temp->next已经指向了原本的temp->next->next所以不需要移动temp的指针
free(node);
}
else
{
temp = temp->next;//没有释放的话就要移动指针
}
}
}
void FreeList(list *head)
{
list *temp ;
while(head->next !=NULL)
{
temp = head->next;
head->next = temp->next;
free(temp);
}
//最后别忘了释放头指针
free(head);
}
结尾
文章这就结束了,也不知道能不能看懂。这篇文章两天前就该发了,但是因为一直不知道这篇文章的定位所以一直
耽搁到现在。
这个系列应该会继续下去,目的不是给已经在工作的兄弟们或者基础比较好的人看的,主要是希望不会的人,
通过我不成熟的讲解能认知这块知识,因为当年自己实在是坑坑巴巴太久了。
所还是想用点业余时间,在给自己巩固基础的同时给朋友们一点启发,如果你们对文章有什么建议的话也欢迎
给我留言,还不是很成熟希望大家多多指教,提提宝贵的建议,同时其他专栏也将同步更新,会写一些自己在工作中,
遇到的坑和解决方案,同时也会写一些自己觉得还用的库的使用方法等等。
总而言之这是这个系列的开篇,希望自己能坚持下去。如果朋友们有什么不懂的可以一起讨论,有c语言相关的想要了解的,
可以在下面留言。学海无涯,与君共勉。