您当前的位置:首页 > 计算机 > 编程开发 > C语言

单链表的操作:节点查找、节点插入、节点删除、求单链表长度

时间:02-21来源:作者:点击数:

本节讲解一下单链表中节点的查找、插入、删除、求单链表长度等操作。

按序号查找结点值

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

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

LNode GetElem(LinkList L,int i){
    //本算法取出单链表L(带头结点)中第i个位置的结点指针
    int j=1;  //计数,初始为1
    LNode *p = L->next;  //头结点指针赋给p
    if(i==0)
        return L;  //若i等于0,则返回头结点
    if(i<1)
        return NULL;  //若 i 无效,则返回 NULL
    while( p && j<i ) {  //从第1个结点开始找,查找第i个结点
        p=p->next;
        j++;
    }
    return p; //返回第i个结点的指针,如果i大于表长,p=NULL,直接返回p即可
}

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

按值查找表结点

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

LNode *LocateElem (LinkList L, ElemType e) {
    //本算法查找单链表 L (带头结点)中数据域值等于e的结点指针,否则返回NULL
    LNode *p=L->next;
    while( p!=NULL && p->data!=e)  //从第1个结点开始查找data域为e的结点
        p=p->next;
    return p;  //找到后返回该结点指针,否则返回NULL
}

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

插入结点操作

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

算法首先调用上面的按序号查找算法GetElem(L,  i-1),查找第i-1个结点。假设返回的第i-1个结点为*p,然后令新结,点*s的指针域指向*p的后继结点,再令结点*p的指针域指向新插入的结点*s。其操作过程如图2-6所示。 


图2-6  单链表的插入操作

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

p=GetElem(L, i-1) ;  // 语句①,查找插入位置的前驱结点
s->next=p->next;  // 语句②,图 2-6 中辑作步骤 1
p->next=s;  // 语句③,图2-6中操作步骤2

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

扩展:对某一结点进行前插操作

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

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

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

//将结点插入到*P之前的主要代码片段
s->next = p->next; //修改指针域,不能颠倒
p->next = s;
temp = p->data;  //交换数据域部分
p->data=s->data;
s->data=temp;

删除结点操作

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


图2-7  单链表结点的删除

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

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

p=GetElem(L,i-1);  //查找删除位置的前驱结点
q=p->next;  //令q指向被删除结点
p->next=q->next  //将*q结点从链中“断开”
free (q) ;  //释放结点的存储空间

和插入算法一样,该算法的主要时间也是耗费在查找操作上,时间复杂度为O(n)。

扩展:删除结点*p

要实现删除某一个给定结点*p,通常的做法是先从链表的头结点开始顺序找到其前驱结点,然后再执行删除操作即可,算法的时间复杂度为O(n)。

其实,删除结点*p的操作可以用删除*p的后继结点操作来实现,实质就是将其后继结点的值赋予其自身,然后删除后继结点,也能使得时间复杂度为O(1)。

实现上述操作的代码片段如下:

q=p->next;  //令q 向*p的后继结点
p->data=p->next->data;  //和后继结点交换数据域
p->next=q->next;  //将*q结点从链中“断开”
free (q) ;  //释放后继结点的存储空间

求表长操作

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

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

单链表是整个链表的基础,读者一定要熟练掌握单链表的基本操作算法,在设计算法时,建议先通过图示的方法理清算法的思路,然后再进行算法的编写。

方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门