数据结构——链表的基本操作

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;


注意:该函数的参数为无头节点的链表,返回的也是一个无头节点的链表。

以上是关于数据结构——链表的基本操作的主要内容,如果未能解决你的问题,请参考以下文章

数据结构——双向链表的实现

数据结构——链表的基本操作

五双向链表

大话数据结构Java程序——双向链表的实现

[数据结构]双向链表(C语言)

什么叫带头结点的链表? 什么叫不带头结点的链表?