数据结构学习:1.链表

本文介绍了链表的基本概念,包括其非连续内存空间的特性,以及访问、在头、尾和中间位置添加、删除节点的操作。通过示例代码讲解了如何实现这些操作,并提到了在LeetCode中的相关题目,强调了双指针技巧和链表指向性的理解。同时,文中还讨论了链表节点的动态内存分配和管理。
摘要由CSDN通过智能技术生成

在纸上把过程画下来再写程序

一、基础知识

1.定义

链表是通过指针将零散的内存块串联起来,链表(Linked List)是一种常见的线性结构。它不需要一块连续的内存空间,通过指针即可将一组零散的内存块串联起来。我们把内存块成为链表的节点,为了将所有的节点串起来,每个链表的节点除了存储数据之外,还需要记录链表的下一个节点的地址,这个记录下个节点地址的指针我们叫做后驱指针。搜索链表需要O(N)的时间复杂度,这点和数组类似,但是链表不能像数组一样,通过索引的方式以O(1)的时间读取第n个数。链表的优势在于能够以较高的效率在任意位置插入或者删除一个节点。

2. 常见操作

在访问、添加和删除链表节点时,通常需要操作指针来调整节点之间的链接关系。以下是链表的基本操作:

  1. 访问链表节点:

    • 要访问链表的特定位置上的节点,需要遍历链表并依次访问每个节点,直到到达目标位置。
    • 通过指针的方式,从头节点开始,逐个跟踪指针的next指向下一个节点,直到达到目标节点。
  2. 在链表中添加节点:

    • 在链表的头部添加节点:创建一个新节点,将其指针指向当前头节点,然后将头指针指向新节点。
    • 在链表的尾部添加节点:遍历链表直到达到最后一个节点,创建一个新节点,将最后一个节点的指针指向新节点。
    • 在链表的中间位置添加节点:遍历链表找到目标位置的前一个节点,创建一个新节点,将新节点的指针指向目标位置的节点,然后将前一个节点的指针指向新节点。
  3. 从链表中删除节点:

    • 删除头节点:将头指针指向头节点的下一个节点,并释放原始头节点的内存。
    • 删除尾节点:遍历链表直到达到倒数第二个节点,将倒数第二个节点的指针指向nullptr,并释放最后一个节点的内存。
    • 删除中间位置的节点:遍历链表找到目标位置的前一个节点,将前一个节点的指针指向目标位置的下一个节点,并释放目标位置节点的内存。

3. 一个链表模板(抄leetcode)


  struct ListNode {
      int val;
      ListNode *next;
      ListNode() : val(0), next(nullptr) {}
      ListNode(int x) : val(x), next(nullptr) {}
      ListNode(int x, ListNode *next) : val(x), next(next) {}
  };

二、技巧、注意问题和leetcode上的题目

1.双指针:

定义一个fast一个slow两个指针,通过不同的步长遍历列表来寻找某个位置。

leetcode 2095 删除链表的中间节点:

代码如下:


class Solution {
public:
    ListNode* deleteMiddle(ListNode* head) {
        if(head->next == nullptr){ //为什么要用next
            return nullptr;
        }
        ListNode* fast = head;
        ListNode* slow = head;
        ListNode* pre = nullptr;
        while (fast && fast->next){
            fast=fast->next->next;
            pre = slow;
            slow = slow ->next;
        }
        pre->next = pre->next->next;
        return head;
        
    }
};

错误:删除某个节点要定位到上一个节点处,pre->next = pre->next->next;

不能 用pre=pre->next; 意思一样,但是这是赋值语句。也不能用slow->next 内存消耗会不同。

2. 链表的指向性理解

leetcode206 反转链表

链表问题多关注它的指向性


class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode * cur =head;
        ListNode* pre = nullptr;
        while (cur){
            ListNode* tmp = cur->next;
            
            cur->next = pre;
            pre = cur;
            cur = tmp;

        }
        return pre;

    }
};

3. 其他题目上犯的错误

Leetcode328 奇偶链表:


class Solution {
public:
    ListNode* oddEvenList(ListNode* head) {
        if (head == nullptr){
            return nullptr;
        }
        ListNode* odd = head;
        ListNode* even = head->next;
        ListNode* evenHead = even;
        while (even != 0 && even->next != 0){
            odd->next = even->next;
            odd = odd->next;
            even->next = odd->next;
            even = even->next;
        }
        odd->next = evenHead;
        return head;
    }
};

错误:要用一个中间值evenhead来存储偶数链表


一些自己理解错的点:

1. ListNode * p 是指向结构节点的指针,里面只有一个地址。
ListNode * p= new ListNode()是一个结构节点,里面有val和指向下一个节点的结构体指针,而且该节点已经被系统分配内存,在函数体里不会被自动释放。

"ListNode * p" 声明了一个指向 ListNode 类型对象的指针变量 p,但它并没有被初始化,即它的值是未定义的,可能指向任意的内存地址。在使用该指针之前,需要确保为其分配内存或将其指向一个有效的 ListNode 对象。

"ListNode * p = new ListNode()" 则声明了一个指向 ListNode 类型对象的指针变量 p,并通过 new 运算符在堆(heap)上分配了内存空间来存储 ListNode 对象的实例。这样,p 指针被初始化为指向该分配的内存空间,可以直接访问和操作该对象的成员。

总结起来,"ListNode * p" 只是声明了一个指针变量,而 "ListNode * p = new ListNode()" 则声明并初始化了一个指针变量,使其指向一个动态分配的 ListNode 对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值