表的连式存储-链表
链表是线性表的一种存储形似。
1.链表的结构特点
值域 | 链域 |
值域(数据域):存储表元素值
链域(指针域):存储后继结点的存储地址(值单向链表)
2.链式存储结构图
首指针(表头指针):指向链表的第一个结点的指针变量。其值为首结点的存储地址。
表尾结点(最后一个结点)的链域值为空(NULL)**^**图中使用这个,表中使用NULL
链表就是v表头指针和一串相继链接的结点的总称
3.单向链表的结点结构的定义
值域 | 链域 |
可以用c语言的结构语言定义
typedef struct linkednode //结点类型
{
int data; // 值域
struct linkednode *next; //链域
}snode,*ptr; //结点类型名snode和指针类型名ptr
ptr hrad,p,q; //定义指针类型变量
类型 struct linkednode * 与类型 ptr 等价
都是指向 snode 的指针类型
4.结点的链接操作
让前驱结点的链域指向后继结点
q->next=p;
唯有对链域的赋值操作才是结点的链接操作,其余形式的同类型指针之间的赋值操作,仅仅是改变指针的指向关系,丝毫不会改变结点的链接关系。
5.指定位置的插入
1.插在表头
将已经创建的指针p插入到现已存在的链域上,并且插入到头部
p->next=head;
head=p;
//修改表头指针
2.插在表中
将已创建的指针p插入到现已存在的链域上,并且插入到表中,其中是将指针p插入到指针q后面
p->next=q->next;
q->next=p;
6.指定位置的删除
1.删除表头
q=head;
head=head->next;
free(q);
2.删除表中结点
p=q->next;
q->next=p->next;
free§;
7.链表的特点
- 结点地址不连续。
- 插入/删除不移动结点,耗时为o(1)。
- 用于动态管理
核心:
- 使用指向结点(结构类型)的指针。
- 执行期间,调用动摇存储管理函数产生结点、回收结点。
8.向前插入法造表
链表的构造过程,其实就是反复插入的过程,从空链表开始,一个一个地想表中插入数据元素即可。
构造链表的通用算法
- 构造“空链表”
- 读入第一个元素值
- 当读入的元素值不是”输入结束标记“时,循环执行步骤4~7
- 申请一个新结点
- 将读入的元素存入新结点的值域
- 将新结点“插在”链表的表头处
- 读入“下一个”元素值,转步骤3
- 构造完成,返回首指针
ptr createlindedA()
{
ptr head,p;int x;
head=NULL;//将表头指针置空
scanf("%d",&x);//读入第一个元素
while(x!=End_elm)//当读的不是结束标记是循环
{
p=(ptr)malloc(sizeof(snode));//申请一个存储节点
p->data=x;//置结点的值域
p->next=head;//插在表头处
head=p;//表头指针指向新结点
scanf("%d",&x);//读入下一个元素
}
return(head);//返回表头指针
}
9.向后插入法
构造链表的通用算法
- 构造“空链表”
- 读入第一个元素值
- 当读入的元素值不是”输入结束标记“时,循环执行步骤4~7
- 申请一个新结点
- 将读入的元素存入新结点的值域
- 将新结点“插在”链表的表尾处
- 读入“下一个”元素值,转步骤3
- 构造完成,返回首指针
ptr createlindedA()
{
ptr head,last,p;int x;
head=NULL;//将表头指针置空
scanf("%d",&x);//读入第一个元素
while(x!=End_elm)//当读的不是结束标记是循环
{
p=(ptr)malloc(sizeof(snode));//申请一个存储节点
p->data=x;//置结点的值域
if (head==NULL)
{p->next=head;head=p;last=p;}
else
{last->next=p;p->next=NULL;laxt=p}
scanf("%d",&x);//读入下一个元素
}
p=head;head=head->next;free(p);
return(head);//返回表头指针
}
修改代码
ptr createlindedA()
{
ptr head,last,p;int x;
head=last=(ptr)malloc(sizeof(snode));
last->next=NULL;
scanf("%d",&x);//读入第一个元素
while(x!=End_elm)//当读的不是结束标记是循环
{
p=(ptr)malloc(sizeof(snode));//申请一个存储节点
p->data=x;//置结点的值域
last->next=p;
p->next=NULL;
laxt=p
scanf("%d",&x);//读入下一个元素
}
p=head;head=head->next;free(p);//删除辅助头结点
return(head);//返回表头指针
}
向前向后插入法的比较:
- 空链表的形式不同
- 插入部位不同
- 使用工作指针的个数不同
- 监督元的处理不同
- 结点排列次序不同
10.单向简单链表的输出
void outlinkA(ptr p)//p是其实指针
{
while(p!=NULL)
{
print("%5d",p->data); //输出结点值
p=p->next; //滑动指针
}
printf("\n");
}
11.单向简单链表的查找
void outlinkA(ptr p,int x)//p是其实指针
{
while(p!=NULL)
{
if(p->data==x)return p; //找到x,返回p
p=p->next; //滑动指针
}
return NULL; //查找不成功,返回空指针
}
12.自己的理解
注:
结点名:a,b,c
结点的值域:1,2,3,4
结点的链域:b,c,d,e
^表示null
讲解:
a->next的值为b结点的存储id
a->data的值为他的值域是1
f->next的值为空,是NULL。
实例一:已知b和c结点,然后删除c结点
b->next=c->next;
**意义:**根据上面的讲解,因为b的链域存的是c,要删除c我们只需要将b的链域改为c的链域值即可,也可以怎么理解,将原本放在b链域中的值改为我们想要的某个结点值。
实例二:在bc中间添加结点为p的结点
出于安全,我们应该先将p链接到c上,然后在将b连接到p上
p->next=c;
b->next=p;
当然,如果不按照我上面这个走,还是可以按照自己的方法写,这里举一个例子
b->next=p;
p->next=c;
难理解点:
b->next=c;
b->next=c->next;
上面这两个是完全不相同的概念,
第一个是把c结点作为b结点的链域,而第二个是把c结点的链域作为b结点的链域,意思是删除c结点。