11判断单链表是否有环★★★★★

本文介绍了一种使用快慢指针的方法来判断单链表是否存在环,并提供了完整的Python实现代码。该方法仅需O(n)的时间复杂度和O(1)的空间复杂度。

题目描述(难度:中等)

单链表有环指的是单链表中某个结点的 next 域指向的是链表中在它之前的某一个结点,这样在链表的尾部形成一个环形结构。如何判断单链表是否有环存在?(来源:阿里)

单链表结构

在这里插入图片描述

class LNode:
    def __init__(self):
        self.data = None # 数据域
        self.next = None # 指针域
  • 由于单链表中每个结点的地址都存储在其前驱结点的指针域中,因此,对单链表中任何一个结点的访问只能从链表的头指针开始进行遍历。
  • 注意:在修改结点指针域的时候,记录下后继结点的地址,否则会丢失后继结点。

方法一:快慢指针

在这里插入图片描述

# 构造链表
def constructList():
    i = 1
    head = LNode()
    head.next = None
    cur = head
    # 构造第一个链表
    while i < 8:
        tmp = LNode()
        tmp.data = i
        tmp.next = None
        cur.next = tmp
        cur = tmp
        i += 1
    cur.next = head.next.next.next
    return head

# 判断单链表是否有环
def isLoop(head):
    if head == None or head.next == None:
        return None
    # 初始slow与fast都指向链表第一个结点
    slow = head.next
    fast = head.next
    while fast != None and fast.next != None:
        slow = slow.next # 向前移一步
        fast = fast.next.next # 向前移两步
        if slow == fast:
            return slow
    return None

# 找出环的入口点
def findLoopNode(head, meetNode):
    first = head.next
    second = meetNode 
    # first和second指针每次前移一步,当first==second时,此时指向的结点为入口点
    while first != second:
        first = first.next
        second = second.next
    return first

复杂度分析

  • 时间复杂度为 O ( n ) O(n) O(n),只对链表进行一次遍历。
  • 空间复杂度为 O ( 1 ) O(1) O(1),只需常量个指针变量来保存结点的地址信息。

主函数

if __name__ == "__main__":
    head = constructList()  # 头结点
    meetNode = isLoop(head) # 指向fast==slow时的结点
    loopNode = None
    if meetNode != None:
        print("有环")
        loopNode = findLoopNode(head, meetNode)
        print("环的入口点为:", loopNode.data)
    else:
        print("无环")

在这里插入图片描述

参考文献

[1] 何海涛. 剑指Offer:名企面试官精讲典型编程题(第2版). 北京: 电子工业出版社,2017
[2] 张波,楚秦. Python程序员面试算法宝典. 北京:机械工业出版社,2018

<think>我们首先需要实现单链表的基本运算和整体建表算法。根据要求,我们需要编写两个文件:linklist.cpp(包含单链表的基本操作函数)和exp2-2.cpp(主程序,调用这些函数完成指定功能)。 单链表的基本操作包括: 初始化单链表 尾插法插入元素 输出单链表 计算单链表长度 判断单链表是否为空 获取指定位置的元素 查找元素的位置(返回第一个匹配的位置) 在指定位置插入元素 删除指定位置的元素 释放单链表 我们将设计一个结构体来表示单链表的结点,每个结点包含一个字符类型的数据和一个指向下一个结点的指针。 在linklist.cpp中,我们将实现以下函数: - void InitList(LinkNode *&L):初始化单链表,创建一个头结点。 - void DestroyList(LinkNode *&L):释放单链表。 - bool ListEmpty(LinkNode *L):判断单链表是否为空(只有头结点为空)。 - int ListLength(LinkNode *L):返回单链表的长度(头结点不算)。 - void DispList(LinkNode *L):输出单链表的所有元素。 - bool GetElem(LinkNode *L, int i, ElemType &e):获取单链表中第i个位置的元素(i从1开始计数)。 - int LocateElem(LinkNode *L, ElemType e):查找元素e在单链表中首次出现的位置(位置从1开始),若不存在返回0。 - bool ListInsert(LinkNode *&L, int i, ElemType e):在单链表的第i个位置插入元素e(注意:头结点是第0个位置,然后第1个位置是第一个元素结点)。 - bool ListDelete(LinkNode *&L, int i, ElemType &e):删除单链表的第i个位置的元素,并用e返回其值(这里我们不需要返回删除的值,但为了通用性保留,在调用时如果不关心删除的值,可以传入一个临时变量)。 另外,我们还需要一个尾插法建立单链表的函数,但是题目要求的是整体建表算法,我们可以通过多次调用尾插法插入函数来实现,也可以单独写一个尾插法建立单链表的函数。为了简单,我们直接使用ListInsert函数在尾部插入(但这样每次插入都要遍历到尾部效率低)。另一种方法是维护一个尾指针,这样尾插时可以直接插入。这里我们为了效率,在插入时维护一个尾指针。 然而,考虑到我们需要频繁在尾部插入多个元素,我们可以设计一个尾插法插入元素的函数,这样效率更高。但是根据题目要求,我们只需要在exp2-2.cpp中完成指定的功能,因此我们可以选择在linklist.cpp中实现一个尾插法插入的函数: void InsertRear(LinkNode *&L, ElemType e); // 使用尾指针快速插入 但是,为了保持接口的一致性,我们也可以不使用额外的尾指针,而是每次插入时遍历到尾部。由于我们一次插入的元素数量不多,遍历到尾部也是可以接受的。不过,为了效率,我们可以在链表结构中增加一个尾指针,但这样就需要定义一个新的链表结构体(包含头指针和尾指针)。由于题目没有明确要求,我们这里采用最简单的单链表(只包含头指针),然后在插入到尾部时遍历链表。 但是,注意题目要求“整体建表算法”,我们可以用尾插法建立整个链表(一次建立多个元素)。我们可以写一个函数:void CreateListR(LinkNode *&L, ElemType a[], int n); 用数组a中的n个元素建立单链表(尾插法)。不过,题目要求的功能是依次插入a、b、c、d、e,我们可以调用多次尾插函数来实现。 权衡之后,我们决定在linklist.cpp中实现以下函数(使用头指针和遍历方式): 初始化、销毁、判断空、求长度、输出、按位置查找元素、按元素查找位置、按位置插入(包括在任意位置插入,包括头部和尾部)、按位置删除。 另外,我们单独实现一个尾插法的插入函数(但不维护尾指针,而是每次遍历到尾部),这样在插入多个元素时效率会低,但为了简单,我们先这样实现。 另一种方案是:我们在主程序exp2-2.cpp中,通过循调用按位置插入函数,每次插入到当前链表的末尾(位置为长度+1)。这样我们就可以利用按位置插入函数实现尾插。这样就不需要单独写尾插函数了。 但是,按位置插入函数在插入到末尾时需要遍历整个链表,时间复杂度为O(n),而如果我们在链表中维护一个尾指针,则可以在O(1)时间内完成插入。考虑到这是一个练习题,我们选择简单实现,即不维护尾指针。 因此,我们将在linklist.cpp中实现一个通用的插入函数ListInsert,它可以在任意位置插入(包括在第一个元素之前插入,即在头结点后插入,以及插入到末尾)。这样,尾插就可以通过调用ListInsert(L, n+1, e)来实现(n为当前长度)。 然而,注意题目要求(2)依次采用尾插法插入a、b、c、d、e元素。尾插法通常是指每次将新结点插入到链表的尾部,所以我们需要能够找到链表的尾部。如果我们每次插入都从头遍历,那么插入n个元素的时间复杂度就是O(n^2)。为了避免这种情况,我们可以用一个指针记住当前的尾结点,这样每次插入时就可以直接插入。 因此,我们修改设计:在单链表的结构中,我们添加一个尾指针。但是题目没有要求使用带尾指针的链表,而且引用[2]中也没有提到尾指针。为了简单,我们仍然使用普通的单链表(只有头指针),然后在插入时遍历。因为最多5个元素,效率问题不大。 但是,为了练习,我们可以采用两种方法: 方法1:在链表中增加尾指针(需要重新定义链表结构体) 方法2:在插入多个元素时,用一个临时指针记录当前尾结点,这样在插入一组元素时,我们可以做到O(n)的时间。 由于题目要求的功能是依次插入多个元素,我们可以在exp2-2.cpp中用一个循来插入,并且在循内部保持一个指向尾结点的指针,这样我们就不需要在linklist.cpp中改变结构。但是,linklist.cpp中的函数并不知道我们在循中保持了尾指针,所以这种方法只能用在exp2-2.cpp中,而不能封装到linklist.cpp的函数中。 综合考虑,我们决定在linklist.cpp中不维护尾指针,而是提供一个尾插的函数,该函数每次遍历到链表尾部,然后插入。这样虽然每次插入是O(n),但总时间是O(n^2)。由于n=5,可以接受。 步骤: 1. 定义单链表结点类型和元素类型。 2. 在linklist.cpp中实现各个基本运算函数。 3. 在exp2-2.cpp中调用这些函数完成题目要求的功能。 具体实现如下: 首先,我们在linklist.h中定义结构和函数声明(为了模块化,我们最好创建一个头文件,但题目要求是.cpp文件,所以我们可以将声明和实现都放在.cpp文件中,但这样exp2-2.cpp就不能包含linklist.h了。因此,我们将linklist.cpp设计为包含函数定义,然后在exp2-2.cpp中通过extern声明函数,或者在linklist.cpp中声明一个头文件linklist.h,然后在两个.cpp中都包含它。 为了清晰,我们创建linklist.h和linklist.cpp,但是题目要求的是linklist.cpp和exp2-2.cpp,所以我们可以将linklist.h的内容放在linklist.cpp中,然后在exp2-2.cpp中包含linklist.h?但是题目没有要求头文件,因此我们可以将结构定义和函数声明放在linklist.cpp的开头,然后在exp2-2.cpp中再写一遍?这样不好。 另一种方法是,将结构定义和函数声明放在一个头文件linklist.h中,然后在两个.cpp中都包含它。这样更符合实际编程。但题目要求是编写linklist.cpp和exp2-2.cpp,所以我们可以这样安排: 文件:linklist.h 文件:linklist.cpp 文件:exp2-2.cpp 但是题目没有要求linklist.h,所以如果我们不想多一个文件,可以将linklist.h的内容直接放到exp2-2.cpp的开头(这样重复代码,不好)或者在linklist.cpp中定义结构,然后在exp2-2.cpp中再定义一遍(更不好)。 因此,我们按照实际工程做法,创建linklist.h,这样两个cpp文件都可以包含它。虽然题目要求中没有提到,但我们可以理解为linklist.cpp需要包含这些定义,而exp2-2.cpp也需要包含这些定义。 所以,我们创建三个文件: linklist.h:包含单链表的结构定义和函数声明 linklist.cpp:包含函数实现 exp2-2.cpp:主程序 但是,如果题目严格要求只能有两个文件:linklist.cpp和exp2-2.cpp,那么我们可以将linklist.h的内容放在linklist.cpp中,然后在exp2-2.cpp中通过包含linklist.cpp来获取声明?这通常不是好做法,因为包含.cpp文件会导致重复定义(如果在多个地方包含的话)。 因此,我们采用另一种方式:将linklist.h的内容放在linklist.cpp中,然后在exp2-2.cpp中手动重复这些声明(因为只有exp2-2.cpp需要,而且声明不多,可以接受)。 考虑到代码清晰和可维护性,我们还是创建三个文件,并在回答中说明。如果用户要求必须两个文件,我们再调整。 根据题目要求,用户要求编写两个文件:linklist.cpp和exp2-2.cpp。因此,我们这样安排: 在linklist.cpp中,我们定义: #include <iostream> using namespace std; typedef char ElemType; typedef struct LNode { ElemType data; struct LNode *next; } LinkNode; // 函数声明 void InitList(LinkNode *&L); void DestroyList(LinkNode *&L); bool ListEmpty(LinkNode *L); int ListLength(LinkNode *L); void DispList(LinkNode *L); bool GetElem(LinkNode *L, int i, ElemType &e); int LocateElem(LinkNode *L, ElemType e); bool ListInsert(LinkNode *&L, int i, ElemType e); bool ListDelete(LinkNode *&L, int i, ElemType &e); // 另外,我们实现一个尾插法的插入函数(可选),但这里我们用ListInsert来实现尾插,所以可以不提供。 // 但是,为了效率,我们提供一个专门尾插的函数,这样在exp2-2.cpp中插入多个元素时,可以避免每次遍历整个链表。 // 这个函数叫做InsertR,它直接在链表尾部插入一个元素,而不需要指定位置。 bool InsertR(LinkNode *&L, ElemType e); // 在尾部插入元素 然后,在exp2-2.cpp中,我们需要包含头文件,但为了简化,我们直接在exp2-2.cpp中再次定义这些结构体和函数声明(因为函数实现在linklist.cpp中,所以我们需要在exp2-2.cpp中声明这些函数,否则无法链接)。 具体编写: linklist.cpp: 包含所有函数的实现。 exp2-2.cpp: 首先包含<iostream>,然后定义相同的结构体和函数声明(或者通过extern声明函数),然后写main函数。 但是,重复定义结构体和函数声明会显得冗余,而且容易出错。因此,我们建议将linklist.cpp拆分为linklist.h和linklist.cpp,然后在exp2-2.cpp中包含linklist.h。不过,题目要求是linklist.cpp,所以我们这里采用将linklist.h的内容放在linklist.cpp中,然后在exp2-2.cpp中复制一份结构定义和函数声明(因为只有这一个程序使用,而且函数不多)。 为了减少重复,我们在linklist.cpp中定义结构体和函数,在exp2-2.cpp中手动写一遍这些声明。这样虽然重复,但可以满足要求。 现在,我们开始编写代码。 注意:单链表通常有一个头结点,这样插入删除操作会更统一。 我们设计的链表带有头结点。 步骤: 初始化:创建头结点,指针域为空。 尾插法:找到最后一个结点,在其后插入新结点。 其他操作都按常规。 具体实现: 1. 初始化单链表 void InitList(LinkNode *&L) { L = new LinkNode; // 创建头结点 L->next = NULL; } 2. 销毁单链表 void DestroyList(LinkNode *&L) { LinkNode *p = L, *q; while (p != NULL) { q = p->next; delete p; p = q; } L = NULL; // 将L置为NULL } 3. 判断单链表是否为空 bool ListEmpty(LinkNode *L) { return (L->next == NULL); } 4. 求单链表的长度 int ListLength(LinkNode *L) { int n = 0; LinkNode *p = L->next; while (p != NULL) { n++; p = p->next; } return n; } 5. 输出单链表 void DispList(LinkNode *L) { LinkNode *p = L->next; while (p != NULL) { cout << p->data << " "; p = p->next; } cout << endl; } 6. 按位置查找元素 bool GetElem(LinkNode *L, int i, ElemType &e) { // i从1开始计数,即头结点后的第一个结点为第1个结点 int j = 0; LinkNode *p = L; while (j < i && p != NULL) { j++; p = p->next; } if (p == NULL) // 位置i超出范围 return false; else { e = p->data; return true; } } 7. 按元素值查找位置(返回第一个匹配的位置) int LocateElem(LinkNode *L, ElemType e) { int i = 1; LinkNode *p = L->next; while (p != NULL) { if (p->data == e) return i; p = p->next; i++; } return 0; // 未找到返回0 } 8. 按位置插入(在第i个位置插入,即在第i-1个结点后插入,头结点是第0个结点) bool ListInsert(LinkNode *&L, int i, ElemType e) { // 找到第i-1个结点 int j = 0; LinkNode *p = L, *s; while (j < i-1 && p != NULL) { j++; p = p->next; } if (p == NULL) // 位置i不合法(i太大) return false; else { s = new LinkNode; s->data = e; s->next = p->next; p->next = s; return true; } } 9. 按位置删除(删除第i个结点) bool ListDelete(LinkNode *&L, int i, ElemType &e) { // 找到第i-1个结点 int j = 0; LinkNode *p = L, *q; while (j < i-1 && p != NULL) { j++; p = p->next; } if (p == NULL || p->next == NULL) // 第i-1个结点不存在或第i个结点不存在 return false; else { q = p->next; e = q->data; // 返回被删除元素的值 p->next = q->next; delete q; return true; } } 10. 尾插法插入一个元素(可选) 我们实现一个函数InsertR,它直接在尾部插入元素。 bool InsertR(LinkNode *&L, ElemType e) { LinkNode *p = L; // 找到最后一个结点 while (p->next != NULL) { p = p->next; } LinkNode *s = new LinkNode; s->data = e; s->next = NULL; p->next = s; return true; } 但是,为了减少代码量,我们也可以不写这个函数,而在exp2-2.cpp中调用ListInsert(L, ListLength(L)+1, e)来实现。但是这样效率低,因为每次插入都要遍历整个链表。因此,我们选择实现InsertR函数。 现在,我们在linklist.cpp中实现以上所有函数(包括InsertR)。 然后,在exp2-2.cpp中,我们需要重复这些声明(因为linklist.cpp中已经定义,我们在exp2-2.cpp中需要声明这些函数才能使用)。 exp2-2.cpp的内容: #include <iostream> using namespace std; typedef char ElemType; typedef struct LNode { ElemType data; struct LNode *next; } LinkNode; // 函数声明 void InitList(LinkNode *&L); void DestroyList(LinkNode *&L); bool ListEmpty(LinkNode *L); int ListLength(LinkNode *L); void DispList(LinkNode *L); bool GetElem(LinkNode *L, int i, ElemType &e); int LocateElem(LinkNode *L, ElemType e); bool ListInsert(LinkNode *&L, int i, ElemType e); bool ListDelete(LinkNode *&L, int i, ElemType &e); bool InsertR(LinkNode *&L, ElemType e); // 尾插函数 int main() { // 按照题目要求完成功能 LinkNode *h; // 单链表头指针 // (1) 初始化单链表h InitList(h); // (2) 依次采用尾插法插入a、b、c、d、e元素 InsertR(h, 'a'); InsertR(h, 'b'); InsertR(h, 'c'); InsertR(h, 'd'); InsertR(h, 'e'); // (3) 输出单链表h cout << "单链表h: "; DispList(h); // (4) 输出单链表h的长度 cout << "单链表h的长度: " << ListLength(h) << endl; // (5) 判断单链表h是否为空 if (ListEmpty(h)) cout << "单链表h为空" << endl; else cout << "单链表h不为空" << endl; // (6) 输出单链表h的第3个元素 ElemType e; if (GetElem(h, 3, e)) cout << "第3个元素为: " << e << endl; else cout << "第3个元素不存在" << endl; // (7) 输出元素a的位置 int pos = LocateElem(h, 'a'); if (pos) cout << "元素a的位置为: " << pos << endl; else cout << "元素a不存在" << endl; // (8) 在第4个元素位置上插入f元素 // 注意:这里在第4个位置插入,即在第4个元素之前插入,插入后成为第4个元素 if (ListInsert(h, 4, 'f')) cout << "在第4个位置插入f成功" << endl; else cout << "插入失败" << endl; // (9) 输出单链表h cout << "插入f后的单链表h: "; DispList(h); // (10) 删除单链表h的第3个元素 ElemType deletedElem; if (ListDelete(h, 3, deletedElem)) cout << "删除第3个元素成功,删除的元素为: " << deletedElem << endl; else cout << "删除失败" << endl; // (11) 输出单链表h cout << "删除第3个元素后的单链表h: "; DispList(h); // (12) 释放单链表h DestroyList(h); return 0; } 最后,我们需要编译这两个文件。在编译时,需要将linklist.cpp和exp2-2.cpp一起编译。 注意:在linklist.cpp中,我们包含了所有函数的实现。在exp2-2.cpp中,我们包含了相同的结构定义和函数声明。 由于时间关系,我们这里只给出代码框架。实际编写时,请确保所有函数的正确性。 下面,我们给出完整的文件内容: 文件:linklist.cpp </think>### C++实现单链表基本运算 以下是完整的`linklist.cpp`和`exp2-2.cpp`实现,满足单链表的所有基本运算要求(初始化、尾插法插入、输出链表、计算长度、判空、获取元素、查找位置、插入元素、删除元素、释放链表等)。 --- #### 文件:linklist.cpp ```cpp #include <iostream> using namespace std; typedef char ElemType; // 单链表节点结构 typedef struct LNode { ElemType data; struct LNode* next; } LinkNode; // 1. 初始化单链表 void InitList(LinkNode*& L) { L = new LinkNode; // 创建头结点 L->next = NULL; } // 2. 尾插法插入元素 void InsertRear(LinkNode*& L, ElemType e) { LinkNode* newNode = new LinkNode; newNode->data = e; newNode->next = NULL; LinkNode* p = L; while (p->next != NULL) // 定位到尾节点 p = p->next; p->next = newNode; // 尾插新节点 } // 3. 输出单链表 void DispList(LinkNode* L) { LinkNode* p = L->next; // 跳过头结点 while (p != NULL) { cout << p->data << " "; p = p->next; } cout << endl; } // 4. 计算单链表长度 int ListLength(LinkNode* L) { int len = 0; LinkNode* p = L->next; while (p != NULL) { len++; p = p->next; } return len; } // 5. 判断链表是否为空 bool ListEmpty(LinkNode* L) { return (L->next == NULL); } // 6. 获取第i个元素 bool GetElem(LinkNode* L, int i, ElemType& e) { if (i < 1) return false; LinkNode* p = L->next; int count = 1; while (p != NULL && count < i) { p = p->next; count++; } if (p == NULL) return false; e = p->data; return true; } // 7. 查找元素位置 int LocateElem(LinkNode* L, ElemType e) { int pos = 1; LinkNode* p = L->next; while (p != NULL) { if (p->data == e) return pos; p = p->next; pos++; } return 0; // 未找到返回0 } // 8. 在第i个位置插入元素 bool ListInsert(LinkNode*& L, int i, ElemType e) { if (i < 1) return false; LinkNode* p = L; int count = 0; // 定位到第i-1个节点 while (p != NULL && count < i - 1) { p = p->next; count++; } if (p == NULL) return false; LinkNode* newNode = new LinkNode; newNode->data = e; newNode->next = p->next; p->next = newNode; return true; } // 9. 删除第i个元素 bool ListDelete(LinkNode*& L, int i, ElemType& e) { if (i < 1) return false; LinkNode* p = L; int count = 0; // 定位到第i-1个节点 while (p->next != NULL && count < i - 1) { p = p->next; count++; } if (p->next == NULL) return false; LinkNode* delNode = p->next; e = delNode->data; p->next = delNode->next; delete delNode; return true; } // 10. 释放单链表 void DestroyList(LinkNode*& L) { LinkNode* p = L; while (p != NULL) { LinkNode* temp = p; p = p->next; delete temp; } L = NULL; // 防止野指针 } ``` --- #### 文件:exp2-2.cpp ```cpp #include <iostream> #include "linklist.cpp" // 包含链表操作实现 int main() { LinkNode* h; // 单链表头指针 // (1) 初始化单链表h InitList(h); cout << "初始化完成" << endl; // (2) 尾插法插入a,b,c,d,e InsertRear(h, 'a'); InsertRear(h, 'b'); InsertRear(h, 'c'); InsertRear(h, 'd'); InsertRear(h, 'e'); cout << "插入a,b,c,d,e完成" << endl; // (3) 输出单链表h cout << "单链表h: "; DispList(h); // 输出: a b c d e // (4) 输出长度 cout << "长度: " << ListLength(h) << endl; // 5 // (5) 判断是否为空 cout << "是否为空: " << (ListEmpty(h) ? "是" : "否") << endl; // 否 // (6) 输出第3个元素 ElemType e; if (GetElem(h, 3, e)) cout << "第3个元素: " << e << endl; // c else cout << "位置无效" << endl; // (7) 查找元素a的位置 int pos = LocateElem(h, 'a'); if (pos > 0) cout << "a的位置: " << pos << endl; // 1 else cout << "未找到" << endl; // (8) 在第4个位置插入f if (ListInsert(h, 4, 'f')) { cout << "插入f后的链表: "; DispList(h); // a b c f d e } // (9) 输出单链表h cout << "当前链表: "; DispList(h); // (10) 删除第3个元素 ElemType deletedElem; if (ListDelete(h, 3, deletedElem)) { cout << "删除元素: " << deletedElem << endl; // c cout << "删除后链表: "; DispList(h); // a b f d e } // (11) 输出单链表h cout << "最终链表: "; DispList(h); // (12) 释放单链表 DestroyList(h); cout << "链表已释放" << endl; return 0; } ``` --- ### 功能验证说明 1. **初始化**:创建头结点 2. **尾插法**:依次插入a,b,c,d,e → `a b c d e` 3. **输出链表**:遍历输出所有元素 4. **计算长度**:返回节点数量(头结点不计入) 5. **判空**:检查头结点是否为空 6. **获取第3个元素**:返回`c`(索引从1开始) 7. **查找元素位置**:`a`位于位置1 8. **位置插入**:在第4位插入`f` → `a b c f d e` 9. **删除元素**:删除第3个元素`c` → `a b f d e` 10. **释放链表**:递归删除所有节点 > 编译命令:`g++ exp2-2.cpp -o exp2-2 && ./exp2-2` --- ### 相关问题 1. 单链表头结点有什么作用?能否省略头结点? 2. 尾插法相比头插法有什么优缺点? 3. 如何检测单链表是否存在? 4. 在单链表中实现元素排序有哪些方法? > 参考资料:数据结构单链表基本操作实现[^1][^2]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FriendshipT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值