数据结构——链表的基本操作
Posted ABded
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构——链表的基本操作相关的知识,希望对你有一定的参考价值。
文章目录
1.链表的基本概念
- 概念:链表是一种线性的数据结构,通过指针将零散的内存块连接起来,链表的没个内存块成为节点。
- 链表的实现方法:链表以结构体为节点(包含数据域和指针域),利用数据域来存储数据,然后将每个节点的指针域都指向下一个节点,以此来实现数据的存储。
//节点示例:
typedef struct node
int num;//数据域
struct node * next;//指针域
Node;
- 链表的优缺点(相较于数组而言):
优点:可以不断扩展链表的长度,删除和插入操作比较方便。
缺点:创建,查找操作较为麻烦。 - 图示:
说明:上图所示的链表是一个无头节点的单链表,关于链表有无头节点的问题我们会在3.3.1中讨论。
2.创建链表
2.1尾插法
-
操作:将新节点不断地插入链表的末尾从而来创建链表。
-
特点:数据的输入顺序与链表的存储顺序相同。
-
代码示例:
Node * create()
Node *head,*new,*end;
head=end=(Node *) malloc(sizeof(Node));
int x;
while(scanf("%d",&x)==1&&x!=-1)//当输入-1时循环结束
//准备新节点
new=(Node *) malloc(sizeof(Node));
new->num=x;
new->next=NULL;
end->next=new;//将新节点连接到链表上
end=new;//更新尾结点
return head;
2.2头插法
- 操作:每次都会将新节点插在头结点之后。
- 特点:数据的输入顺序与链表的存储顺序相反。
- 代码示例:
Node * create()
Node *head,*new;
head=(Node *) malloc(sizeof(Node));
head->next=NULL;//保障末尾节点的指针域指向NULL
int x;
while(scanf("%d",&x)==1&&x!=-1)
//准备新节点
new=(Node *) malloc(sizeof(Node));
new->num=x;
new->next=head->next;
//将新节点连接到链表上
head->next=new;
return head;
3.链表的遍历及其增删改查
3.1链表的遍历
- 思路:先定义一个节点让它等于链表的第一个节点,再在遍历时不断对其进行判断,若为NULL则链表到头了应结束循环,若不为NULL则该节点等于链表的下一个节点并继续循环。
- 代码示例:
Node *head=create();
Node *q=head->next;
while(q!=NULL)
printf("%d ",q->num);
q=q->next;//移动节点
3.2增加新节点
- 思路:遍历链表找到要插入位置的前一个节点,操作指针插入新节点即可.
- 代码示例:
void insert(Node ** head,int number,int data)
int i=0;
Node *q=(Node *) malloc(sizeof(Node));
q->num=data;
Node *p=head;
while(p!=NULL)
if(i==number)
q->next=p->next;
p->next=q;
break;
else
i++;
p=p->next;
说明:该函数第一个参数是链表头的地址,第二个参数是要插入的位置,第三个参数是新节点的数据。而第一个参数之所以是链表头的地址而非是链表头是因为:假若你想要插入到第0个节点之后(第一个节点之前),那么就要操作头结点的指针了,为了使在函数中的操作能影响到链表头,所以传入的是链表头的地址。
3.3删除节点
3.3.1按位删除节点
- 思路:删除节点的关键在于找到要删除节点的前驱节点,然后通过改变前驱节点的指针域来完成删除操作。
- 代码示例:
void delete(Node ** head,int number)//与3.2同理
int i=1;
Node *p=head,*q=p->next;
while(q!=NULL)
if(number==i)
p->next=q->next;
free(q);//释放内存
break;
else
i++;
p=q;
q=q->next;
说明:这里我们就要讲解一下链表有无头结点的区别了,从开始到现在我们使用的都是有头结点的链表,而头结点一般是不存放数据的,那为什么还要有头结点呢?
事实上头结点的存在是为了方便我们操作链表的,就比如说删除节点的这一操作:假设我现在要删除的就是第一个节点,那么按照思路我么也是要先找到它的前驱节点的,而倘若是无头节点的链表,第一个节点前也就不存在节点;而倘若是有头结点的链表就会轻而易举的得到它的前驱节点了。
3.3.2按数据删除节点
- 思路:和3.3.1一样的原理,不过这里我们要注意:我们删除的可能不止一个节点,而当我们删除了一个节点之后,我们维系的前后指针关系就会被破坏,因此每当我们删除一个接点之后要及时恢复前后指针的关系。
- 代码示例:
void delete(Node ** head,int number)
Node *p=head,*q=p->next;
while(q!=NULL)
if(q->num==number)
p->next=q->next;
free(q);
q=p->next;//恢复前后节点的关系
else
p=q;
q=q->next;
3.4修改数据
- 思路:遍历链表找到需要改变数据的节点修改其数据即可。
- 代码示例:相信你自己一定能够实现,我这里就省略了。
3.5查找数据
- 思路:遍历链表根据条件进行查找即可。
- 代码示例:略.
4.链表的升序合并,冒泡排序,逆置
4.1升序合并
- 思路:定义两个指针指向两个链表的首节点,再拿这两个节点进行比较,找出较小的节点存储起来,然后指向该链表的指针后移一位。重复上述操作,直到有一个指针为空,再将另一个链表中的剩余节点补在已存储节点之后即可。
- 图示:
- 代码示例:
//由于思路中有重复操作的思想所以这里采用递归的方法实现
Node * merge(Node *head1,Node *head2)
if(head1==NULL)
return head2;
else if(head2==NULL)
return head1;
else
if(head1->num<head2->num)
head1->next=merge(head1->next,head2);
return head1;
else
head2->next=merge(head1,head2->next);
return head2;
注意:该函数的两个参数均为无头结点的链表,返回值也是一个无头节点的链表。
4.2冒泡排序
- 思路:与数组的冒泡排序思想基本一致,不过这里在做交换时,可以交换节点也可以交换数据,其中最容易出错的就是交换节点了。废话不多说直接上代码。
- 代码示例(交换节点):
void sort(Node * head)
Node *p,*prep,*tail;//p为当前节点,prep为p的前驱节点,tail控制循环的深度
tail=NULL;
while(head->next!=tail)
prep=head;
p=head->next;
while(p->next!=tail)
if(p->num>p->next->num)
//这里只需先删除p节点,再往p->next后插入一个p节点即可
prep->next=p->next;
p->next=p->next->next;
prep->next->next=p;
//当你交换了节点之后prep和p的前后关系就被打破了,这里要做调整
prep=prep->next;
continue;
p=p->next;
prep=prep->next;
tail=p;//p之后的节点已为有序
4.3逆置
- 思路:我们需准备好当前节点的前驱结点以及一个用于暂存的节点,我们可以用暂存节点暂存当前节点的下一节点,然后将当前节点指向前驱结点,前驱结点后移,再利用暂存节点继续进行循环直至链表结束。
- 代码示例:
Node * reverse(Node *head)
Node *pre,*temp;
pre=NULL;
while(head!=NULL)
temp=head->next;//暂存下一节点
head->next=pre;//将当前节点指向前驱节点
pre=head;//前驱节点后移
head=temp;//利用暂存节点继续进行循环
return pre;
注意:该函数的参数为无头节点的链表,返回的也是一个无头节点的链表。
以上是关于数据结构——链表的基本操作的主要内容,如果未能解决你的问题,请参考以下文章