c语言用指针删除节点,单链表的操作:节点查找、节点插入、节点删除、求单链表长度...

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

按序号查找结点值

在单链表中从第一个结点出发,顺指针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

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所示。

657d0fd9a8b8ff833f404d0e2801c974.png

图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所示。

db9a2f149d5af0faea2b801b2fdf0994.png

图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)。

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

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

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值