数据结构与算法学习笔记-线性表(3)

2.2.2线性表的链式存储

线性表的链式存储结构

链式存储:用一组任意的存储单元存储线性表中的数据元素。用这种方法存储的线性表简称线性链表。

存储链表中结点的一组任意的存储单元可以是连续的,也可以是不连续的,甚至是可以零散分布在内存的任意位置上的。

结点的逻辑顺序和物理顺序不一定相同。

为了正确表示结点间的逻辑关系,在存储每个结点值的同时,还必须存储指示其直接后继结点的地址,称为指针(pointer),这两部分组成了链表中的结点结构。

链表是通过每个结点的指针域将线性表的n个结点按其逻辑次序链接在一起的。

在这里插入图片描述

我们把存储数据元素信息的域称为数据域,把存储直接后继位置的域称为指针域。指针域中存储的信息称为指针或链。这两部分信息组成数据元素称为存储映像,称为结点(Node)。

n个结点链接成一个链表,即为线性表(a1,a2,a3,…,an)的链式存储结构。

因为此链表的每个结点中只包含了一个指针域,所以叫做单链表。

如下图所示:
在这里插入图片描述

我们把链表中的第一个结点的存储位置叫做头指针,最后一个结点指针为空(NULL)。

头指针:

  • 头指针是指链表指向第一个结点的指针,若链表有头结点(head),则是指向头结点的指针。
  • 头指针具有标识作用,所以常用头指针冠以链表的名字(指针变量的名字)。
  • 无论链表是否为空,头指针均不为空。
  • 头指针是链表的必要元素。

头结点:

  • 头结点是为了操作的统一和方便而设立的,放在第一个元素的结点之前,其数据域一般无意义(但也可用来存放链表的长度)。
  • 头结点不一定是链表的必须要素。

单链表图例:
在这里插入图片描述

typedef struct Lnode{
    ElemType data;   /*数据域,保存结点的值*/
    struct Lnode *next;   /*指针域*/
}Lnode, *LinkList;   /*结点的类型*/

结点的实现:

​ 结点是通过动态分配和释放来实现的,即需要时分配,不需要时释放。

动态分配:

​ p = (LNode* )malloc(sizeof(LNode));

函数malloc分配了一个类型为LNode的结点变量的空间,并将其首地址放入指针变量p中。

动态释放:free(p);

系统回收由指针变量p所指向的内存区。p必须是最近一次调用malloc函数时的返回值。

单线性链表的基本操作
  1. 建立单链表

    设结点的数据类型是整型,32767作为结束标志。

    动态地建立单链表的方法有如下两种:

    头插入法,尾插入法

    (1)头插入法建表

    ​ 从一个空表开始,重复读入数据,生成新结点,将读入数据存放到新结点的数据域中,然后将新结点插入到当前链表的表头上,直到读入结束标志为止,即每次插入的结点都作为链表的第一个结点。

    1. 建立一个“空表”;
    2. 输入数据元素an,建立结点并插入;
    3. 输入数据元素an-1,建立结点并插入;
    4. 依次类推,直至输入a1为止。

    算法描述:

    LNode *create_LinkList(void)
        /*头插入法创建单链表,链表的头结点head作为返回值*/
    {
        int data;
        LNode *head,*p;
        head = (LNode *)malloc(sizeof(LNode));
        head->next=NULL;/*创建链表的表头结点head*/
        while1{
            scanf("%d",&data);
            if (data==32767) break;
            p = (LNode *)malloc(sizeof(LNode));
            p->data=data;/*数据域赋值*/
            p->next = head>next;
            head->next=p;
            /*钩链,新创建的结点总是作为第一个结点*/
        }return (head);
    }
    

    (2)尾插入法建表

    ​ 头插入建立链表虽然简单,但生成的链表中结点的次序和输入的顺序相反

    若希望二者次序一致,采用尾插入法建表,该方法是将新结点插入到当前链表的表尾,使其成为当前链表的尾节点。

    每次将新结点加在插到链表的表尾;

    设置一个尾指针last,总是指向表中最后一个结点,新结点插在它的后面;

    尾指针last初始时为指向表头结点地址。

    算法描述:

    LNode *create_LinkList(void){
        /*尾插入法创建单链表,链表的头结点head作为返回值*/
        LNode *head,*last,*q;
        head = p = (LNode *)malloc(sizeof(LNode));
        p->next=NULL;/*创建单链表的表头结点head*/
        while(1){
            scanf("%d",&data);
            if (data==32767) break;
            q = (LNode *)malloc(sizeof(LNode));
            q->data = data;/*数据域赋值*/
            q->next = last->next;
            last->next = q;
            last = q;
            /*钩链,新创建的结点总是作为最后一个结点*/
        }  return (head);
    }
    
    • 无论是哪种插入方法,如果需要插入建立的单线性链表的结点是n个,算法的时间复杂度均为O(n)。
    • 对于单链表,无论是哪种操作,只要涉及到钩链,如果没有明确给出直接后继,钩链的次序必须是**“先右后左”**。
  2. 单链表的查找

    (1)按序号查找,取单链表中的第i个元素。

    从链表的头结点出发,沿链域next逐个结点往下搜索,直到搜索到第i个结点为止。因此,链表不是随机存取结构。

    设单链表的长度为n,要查找表中第i个结点,仅当1<=i<=n时,i的值是合法的。

    算法描述:

    ElemType Get_Elem(LNode *L,int i){
        int j; LNode *p;
        p=L->next;j=1;/*使p指向第一个结点*/
        while (p!=NULL&&j<i){
            p = p->next;j++ }/*移动指针p,j计数*/
        if (j!=i)
            return (-32767);
        else
            return(p->data);/*p为NULL表示i太大;j>i表示i为0*/
    }
    

    移动指针p的频率:

    ​ i<1时:0次;i ∈ [1,n] : i - 1 次; i>n : n 次。

    所以:时间复杂度为O(n)

    (2)按值查找

    ​ 按值查找是在链表中,查找是否有结点值等于给定值key的结点

    若有,则返回首次找到的值为key的结点的存储位置;

    否则返回NULL。查找时从开始结点出发,沿链表逐个将结点的值和给定值key作比较。

    算法描述:

    LNode *Locate_Node(LNode *L,int key)
        /*在以L为头结点的单链表中查找值为key的第一个结点*/
    { LNode *p = L->next;
     while (p!=NULL&&p->data!=key)
         p = p->next;
     if (p->data == key) return p;
     else{
         printf("所要查找的结点不存在!\n");
         return (NULL);
     }
    

    算法的执行与形参key有关,平均时间复杂度为O(n)。

  3. 单链表的插入

    插入运算是将值为e的新结点插入到表的第i个结点的位置上,即插入到ai-1与ai之间。因此,必须首先找到ai-1所在的结点p,然后生成一个数据域为e的新结点q,q结点作为p的直接后继结点。

如下图所示:
在这里插入图片描述

(1)在第一个结点前插入q:

q- >next = head- >next , head- >next = q;

(2)在链表中间p后插入q:

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

(3)在链表末尾插入q:

设尾指针为p,

p - > next = q , q - >next = null , p = q;

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

算法描述:

void Insert_LNode(LNode *L,int i,ElemType e)
    /*在L为头结点的单链表的第i个位置插入值为e结点*/
{
    int j = 0;LNode *p,*q;
    p = L -> next;
    while(p!=NULL&&j<i-1){
        p = p ->next;j++;  }
    if (j!=i-1)
        printf("i太大或i为0!!\n");
    else{
        q = (LNode *)malloc(sizeof(LNode));
        q ->data = e; q ->next = p ->next;
        p ->next = q;
    }
}

​ 设链表的长度为n,合法的插入位置是1<=i<=n。算法的时间主要耗费移动指针p上,故时间复杂度为O(n)。

4.单链表的删除

(1)按序号删除

​ 删除单链表中的第i个结点。

​ 为了删除第i个结点ai,必须找到结点的存储地址。该存储地址是在其直接前趋结点ai-1的next域中,因此,必须首先找到ai-1的存储位置p,然后令p->next指向ai的直接后继结点,即把ai从链上摘下。最后释放结点ai的空间,将其归还给“存储池”。

​ 设单链表长度为n,则删去第i个结点仅当1<=i<=n时是合法的。则当i=n+1时,虽然被删除结点不存在,但其前趋结点却存在,是终端结点。故判断条件之一是p->next != NULL。时间复杂度为O(n)。

如下图所示:
在这里插入图片描述

算法描述:

void Delete_LinkList(LNode *L,int i)
    /*删除以L为头结点的单链表中的第i个结点*/
{
    int j=1; LNode *p, *q;
    p = L;q = L->next;
    while (p->next!=NULL&&j<i)
    {
        p=q;q=q->next;j++;
    }
    if (j!=i)  printf("i太大或i为0!!\n");
    else{
        p ->next = q ->next; free(q);
    }
}

(2)按值删除

​ 删除单链表中值为key的第一个结点。

​ 与按值查找相类似,首先要查找值为key的结点是否存在:

​ 若存在,则删除;

​ 否则返回NULL。

算法描述:

void Delete_Link(LNode *L,int key)
    /*删除以L为头结点的单链表中值为key的第一个结点*/
{
    LNode *p = L, *q = L ->next;
    while (q!=NULL&& q ->data!=key){
        p=q; q = q ->next;
    }
    if (q ->data==key){
        p ->next = q->next;free(q);
    }
    else  printf("所要删除的结点不存在!!\n");
}

算法的执行与形参key有关,平均时间复杂度为O(n)。

链表上实现插入和删除运算,无需移动结点,仅需修改指针。

解决了顺序表的插入或删除操作需要移动大量元素的问题。

例题1:

删除单链表中值为key的所有结点。

基本思想:从单链表的第一个结点开始,对每个结点进行检查,若结点的值为key,则删除之,然后检查下一个结点,直到所有的结点都检查。

算法描述:

void Delete_LinkList_Node(LNode *L,int key)
    /*删除以L为头结点的单链表中值为key的第一个结点*/
{
    LNode *p=L,*P = L->next;
    while (q!=NULL){
        if (q->next==key){
            p->next=q->next; free(q);
            q = p->next;  }
        else{
            p = q;q = q->next;}
    }
}

例题2:

删除单链表中所有值重复的结点,使得所有结点的值都不相同。

基本思想:从单链表的第一个结点开始,对每个结点进行检查,检查链表中该结点的所有后继节点,只要有值和该结点的值相同,则删除之;然后检查下一个结点,直到所有的结点都检查。

void Delete_Node_value(LNode *L)
    /*删除以L为头结点的单链表中所有值相同的结点*/
{
    LNode *p=L->next, *q,*ptr;
    while (p!=NULL)/*检查链表中所有的结点*/
    {
        *q = p,*ptr = p->next;
        /*检查结点p的所有后继结点ptr*/
        while (ptr!=NULL){
            if (ptr ->data==p->data){
                q->next=ptr->next;
                free(ptr); ptr=q->next;  }
            else   {q = ptr;ptr=ptr->next;}
        }
  p = p->next;      
    }
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值