算法训练营 Day 3

1、移除链表元素

第一种方法,使用不带虚你头结点的链表

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        // 删除头结点
        while (head != nullptr && head->val == val) {
            ListNode* tmp = head;
            head = head->next;
            delete tmp;
        }

        // 删除非头结点
        ListNode* cur = head;
        while (cur != nullptr && cur->next!= nullptr) {
            if (cur->next->val == val) {
                ListNode* tmp = cur->next;
                cur->next = cur->next->next;
                delete tmp;
            } else {
                cur = cur->next;
            }
        }
        return head;
    }
};

第二种方法,使用带虚拟头结点的链表

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        ListNode* dummyHead = new ListNode(0); 
        dummyHead->next = head; // 把不带虚拟头的转化为带虚拟头的
        ListNode* cur = dummyHead;
        while (cur->next != nullptr) {
            if(cur->next->val == val) {
                ListNode* tmp = cur->next;
                cur->next = cur->next->next;
                delete tmp;
            } else {
                cur = cur->next;
            }
        }
        head = dummyHead->next;
        delete dummyHead;
        return head;
    }
};

2、设计链表

使用带虚拟头结点的链表,这样更方便

使用C++去实现:

#include <iostream>

using namespace std;

class MyLinkedList {
public:
    // 定义链表节点结构体
    struct LinkedNode { //类中可以定义结构体,也可以定义类
        int val;
        LinkedNode* next;
        explicit 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); //当新建一个结点时,它的next指针已经指向了nullptr
        LinkedNode* cur = _dummyHead;

        while(cur->next) //找到最后的一个结点
            cur = cur->next;

        cur->next = newNode;
        _size++;
    }

    // 在第index个节点之前插入一个新节点
    void addAtIndex(int index, int val) {
        // 如果index大于链表的长度,则返回空
        if(index > _size)
            return;
        // 如果index小于0,则在头部插入节点
        if(index < 0)
            index = 0;
        LinkedNode* newNode = new LinkedNode(val);
        LinkedNode* cur = _dummyHead;

        while(index--) //找到前驱结点
            cur = cur->next;

        newNode->next = cur->next;
        cur->next = newNode;
        _size++;
    }

    // 删除第index个节点
    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;
        tmp = nullptr; //如果确定之后不会再使用tmp,也可以不设置其为nullptr
        _size--;
    }

    // 打印链表
    void printLinkedList() {
        LinkedNode* cur = _dummyHead; //由于在类里,所以不用再传结构体指针
        while (cur->next) {
            cout << cur->next->val << " -> ";
            cur = cur->next;
        }
        cout << "nullptr" << endl;
    }
private:
    int _size; //链表长度,不包括虚拟头
    LinkedNode* _dummyHead; //声明一个指向虚拟头的结构体指针
};

int main() {
    MyLinkedList LinkedList;
    
    for (int i = 1; i <= 5; ++i)
        LinkedList.addAtTail(i);

    LinkedList.printLinkedList();
}

delete命令指示释放了tmp指针原本所指的那部分内存,被delete后的指针tmp的值(地址)并非就是NULL,而是随机值。也就是被delete后,如果不再加上一句tmp=nullptrtmp会成为乱指的野指针,如果之后的程序不小心使用了tmp,会指向难以预想的内存空间。

使用C语言去实现:

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

typedef int E;

// 带头结点链表
typedef struct ListNode {
    E val;
    struct ListNode *next;
} ListNode, *Node; //ListNode是结点,Node是链表头指针

void initList(Node head) {
    head->next = NULL;
}

bool insertList(Node head, E val, int index) {
    if (index < 1) { //限定左边界
        printf("插入失败\n");
        return false;
    }

    while (--index) {
        head = head->next; //找到要插入位序的前驱节点
        if (!head) { //限定右边界
            printf("插入失败\n");
            return false;
        }
    }

    Node node = malloc(sizeof(ListNode));
    if (!node) {
        printf("内存分配失败\n");
        return false;
    }

    node->val = val;
    node->next = head->next;
    head->next = node;
    return true;
}

//头插法
bool insertFrontList(Node head, E val) {
    Node node = malloc(sizeof(ListNode));
    if (!node) {
        printf("内存分配失败\n");
        return false;
    }

    node->val = val;
    node->next = head->next;
    head->next = node;
    return true;
}

//尾插法
bool insertEndList(Node head, E val) {
    Node node = malloc(sizeof(ListNode));
    if (!node) {
        printf("内存分配失败\n");
        return false;
    }

    while (head->next)
        head = head->next; 
    
    node->val = val;
    node->next = NULL;
    head->next = node;
    return true;
}

bool deleteList(Node head, int index) {
    if (index < 1) { //限定左边界
        printf("删除失败\n");
        return false;
    }

    while (--index) {
        head = head->next; //找到要删除位序的前驱节点
        if (!head->next) { //限定右边界
            printf("删除失败\n");
            return false;
        }
    }
    
    if(head->next == NULL) {
        printf("删除失败\n");
        return false;
    }

    Node tmp = head->next;
    head->next = head->next->next;
    free(tmp);
    return true;
}

E *getList(Node head, int index) { //这里返回指针是为了防止隐式类型转换
    if (index < 1) { //限定左边界
        printf("获取失败\n");
        return NULL;
    }    

    do { //因为头结点不存放数据,所以用do...while
        head = head->next; //这里head指向该位序的节点
        if (!head) {
            printf("获取失败\n");
            return NULL;
        }
    } while (--index);
    return &head->val;
}

int findList(Node head, E val) {
    head = head->next; //带结点和不带结点的区别
    int i = 1;
    while (head) {
        if (head->val == val)
            return i;
        head = head->next;
        i++;
    }
    return -1;
}

int sizeList(Node head) {
    int i = 0;
    while (head->next) {
        head = head->next;
        i++;
    }
    return i;
}

void printList(Node head) {
    while (head->next) {
        head = head->next;
        printf("%d -> ", head->val);
    }
    printf("NULL\n");
}

int main() {
    ListNode head;
    initList(&head);
    
    for (int i = 1; i <= 2; ++i) 
        insertList(&head, i * 100, i);
    
    deleteList(&head, 1);
    printList(&head);
}

3、反转链表

截屏2024-05-12 11.33.49

第一种方法,双指针,先用C来实现

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

typedef struct ListNode {
    int val;
    struct ListNode *next;
} ListNode, *Node;

void initList(Node *head) {
    *head = NULL;
}

bool insertEndList(Node *head, int val) {
    Node node = malloc(sizeof(ListNode));
    if (!node) {
        printf("内存分配失败\n");
        return false;
    }

    node->val = val;
    node->next = NULL;

    if (!*head) {
        *head = node;
    } else {
        Node tmp = *head; //保持头结点不动
        while (tmp->next)
            tmp = tmp->next;
        tmp->next = node;
    }
    return true;
}

Node reverseList(Node head) { //由于改变了头指针指向的内容,所以只能返回指针类型
    Node node = NULL;
    Node tmp;
    while (head) {
        tmp = head->next; 
        head->next = node;
        node = head;
        head = tmp;
    }
    return node;
}

void printList(Node head) { //只读取,不改变结构体指针的指向
    while (head) {
        printf("%d -> ", head->val);
        head = head->next;
    }
    printf("NULL\n");
}

int main() {
    Node head;
    initList(&head);

    for (int i = 1; i <= 5; ++i)
        insertEndList(&head, i);
    printList(head);

    Node node = reverseList(head);
    printList(node);
}

再用C++来实现

#include <iostream>

using namespace std;

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

class Solution {
public:
    ListNode *reverseList(ListNode *head) {
        ListNode *temp; // 保存cur的下一个节点
        ListNode *cur = head;
        ListNode *pre = nullptr;
        while (cur) {
            temp = cur->next;  // 保存一下 cur的下一个节点,因为接下来要改变cur->next
            cur->next = pre; // 翻转操作
            // 更新pre 和 cur指针
            pre = cur;
            cur = temp;
        }
        return pre; //这里其实可以直接移动head
    }
};

void printList(ListNode* head) {
    ListNode* cur = head;
    while (cur) {
        cout << cur->val << " -> ";
        cur = cur->next;
    }
    cout << "nullptr" << endl;
}

int main() {
    // 创建一个简单的链表: 1 -> 2 -> 3 -> 4 -> 5
    ListNode* head = new ListNode(1);
    head->next = new ListNode(2);
    head->next->next = new ListNode(3);
    head->next->next->next = new ListNode(4);
    head->next->next->next->next = new ListNode(5);
    printList(head);

    Solution solution;
    ListNode *node = solution.reverseList(head);
    printList(node);
}

第二种方法,利用递归

class Solution {
public:
    ListNode* reverse(ListNode* pre,ListNode* cur){
        if(cur == NULL) 
            return pre;
        ListNode* temp = cur->next;
        cur->next = pre;
        return reverse(cur,temp);
    }
    ListNode* reverseList(ListNode* head) {
        return reverse(NULL, head);
    }
};

第二种递归不太好理解,

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if(head == NULL) 
            return NULL;
        if (head->next == NULL) 
            return head;
        
        // 递归调用,翻转第二个节点开始往后的链表
        ListNode *last = reverseList(head->next);
        // 翻转头节点与第二个节点的指向
        head->next->next = head;
        // 此时的 head 节点为尾节点,next 需要指向 NULL
        head->next = NULL;
        return last;
    }
}; 
  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
邓俊辉教授是计算机科学与技术领域著名的教育家和研究者。他在清华大学担任教授,并负责计算机算法与理论方向的研究和教学工作。邓俊辉教授是中国计算机学会副理事长、国际著名科技出版社Springer中国系列丛书主编、IEICE China Communications主编、Journal of Internet Technology编委、《数据结构与算法教程》作者等。 在邓俊辉教授的指导下,他办了多次Dijkstra算法训练营,旨在培养学生对于算法学习的兴趣与能力。Dijkstra算法是一种用于图论中求解最短路径问题的经典算法,具有广泛的应用领域,如路由算法、网络规划和GPS导航系统等。在训练营中,邓俊辉教授通过讲解算法的原理和思想,引导学生进行编程实践和案例分析,帮助他们深入理解Dijkstra算法的应用场景与实际解决问题的能力。 邓俊辉教授所组织的Dijkstra算法训练营受到了广大学生的欢迎和积极参与。通过训练营的学习,学生不仅可以掌握Dijkstra算法的具体实现过程,还能了解算法设计的思路和应用的局限性。在训练营中,学生还可以与同学们进行交流和合作,共同解决实际问题,促进彼此的学术成长和人际交往能力的培养。 总之,邓俊辉的Dijkstra算法训练营为学生提供了一个系统、全面学习算法知识的平台,帮助他们培养解决实际问题的能力和思维方式。通过这样的培训,学生不仅能在学术领域取得更好的成绩,还可以为将来的职业发展打下坚实的基础。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值