数据结构与算法之美(笔记1)数组

随机访问

链表适合插入,删除,时间复杂度是O(1),数组支持随机访问,根据下标随机访问的时间复杂度是O(1)。

 

插入,删除

插入操作

假设数组的长度为 n,现在,如果我们需要将一个数据插入到数组中的第K个位置。为了把第K个位置腾出来,我们需要把 k后的 n-k  位置顺序向后移动一位。

最好时间复杂度:在最后一位插入,时间复杂度为:O(1)。

最坏时间复杂度:在第一位插入,时间复杂度是:O(n)。

平均时间复杂度:我们有可能在n个位置插入,每一种情况的可能都是1/n,所以平均情况时间复杂度为 (1+2+…n)/n=O(n)。

如果数组中的数据是有序的,我们在某个位置插入一个新的元素时,就必须按照刚才的方法搬移 k 之后的数据。但是,如果数组中存储的数据并没有任何规律,数组只是被当作一个存储数据的集合。那么这种情况之下,我们可以把插入的数据放到数组的最后一位,然后与第K个位置进行交换,这样就把时间复杂度降到O(1)了。

删除操作

与插入一样,我们删除数据之后,也要搬移数据。

最好时间复杂度:在末位进行删除,O(1)。

最坏时间复杂度:搬移n-1个位置,O(n)。

平均时间复杂度:与插入类似,时间复杂度为O(n)。

优化:在某些情景下,我们可以将多次删除操作在一起执行,删除的效率会提高。我们先记录下已经删除的数据,当数组没有空间储存的时候,再触发一次真正的数据搬移。

 

容器与数组

容器最大的优势就是可以将很多数组操作的细节封装起来。比如,数组插入,删除数据时候,需要搬移其他数据。另外,它还支持动态扩容。

1.如果特别关注性能,首选数组。

2.如果数据的大小已经事先知道,优先使用数组。

3.二维矩阵,使用数组更加直观。

所以,对于平常的开发,直接使用容器就足够了,省时省力。毕竟损耗一丢丢性能,完全不会影响系统整体的性能。但如果是一些底层的开发,什么网络框架,性能优化需要做到极致,就要使用数组。

 

为什么大多数编程语言,数组要从0开始编号?

因为数组在计算某个位置的时候(假设从0开始),是通过下面这个公式来计算的:

a[k]_address = base_address + k * type_size

 如果这个时候,是从1开始的话,计算某个数据的位置就是:

a[k]_address = base_address + (k-1)*type_size

对比这两个公式,从1开始编号,每次随机访问数组元素都要进行多了一次减法运算,对与CPU来说,就是多了一次减法指令。 

 

如何实现一个动态扩容的数组?

我们事先给数组指定一个大小size,并且通过一个used表示已经使用了的数量,当used大于等于size的时候,我们就把size扩大两倍。这里我们需要一个size*2的数组,然后把之前的数组复制到这个扩大之后的数组。它的均摊时间复杂度是O(1)。

这里是c++代码实现:

#include <iostream>
using namespace std;

class array
{
private:
    int* arr;
    int capacity;
    int used;

public:
    array(int size){
        if(size == 0){
            size = 1;
        }
        arr = new int[size];
        capacity = size;
        used = 0;
    }
    void print(){
        for(int i=0;i<used;++i){
            cout << arr[i] << " " << endl;
        }
    }
    // 末位添加数据
    void append(int data){
        if(capacity <= used){
            int* temp = new int[capacity*2];
            for(int i=0;i<used;++i){
                temp[i] = arr[i];
            }
            arr = temp;
        }
        arr[used++] = data;
    }
};

测试代码:

#include <array.h>

int main(){
    array a(0);
    a.append(10);
    a.append(9);
    a.print();
}

 

如何实现合并两个有序的数组,成为一个有序的数组?

思路是这样的:假设第一个数组L1有m个数据,第二个数组L2有n个数据,我们创建一个新的,大小为m+n的数组,用两个游标 i和 j 遍历L1和L2,两个数组的数据每一次都相互比较一次,把小的放到新的数组,然后对应的游标向右移动,直到某个游标到达界限,这时候我们再判断哪个数组还没有遍历完,然后将剩下的数据复制到新的数组之中就可以了。它的时间和空间复杂度都是O(n)。

这里是代码实现:

int main(){

    int L1[5] = {1,3,5,7,9};
    int L2[6] = {2,4,6,8,10,12};

    int L3[11] = {0};

    int i=0,j=0,k=0;
    while(i < 5 && j < 6){
        if(L1[i] < L2[j]){
            L3[k++] = L1[i++];
        }else{
            L3[k++] = L2[j++];
        }
    }

    if(i<5){
        for(;i<5;++i){
            L3[k++] = L1[i];
        }
    }else{
        for(;j<6;++j){
            L3[k++] = L2[j];
        }
    }

    for(int i=0;i<11;++i){
        cout << L3[i] << " " << endl;
    }
    return 0;
}

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值