链表的基本概念
链表的基本概念
在链表存储中,每个结点不仅包含所存元素的信息,还包含元素之间逻辑关系的消息,,如单链表中前驱结点包含后继结点的地址信息,这样就通过前驱结点中的地址信息找到后继结点的位置。
链表的示意图如图所示:
如上图所示,4个房间是散落存在的,每个房间的右边有走向下一个房间的方向指示箭头。因此,如果想要访问最后一个房间,就必须从第一个房间开始,就必须从第一个房间开始,依次走过前3个房间,才能来到最后一个房间,而不能直接找出最后一个房间的位置,即链表不支持随机访问。
如上图所示,链表中的每一个结点需要划出一部分空间来存储指向下一个结点位置的指针,因此链表中结点的存储空间利用率较顺序表稍低一些。
如上图所示,链表的结点可以散落在内存中的任意位置,且不需要一次性地划分所有结点所需地空间给链表,而是需要几个结点就临时划分几个。由此可见链表支持存储空间地动态分配。
如上图所示,如果想要在第一个和第二个房间之间插入一个新房间,则只需改动房间后边的方向指示箭头即可,将第一个房间的箭头指向新插入的房间,然后将新插入的房间的箭头指向第二个房间,即在链表中进行插入操作无须移动元素。
单链表的基本概念
单链表:在每个结点中除了包含数据域外,还包含一个指针域,用以指向其后继结点。
如下图所示为带头结点的单链表中
带头结点的单链表中,头指针Head指向头结点,头结点的值域不含任何信息,从头结点的后继结点开始存储数据信息。头指针Head始终不等于NULL,Head->next等于NULL的时候,链表为空。
如下图所示为不带头结点的单链表中
不带头结点的单链表中:的头指针head直接指向开始结点,,即图中的结点1,当Head等于NULL的时候,链表为空。
注意:在题目中要区分头指针和头结点,不论是带头结点的链表还是不带头结点的链表,头指针都指向链表的第一个结点,而头结点是带头结点的链表中的第一个结点,只作为链表存在的标志。
单链表的结点定义
typedf struct Lnode
{
int data;//data中存放结点数据域(默认是int型)
struct Lnode *next;//指向后继结点的指针
}Lnode;//定义单链表结点类型
说明:结点是内存中一片由用户分配的存储空间,只有一个地址来表示它的存在,没有显式的名称,因此我们会在分配链表结点空间的时候,同时定义一个指针,来存储这片空间的地址,(这个过程通俗的讲叫指针指向结点),并且常用这个指针的名称来作为结点的名称。
例如下面代码:
Lnode *A =(Lnode *)malloc(sizeof(Lnode));
用户分配了一片Lnode型空间,也就是构造了一个Lnode型的结点,
这时候定义一个名字为A的指针来指向这个结点,同时我们把A也当作这个结点的名字。
注意,这里A命名了两个东西,一个是结点,另一个是指向这个结点的指针。
对单链表的操作
(1)不带头结点的尾插法
用尾插法建立链表(不带头结点的)
主要参考此图来写代码
代码如下:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
typedef struct lnode
{
int data;
struct lnode *next;
}lnode;
int main (int argc, char **argv)
{
lnode *hand=NULL;//定义一个头指针
lnode *new_lnode;//定义一个新节点的指针,用来指向malloc分配的地址
int i;
lnode *tail;//定义一个新的指针,始终指向终端节点
for(i=0; i<10; i++)//循环申请10个节点来存放数字1~10
{
new_lnode = (lnode *)malloc(sizeof(lnode));//new_lnode指向新申请的节点
memset(new_lnode, 0, sizeof(lnode));//对新申请的节点进行内存清
new_lnode->next=NULL;//将节点的next域清0,这句可以省略
new_lnode->data=i+1;//对节点data域赋值
if( hand == NULL)//判断头指针是否为空,如果为空,链表为空,如果不为空,让hand指向第新申请的节点
{
hand=new_lnode;
}
else//如果头指针不为空,就让tail指向新申请的节点
{
tail->next = new_lnode;//新申请的tail指针,始终指向终端节点
}
tail = new_lnode;//tail始终为终端节点。
}
lnode *search=NULL;//定义一个新的指针,来遍历链表
for(search=hand;search!=NULL;search=search->next)
{
printf("printf lnode %d\n", search->data);
}
return 0;
}
代码运行结果如下
(2)不带头结点的头插法
用头插法建立链表(不带头结点的)
对不带头节点的(如下图)操作
代码如下:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
typedef struct lnode
{
int data;
struct lnode *next;
}lnode;
int main (int argc, char **argv)
{
lnode *hand=NULL;//定义一个头指针
lnode *new_lnode;//定义一个新节点的指针,用来指向malloc分配的地址
int i;
for(i=0; i<10; i++)//循环申请10个节点来存放数字1~10
{
new_lnode = (lnode *)malloc(sizeof(lnode));//new_lnode指向新申请的节点
memset(new_lnode, 0, sizeof(lnode));//对新申请的节点进行内存清
new_lnode->next=NULL;//将节点的next域清0,这句可以省略
new_lnode->data=i+1;//对节点data域赋值
if( hand == NULL)//判断头指针是否为空,如果为空,链表为空,如果不为空,让hand指向第新申请的节点
{
hand=new_lnode;
}
else
{
new_lnode->next = hand;//头指针始终指向新申请的节点的指针域
hand = new_lnode;//hand指向新节点
}
}
lnode *search=NULL;//定义一个新的指针,来遍历链表
for(search=hand;search!=NULL;search=search->next)
{
printf("printf lnode %d\n", search->data);
}
return 0;
}
运行结果如下:
通过上面写无头结点的头插法和尾插法,我们可以发现他们变化的地方是:尾插法定义了一个新的tail指针,用来指向申请新节点的最后一个,而头插法,没有定义,直接让头指针,指向每次新来的节点。可以看以下如下图的两个对比
(3)带头结点的尾插法
接下来我们将要写一下带头结点的链表的尾插法
用尾插法建立链表(带头结点的)
头结点的链表图
代码如下:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
typedef struct lnode
{
int data;
struct lnode *next;
}lnode;
int main (int argc, char **argv)
{
lnode *hand=NULL;//定义一个头指针
lnode *new_lnode;//定义一个新节点的指针,用来指向malloc分配的地址
int i;
lnode *tail=NULL;
lnode *node;//定义一个新的指针,指向头节点的
node=(lnode *) malloc(sizeof(lnode));
memset(node,0 ,sizeof(lnode));
node->next=NULL;
tail=hand=node;//hand指向头结点,因此此时头节点就是终端节点
for(i=0; i<10; i++)//循环申请10个节点来存放数字1~10
{
new_lnode = (lnode *)malloc(sizeof(lnode));//new_lnode指向新申请的节点
memset(new_lnode, 0, sizeof(lnode));//对新申请的节点进行内存清0
new_lnode->next=NULL;//将节点的next域清0,这句可以省略
new_lnode->data=i+1;//对节点data域赋值
tail->next=new_lnode;//用tail来指向新的节点
tail=tail->next;//tail指向终端节点
}
lnode *search=NULL;//定义一个新的指针,来遍历链表
for(search=node;search!=NULL;search=search->next)//最后遍历的结果为0~1,因为头节点的data为0
{
printf("printf lnode %d\n", search->data);
}
return 0;
}
代码运行结果如下:
(4)带头结点的头插法
接下来我们将要写一下带头结点的链表的头插法
用头插法建立链表(带头结点的)
头结点的链表图
代码如下:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
typedef struct lnode
{
int data;
struct lnode *next;
}lnode;
int main (int argc, char **argv)
{
lnode *hand=NULL;//定义一个头指针
lnode *new_lnode;//定义一个新节点的指针,用来指向malloc分配的地址
int i;
lnode *node;//定义一个新的指针,指向头节点的
node=(lnode *) malloc(sizeof(lnode));
memset(node,0 ,sizeof(lnode));
node->next=NULL;
hand=node;//hand指向头结点,因此此时头节点就是终端节点
for(i=0; i<10; i++)//循环申请10个节点来存放数字1~10
{
new_lnode = (lnode *)malloc(sizeof(lnode));//new_lnode指向新申请的节点
memset(new_lnode, 0, sizeof(lnode));//对新申请的节点进行内存清0
new_lnode->next=NULL;//将节点的next域清0,这句可以省略
new_lnode->data=i+1;//对节点data域赋值
new_lnode->next=node->next;//new_lnode所指新节点的指针域next指向头结点开始的节点。
node->next=new_lnode;//头结点的指针域next指向new_lnode节点,使得new_lnode成为新的开始节点。
}
lnode *search=NULL;//定义一个新的指针,来遍历链表
for(search=node;search!=NULL;search=search->next)//最后遍历的结果为0~1,因为头节点的data为0
{
printf("printf lnode %d\n", search->data);
}
return 0;
}
代码结果如下:
单链表的带头结点和不带头结点的头插法和尾插法的代码都已经写完了,大家可以把带头的尾插法和不带头的尾插法联合起来学习,会更好的理解,如果有错误的地方还行指出!谢谢!