单链表的基础操作

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

按序号查找结点值

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

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

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

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

按值查找表结点

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

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

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

插入结点操作

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

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


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

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

  1. p=GetElem(L, i-1) ; // 语句①,查找插入位置的前驱结点
  2. s->next=p->next; // 语句②,图 2-6 中辑作步骤 1
  3. 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)。算法的代码片段如下:

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

删除结点操作

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


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

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

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

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

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

扩展:删除结点*p

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

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

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

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

求表长操作

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

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

时间: 2024-10-02 20:23:06

单链表的基础操作的相关文章

单链表的基础实现

单链表的基础实现时间:2006/03/23测试环境:TC2.0 #include <stdio.h> #define LEN sizeof(struct LNode) #define NULL 0 typedef int ElemType; struct LNode { ElemType data; struct LNode *next; }; /*创建一个带空头结点的单链表*/ struct LNode *CreatList() { struct LNode *head,*p,*temp;

数据结构关于单链表的一些操作的源代码

单链表的可以有许多问题,这是我特意整理一下的有关他的相关操作,给出代码,有需要的可以自己调试,重要的就是关于环的一些操作: #include <iostream>#include <cstdio>#include <cstdlib>#include <ctime>using namespace std;typedef int Elemtype;typedef struct Node{ Elemtype data; struct Node *next;}Nod

详谈单链表的有关操作集锦~

1.单链表    在 Java 中没有显式的指针类型,然而实际上对象的访问就是使用指针来实现的,即在Java 中是使用对象的引用来替代指针的.因此在使用 Java 实现该结点结构时,一个结点本身就是一个对象.结点的数据域 data 可以使用一个 Object 类型的对象来实现,用于存储任何类型的数据元素,并通过对象的引用指向该元素:而指针域 next 可以通过节点对象的引 用来实现.      单链表结点结构是结点的一种最简单的形式,除此之外还有其他不同的结点结构,但是这些结点结构都有一个数据域

链表的基础操作专题小归纳

后天就要程序设计考试了,今晚抓紧复习链表ing! 这次就来总结一下链表的六大基础操作: (1)创建链表 (2)遍历链表 (3)在链表中检索 (4)向链表中插入一项 (5)从链表中删除一项 (6)交换链表中两项的位置 全部都放在一个代码里了,这样好操作一点 /笑哭 至于链表的引申操作,什么头插法尾插法的,都是这六大基础操作之外的事情,有兴趣的话烦请各位自己去了解啦,我这里就不介绍了~ 注释都非常详细,我是真的没有啥时间画图做解释了,将就着看吧~ (毕竟我个人也不喜欢一上来啥也不说就版代码的,那会让

单链表的相关操作

#ifndef _SLIST_H #define _SLIST_H #ifdef __cplusplus extern "C" { #endif /*******1. 不带头结点的单链表*****/ /***** *@链表结点结构定义 *@ m_data:数据 *@m_pNext:指向下一结点的指针 ***/ struct listNode { int m_data; listNode* m_pNext; }; /******* *@ 用数组array初始化链表,数组元素个数为n *@

算法数据结构 单链表的实现+操作 以及和顺序表的对比

顺序表和单链表的优缺点对比: 顺序表的优点,无需为表示表中元素之间的逻辑关系而增加额外的存储空间: 可以快速的存取表中的任意位置的元素. 顺序表的缺点,插入后删除操作需要移动大量元素: 当线性表长度不稳定时,存储空间难确定,容易造成存储空间碎片. 对于单链表 链式存储即元素存储的内存单元可以是不连续,分散的.对于元素间如何来维护他们的关系(即逻辑结构,每个元素的前驱和后继.) 即用到一个指针域来存储他和前驱或是后继直接的关系. 如上面的是一个单链表的指针结构,即每个元素中存储了他的后继元素的内存

链表-单链表的各种操作

单链表的结构体的定义 typedef struct LNode { ElemType data; struct LNode *next; }LinkList; 基本的单链表的操作 /* 功能:构建一个空的带头节点的单链表*/ Status InitList (struct LNode **L) { (*L) = (struct LNode *)malloc(sizeof(struct LNode)); //产生头节点 if(!*L) exit(OVERFLOW); (*L)->next = NU

单链表的常用操作(二)

接上一篇单链表的基本操作,我又整理了一些链表常考的题目,并且不断更新中... 1.查找链表中倒数第k个节点以及删除倒数第k个节点 //给两个指针p和q,让其中一个指针p领先q指针k步, //然后再同时移动p和q指针,当领先的指针p先到达链表尾部时,后面的指针q所指向的节点恰好为倒数第k个节点. Node* GetKthNode(int k) { Node *p=head; Node *q=head; while(k>1&&p->next!=NULL) { p=p->nex

带头节点的单链表的插入操作

1.偶然看到了十字链表的应用,想到之前在<数据结构与算法分析>的链表一章中,需要用多重表实现一个简单的查询功能.功能需求如下: “已知 学生 和 学校课程 总数 分别为 40000 和 2500,现在需要得到两份报告,一份显示每门课成注册的所有学生信息, 一份显示每个学生注册了哪些课程.” 显然可以用一个 40000 * 2500 个元素的二维数组来解决,但是每个学生选课数目很少,因此会浪费很多空间.因此选择十字链表来实现. 既然是链表,那么肯定要有插入操作,于是便有了本文.算是对功能实现前的