数据结构学习笔记——线性表的链式存储结构

继续学习数据结构:
前面的线性表的顺序存储结构,它是有缺点的,最大的缺点就是删除和插入时需要移动大量元素,明显需要消耗打开量时间。为了解决这种问题,引入了线性表的链式存储结构。
链表中有两个易混的概念:
(1)头指针:头指针是指向第一个节点的指针
(2)头结点:头结点是第一个节点之前的节点,这个节点中只有指针域有值,且值是第一个节点的地址,它的数据域中无值。
1.下面首先是定义节点的数据类型:

typedef struct Node
{
ElemType data;
struct Node *next;
}Node;
typedef struct Node *LinkList;

注意:这里面处理的细节:
(1)定义的同时改名为Node。
(2)更关键的是下面的语句typedef struct Node *LinkList;是指将struct Node * 型的数据类型改名为LinkList,所以后面定义此节点数据类型的指针节可以直接用LinkList来定义,就像定义节点数据类型时,可以用Node来定义一样。
(3)注意,如果写成下面这种形式也是一样的:

typedef struct Node
{
ElemType data;
struct Node *next;
}Node,*LinkList;

2.单链表的读取

//获得链表第i个数据的算法 
//初始条件:顺序线性表L已存在,1<=i<=ListLength(L) 
//操作结果:用e返回L中第i个数据元素的值
Status GetElem(LinkList L,int i,ElemType *e)
{
int j;
LinkList p;
p=L->next;//L是头节点,则此时p指向表中第一个节点。
j=1;
while(p&&j<i)
{
 p=p->next;
 j++;
}
if(!p||j>i)
{
 return ERROR;
}
*e=p->data;
return OK; 
} 

解析:
①函数参数中的L是:LinkList L 来的,注意这种表示L其实是头结点,所以p=L->next 就使得p指向第一个节点。
②在下面的循环中,注意j和p的对应关系,j初始值为1,p在循环开始时也指向的是第一个节点。然后每次循环都会使得j加1,使得p指向下一个节点。所以j和p是一一对应的。
③因此j时用来计数的,用在while循环中,判断语句是

while(p&&j<i)
...

即是p不为空且计数器j还没有到i时,循环都会继续
④然后,

if(!p||j>i)
 {
  return ERROR;
 }

表示循环完整个链表,没有达到i,也就是i的值不正确,i小于1或i大于总数据个数,就报错。
⑤最后,

 *e=p->data;

如果在上面找到了正确的第i个节点,就用e返回这第i个节点的数据。
3.单链表的插入和删除

//单链表的插入
//初始条件,顺序线性表L已存在,1<=i<=ListLength(L)
//操作结果,在L中第i个节点位置之前插入新的数据元素e,L的长度加1
Status ListInsert(LinkList *L,int i,ElemType e)
{
 int j;
 LinkList p,s;
 p=*L;
 j=1;'
 while(p&&j<i)
 {
  p=p->next'
  j++;
 }
 if(!p||j>i)
 {
  return ERROR;
 }
 s=(LinkList)malloc(sizeof(Node));
 s->data=e;
 s->next=p->next;
 p->next=s;
 return OK;
} 

解析:
①难理解的是此处对L的定义是:LinkList *L ,则L是一个二级指针,并且这里可以从下面一句 p=*L;且说明了此时p指向头结点,所以L原来一定是指向头结点的指针的指针。
②注意这里p与j的对应关系变了:p指向头结点,可以看成是指向第0个节点,而 j 初始值还是1,同时每次循环p还是会指向下一个,j还是会加1,所以当 j 达到 i 时,p指向的就是第i-1个节点。
③为什么要使p指向的是第i-1个节点呢?主要是因为这里使用的是尾插法:p指向的是第i-1个元素,那么将新的节点s插在p后面,就是插在了第 i 个位置上。
④当然还有前插,在我之前的管理系统里面用的插入都是用的前插,这里我也说明,好将两种方法进行比较。

Status ListInsert(LinkList *L,int i,ElemType e)
{
 int j;
 LinkList h,q,p,s;
 h=*L;q=*L;p=q->next;
 j=1;
 while(p&&j<i)
 {
  q=p;
  p=p->next;
  j++;
 }
 if(!p||j>i)
 {
  return ERROR;
 }
 s=(LinkList)malloc(sizeof(Node));
 s->data=e;
 s->next=p; 
 q->next=s;
 return OK;
}

简单来说就是定义一个q始终指向当前节点p的前一个节点;且p是从第一个节点开始进入循环的,p和j就是一一对应的,j 等于 i 的时候,p就指向的是第 i 个节点;核心部分 s->next=p; q->next=s;很容易理解,不多阐述。

//单链表的删除
//初始条件:顺序线性表L已存在,1<=i<=ListLength(L)
//操作结果:删除L中的第i个节点,并用e返回其值,L的长度减1
Status ListDelete(LinkList *L,int i,ElemType *e)
{
 int j;
 LinkList p,q;
 p=*L;
 j=1;
 while(p->next&&j>i)
 {
  p=p-next;
  j++; 
 }
 if(!(p->next)||j>i)
 {
  return ERROR;
 }
 q=p->next;
 p->next=q->next;
 *e=q->data;
 free(q);
 return OK;
} 

解析:
①注意p还是如上面插入时一样的操作,所以p在执行了语句p=*L;后就是指向的头结点。
②循环体while的判断语句:

 while(p->next&&j>i)

用的是p->next,则p->next开始指向的是第一个节点,j 开始也是等于1,所以j 就是与p->next同步变化的,所以当j 等于 i 时,p->next 就指向的是第i 个节点,所以p就指向的是第i-1个节点。
③下面的实现删除的核心部分

q=p->next;
 p->next=q->next;

可以比较我之前在管理系统当中用的方法

q->next=p->next;
free(p);

4.单链表的整表创建:

//单链表的整表创建
//随机产生n个元素的值,采用头插法,建立带头节点的单链线性表L 
void CreateListHead(LinkList *L,int n)
{
 LinkList p;
 int i;
 srand(time(0));
 *L=(LinkList)malloc(sizeof(Node));
 (*L)->next=NULL;
 for(i=0;i<n;i++)
 {
  p=(LinkList)malloc(sizeof(Node));
  p->data=rand()%100+1;
  p->next=(*L)->next;
  (*L)->next=p;
 } 
} 

解析:
①产生1001以内的随机数的方法:

 srand(time(0));
 p->data=rand()%100+1;

注意需要引入的头文件包括两个:

#include<stdlib.h>
#include<math.h>

②这里使用的是头插法:每一个新的节点进来都插在头结点之后,都是成为第一个节点。
③链表创建时当然还有尾插法,我之前的管理系统当中创建链表时用的方法就是尾插法,如下
尾插法(1):与上面头插法出自同一本书,以L为头指针的尾插法

void CreateListTail(LinkList *L,int n)
{
 LinkList p,r;
 int i;
 srand(time(0));
 *L=(LinkList)malloc(sizeof(Node));
 r=*L;
 for(i=0;i<n;i++)
 {
  p=(Node *)malloc(sizeof(Node));
  p->next=rand()%100+1;
  r->next=p;
  r=p;
 }
 r->next=NULL;
}

注意:这里面和下面的方法中,唯一的不同就是头结点的表示方法不同,这里因为L是由LinkList * 定义来的,而LinkList 本身又是节点数据类型的指针,是Node * 型的,所以L就是Node型的二级指针,所以*L就表示的是一个节点的指针,语句

 *L=(LinkList)malloc(sizeof(Node));

就是在为头结点开辟空间。
尾插法(2):我自己之前管理系统当中的

JD *h,*q,*p;
q=(JD *)malloc(sizeof(JD));
h=q;
while(1)
{
 p=(JD *)malloc(sizeof(JD));
 p->data=rand()%100+1;
 q->next=p;
 q=p;
 
 //注意添加跳出循环条件
}
q=0;
return h;

可以将这两种方法进行比较,并注意这里返回的是头结点自身的指针,是JD * 型的指针,相当于这里的LinkList 型。如果这种建立链表时,返回的是JD **型,或者说是这里的LinkList *型的,那么在之前的插入和删除两个操作中使用到的 p=*L; 就能够正确理解,p确实是指向的头结点了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值