c++数据结构——链表

1.链表基础知识:

  1. 链表分类:单链表、双链表、循环链表
  2. 链表的存储:散乱分布,通过指针域的指针链接在一起
  3. 链表的定义:此处给出单链表的c++定义
struct ListNode {
   int val;  // 节点上存储的元素
   ListNode *next;  // 指向下一个节点的指针
   ListNode(int x) : val(x), next(NULL) {}  // 节点的构造函数
};
  1. 链表初始化及调用:
ListNode* head = new ListNode(5); //使用上述定义中的构造函数来初始化
ListNode* head = new ListNode(); //使用c++默认构造函数来初始化
head -> val = 5;
int a = head -> val;  //调用的方式
ListNode* temp = head;  //head其实是链表的首地址,有了首地址就有了整个链表
temp = head -> next;
  1. 链表的操作:删除节点、增加节点
    删除节点
    在这里插入图片描述
  2. 性能分析
    链表的查询都是要从头节点开始遍历的,所以时间复杂度是O(n),但是空间复杂度较低,一般都是O(1)
    在这里插入图片描述

2.典型题

2.1删除节点

leetcode地址:https://leetcode-cn.com/problems/remove-linked-list-elements/

2.1.1 直接删除

直接用原链表进行删除操作(在删除头节点时需要一段单独的逻辑去判断和操作),注意在c++中内存是要自己释放的,用delete关键字。

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        //删除头节点
        while((head != NULL)&&(head->val == val)){ //不是空链表且头节点的值与目标值相等
            ListNode* temp = head;
            head = head->next;
            delete temp;
        }
        //删除非头节点
        ListNode*  cur = head;  //头节点不动,
        while((cur != NULL)&&(cur->next != NULL)){//不是空链表且不是尾部节点
            if(cur->next->val == val){ //如果下一个节点的值等于目标值,跳过,且删除
                ListNode*  temp = cur->next; //
                cur->next = cur->next->next;
                delete temp; 
            }
            else{
                cur = cur->next;
            }
        }
        return head;
    }
};

2.1.2 虚拟头节点

设置一个虚拟头节点再进行删除操作。
在这里插入图片描述

class Solution {  //虚拟头节点的方法
public:
    ListNode* removeElements(ListNode* head, int val) {
        ListNode* dummynode = new ListNode(0); //新建一个链表,虚拟头节点
        dummynode->next = head;   
        ListNode* cur = dummynode; //
        while(cur->next != NULL){  
            if(cur->next->val == val){
                ListNode* temp = cur->next;
                cur->next = cur->next->next;
                delete temp;
            }
            else{
                cur = cur->next;
            }
        }
        head = dummynode->next;  //改变链表头节点的位置,还原链表
        delete dummynode;   //虚拟的头节点要删除掉
        return head;       
    }
};

2.2设计链表

leetcode地址:https://leetcode-cn.com/problems/design-linked-list/
在这里插入图片描述
这道题目设计链表的五个接口:

  • 获取链表第index个节点的数值
  • 在链表的最前面插入一个节点
  • 在链表的最后面插入一个节点
  • 在链表第index个节点前面插入一个节点
  • 删除链表的第index个节点
class MyLinkedList {
public:
    // 定义链表节点结构体
    struct LinkedNode {
        int val;
        LinkedNode* next;
        LinkedNode(int val):val(val), next(nullptr){}
    };

    // 初始化链表
    MyLinkedList() {
        _dummyHead = new LinkedNode(0); // 这里定义的头结点 是一个虚拟头结点,而不是真正的链表头结点
        _size = 0;
    }

    // 获取到第index个节点数值,如果index是非法数值直接返回-1, 注意index是从0开始的,第0个节点就是头结点
    int get(int index) {
        if (index > (_size - 1) || index < 0) {
            return -1;
        }
        LinkedNode* cur = _dummyHead->next;
        while(index--){ // 如果--index 就会陷入死循环
            cur = cur->next;
        }
        return cur->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为0,那么新插入的节点为链表的新头节点。
    // 如果index 等于链表的长度,则说明是新插入的节点为链表的尾结点
    // 如果index大于链表的长度,则返回空
    void addAtIndex(int index, int val) {
        if (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 大于等于链表的长度,直接return,注意index是从0开始的
    void deleteAtIndex(int index) {
        if (index >= _size || index < 0) {
            return;
        }
        LinkedNode* cur = _dummyHead;
        while(index--) {
            cur = cur ->next;
        }
        LinkedNode* tmp = cur->next;
        cur->next = cur->next->next;
        delete tmp;
        _size--;
    }

    // 打印链表
    void printLinkedList() {
        LinkedNode* cur = _dummyHead;
        while (cur->next != nullptr) {
            cout << cur->next->val << " ";
            cur = cur->next;
        }
        cout << endl;
    }
private:
    int _size;
    LinkedNode* _dummyHead;

};

2.3反转链表

leetcode地址:https://leetcode-cn.com/problems/reverse-linked-list/

2.3.1双指针法

初始化如下图,再定义一个中间链表节点temp,接收cur节点。cur做翻转节点,将next指向pre。
在这里插入图片描述

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* pre = new ListNode();  //初始化一个 null指针的节点
        pre = nullptr;
        ListNode* cur = new ListNode();
        cur = head;
        ListNode* temp;
        while(cur != nullptr){
            temp = cur->next;  //
            cur->next = pre;
            pre = cur;
            cur = temp;
        }
        return pre;
    }
};

2.3.2递归法

2.4 两两交换链表中的节点

leetcode地址:https://leetcode-cn.com/problems/swap-nodes-in-pairs/在这里插入图片描述
还是要虚拟一个头节点比较好做,指向原始节点,这样就是以3个节点为一个单元进行操作。

class Solution {    //空间复杂度 O(1), 时间复杂度O(n) 
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode* temp;
        ListNode* temp1;
        ListNode* dummynode = new ListNode(0);
        dummynode->next = head;  //虚拟头节点指向 原始头节点
        ListNode* cur = dummynode;
        while(cur->next!=nullptr && cur->next->next!=nullptr){
            temp = cur->next;  //保存临时节点
            temp1 = cur->next->next->next;
            
            cur->next = temp->next;   //step1
            cur->next->next = temp;  //step2
            temp->next = temp1;    //step3
            cur = temp;            //准备下一轮
        }
        return dummynode->next;
    }
};

2.5 删除倒数第n个节点

leetcode地址:https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/
在这里插入图片描述

2.5.1 两次扫描

第一次计算节点个数,第二次删除节点

class Solution {   //空间复杂度 O(1), 时间复杂度O(n) 
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummynode = new ListNode(0);
        dummynode->next = head;  //还是要虚拟一个头节点,因为有可能删除的是头节点
        int count = 0;
        int time = 0;
        ListNode* temp = dummynode;  //第一轮计数用
        ListNode* temp2 = dummynode;  // 第二轮扫描
        ListNode* temp1; //删除节点用
        
        while(temp->next != nullptr){  //第一轮扫描确定链表元素个数
            temp = temp->next;
            count++;
        }
      //  printf("%d", count);  //测试计数值
        while(temp2->next != nullptr){
            time++;
            if(time == count-n+1){   
               // printf("%d", time); //测试判断是否正确
                temp1 = temp2->next;
                temp2->next = temp1->next;
                delete temp1;
            }
            else{
                temp2 = temp2->next;
            }
        }
        return dummynode->next;  
    }
};

2.5.2 单次扫描完成

双指针法,其实也不止扫描了一次,快指针一次,慢指针一次,但是在时间上是有所重叠的。

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummyHead = new ListNode(0);  //虚拟头节点,便于删除真实头节点
        dummyHead->next = head;
        ListNode* slow = dummyHead;
        ListNode* fast = dummyHead;
        while(n-- && fast != NULL) {  //fast指针走n步
            fast = fast->next;
        }
        fast = fast->next; // fast再提前走一步,因为需要让slow指向删除节点的上一个节点
        while (fast != NULL) {
            fast = fast->next;
            slow = slow->next;
        }
        slow->next = slow->next->next;
        return dummyHead->next;  //有虚拟头节点的程序,返回的都应该是虚拟头节点下面一个指向
    }
};

2.6 链表相交

leetcoed地址:https://leetcode-cn.com/problems/intersection-of-two-linked-lists-lcci/
在这里插入图片描述
长链表与短链表头部对齐再往下对比是否相等。相交,即是同一节点,值、指针都相等才行。
在这里插入图片描述

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode* temp = headA;
        ListNode* temp1 = headB;
        int lenA = 1, lenB = 1;
        while(temp != NULL && temp->next != NULL){ //计算链表A的长度,要判断空链表的情况
            lenA++;  
            temp = temp->next;
        }
        //printf("%d", lenA);
        while(temp1 != NULL &&temp1->next != NULL){  //计算链表B的长度,要判断空链表的情况
            lenB++;
            temp1 = temp1->next;
        }    
        //printf("%d", lenB);   
        temp = headA;
        temp1 = headB;
        if(lenB > lenA){
            swap(lenA,lenB);
            swap(temp,temp1);
        }
       int dlen = lenA-lenB;
        while(dlen--){
            temp = temp->next;
        }
        while(temp != NULL && temp1 != NULL){ //要一直走到空指针,空链表的判断一个都不能少。
            if(temp == temp1){
                return temp;
            }
            temp = temp->next;
            temp1 = temp1->next;
        }
        return 0;

    }
};

2.7 环形链表

leetcode地址:https://leetcode-cn.com/problems/linked-list-cycle-ii/
在这里插入图片描述
解题分析链接:https://github.com/youngyangyang04/leetcode-master/blob/master/problems/0142.%E7%8E%AF%E5%BD%A2%E9%93%BE%E8%A1%A8II.md

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode* fast = head;
        ListNode* slow = head;
        while(fast != NULL && fast->next != NULL) {
            slow = slow->next;
            fast = fast->next->next;
            // 快慢指针相遇,此时从head 和 相遇点,同时查找直至相遇
            if (slow == fast) {
                ListNode* index1 = fast;
                ListNode* index2 = head;
                while (index1 != index2) {
                    index1 = index1->next;
                    index2 = index2->next;
                }
                return index2; // 返回环的入口
            }
        }
        return NULL;
    }
};
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值