数据结构-单链表

189 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第4天,点击查看活动详情

单链表上的基本操作

1.按序号查找结点值

       在单链表中从第一个结点出发,顺指针next域逐个往下搜索,直到找到第i个结点为止,否则返回最后一个结点指针域NULL。

       按序号查找结点值的算法如下:

LNode *GetElem (LinkList L,int i){
    int j=1;
    LNode *p=L->next;
    if(i==0)
        return L;
    if(i<1)
        return NULL;
    while(p && j<i){
        p=p->next;
        j++;
    }
    return p;
}

       按序号查找操作的时间复杂度为O(n)。

2.按值查找表结点

       从单链表的第一个结点开始,由前往后依次比较表中各结点数据域的值,若某结点数据域的值等于给定值e,则返回该结点的指针;若整个单链表中没有这样的结点,则返回NULL。

       按值查找表结点的算法如下:

LNode *LocateElem (LinkList L, ElemType e)(
    LNode *p=L->next;
    while(p!=NULL && p->data!=e)
        p=p->next;
    return p;

3.插入结点操作

       插入结点操作将值为x的新结点插入到单链表的第i个位置上。先检查插入位置的合法性,然后找到待插入位置的前驱结点,即第i-1个结点,再在其后插入新结点。

       算法首先调用按序号查找算法GetElem(L,i-1),查找第i-1个结点。假设返回的第i-1个结点为*p,然后令新结点*s的指针域指向*p 的后继结点,再令结点*p的指针域指向新插入的结点*s。

       实现插入结点的代码段如下:

    p=GetElem(L,i-1);
    s->next=p->next;
    p->next=s;

       算法中,第二句和第三句的顺序不能颠倒,否则,当先执行p->next=s后,指向其原后继的指针就不存在,再执行s->next=p->next时,相当于执行了s->next=s,显然是错误的。本算法主要的时间开销在于查找第i-1个元素,时间复杂度为O(n)。若在给定的结点后面插入新结点,则时间复杂度仅为O(1)。

4.前插结点操作

       前插操作是指在某结点的前面插入一个新结点,后插操作的定义刚好与之相反。在单链表插入算法中,通常都采用后插操作。

       以上面的算法为例,首先调用函数GetElem()找到第i-1个结点,即插入结点的前驱结点后,再对其执行后插操作。由此可知,对结点的前插操作均可转化为后插操作,前提是从单链表的头结点开始顺序查找到其前驱结点,时间复杂度为O(n)。

       此外,可采用另一种方式将其转化为后插操作来实现,设待插入结点为*s,将*s插入到*p的前面。我们仍然将*s插入到*p的后面,然后将p->data与s->data交换,这样既满足了逻辑关系,又能使得时间复杂度为O(1)。算法的代码片段如下:

    s->next=p->next;
    p->next=s;
    temp=p->data;
    p->data=s->data;
    s->data=temp;

5.删除结点操作

       删除结点操作是将单链表的第i个结点删除。先检查删除位置的合法性,后查找表中第i-1个结点,即被删结点的前驱结点,再将其删除。

       假设结点*p为找到的被删结点的前驱结点,为实现这一操作后的逻辑关系的变化,仅需修改*p的指针域,即将*p的指针域next指向*q的下一结点。

实现删除结点的代码片段如下:

    p=GetElem(L,i-1);
    q=p->next;
    p->next=q->next;
    free(q);

6.求表长操作

       求表长操作就是计算单链表中数据结点(不含头结点)的个数,需要从第一个结点开始顺序依次访问表中的每个结点,为此需要设置一个计数器变量,每访问一个结点,计数器加1,直到访问到空结点为止。算法的时间复杂度为O(n)。

       需要注意的是,因为单链表的长度是不包括头结点的,因此不带头结点和带头结点的单链表在求表长操作上会略有不同。对不带头结点的单链表,当表为空时,要单独处理。