• 数据结构与算法 -- 数组


    一、数组基本概念

             数组(Array)是一种线性表数据结构。它用一组连续的内存空间,来存储一组具有相同类型的数据。

    线性表:线性表就是数据排成像一条线一样的结构。每个线性表上的数据最多只有前和后两个方向。其实除了数组,链表、队列、栈等也是线性表结构。

     连续的内存空间和相同类型的数:因为这两个限制,它才有了一个堪称“杀手锏”的特性:“随机访问”。但有利就有弊,这两个限制也让数组的很多操作变得非常低效,比如要想在数组中删除、插入一个数据,为了保证连续性,就需要做大量的数据搬移工作。

            数组是适合查找操作,但是查找的时间复杂度并不为 O(1)。即便是排好序的数组,你用二分查找,时间复杂度也是 O(logn)。数组支持随机访问,根据下标随机访问的时间复杂度为 O(1)。

    二、数组的“插入” 和“删除”

             假设数组的长度为 n,现在,如果我们需要将一个数据插入到数组中的第 k 个位置。为了把第 k 个位置腾出来,给新来的数据,我们需要将第 k~n 这部分的元素都顺序地往后挪一位。数组插入操作的最好时间复杂度为O(1),最坏时间复杂度为O(n),平均时间复杂度为O(n)。

             如果数组中的数据是有序的,我们在某个位置插入一个新的元素时,就必须按照刚才的方法搬移 k 之后的数据。但是,如果数组中存储的数据并没有任何规律,数组只是被当作一个存储数据的集合。在这种情况下,如果要将某个数据插入到第 k 个位置,为了避免大规模的数据搬移,我们还有一个简单的办法就是,直接将第 k 位的数据搬移到数组元素的最后,把新的元素直接放入第 k 个位置。该场景下,在第k个位置插入一个元素的时间复杂度就变为O(1)。

     

             如果我们要删除第 k 个位置的数据,为了内存的连续性,也需要搬移数据,不然中间就会出现空洞,内存就不连续了。如果删除数组末尾的数据,则最好情况时间复杂度为 O(1);如果删除开头的数据,则最坏情况时间复杂度为 O(n);平均情况时间复杂度也为 O(n)。

            在某些特殊场景下,我们并不一定非得追求数组中数据的连续性。如果我们将多次删除操作集中在一起执行,这样能够有效提升删除的效率。删除数据时可以先记录下已经删除的数据。每次的删除操作并不是真正地搬移数据,只是记录数据已经被删除。当数组没有更多空间存储数据时,再触发执行一次真正的删除操作。

    三、数组越界

             在使用数组的时候,一定要警惕数组越界,像C语言这种没有数组越界检测机制的语言,出现数组越界可能会带来意想不到的逻辑错误。在进行程序开发过程中,一定记得数组边界条件的检查,防止数组越界。

    四、容器与数组的关系

             ArrayList 、vector最大的优势就是可以将很多数组操作的细节封装起来。比如前面提到的数组插入、删除数据时需要搬移其他数据等。另外,它还有一个优势,就是支持动态扩容。动态扩容操作涉及内存申请和数据搬移,是比较耗时的。所以,如果事先能确定需要存储的数据大小,最好在创建 ArrayList 的时候事先指定数据大小。指定大小能够减少不必要的内存申请和数据搬移,提升性能。

            对于业务开发,直接使用容器就足够了,省时省力。毕竟损耗一丢丢性能,完全不会影响到系统整体的性能。但如果你是做一些非常底层的开发,比如开发网络框架,性能的优化需要做到极致,这个时候数组就会优于容器,成为首选。

    五、常见数组 题目

    1、实现两个有序数组合并为一个有序数组

    1. package main
    2. import (
    3. "fmt"
    4. )
    5. func mergeTwoArray(arr1 []int, arr2 []int) []int {
    6. lengthOfArray1 := len(arr1)
    7. lengthOfArray2 := len(arr2)
    8. mergedArray := make([]int, 0)
    9. i := 0
    10. j := 0
    11. for i < lengthOfArray1 && j < lengthOfArray2 {
    12. if arr1[i] <= arr2[j] {
    13. mergedArray = append(mergedArray, arr1[i])
    14. i++
    15. } else if arr1[i] >= arr2[j] {
    16. mergedArray = append(mergedArray, arr2[j])
    17. j++
    18. }
    19. }
    20. for i
    21. mergedArray = append(mergedArray, arr1[i])
    22. i++
    23. }
    24. for j
    25. mergedArray = append(mergedArray, arr2[j])
    26. j++
    27. }
    28. return mergedArray
    29. }
    30. func main() {
    31. arr1 := []int{1, 3, 5, 7}
    32. arr2 := []int{2, 4, 6, 8, 9, 10}
    33. arr := mergeTwoArray(arr1, arr2)
    34. fmt.Println(arr)
    35. }
    36. [1 2 3 4 5 6 7 8 9 10]

    声明:参考极客时间《数据结构与算法之美》专栏

     

     

     

  • 相关阅读:
    路由器——交换机——网络交换机:区别
    mac M2 pytorch_geometric安装
    C++ STL重点、难点复习总结
    spring 请求 出现实体类大小写不一致 出现的问题
    箭头函数的缺点
    ant的javac任务的相关属性配置
    C#设计模式详解(1)——Template Method(模板方法)
    如何搭建跨境独立站?
    一个简单的HTML篮球网页【学生网页设计作业源码】
    【Transformer 相关理论深入理解】注意力机制、自注意力机制、多头注意力机制、位置编码
  • 原文地址:https://blog.csdn.net/u012967763/article/details/126451240