数组去重十种方法(C++)

news2024/10/6 6:17:20

C++数组去重方法

在C++中,数组去重是一个常见的操作,以下是一些常见的数组去重方法:

一、使用std::sortstd::unique(STL方法)

  1. 原理

    • 首先,std::sort函数用于对数组进行排序。它基于比较操作将数组元素按特定顺序(默认是升序)排列。例如,对于一个整数数组int arr[] = {5, 3, 4, 3, 2, 5};std::sort会将其重新排列为有序的序列,如{2, 3, 3, 4, 5, 5}

    • 然后,std::unique函数用于去除相邻的重复元素。它通过将不重复的元素移动到数组的前面部分来实现去重。在上面排序后的数组中,std::unique会将不重复的元素2, 3, 4, 5移动到数组的前面,返回一个指向去重后数组末尾的迭代器(实际上是一个指针,在数组场景下)。

  2. 示例代码:

#include <iostream>
#include <algorithm>
int main() {
    int arr[] = {5, 3, 4, 3, 2, 5}; 
    int n = sizeof(arr)/sizeof(arr[0]); 
    std::sort(arr, arr + n); 
    int new_end = std::unique(arr, arr + n)-arr; 
    for (int i = 0; i < new_end; i++) {
        std::cout << arr[i] << " "; 
    }
    return 0; 
}
  1. 在这个示例中,首先计算数组的大小,然后对数组进行排序,接着使用std::unique去重,最后遍历去重后的数组部分并输出。

  2. 注意事项

    • std::unique只能去除相邻的重复元素,所以在使用之前通常需要先对数组进行排序。

    • 它不会改变数组的大小,只是将重复的元素移到数组的末尾部分,返回的是去重后有效元素的末尾位置。

二、使用std::set容器

  1. 原理

    • std::set是C++ STL中的关联容器,它的特性是元素唯一。当向std::set中插入元素时,它会自动检查元素是否已经存在,如果不存在则插入,否则忽略。

  2. 示例代码:

#include <iostream>
#include <set>
int main() {
    int arr[] = {5, 3, 4, 3, 2, 5}; 
    int n = sizeof(arr)/sizeof(arr[0]); 
    std::set<int> s; 
    for (int i = 0; i < n; i++) {
        s.insert(arr[i]);  
    }
    for (auto it = s.begin();  it!= s.end();  it++) {
        std::cout << *it << " "; 
    }
    return 0; 
}
  1. 这里首先创建一个std::set,然后遍历数组将元素插入到std::set中,最后遍历std::set输出去重后的元素。

  2. 注意事项

    • std::set中的元素是按照特定顺序存储的(默认是升序),如果不需要这种顺序,可以考虑使用std::unordered_set,它在插入和查找操作上可能会更高效。

    • 使用std::set去重会改变元素的存储结构,并且如果需要将去重后的结果再转换回数组,需要额外的操作。

三、手动比较法(双循环法)

  1. 原理

    • 这种方法使用两层循环。外层循环遍历数组的每个元素,内层循环用于比较当前元素与之前已经处理过的元素是否相同。如果找到相同的元素,则说明当前元素是重复的,可以跳过或者进行相应的处理。

  2. 示例代码:

#include <iostream>
int main() {
    int arr[] = {5, 3, 4, 3, 2, 5}; 
    int n = sizeof(arr)/sizeof(arr[0]); 
    int new_size = 0; 
    for (int i = 0; i < n; i++) {
        bool is_duplicate = false; 
        for (int j = 0; j < new_size; j++) {
            if (arr[i] == arr[j]) {
                is_duplicate = true; 
                break; 
            }
        }
        if (!is_duplicate) {
            arr[new_size] = arr[i]; 
            new_size++; 
        }
    }
    for (int i = 0; i < new_size; i++) {
        std::cout << arr[i] << " "; 
    }
    return 0; 
}
  1. 在这个示例中,new_size用于记录去重后的数组大小。外层循环每次取一个元素,内层循环检查该元素是否已经在前面出现过,如果没有则将其放入去重后的数组部分。

  2. 注意事项

    • 这种方法的时间复杂度较高,为O(n^2)O(n2),其中n是数组的大小。当数组规模较大时,性能可能会比较差。

    • 它不需要额外的容器,但代码相对复杂一些。

四、利用std::map记录元素出现次数

  1. 原理

    • std::map是C++ STL中的关联容器,它以键 - 值对的形式存储数据。在这里,可以将数组元素作为键,元素出现的次数作为值。在遍历数组时,如果元素第一次出现,则将其插入std::map中,值设为1;如果元素已经存在,则将对应的值加1。最后,只遍历值为1的键,即为去重后的元素。

  2. 示例代码:

#include <iostream>
#include <map>
int main() {
    int arr[] = {5, 3, 4, 3, 2, 5}; 
    int n = sizeof(arr)/sizeof(arr[0]); 
    std::map<int, int> m; 
    for (int i = 0; i < n; i++) {
        if (m.find(arr[i])!=  m.end())  { 
            m[arr[i]]++; 
        } else {
            m[arr[i]] = 1; 
        }
    }
    for (auto it = m.begin();  it!= m.end();  it++) {
        if (it->second == 1) {
            std::cout << it->first << " "; 
        }
    }
    return 0; 
}
  1. 首先创建std::map,然后遍历数组更新元素的出现次数,最后遍历std::map输出只出现一次的元素。

  2. 注意事项

    • 这种方法使用了额外的std::map容器来存储元素的出现次数信息,会占用一定的额外空间。

    • 相比于直接比较的方法,虽然时间复杂度在平均情况下可能较好,但对于一些特殊情况可能会有一定的性能开销。

五、使用std::unordered_set(哈希表去重)

  1. 原理

    • std::unordered_set是基于哈希表实现的关联容器。它的插入和查找操作平均时间复杂度接近常数时间O(1)O(1)。当向std::unordered_set中插入元素时,它会根据元素的哈希值快速判断元素是否已经存在,如果不存在则插入,从而实现去重。

  2. 示例代码:

#include <iostream>
#include <unordered_set>
int main() {
    int arr[] = {5, 3, 4, 3, 2, 5}; 
    int n = sizeof(arr)/sizeof(arr[0]); 
    std::unordered_set<int> s; 
    for (int i = 0; i < n; i++) {
        s.insert(arr[i]);  
    }
    for (auto it = s.begin();  it!= s.end();  it++) {
        std::cout << *it << " "; 
    }
    return 0; 
}
  1. 与使用std::set类似,只是这里使用的是std::unordered_set,它在性能上可能更适合一些不需要排序且元素数量较多的情况。

  2. 注意事项

    • std::unordered_set的哈希函数可能会导致哈希冲突,在极端情况下可能会影响性能。不过在大多数情况下,它的性能表现较好。

    • 同样,将去重后的结果转换回数组可能需要额外的操作。

六、标记法

  1. 原理

    • 可以创建一个额外的布尔类型数组来标记已经出现过的元素。遍历原始数组时,对于每个元素,检查其在标记数组中的对应位置是否已经被标记。如果没有被标记,则说明该元素是第一次出现,将其加入去重后的结果中,并在标记数组中标记该元素;如果已经被标记,则说明是重复元素,跳过。

    • 示例代码:

#include <iostream>
int main() {
    int arr[] = {5, 3, 4, 3, 2, 5}; 
    int n = sizeof(arr)/sizeof(arr[0]); 
    bool marked[n]; 
    for (int i = 0; i < n; i++) {
        marked[i] = false; 
    }
    int new_size = 0; 
    for (int i = 0; i < n; i++) {
        if (!marked[i]) {
            arr[new_size] = arr[i]; 
            new_size++; 
            for (int j = i + 1; j < n; j++) {
                if (arr[i] == arr[j]) { 
                    marked[j] = true; 
                }
            }
        } 
    }
    for (int i = 0; i < new_size; i++) {
        std::cout << arr[i] << " "; 
    }
    return 0; 
}
  1. 这里首先初始化标记数组,然后在遍历原始数组时,根据标记数组判断元素是否重复,并更新标记数组和去重后的数组。

  2. 注意事项

    • 这种方法需要额外创建一个与原始数组大小相同的布尔类型数组来进行标记,会占用一定的额外空间。

    • 它的时间复杂度取决于原始数组中重复元素的分布情况,但在最坏情况下也是O(n^2)O(n2)。

七、排序后单循环比较(优化双循环法)

  1. 原理

    • 先对数组进行排序,这样相同的元素就会相邻。然后只需要一次循环遍历数组,比较当前元素和下一个元素是否相同。如果不同,则说明当前元素是不重复的,可以进行相应处理;如果相同,则说明是重复元素,可以跳过。

  2. 示例代码:

#include <iostream>
#include <algorithm>
int main() {
    int arr[] = {5, 3, 4, 3, 2, 5}; 
    int n = sizeof(arr)/sizeof(arr[0]); 
    std::sort(arr, arr + n); 
    int new_size = 0; 
    for (int i = 0; i < n - 1; i++) {
        if (arr[i]!= arr[i + 1]) {
            arr[new_size] = arr[i]; 
            new_size++; 
        }
    }
    arr[new_size] = arr[n - 1]; 
    new_size++; 
    for (int i = 0; i < new_size; i++) {
        std::cout << arr[i] << " "; 
    }
    return 0; 
}

  1. 先对数组排序,然后在循环中比较相邻元素,最后将最后一个元素处理并输出去重后的数组。

  2. 注意事项

    • 相比于普通的双循环法,这种方法利用了排序后相同元素相邻的特性,减少了比较的次数,时间复杂度为O(nlogn + n)O(nlogn+n),其中nlognnlogn是排序的时间复杂度,n是遍历数组的时间复杂度。

    • 不过它需要先对数组进行排序,如果不需要排序后的结果,这可能会是一个额外的开销。

八、利用数组指针和动态内存分配

  1. 原理

    • 可以创建一个动态分配内存的新数组,然后通过指针遍历原始数组。对于原始数组中的每个元素,检查它是否已经存在于新数组中。如果不存在,则将其添加到新数组中。这种方法可以避免使用一些复杂的STL容器,但需要手动管理内存。

  2. 示例代码:

#include <iostream>
int main() {
    int arr[] = {5, 3, 4, 3, 2, 5}; 
    int n = sizeof(arr)/sizeof(arr[0]); 
    int* new_arr = new int[n]; 
    int new_size = 0; 
    for (int i = 0; i < n; i++) {
        bool is_duplicate = false; 
        for (int j = 0; j < new_size; j++) {
            if (arr[i] == new_arr[j]) { 
                is_duplicate = true; 
                break; 
            }
        }
        if (!is_duplicate) {
            new_arr[new_size] = arr[i]; 
            new_size++; 
        }
    }
    for (int i = 0; i < new_size; i++) {
        std::cout << new_arr[i] << " "; 
    }
    delete[] new_arr; 
    return 0; 
}
  1. 这里动态分配了一个新数组,然后通过双循环比较将不重复的元素放入新数组,最后输出并释放内存。

  2. 注意事项

    • 这种方法需要手动管理动态分配的内存,如果忘记释放内存会导致内存泄漏。

    • 双循环比较导致时间复杂度为O(n^2)O(n2),性能在数组较大时可能较差。

九、使用std::vectorstd::erase - std::remove组合(针对std::vector类型数组)

  1. 原理

    • 在C++中,std::vector是一个动态数组容器。std::remove函数实际上并不会真正删除元素,而是将不想要的元素移到容器的末尾,并返回一个指向新的逻辑末尾的迭代器。然后std::erase函数根据这个迭代器真正地从std::vector中删除元素。

  2. 示例代码:

#include <iostream>
#include <vector>
#include <algorithm>
int main() {
    std::vector<int> v = {5, 3, 4, 3, 2, 5}; 
    v.erase(std::remove(v.begin(),  v.end(),  3), v.end());  
    v.erase(std::remove(v.begin(),  v.end(),  5), v.end());  
    for (auto it : v) {
        std::cout << it << " "; 
    }
    return 0; 
}
  1. 这里先使用std::remove将指定元素(这里是3和5)移到std::vector的末尾,然后使用std::erase删除这些元素。需要注意的是,如果要去重所有元素,可能需要多次调用std::removestd::erase或者采用更复杂的逻辑。

  2. 注意事项

    • 这种方法主要针对std::vector类型的数组,如果是普通数组需要先转换为std::vector

    • std::remove的使用可能会比较复杂,尤其是对于多种元素去重或者完全去重的情况。

十、自定义哈希函数去重(针对自定义类型数组)

  1. 原理

    • 当数组中的元素是自定义类型时,可以定义一个哈希函数来计算元素的哈希值。然后可以使用类似于std::unordered_set的原理,创建一个基于自定义哈希函数的容器或者数据结构,通过比较哈希值来判断元素是否重复。

  2. 示例代码(假设自定义类型为MyType:

#include <iostream>
#include <unordered_set>
struct MyType {
    int a; 
    int b; 
    // 假设根据a和b计算哈希值
    size_t operator()(const MyType& obj) const {
        return std::hash<int>()(obj.a)^ std::hash<int>()(obj.b); 
    }
}; 
int main() {
    MyType arr[] = { {1, 2}, {3, 4}, {1, 2}, {5, 6} }; 
    int n = sizeof(arr)/sizeof(arr[0]); 
    std::unordered_set<MyType, MyType> s; 
    for (int i = 0; i < n; i++) {
        s.insert(arr[i]);  
    }
    for (auto it = s.begin();  it!= s.end();  it++) {
        std::cout << "(" << it->a << ", " << it->b << ") "; 
    }
    return 0; 
}

这里定义了MyType结构体,并为其定义了一个哈希函数。然后使用std::unordered_set结合自定义哈希函数对MyType类型的数组进行去重。

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

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

相关文章

【小沐学GIS】blender导入OpenTopography地形数据(BlenderGIS、OSM、Python)

文章目录 1、简介1.1 blender1.2 OpenStreetMap地图 2、BlenderGIS2.1 下载BlenderGIS2.2 安装BlenderGIS2.3 申请opentopography的key2.4 抓取卫星地图2.5 生成高度图2.6 获取OSM数据 结语 1、简介 1.1 blender https://www.blender.org/ Blender 是一款免费的开源 3D 创作套…

[uni-app]小兔鲜-07订单+支付

订单模块 基本信息渲染 import type { OrderState } from /services/constants import type { AddressItem } from ./address import type { PageParams } from /types/global/** 获取预付订单 返回信息 */ export type OrderPreResult {/** 商品集合 [ 商品信息 ] */goods: …

微信小程序地理定位与逆地址解析详解

地理定位 1 原理与思路 在微信小程序中&#xff0c;地理定位功能可以通过调用微信提供的API接口来实现。这些接口允许我们获取用户的当前位置或者让用户通过地图选择位置。获取到位置信息后&#xff0c;我们可以使用逆地址解析来获取详细的地址信息&#xff0c;如省、市、区、…

CUDA安装教程

文章目录 一、CUDA的下载和安装1.1 查看NVIDIA适配CUDA版本1.2 下载CUDA Toolkit1.3 安装CUDA 二、环境配置三、查看是否安装成功 一、CUDA的下载和安装 CUDA在深度学习中允许开发者充分利用NVIDIA GPU的强大计算能力来加速深度学习模型的训练和推理过程。 1.1 查看NVIDIA适配…

15分钟学 Python 第39天:Python 爬虫入门(五)

Day 39&#xff1a;Python 爬虫入门数据存储概述 在进行网页爬虫时&#xff0c;抓取到的数据需要存储以供后续分析和使用。常见的存储方式包括但不限于&#xff1a; 文件存储&#xff08;如文本文件、CSV、JSON&#xff09;数据库存储&#xff08;如SQLite、MySQL、MongoDB&a…

多模态理论基础——什么是多模态?

文章目录 多模态理论1.什么是多模态&#xff08;multimodal&#xff09;2.深度学习中的多模态 多模态理论 1.什么是多模态&#xff08;multimodal&#xff09; 模态指的是数据或者信息的表现形式&#xff0c;如文本、图像、音频、视频等 多模态指的是数据或者信息的多种表现…

算法笔记(十)——队列+宽搜

文章目录 N 叉数的层序遍历二叉树的锯齿形层序遍历二叉树最大宽度在每个树行中找最大值 BFS是图上最基础、最重要的搜索算法之一&#xff1b; 每次都尝试访问同一层的节点如果同一层都访问完了&#xff0c;再访问下一层 BFS基本框架 void bfs(起始点) {将起始点放入队列中;标记…

一款基于.NET开发的简易高效的文件转换器

前言 今天大姚给大家分享一款基于.NET开发的免费&#xff08;GPL-3.0 license&#xff09;、简易、高效的文件转换器&#xff0c;允许用户通过Windows资源管理器的上下文菜单来转换和压缩一个或多个文件&#xff1a;FileConverter。 使用技术栈 ffmpeg&#xff1a;作为文件转换…

vite学习教程03、vite+vue2打包配置

文章目录 前言一、修改vite.config.js二、配置文件资源/路径提示三、测试打包参考文章资料获取 前言 博主介绍&#xff1a;✌目前全网粉丝3W&#xff0c;csdn博客专家、Java领域优质创作者&#xff0c;博客之星、阿里云平台优质作者、专注于Java后端技术领域。 涵盖技术内容&…

Python | Leetcode Python题解之第457题环形数组是否存在循环

题目&#xff1a; 题解&#xff1a; class Solution:def circularArrayLoop(self, nums: List[int]) -> bool:n len(nums)def next(cur: int) -> int:return (cur nums[cur]) % n # 保证返回值在 [0,n) 中for i, num in enumerate(nums):if num 0:continueslow, fas…

Qt中使用QPainter绘制阴影

困扰了很久的问题&#xff0c;今天终于明白了如何绘制QGraphicDropShadowEffect同样效果的阴影&#xff0c;故写下这篇文章分享给大家。其方法是复制Qt源代码中QGraphicDropShadowEffect绘制实现的核心代码然后稍作修改实现&#xff0c;先看效果和封装过后的源代码&#xff1a;…

在 Ubuntu 安装 Python3.7(没有弯路)

注&#xff1a;当前Ubuntu版本为18.04 下载Python源码包 wget https://www.python.org/ftp/python/3.7.12/Python-3.7.12.tgz安装前准备 安装依赖组件 apt-get updateapt-get install build-essential zlib1g-dev libncurses5-dev libgdbm-dev libnss3-dev libssl-dev libs…

c++----多态(初识)

大家好&#xff0c;今天我们来讲讲我们c中的一个关键知识&#xff0c;叫做多态。但是我们学习多态之前必须将我们前面学习过的继承学习过后才能学习。当然大家可能会先想什么叫多态&#xff0c;我们从名字上上看的话就是多种姿态嘛。毕竟看起来这么容易理解&#xff0c;但其实也…

服务器conda环境安装rpy2

参考博客 https://stackoverflow.com/questions/68936589/how-to-select-r-installation-when-using-rpy2-on-conda 现在我遇到这样一个问题&#xff0c;服务器系统环境没有R(没有权限安装&#xff09;&#xff0c;我只能在minconda的conda环境中使用R, 使用方法如下 我现在…

Rocky Linux 9搭建K8s-1.28.0+docker一主多从集群测试环境

集群类型&#xff1a; Kubernetes集群大体上分为两类&#xff1a;一主多从和多主多从 一主多从&#xff1a;一台master节点和多台node节点&#xff0c;搭建简单&#xff0c;但是有单机故障风险&#xff0c;适用于测试环境 多主多从&#xff1a;多台master节点和多台node节点&am…

ELK日志收集之ES的DSL查询语句

一、简介 在Elasticsearch中&#xff0c;我们可以使用Elasticsearch-DSL(Elasticsearch Domain Specific Language)来构建和执行复杂的搜索查询。官方Query DSL指导文档。 叶查询&#xff1a;在特定字段中寻找特定值,例如 match ,term 或 range。 复合查询&#xff1a;具有查询…

【进阶OpenCV】 (5)--指纹验证

文章目录 指纹验证1. 验证原理2. 读取图片3. 计算特征匹配点 总结 指纹验证 指纹验证基于人类指纹的独特性和稳定性。每个人的指纹在图案、断点和交叉点上各不相同&#xff0c;这种唯一性和终生不变性使得指纹成为身份验证的可靠手段。指纹识别技术通过采集和分析指纹图像&…

39 C 语言枚举类型、枚举常量、枚举变量、枚举的遍历、枚举数组、枚举与 switch

目录 1 什么是枚举 2 定义枚举类型 2.1 语法格式 2.2 枚举元素的特点 2.3 案例演示 3 枚举变量 3.1 什么是枚举变量 3.2 定义枚举变量的多种方式 3.3 案例演示 1&#xff1a;标准版枚举类型 3.4 案例演示 2&#xff1a;简化版枚举类型 3.5 案例演示 3&#xff1a;匿…

【教学类-77-01】20241005青花瓷立体书

背景需求&#xff1a; 今天翻到小红书上一个青花瓷立体书 &#x1f1ed;&#x1f1f0;香港免费展览&#xff5c;青花瓷立体纸艺观展册&#x1f4d6; - 小红书 (xiaohongshu.com)https://www.xiaohongshu.com/discovery/item/6426a8fb000000001303653e?app_platformandroid&a…

买卖股票大合集

刷题刷题往死里刷。 121. 买卖股票的最佳时机 链接 121. 买卖股票的最佳时机 思路&#xff1a; 二次做所以有思路了&#xff0c; 从头遍历数组&#xff0c;维持一个最小值&#xff0c;且遇到一个值就计算差值&#xff0c;且维护这个最大值为答案。 class Solution {public …