数据结构之线性表——(二、链式存储结构-单链表)
链式存储结构以及基本运算的实现
背景:由于线性表的存储特点是用物理上的相邻实现逻辑上的相邻,他要求用连续的存储单元顺序存储线性表中的各个元素,所以,对线性表中的元素进行插入、删除时要移动元素,很影响运算的效率。而在链式存储结构中,存储单元的地址可连续,也可不连续,这就意味着这些数据元素可以存放在内存中未被占用的任何位置。它不要求逻辑上相邻的两个数据元素物理上也相邻,其逻辑关系是通过“链”建立起数据元素之间的关系,因此对线性表的插入、删除操作时不需要移动数据,提高了效率。
一、单链表
单链表的表示
链表是通过一组任意的存储单元来存储线性表中的数据元素的,每个数据元素(ai),除了存放自身的数据外,还需要存放其后继ai+1所在的存储单元的地址,这两部分信息组成一个“结点”,存放数据元素信息的数据域为data,存放其后继地址的称为指针域next,所以n个数据元素的线性表通过每个结点的指针域连成一条“链”,故称为链表。其结点的结构如下:
注:在这里我们称为单链表,单链表是因为其每个结点中只有一个指向后继的指针,所以称为单链表。
1、单链表的定义
单链表是由一个个结点构成的,使用单链表首先要定义一个结点,也可称为结构体,其定义如下:
typedef struct node
{
DataType data;
struct node *next;
}Lnode,*LinkList;
/*Lnode是结构体别名,用其定义结点如:Lnode n;
但是用 *LinkList定义的是指针。 */
定义头指针变量
LinkList H; //linkList是指向Lnode类型结点的指针类型
在上图中,顺序存储结构的地址是连续的,而且其数据域中的数据也是按照逻辑结构自上而下的。在链式存储结构中,每个结点(node)不仅存储了数据本身(A),还存放了下一个结点的地址(0x5),但头指针中没有存放数据,它存放的是第一个结点(A)的地址。在顺序存储结构里我们将A称为B的直接前驱,B称为A的直接后继,由于是单向链表,在这个链式存储中只有前驱到后继的指针,前驱和后继的关系由指针来链接,也就是说,如果我们要查找后面的元素必须要从头查找。
作为线性表的一种存储结构,我们关心的是结点之间的逻辑结构,而对每个结点之间的实际地址不感兴趣。
单链表分为带头和不带头两种
对于线性表来说,有头有尾,链表也同样,我们把链表中第一个结点存储的位置叫做头指针,通常用“头指针”来标识一个单链表。在上图带头结点单链表示意图中,第一个结点的地址放在了指针变量H中,链表的最后一个结点指针为空(NULL)。
在下图中,假设p是指向线性表第i个元素的指针,则结点ai的数据域用p->data来表示,即p->data的值是一个数据元素,p->data = ai;结点ai的指针域用p->next表示,即p->next是一个指针,(p->next)->data = ai+1。
2、单链表的基本操作
(1)初始化创建单链表
在每次将新节点插入到单链表的尾部时,我们需要加入一个指针 r来始终指向单链表中的为节点。
步骤:
1、初始化,头指针H=NULL,尾指针r=NULL
2、线性表中元素的顺序依次读入数据元素,如果不是结束标志,申请结点。
3、将新节点插入到r所指结点的后面,然后r指向新节点。(第一个结点不同)
LinkList Creat_LinkList()
{
LinkList L = NULL; //头指针L置空
Lnode *s,*r=NULL;
char x,flag='0';
printf("请输入数据,输入0结束\n");
scanf("%d",&x);
while(x!=flag)
{
s = (Lnode *)malloc(sizeof(Lnode)); //申请一个新结点
s->data = x; //将输入的x放在结点的数据域中
if(L==NULL) L = s; //如果头指针为空,将结点s的指针放在头指针中
else r->next = s; //若不为空放在指针r 中
r = s; //r始终指向最后一个结点
printf("请输入数据,输入0结束\n");
scanf("%c",&x);
}
if(r!=NULL)
r->next = NULL; //对于非空表,最后结点的指针域放空指针
return L;
}
(2)按序号查找结点
1、从链表的第一个数据元素结点起,判断当前结点是否是第i个结点。
2、若是第i个结点,则返回,该结点的指针,否则继续下一个,直至结束为止。
3、若没有第i个结点时返回为空。
//按序号查找结点
Lnode *Get_LinkList(LinkList L,int i)
{
Lnode *p =L; //把第一个结点的指针放在p里面
int j=1;
if(i==1) return p;
if(p==NULL) {
printf("表为空");return NULL; }
while((p->next!=NULL) && j<i) //判断当前结点是否是第i个结点
{
p = p->next; ++j;}
if(j==i) return p; //如果是第i个结点,则返回该节点的指针
else return NULL;
}
(3)单链表的插入
设p指向单链表中某结点,s指向待插入的值为x的新节点,将结点s插入到结点p的后面,插入的示意图如下:
- s->next = p->next;
- p->next = s; 先挂链再改链,顺序不能交换
1、查找第i-1个结点,如果找到继续,否则结束
2、申请、填装新节点。
3、将新节点插入,结束。
Lnode * Insert_LinkList(LinkList L,int i,char x)
{
Lnode *p,*s; //定义p s 两个指针
if(i==1) //如果插入的位置是第一个呢
{
s = (Lnode *)malloc(sizeof(Lnode));
s->data = x;
s->next = L; //把老的第一个的地址放进新第一个里面
L = s; return L;//把新第一个的地址放在头指针里面
}
p = Get_LinkList(L,i-1);
if( p==NULL) //第i个结点前面位置不存在不能插入
{
printf("插入位置前面没有元素!\n");
retur