C语言线性链表
什么是线性链表?
链式存储的通俗解释:
首先先介绍一下链式存储,链式存储就是当C语言储存数据时,数据不是在内存上一个挨着一个存储的,而是跳跃着存储的,在存储一个数据时,同时也存着下一个数据的地址(就是告诉你下一个数据在哪)。
什么是线性呢:
一根线上的一小段前后都只有一个链接的地方,在数据中的线性的意思就是一个数据,前面和他直接有关的只有一个数据,后面和他直接有关也只有一个数据,数据像穿成了一条线一样。
线性链表也叫单向链表。
线性链表的实现
一.链表中每一个元素是如何定义的
定义:结构体和指针/自引用结构
struct node{
ElemType data;
struct node *link;
};
说明:
结构体里面的元素:
第一行elemtype是数据类型,data是变量名,
第二行是一个指针,这个指针的类型是指向一个这样结构体的指针。这就是一个自引用结构体,也就是结构体里的一个指针,这个指针指向的数据是由他所在的结构体定义的。
建议使用typedef来定义这个结构体的名字和结构体类型指针的名字。
struct node{
ElemType data;
struct node *link;
}
typedef struct node Node;
typedef struct node* Nodeptr;
Nodeptr list,p;
说明:
最后一行就是定义了两个指向struct node类型结构体的指针。
几种定义结构体的方法:
struct student{
int id;
char *name;
struct student* link;
};
struct student a1,a2;
这是最简单的一种方法,定义一个结构体类型,然后定义两个变量a1,a2。
方法二:
struct student{
int id;
char *name;
struct student *link;
};
typedef struct student Student;
typedef struct srudent* Studentptr;
Student a1,a2; //定义两个变量
Studentptr p1,p2; //定义两个指针
说明:这就是上文建议使用的typedef,他就是把这个数据结构体的名字用你自定义的符号代替。比如合理Student就是代替struct student;
倒数第二行定义了两个变量,a1,a2;
最后一行定义了两个指向这种结构体的指针,p1,p2;
二. 创建一个链表
我们需要考虑的就是创建元素(结构体),在元素里面输入数据,把元素连接起来形成链表;
Noteptr createList(int n)
{
Nodeptr p,q,list=NULL;
int i;
for(i=0;i<n;i++)
{
q=(Nodeptr)malloc(sizeof(Node));
q->data=read();
q->link=NULL;
if(list==NULL)
list=p=q;
else
p->link=q;
p=q;
}
return list;
}
代码的理解:
```c
Nodeptr createList(int n)//定义含有n个元素的链表
{//p是链表最后一个元素,q是新增的,list是第一个
Nodeptr p,q,list=NULL;
int i;
for(i=0;i<n;i++)
{
p=(Nodeptr)malloc(sizeof(Node));//开空间
p->data=read();//附上数据
p->link=NULL;//指针初始化
if(list==NULL) //如果链表为空
list=p=q;
else //把q连进链表
p->link=q;
p=q; //把q连在p之后,p就不是组后一个了,q是最后一个
//把p赋给q,让p再变成最后一个
}
return list;
}
总结一下,三步走战略
新增元素,空间数据和链接
链接元素
控制末尾
线性表的操作
一. 求链表的长度
中心思想:遍历,计数
int getlength(Nodeptr list)
{
Nodeptr p;
int n=0;
for(p=list;p!=NULL;p=p->link)
n++;
return n;
}
代码解释:
int getlength(Nodeptr list)//传进来需要计数的链表
{
Nodeptr p;//创造一个结点用来遍历
int n=0; //计数器
for(p=list;P!=NUll;p=p->link) //让p指向下一个结点
n++; //循环用来计数,如果不是最后一个就加1
return n; //返回元素的个数
}
总结一下:三步走:
结点,计数器,遍历
二. 插入
1.在第一个节点前插入
list=insertFirst(list,item);
Nodeptr insertFirst(Nodeptr list,Elemtpye item)
{
Nodeptr p;
p=(Nodeptr)malloc(sizeof(Node))
p->data=item;
p->link=list;
return p;
}
list=insertFirst(list,item)
//传入的时候,相当于传入了指针的值,无法改变值,所以return p
Nodeptr insertFirst(Nodeptr list,Elemtype item)
{
Nodeptr p;
p=(Nodeptr)malloc(sizeof(Node));
p->data=item;
p->link=list;
return p;
}
2.在p指针所指的结点之后插入一个数据
void insertNode(Nodeptr p;ELemType item)
{
Nodeptr q;
q=(Nodeptr)malloc(sizeof(Node));
q->data=item;
q->link=p->link;
p->link=q;
}
这个不用把list传进来,因为p是个指针,直接对p下手就行,不涉及遍历和链表中的其他元素。
3.在第n个结点后面插入
这个就可以看出来跟上一个不一样,需要传进来list来数出第n个节点
void insertNode(Nodeptr list;int n;ELemType item)
{
Nodeptr q,p=list;
int i;
for(i=1;i<=n-1;i++){
if(p->link==NULL)
break;
p=p->link;
}
q=(Nodeptr)malloc(sizeof(Node))
q->data=item;
q->link=p->link;
p->link=q;
}
出循环的时候p指向的是第n个结点
4.有序链表中,插入一个结点
比如递增链表
list=insertNode(list,tiem);
Nodeptr insertNode(Nodeptr list,ElemTpye item)
{
Nodeptr p,q,r;
r=(Nodeptr)malloc(sizeof(Node));
r->data=item;
r->link=NULL;
if(list==NULL)
return r;
for(p=list;item>p->data&&p!=NULL;q=p,p=p->link)
;
if(p==list){
r->link=list;
return r;
}
else{
q->link=r;
r->link=p;
}
return list;
}
代码理解:
list=insertNode(list,item);
Nodeptr insertNode(Nodeptr list,ElemType item)
{//p是r后面的元素,q是r前面的元素。
Nodeptr p,q,r;
r=(Nodeptr)malloc(sizeof(Node))
r->data=item;
r->link=NULL;
if(list==NULL) //如果list中还没有元素
return r;
for(p=list;item>p->data&&p!=NULL;q=p,p=p->link)
;
if(p==list){
r->link=p;
return r;
}
else{
q->link=r;
r->link=p;
}
return list;
}
三. 删除
- 从非空线性表中删除p指向的连接点
a.假设r是p的前驱节点(前面的那一个结点)
Nodeptr deletep(Nodeptr list,Nodeptr r,Nodeptr p)
{
if(p==list)
list=p->link;
else
r->link=p->link;
free(p);
return list;
}
提示:一定要考虑要删除的结点是第一个结点这种情况。
b.我们不知道p的前驱指针时,就要先找到p的前驱指针,再对p下手;
Nodeptr deletep(Nodeptr list,Nodeptr p)
{
Nodeptr r;
if (p==list){
list=p->link;
free(p);
}
else{
for(r=list;r->link!=p&&r->link!=NULL;r=r->link)
;
if(r->link!=NULL){
r->link=p->link;
free(p);
}
}
return list;
}
要注意的是,p结点不一定在这里,所以for循环里的判断和后面的条件语句都要特别注意。
删除的时候不要忘记free!!!
链表中注意事项总结:
1.上一个结点
2.插入到第一个元素
3.链表为空
4.一个节点后面第k个节点需要将p=p->link执行k-1遍。
5.传入list如果list变了的话,在函数中是没有办法修改的,所以要return地址。
6.涉及到查找的操作(查找删除)一定要考虑没找到这种情况。
总结:首,无,空(三个特殊情况)