【数据结构】链表头插法和尾插法

1 什么是链表?

链表是一种常用的数据结构,由若干个节点组成,每个节点都有数据部分和指针部分:数据部分用来存储节点的值,指针则指向下一个节点。

2 节点的实现

struct ListNode{
    // 数据
    int val;
    // 下一个链表节点的地址
    ListNode *next;
};

3 链表的实现

链表的结构

  • 链表的首节点称为头节点,最后一个节点称为尾节点
  • 尾节点指向的是NULL
  • 链表的实现,需要一个头节点的地址当前节点的地址,因此我们需要使用两个指针存储头节点的地址(Head)和当前节点的地址(cur)
#include <iostream>

using namespace std;
struct ListNode{
    // 数据
    int val;
    // 下一个链表节点的地址
    ListNode *next;
};
int main() {
    ListNode *L =new ListNode; //存储头节点的地址

    L->val = 0; //头节点所存储的值
    L->next = NULL; // 指向第二个节点的地址

    ListNode *l1 = new ListNode;
    l1->val = 10;
    l1->next = NULL;

    ListNode *l2 = new ListNode;
    l2->val = 30;
    l2->next = NULL;
	// 此时三个节点并没有链接,下面我们对三个节点进行连接
	
    // 将三个节点组成链表
    L->next = l1; // 头节点的指针指向L1的地址
    l1->next = l2;

    // 遍历链表
    ListNode *cur = L;
    while (cur != NULL){
        cout << cur->val << " " << endl;
        cur = cur->next;
    }
    cout << endl;
    
	return 0;
}

此时,三个节点的示意图如下所示:
在这里插入图片描述

4 将遍历链表的功能函数化

可以将

    ListNode *cur = L;
    while (cur != NULL){
        cout << cur->val << " " << endl;
        cur = cur->next;
    }
    cout << endl;

这一段代码封装成函数,已重复使用。
如下所示:

void printList(ListNode *L){
    // 遍历链表
    ListNode *cur = L;
    while (cur != NULL){
        cout << cur->val << " " << endl;
        cur = cur->next;
    }
    cout << endl;
}

5 通过尾插法创建链表

  • 判断链表是否尾空,如果是空,则创建新节点,把新节点的指针设为NULL,并让head指向新节点的地址
  • 如果不为空,则找到尾节点,创建新节点,让尾节点的指针指向新创的节点的地址,并把新创建节点的指针设置尾NULL
    如下所示:
    在这里插入图片描述
    在该函数中,如果不使用引用,而是简单地使用ListNode *L,函数内部对L的任何修改都仅仅是在函数的局部副本上进行的,对原来的指针没有影响。也就是说,外部传入的指针不会因为函数内部的操作而改变。
void InsertTail(ListNode *&L,int v){
	// 判断是否为空链表
	if(L == NULL){
		// 为空链表
		ListNode *node = new ListNode;
		L = node;
		L->val = v;
		L->next = NULL;
		return;
	}
	// 不为空链表
	// 先找到尾节点,尾节点的next指向尾NULL
	// 定义当前的指针cur,如果cur->next == NULL,说明已经找到最后一个节点了
	ListNode *cur = L;
	while(cur->next == NULL){
		cur = cur->next;
	}
	ListNode *newNode = new ListNode;
	newNode->val = V;
	newNode->next = NULL;
	
	cur->next = newNode;
}

6 通过头插法插入节点

步骤:

  • 创建一个新的节点。
  • 将新节点的 next 指针指向当前的头节点。
  • 将头节点指针 L 更新为新节点。
void InsertHead(ListNode *&L,int v){
    ListNode *newNode = new ListNode;
    newNode->val = v;
    newNode->next = L;
    L = newNode;
}

下面通过实际的代码 来实现尾插法和头插法的实现:

#include <iostream>

using namespace std;
struct ListNode{
    // 数据
    int val;
    // 下一个链表节点的地址
    ListNode *next;
};

void printList(ListNode *L){
    // 遍历链表
    ListNode *cur = L;
    while (cur != NULL){
        cout << cur->val << " " << endl;
        cur = cur->next;
    }
    cout << endl;
}

// 尾插法创建链表
void InsertTail(ListNode *&L,int v){
    // 如果这个是空列表,说明L == null;
    if(L == NULL){
        ListNode *node = new ListNode;
        L = node;
        L->val = v;
        L->next = NULL;
        return;
    }
    // 先找到尾节点
    // 尾节点的next是指向NULL
    // 定义一个指针cur->next == NULL ,说明cur已经到最后一个节点了
    ListNode *cur = L;
    // 找到最后一个节点
    while (cur->next != NULL){
        cur = cur->next;
    }
    // 此时cur应该已经指向最后一个节点
    // 先定义一个节点
    ListNode *newNode = new ListNode;
    newNode->val = v;
    newNode->next = NULL; // 新节点的

    cur->next = newNode;
}

// 定义头插法插入节点
void InsertHead(ListNode *&L,int v){
    ListNode *newNode = new ListNode;
    newNode->val = v;
    newNode->next = L;
    L = newNode;
}
int main() {
    // 通过尾插法插入元素
    ListNode *L = NULL;
    InsertTail(L,0);
    InsertTail(L,10);
    InsertTail(L,20);

    printList(L);// 打印出 0 10 20 
	
    // 使用头插法插入元素
    InsertHead(L,90);
    InsertHead(L,80);

    printList(L);// 打印出80 90 0 10 20
    return 0;
}

<think>嗯,用户想了解链表的头尾插法。首先,我得回忆一下这两种方的基本概念。链表是由节点组成的,每个节点包含数据指向下一个节点的指针。头尾插法都是向链表中添加节点的方,但它们的入位置不同。 头是在链表的头部入新节点,也就是每次新节点都成为新的头节点。这样的话,原来的头节点会变成第二个节点,依此类推。这样操作的话,链表的顺序会入的顺序相反。比如,如果依次入1、2、3,那么链表会是3->2->1。需要维护一个头指针,每次入时调整指针的指向。时间复杂度是O(1),因为不需要遍历链表尾插法则是在链表的尾部入新节点,这样新节点总是最后一个节点。这样的话,链表的顺序入顺序一致。例如,入1、2、3,链表就是1->2->3。不过,每次入需要找到链表的尾部,所以如果只有头指针的话,时间复杂度会是O(n),但如果有尾指针的话,可以优化到O(1)。 接下来要考虑用户可能的疑问。比如,这两种方的应用场景有什么区别?头适合需要逆序的情况,或者频繁在头部入,比如实现栈;尾插法则适合维护入顺序,比如队列。用户可能还会想知道如何实现这两种方,是否需要额外的指针。 然后要检查是否覆盖了关键点,比如步骤、时间复杂度、示意图、优缺点应用场景。可能还需要用例子来说明,比如入顺序不同导致链表结构不同。另外,是否需要考虑链表为空的情况,这时候头尾插法都需要处理头指针尾指针的初始化。 还要确保回答符合系统级指令,数学表达式用$...$,独立公式用$$...$$,虽然这里可能涉及代码,但用户没有特别要求代码,所以可能不需要。不过如果有伪代码的话,可能需要用正确的格式。但用户的问题主要是介绍概念,所以重点放在文字描述结构上。 结构方面,应该先定义两种方,然后分步骤说明,比较它们的区别,优缺点,应用场景,可能的问题等。需要确保语言清晰,逻辑连贯,帮助用户逐步理解。 可能用户是刚开始学习数据结构的学生,所以需要避免过于复杂的术语,用简单易懂的例子说明。同时,确保回答正确,比如头的时间复杂度确实是O(1),而尾插法在没有尾指针时是O(n)。如果有尾指针,尾插法也是O(1),这点需要说明清楚。 还要注意回答中使用中文,符合格式要求。不需要使用代码块,但如果有必要,可以用自然语言描述步骤。例如,头的步骤:创建新节点,新节点的next指向原头节点,头指针指向新节点。尾插法的步骤:找到当前尾节点,将尾节点的next指向新节点,更新尾指针到新节点。 最后总结两者的区别,帮助用户对比记忆。可能还需要提到内存管理,比如是否需要额外空间,但链表本身是动态分配,所以入节点不需要预先分配固定空间。 总之,整个思考过程需要覆盖用户的问题,确保信息准确,结构清晰,易于理解,同时符合格式要求。</think>### 链表尾插法详解 链表是一种基础数据结构,其核心是通过**节点**(Node)的指针接实现动态存储。头尾插法是两种向链表入新节点的基本方,它们在操作逻辑应用场景上有显著差异。 --- #### **一、头(Head Insertion)** **定义**:将新节点入到链表的**头部**,使其成为新的头节点。 **步骤**: 1. 创建新节点,存储数据。 2. 将新节点的`next`指针指向原头节点。 3. 更新链表的头指针(head),使其指向新节点。 **示意图**: 原始链表:`head -> A -> B -> C` 入节点`D`后:`head -> D -> A -> B -> C` **特点**: - **时间复杂度**:$O(1)$(直接操作头指针)。 - **链表顺序**:入顺序与最终链表顺序**相反**。例如,依次入1、2、3,结果为`3->2->1`。 - **适用场景**:需要逆序处理数据(如实现栈)、快速入头部。 **代码逻辑示例**: ```python new_node.next = head # 新节点指向原头节点 head = new_node # 更新头指针 ``` --- #### **二、尾插法(Tail Insertion)** **定义**:将新节点入到链表的**尾部**,使其成为新的尾节点。 **步骤**: 1. 创建新节点,存储数据。 2. 找到链表的尾节点(需遍历或维护尾指针)。 3. 将尾节点的`next`指针指向新节点。 4. 更新尾指针(若有)。 **示意图**: 原始链表:`head -> A -> B -> C` 入节点`D`后:`head -> A -> B -> C -> D` **特点**: - **时间复杂度**: - 无尾指针时:$O(n)$(需遍历到尾部)。 - 有尾指针时:$O(1)$(直接操作尾指针)。 - **链表顺序**:入顺序与最终链表顺序**一致**。例如,依次入1、2、3,结果为`1->2->3`。 - **适用场景**:维护数据原始顺序(如实现队列)。 **代码逻辑示例**: ```python if head is None: # 链表为空 head = new_node tail = new_node else: tail.next = new_node # 原尾节点指向新节点 tail = new_node # 更新尾指针 ``` --- #### **三、头尾插法的对比** | **特性** | **头** | **尾插法** | |----------------|-------------------------------|-------------------------------| | 入位置 | 头部 | 尾部 | | 时间复杂度 | $O(1)$ | 无尾指针时$O(n)$,有尾指针时$O(1)$ | | 数据顺序 | 逆序 | 正序 | | 典型应用 | 栈、逆序链表 | 队列、维护原始顺序 | --- #### **四、常见问题** 1. **如何选择头尾插法?** - 若需快速入且不关心顺序(如撤销操作),用头。 - 若需保留入顺序(如日志记录),用尾插法。 2. **是否需要额外指针?** - 头只需维护头指针。 - 尾插法建议维护尾指针以优化性能。 3. **空链表的处理** - 入第一个节点时,头指针尾指针需同时指向该节点。 --- #### **总结** 头尾插法链表操作的核心方,理解其差异有助于灵活选择数据结构。头适合高频头部操作,尾插法则更适合顺序敏感的场景。实际应用中,可结合需求选择或混合使用(如双向链表)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值