什么是单链表?
单链表是一种常见的链式数据结构,用于存储和操作数据元素的集合。它由一系列的节点组成,每个节点包含两个部分:数据域和指针域。
单链表的每个节点包含了存储数据的数据域,以及指向下一个节点的指针域。通过这些指针域,节点之间可以按顺序连接起来,形成一个链式结构。链表的最后一个节点通常指向一个特殊的空节点(NULL或nullptr),表示链表的结束。
相比于数组,链表的一大优势是它的动态性。在链表中,节点的添加、删除可以通过修改指针的指向来完成,不需要像数组那样进行元素的移动。这使得链表在插入和删除操作频繁的场景中具有更高的效率。
链表的一个缺点是访问节点需要从头节点开始依次遍历,因为链表中的节点并不是在内存中连续存储的。这导致了链表的访问时间复杂度为O(n),其中n是链表的长度。相比之下,数组可以通过索引直接访问元素,时间复杂度为O(1)。
1. 按位查找
-
单链表按位查找是指根据节点在链表中的位置(即节点序号或下标)来查找节点的操作。通常情况下,我们需要查找的节点序号是从1开始计数的,即第1个节点、第2个节点、第3个节点等。
-
单链表按位查找的基本思路是从链表头节点开始,遍历链表,直到找到第k个节点,或者链表遍历结束。如果链表遍历结束仍未找到第k个节点,则返回空指针。
平均时间复杂度:O(n)
//按位查找
LNode *GetElem(LinkList L, int i)
{if(i < 0) //判断i是否合法return NULL;LNode *p; //指针p指向当前扫描到的结点int j = 0; //当前p指向的是第几个结点p = L; //L指向头结点, 头结点是第0个结点(不存数据)while(p != NULL && j < i) //循环找到第i个结点{p = p->next; //让p指针依次往后移j++;}return p;
}
i 的值有两种极端情况,分别是 i 取最小0 和取最大5
若 i = 0; while循环不符合,返回return p; 此时p指针会指向头结点, 如下图所示
若 i = 5; j++递增到5后,此时p指针指向链表末尾空指针NULL,p != NULL不满足,退出while循环,p返回NULL
所以当 i 不合法时,最终返回的都是NULL, 那么当别人调用此基本操作时,只要判断下此次的返回值是不是NULL, 就能知道这次的按位查找是否查找成功,这样的算法才具有健壮性。
既然我们实现了按位查找的操作,那么以后按位插入和按位删除时,就可以直接调用基本操作来实现。如下图所示:
基本封装的好处:简洁易维护
2. 按值查找
时间复杂度:O(n)
单链表按值查找需要遍历链表的每一个节点,直到找到节点的值等于目标值,或者遍历结束没有找到目标值,返回空指针。
- 在按值查找单链表时,需要注意以下几点:
- 单链表查找需要遍历整个链表,时间复杂度为 O(n),其中 n 是链表节点的个数。
- 当链表为空时,需要特别处理。
- 如果目标值在链表中不存在,可能需要额外处理,比如返回一个空指针,或者打印出 “Not Found” 等提示信息。
- 需要根据具体问题和代码实现,特别注意链表头节点指针的正确性,以及节点指针的移动和连接等操作。
假设本例中的ElemType是int类型
//按值查找, 找到数据域 == e 的结点
LNode *LocateElem(LinkList L, ElemType e)
{LNode *p = L->next;//从第1个结点开始查找数据域为e的结点while(p != NULL && p->data != e)p = p->next;return p; //找到后返回该结点指针,否则返回NULL
}
若e = 8, 当 p指向第一个结点时,5 != 8,p移向下一个结点
当p移动到第2个结点时,找到数据8,此时while循环退出,返回 return p
当e = 6时,p指针不断往后移,当p指向NULl时,while循环不满足,退出循环,返回return p
当LocateElem()函数的调用者接受到NULL时,就说明并不存在数据域等于6的结点。
3. 求单链表的长度
时间复杂度:O(n)
//求表的长度
int length(LinkList L)
{int len = 0; //统计表长LNode *p = L;while(p->next != NULL){p = p->next;len++;}return len;
}