算法范本:链表

一、链表基础

链表的基础操作包括链表的创建和增删查改。其中链表的索引index一般从0开始。这里有以下几点需要注意:

  • 链表是一个类,节点结构体是类中的一个私有结构体
  • 对于一个链表,其私有数据需要包括节点结构体,虚拟头结点,节点数量
  • 对于节点结构体,需要在定义结构体的时候需要定义构造函数,且不要忘记结构体定义最后的;
  • 对于链表,需要在public中定义链表的初始化构造函数
class MyLinkedList {

private:
	/---链表依赖的基础结构:节点,节点数量,虚拟头结点---/
    struct LinkedNode{	
        int val;
        LinkedNode* next;
        LinkedNode() = default;
        LinkedNode(int x) : val(x), next(nullptr) {}
    };  /定义结构体的;不要忘记
    int _size;
    LinkedNode* _dummyhead;
    
public:
    /---链表初始化函数---/
    MyLinkedList() {
        _size = 0;
        _dummyhead = new LinkedNode(0);
    }
    
    /---给定节点index,如果index合法,返回节点数值;如果index非法,返回-1---/
    int get(int index) {
        if(index >= _size || index < 0) return -1;
        else{
            LinkedNode* cur = _dummyhead;
            while(index--){
                cur = cur->next;
            }
            return cur->next->val;
        }
    }
    
    /---在链表头部添加一个节点---/
    void addAtHead(int val) {
        LinkedNode* newnode = new LinkedNode(val);
        newnode->next = _dummyhead->next;
        _dummyhead->next = newnode;
        _size++;
    }
    
    /---在链表尾部添加一个节点---/
    void addAtTail(int val) {
        LinkedNode* newnode = new LinkedNode(val);
        LinkedNode* cur = _dummyhead;
        while(cur->next != nullptr){
            cur = cur->next;
        }
        cur->next = newnode;
        _size++;
    }
    
    
    /---给定节点index,在给定index节点前添加一个节点---/
    void addAtIndex(int index, int val) {
        if(index < 0 || index > _size) return;
        LinkedNode* newnode = new LinkedNode(val);
        LinkedNode* cur = _dummyhead;
        while(index--){
            cur = cur->next;
        }
        newnode->next = cur->next;
        cur->next = newnode;
        _size++;
    }
    
    /---给定节点index,删除index对应的节点---/
    void deleteAtIndex(int index) {
        if(index < 0 || index >= _size) return;
        LinkedNode* cur = _dummyhead;
        while(index--){
            cur = cur->next;
        }
        LinkedNode* delnode = cur->next;
        cur->next = delnode->next;
        delete delnode;
        _size--;
    }
};

二、反转链表

反转链表写对的核心在于记住:当前节点next指向前一个节点的时候,前一个节点的next已经完成了修改,其四要素在于:

  • 定义null节点完成头结点的反向
  • 记录当前节点的下一节点
  • 反转当前节点的next指针
  • 移动当前节点和前一个节点

1、循环法

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* cur = head;
        ListNode* pre = nullptr;		/初始化使用null完成头节点反转
        while(cur){
            ListNode* tmp = cur->next;	/记录当前节点下一节点
            cur->next = pre;			/反转当前节点next指针
            pre = cur;					/移动当前处理节点及其前一个节点
            cur = tmp;
        }
        return pre;
    }
};

2、递归法

相较于循环法,递归法使用递归调用隐藏了当前节点next指针反转后当前处理节点的移动

class Solution {
private:
    ListNode* reverse(ListNode* pre, ListNode* cur){
        if(cur == nullptr) return pre;
        ListNode* tmp = cur->next;
        cur->next = pre;

        return reverse(cur, tmp);	/使用递归调用实现当前处理节点移动
    }
public:
    ListNode* reverseList(ListNode* head) {
        return reverse(nullptr, head);
    }
};

三、环形链表判定

使用快慢指针法, 分别定义 fast 和 slow指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点。

  • 如果fastslow指针在途中相遇 ,说明这个链表有环。
  • 如果fast走到了nullptr,则链表没有环
  • 注意while条件为fast != nullptr && fast->next != nullptr
class Solution {
public:
    bool hasCycle(ListNode *head) {
        ListNode* slow = head;
        ListNode* fast = head;
        while(fast != nullptr && fast->next != nullptr){
            fast = fast->next->next;
            slow = slow->next;
            if(fast == slow) return true;
        }
        return false;
    }
};

四、环形链表入口判断

按照环形链表判定后,当fast和slow指针项与后:从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是环形入口的节点。

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {

		/---判定链表是否为环形链表---/
        ListNode* fast = head;
        ListNode* slow = head;
        while(fast != nullptr && fast->next != nullptr){
            fast = fast->next->next;
            slow = slow->next;

			/---如果链表是环形链表,则查找链表入口---/
            if(fast == slow){
                ListNode* index = head;
                while(index != fast){
                    index = index->next;
                    fast = fast->next;
                }
                return index;
            }
            
        }
        return nullptr;
    }
};

五、链表虚拟头结点dummyhead的作用

  • 当链表的头结点可能发生变化时候(原有头结点被删除或更改),使用dummyhead->next返回正确的头结点
  • 当链表进行循环处理时候,添加dummyhead可以使得头结点和其他节点保持一致,即均有一个节点指向自己
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值