C++ 链表概述

news2024/12/23 1:12:26

背景

当需要存储大量数据并需要对其进行操作时,常常需要使用到链表这种数据结构。它可以用来存储一系列的元素并支持插入、删除、遍历等操作。

概念

一般来说,链表是由若干个节点组成的,每个节点包含了两个部分的内容:存储的数据本身和指向下一个节点的指针。这种数据结构,使得每个节点都可以访问它的数据,以及它后面的节点。

在C++中,链表可以通过定义一个节点类来实现。下面是一个简单的链表节点类的示例代码:

class Node {
public:
    int data;
    Node* next;
};

or 

typedef struct Node {
    int data;
    struct Node *next;
}

在上面的代码中,定义了一个名为"Node"的类(或者是结构体,下文默认使用class,操作起来类似),这个类包含了两个成员变量:一个整型数据"data"(常见的一般使用int作为存储数据的类型,也可以使用class或者struct等一些复合类型作为data),以及一个指向下一个节点的指针"next"。这样,就可以使用这个节点类来构造一个链表了。

构建链表:

构建链表时,需要定义一个头节点,它指向链表的第一个元素。头节点通常不包含实际数据,只是用来记录链表的起点。下面是一个简单的创建链表的示例代码:

Node* head = new Node(); // 定义头节点
head->next = nullptr; // 将头节点的指针指向 NULL,表示链表为空

针对链表的操作,一般有添加节点、查询节点、删除节点、遍历节点、去除重复节点以及对链表排序等操作,下文将展开:

添加节点

接下来,可以向链表中添加节点,从而构建一个有数据的链表。下面是一个向链表中添加节点的示例代码:

// 向链表尾部添加一个新节点
void addNode(Node* head, int data) {
    Node* p = head;
    while (p->next != nullptr) {
        p = p->next;
    }
    Node* newNode = new Node();
    newNode->data = data;
    newNode->next = nullptr;
    p->next = newNode;
}

在上面的代码中,定义了addNode函数,其中head是头结点的指针,data是需要插入的数据,该函数的作用是向链表的尾部添加一个新节点。在函数中,首先遍历链表,找到链表的最后一个节点,然后创建一个新的节点,将它的数据设置为传入的"data"参数,并将它添加到链表的末尾。

查询节点

这是一个查找对应节点的代码示例,假设需要查找一个值为value的节点:

Node* findNode(Node* head, int value) {
    Node* p = head->next; // 从头节点的下一个节点开始遍历
    while (p != nullptr) {
        if (p->data == value) { // 找到了目标节点
            return p;
        }
        p = p->next;
    }
    // 没有找到目标节点,返回nullptr
    return nullptr;
}

删除节点

当需要在链表中查找节点或删除节点时,通常需要遍历链表,找到目标节点并进行相应的操作。下面是一个简单的删除节点的代码示例,假设需要删除的节点指针为target:

void deleteNode(Node* head, Node* target) {
    Node* p = head;
    while (p->next != target) { // 找到目标节点的前一个节点
        p = p->next;
    }
    p->next = target->next; // 将前一个节点的指针指向目标节点的下一个节点
    delete target; // 释放目标节点的内存
}

上面的代码中,定义了deleteNode函数,形参为一个头节点head和一个目标节点指针"target",用来删除链表中的目标节点。函数首先遍历链表,找到目标节点的前一个节点,然后将前一个节点的指针指向目标节点的下一个节点,从而删除目标节点。最后,使用"delete"操作符释放目标节点的内存空间。

去除重复节点

最简单的方式就是使用set来实现去重。还有一种比较朴实无华的方式就是使用双重循环的方式来实现去重,不需要使用哈希表。这种方法的思路是:对于每个节点,遍历它后面的所有节点,如果找到与当前节点相同的节点,则将它从链表中删除。下面是一个示例代码:

非hash去重

void removeDuplicates(Node* head) {
    Node* p = head;
    while (p != nullptr) {
        Node* q = p->next;
        Node* prev = p; // 上一个非重复节点
        while (q != nullptr) {
            if (p->data == q->data) {
                prev->next = q->next; // 将当前节点从链表中删除
                delete q;
                q = prev->next; // 将指针移动到下一个节点
            } else {
                prev = q; // 更新上一个非重复节点
                q = q->next; // 将指针移动到下一个节点
            }
        }
        p = p->next; // 将指针移动到下一个节点
    }
}

在上面的代码中,定义了一个removeDuplicates函数,形参为一个头节点head,用于删除链表中的重复节点。函数首先定义了一个指向当前节点的指针p,然后从头节点head开始遍历整个链表。对于每个节点p,定义一个指向它后面节点的指针q,然后遍历节点p后面的所有节点。如果找到一个与节点p的值相同的节点,则将它从链表中删除。否则,将指针q移动到下一个节点,并更新上一个非重复节点的指针prev。最后,将指针p移动到下一个节点。

hash去重

#include <unordered_set>  // 需要放到开头去
void removeDuplicates(Node* head) {
    unordered_set<int> hash; // 哈希表,用于存储每个节点的值
    Node* p = head;
    Node* prev = nullptr; // 上一个非重复节点
    while (p != nullptr) {
        if (hash.count(p->data)) { // 如果当前节点的值在哈希表中出现过
            prev->next = p->next; // 删除当前节点
            delete p;
            p = prev->next; // 将指针移动到下一个节点
        } else {
            hash.insert(p->data); // 将当前节点的值加入到哈希表中
            prev = p; // 更新上一个非重复节点
            p = p->next; // 将指针移动到下一个节点
        }
    }
}

与上述函数一样,形参为头节点head,用于删除链表中的重复节点。函数首先定义了一个unordered_set类型的哈希表(也可以使用set来实现),用于存储每个节点的值。然后,遍历整个链表,对于每个节点,判断它的值是否在哈希表中出现过。如果出现过,则删除当前节点;否则将当前节点的值加入到哈希表中,并更新上一个非重复节点的指针。

排序

此处使用冒泡排序来写一个简单的示例。首先呢链表是不能像数组那样使用下标来访问元素,但是呢,我们可以通过修改节点的指针来改变链表的顺序,从而实现链表的排序。以下是代码示例:

class Node {
public:
    int data;
    Node* next;
};

Node* bubbleSortList(Node* head) {
    if (!head || !head->next) return head; // 链表为空或只有一个节点,直接返回

    Node *cur = head, *tail = nullptr;
    while (tail != head->next) { // tail 指向未排序部分的第一个节点
        while (cur->next != tail) {
            if (cur->data > cur->next->data) { // 如果相邻两个节点顺序不对,则交换它们的值
                int temp = cur->data;
                cur->data = cur->next->data;
                cur->next->data = temp;
            }
            cur = cur->next;
        }
        tail = cur; // 更新 tail,向前移动一位
        cur = head; // cur 重新指向链表头节点
    }
    return head;
}

上述代码中,定义了一个名为"bubbleSortList"的函数,它接受一个链表的头节点指针。函数使用两个指针变量"cur"和"tail"来遍历链表,并对链表节点进行排序。内层循环通过比较相邻节点的值并交换它们的值,实现了冒泡排序的过程。注意,每次内层循环结束后,"tail"指向的是未排序部分的第一个节点,"cur"重新指向链表头节点。

冒泡排序的时间复杂度为O(n^2),在链表中的实现中需要更多的指针操作,因此效率相对较低。但是,冒泡排序是一种稳定的排序算法,可以保留原有相同元素之间的相对位置。

用例测试

以下是全部代码的集合,并编写了用例对功能进行了测试

#include "set.h"
#include <iostream>
#include <unordered_set>
class Node {
public:
    int data;
    Node* next;
};


// 向链表尾部添加一个新节点
void addNode(Node* head, int data) {
    Node* p = head;
    while (p->next !=  nullptr) {
        p = p->next;
    }
    Node* newNode = new Node();
    newNode->data = data;
    newNode->next =  nullptr;
    p->next = newNode;
}

Node* findNode(Node* head, int value) {
    Node* p = head->next; // 从头节点的下一个节点开始遍历
    while (p != nullptr) {
        if (p->data == value) { // 找到了目标节点
            return p;
        }
        p = p->next;
    }
    // 没有找到目标节点,返回nullptr
    return nullptr;
}

void deleteNode(Node* head, Node* target) {
    Node* p = head;
    while (p->next != target) { // 找到目标节点的前一个节点
        p = p->next;
    }
    p->next = target->next; // 将前一个节点的指针指向目标节点的下一个节点
    delete target; // 释放目标节点的内存
}

//void removeDuplicates(Node* head) {
//    Node* p = head;
//    while (p != nullptr) {
//        Node* q = p->next;
//        Node* prev = p; // 上一个非重复节点
//        while (q != nullptr) {
//                if (p->data == q->data) {
//                        prev->next = q->next; // 将当前节点从链表中删除
//                        delete q;
//                        q = prev->next; // 将指针移动到下一个节点
//                    } else {
//                        prev = q; // 更新上一个非重复节点
//                        q = q->next; // 将指针移动到下一个节点
//                    }
//            }
//        p = p->next; // 将指针移动到下一个节点
//    }
//}

void removeDuplicates(Node* head) {
    std::unordered_set<int> hash; // 哈希表,用于存储每个节点的值
    Node* p = head;
    Node* prev = nullptr; // 上一个非重复节点
    while (p != nullptr) {
        if (hash.count(p->data)) { // 如果当前节点的值在哈希表中出现过
            prev->next = p->next; // 删除当前节点
            delete p;
            p = prev->next; // 将指针移动到下一个节点
        } else {
            hash.insert(p->data); // 将当前节点的值加入到哈希表中
            prev = p; // 更新上一个非重复节点
            p = p->next; // 将指针移动到下一个节点
        }
    }
}

Node* bubbleSortList(Node* head) {
    if (!head || !head->next) return head; // 链表为空或只有一个节点,直接返回

    Node *cur = head, *tail =  nullptr;
    while (tail != head->next) { // tail 指向未排序部分的第一个节点
        while (cur->next != tail) {
            if (cur->data > cur->next->data) { // 如果相邻两个节点顺序不对,则交换它们的值
                int temp = cur->data;
                cur->data = cur->next->data;
                cur->next->data = temp;
            }
            cur = cur->next;
        }
        tail = cur; // 更新 tail,向前移动一位
        cur = head; // cur 重新指向链表头节点
    }
    return head;
}

void printList(Node* head) {
    Node* p = head;
    while (p !=  nullptr) {
        std::cout << p->data << " ";
        p = p->next;
    }
    std::cout << std::endl;
}

int main() {

    Node* head = new Node(); // 定义头节点
    head->next =  nullptr; // 将头节点的指针指向  nullptr,表示链表为空

    addNode(head, 5);
    addNode(head, 3);
    addNode(head, 9);
    addNode(head, 1);
    addNode(head, 7);

    std::cout << "Before sorting: ";
    printList(head);

    if (findNode(head, 9)) {
        std::cout << "Found 9 in the list" << std::endl;
    }

    Node *target = findNode(head, 3);
    if (target != nullptr) {
        deleteNode(head, target);
        std::cout << "After removing 3: ";
        printList(head);
    }

    bubbleSortList(head);
    std::cout << "After sorting: ";
    printList(head);

    return 0;
}
//int a[5] {1, 2, 3, 4, 5}, b[5] = {1, 3, 6, 7, 8};
//int x1[2] = {1, 2}, x2[5] {1, 2, 3};
//bool bl;
//Set c(a, 5);
//Set d(b, 5);
//Set e(x1, 5);
//Set f(x2, 5);
//
//Set g;
//std::cout << "Set c: "; c.display();
//std::cout << "Set d: "; d.display();
//
//g = c + d;
//std::cout << "Set g: "; g.display();

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/477630.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【嵌入式环境下linux内核及驱动学习笔记-(6-内核 I/O)-阻塞与非阻塞】

目录 1、阻塞与非阻塞1.1 以对recvfrom函数的调用及执行过程来说明阻塞的操作。1.2 以对recvfrom函数的不断轮询调用为例&#xff0c;说明非阻塞时进程的行为。1.3 简单介绍内核链表及等待队列1.4 等待队列1.4.1 定义等待队列头部&#xff08;wait_queue_head_t&#xff09;1.4…

vue动态添加多组数据添加正则限制

如图新增多条数据&#xff0c;如果删除其中一条正则校验失败的数据&#xff0c;提示不会随之删除&#xff0c;若想提示删除并不清空数据 delete (item, index) {this.applicationForm.reserveInfo.forEach((v, i) > {if (i index) {this.$refs.formValidate.fields.forEac…

UFT——操作模块

示例一 创建一个可重复利用的登录测试更改Action的名称。使用本地数据表。创建一个主调用测试。建立测试迭代。处理缺失的Action。 分析&#xff1a;就是创建一个只有登录的测试起名为login&#xff0c;然后在创建一个主测试起名字比如main&#xff0c;在main中&#xff0c;调用…

微信小程序定义模板

微信小程序提供模板&#xff08;template&#xff09;功能&#xff0c;把一些可以共用的&#xff0c;复用的代码在模板中定义为代码片段&#xff0c;然后在不同的地方调用&#xff0c;可以实现一次编写&#xff0c;多次引用的效果。 首先我们看一下官网是如何操作的 一般的情…

笔记:对多维torch进行任意维度的多“行”操作

如何取出多维torch指定维度的指定“行” 从二维torch开始新建torch取出某一行取出某一列一次性取出多行取出连续的多行取出不连续的多行 一次取出多列取出连续的多列取出不连续的多列 考虑三维torch取出三维torch的任意两行&#xff08;means 在dim0上操作&#xff09;取出连续…

( 字符串) 9. 回文数 ——【Leetcode每日一题】

❓9. 回文数 难度&#xff1a;简单 给你一个整数 x &#xff0c;如果 x 是一个回文整数&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 回文数是指正序&#xff08;从左向右&#xff09;和倒序&#xff08;从右向左&#xff09;读都是一样的整数。 例如…

Git的安装与使用+Git在IDEA中的使用

文章目录 一、Git概述1、版本控制器的方式2、Git的工作流程图 二、Git的安装与常用命令1、Git环境安装2、Git环境基本配置3、获取本地仓库4、基础操作指令 三、分支1、常用指令2、解决合并冲突 四、Git远程仓库1、创建远程仓库2、远程操作仓库3、冲突处理 四、IDEA中使用Git1、…

数据结构——二叉树

二叉树 1 二叉树的种类 1.1 满二叉树 节点数量为 2^k - 1 (k是树的深度&#xff0c;底层的叶子节点都是满的&#xff09; 1.2 完全二叉树 完全二叉树是指除了下面一层外&#xff0c;其余层的节点都是满的&#xff1b; 且最下面一层的叶子节点是从左到右连续的。 下面这个…

pci总线协议学习笔记——PCI总线基本概念

1、pci总线概述 (1)PCI&#xff0c;外设组件互连标准(Peripheral Component Interconnection)&#xff0c;是一种由英特尔&#xff08;Intel&#xff09;公司1991年推出的用于定义局部总线的标准; (2)最早提出的PCI总线工作在33MHz频率之下&#xff0c;传输带宽达到133MB/s(33M…

【LeetCode】236. 二叉树的最近公共祖先

1.问题 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有根树 T 的两个节点 p、q&#xff0c;最近公共祖先表示为一个节点 x&#xff0c;满足 x 是 p、q 的祖先且 x 的深度尽可能大&#xff08;一个节点也可以是…

1992-2022年31省GDP、第一产业增加值、第二产业增加值 第三产业增加值

1992-2022年31省GDP、第一产业增加值、第二产业增加值 第三产业增加值 1、时间&#xff1a;1992-2022年 2、范围&#xff1a;包括31省 3、指标&#xff1a;省GDP、省第一产业增加值、省第二产业增加值、省第三产业增加值 4、缺失情况说明&#xff1a;无缺失 5、来源&#…

【python知识】__init__.py的来龙去脉

一、说明 我们常见__init__.py文件&#xff0c;但说不清楚它的用途&#xff0c;在本文&#xff0c;我将首先把它的来龙去脉说清楚&#xff0c;然后告诉大家&#xff0c;如何编制python工程&#xff0c;培养全局的编程格局。 二、包-模块-函数结构 在Python工程里&#xff0c;当…

playwright连接已有浏览器操作

文章目录 playwright连接已有浏览器操作前置准备打开本地已有缓存的Chrome&#xff08;理解&#xff09;指定端口打开浏览器连接指定端口已启动浏览器&#xff08;推荐&#xff09; playwright连接已有浏览器操作 前置准备 pip install playwright # 安装playwright的python…

红黑树数据结构

现在JAVASE中HashMap中底层源码是由数组链表红黑树进行设计的&#xff0c;然后很多地方也是用到红黑树&#xff0c;这里单独对红黑树数据结构进行简单的介绍。 目录 红黑树概念 红黑树的性质 自平衡规则 代码 红黑树概念 红黑树&#xff0c;是一种二叉搜索树&#xff0c;…

linux常用命令大全(保姆及入门)

linux常用命令大全 一、文件处理命令1、目录处理命令&#xff1a;ls2、目录处理命令2.1 mkdir2.2 pwd2.3 rmdir2.4 cp2.5 mv2.6 rm 3.文件处理命令3.1 touch3.2 cat3.3 tac3.4 more3.5 less3.6 head3.7 tail 4.链接命令4.1 ln 二、权限管理命令2.1 chmod2.2 chown2.3 chgrp 2.4…

VRPTW:新雀优化算法NOA求解带时间窗的车辆路径问题

一、新雀优化算法NOA求解带时间窗的车辆路径问题 1.1VRPTW模型如下&#xff1a; 带时间窗的车辆路径问题(Vehicle Routing Problem with Time Windows, VRPTW) 1.2新雀优化算法NOA求解VRPTW close all clear clc SearchAgents_no30; % 种群大小 Function_nameF1; Max_ite…

准备“开黑”,电脑却“告退”?游戏闪退的解决方法

游戏玩家近期可能会发现&#xff0c;不少大作陆陆续续登录PC市场&#xff0c;比如《死亡岛 2》、《无畏契约》等。但也有不少游戏用户会发现&#xff0c;电脑玩游戏时容易出现闪退的情况。特别是在进行高负荷运算的时候&#xff0c;有一些游戏更为容易出现这种情况&#xff0c;…

[架构之路-176]-《软考-系统分析师》-17-嵌入式系统分析与设计 -1- 实时性(任务切换时间、中断延迟时间、中断响应时间)、可靠性、功耗、体积、成本

目录 前言&#xff1a; 1 7 . 1 嵌 入 式 系 统 概 述 1 . 嵌入式系统的特点 (1) 系统专用性强。 (2) 系统实时性强。 (3) 软硬件依赖性强 (4) 处理器专用。 ( 5 ) 多种技术紧密结合。 (6) 系统透明性。 (7) 系统资源受限。 2 . 嵌入式系统的组成 1 7 . 3 嵌入式实…

拷贝构造函数和赋值重载函数详解

1.拷贝构造函数 1.1拷贝构造函数的概念 拷贝构造函数&#xff1a;只有单个形参&#xff0c;该形参是对本类类型对象的引用(一般常用const修饰)&#xff0c;在用已存在的类类型对象创建新对象时由编译器自动调用。拷贝构造函数也是特殊的成员函数&#xff0c;其特征如下&#…