PTA 变长动态数组

318 阅读7分钟

在C/C++中,原生数组的空间大小是静态的。例如,声明int a[10]之后,数组a的大小就已经固定为10,因此在使用时,常常以数据量的上限作为数组的长度,比如最多有可能要保存100,000个元素时,可以创建一个长度为10610^6的数组。这样做的显著缺点就是,当实际数据量较小时,会造成大量的空间浪费。更合理的方法是因地制宜地对数组空间进行动态分配。这里给出一种最简单的动态数组管理策略:

  1. 使用用户分配的一段连续的内存空间表示数组;
  2. 初始时,数组的容量为16;
  3. (扩展) 当插入元素时,如果空间已满,将数组容量增加至原有的2倍,也就是将原有的空间释放,分配一个大小为原有大小2倍的内存空间,并将原来的元素复制到新的空间中,例如原来的空间大小为16,现在增加到32;
  4. (压缩) 删除数组中元素后,如果剩余元素的数量小于容量的1/4(向下取整)且数组的容量大于16时,删除掉原有的空间,分配一个为原有容量一半的新的内存空间,并将原来的元素复制到新的空间中,例如原来的容量为64,但是删除元素后,数组中只有15个元素,那么将容量压缩到32。

根据以上策略,实现下面的接口。这些接口以C++中容器std::vector为蓝本,接口的具体功能请看函数前的注释。通过实现这些接口,你或许能够受到一些关于面向对象编程(OOP)的启发。

动态数组类型定义:

typedef int Elem;

typedef enum bool
{
    false,
    true
} bool;

typedef struct DynamicArray
{
    size_t capacity;   // 数组的容量,初始值和最小值均为16u
    size_t size;       // 数组中元素的个数
    Elem* data;     // 用于保存数组中元素的内存空间
} DynamicArray;

函数接口定义:

// 创建数组,初始容量为16。
DynamicArray* DynamicArray_new();

// 本题中,在位置pos前插入元素*v,也就是*v插入后,它是数组中的第pos个元素,下标从0开始。
// 注意,插入后的数组可能发生容量的倍增,假设内存分配总是合法的,不需要针对内存不足抛出异常。
// 例如:
//      1. pos = 10  ,表示在第10个元素前插入元素*v,插入后它前面有10个元素,原来的第10个元素在它后面
//      2. pos = 0   ,表示在数组的起始位置插入元素*v
//      3. pos = size,表示在数组的末尾插入元素*v
void DynamicArray_insert(DynamicArray* this, size_t pos, const Elem* v);

// 删除位置pos的元素,可以假定pos总是合法的,即pos=0,1,...,size - 1,删除后的数组不应该有空档。
// 注意,删除数组元素可能会导致容量变更。
// 例如:
//     size = 6, 数组中的元素为0,1,2,3,4,5。删除了pos=3的元素3之后,数组的元素变为0,1,2,4,5,size=5,数组第3个元素为4
Elem DynamicArray_erase(DynamicArray* this, size_t pos);

// 获取位置为pos的元素,假设pos总是合法的。
Elem DynamicArray_get(const DynamicArray* this, size_t pos);

// 将位置为pos的元素设置为*v,假设pos总是合法的。
void DynamicArray_set(DynamicArray* this, size_t pos, const Elem* v);

// 返回数组的容量。
size_t DynamicArray_capacity(const DynamicArray* this);

// 返回数组的元素个数。
size_t DynamicArray_size(const DynamicArray* this);

// 返回数组元素是否为空,为空则返回true,否则返回false。
bool DynamicArray_empty(const DynamicArray* this);

// 重设数组元素个数为new_size,注意,容量可能因此发生变更。
// 新的数组元素个数如果比原来的数组更多,将原有数组的元素复制到新数组对应位置即可。
// 新的数组元素个数如果比原来的数组更少,超出的部分截断即可。
// 例如:
//    1. 原数组为0,1,2,3,4,5,新数组大小为4,那么新数组变为0,1,2,3,size=4
//    2. 原数组为0,1,2,3,新数组大小为6,那么数组变为0,1,2,3,?,?,size=6,其中?表示任意值,即这些多出的元素
//       不需要初始化
void DynamicArray_resize(DynamicArray* this, size_t new_size);

// 删除数组。不要忘记释放原有数组的空间,否则会造成内存泄漏。
void DynamicArray_delete(DynamicArray* this);

裁判测试程序样例:

int main()
{
    char line[100];
    DynamicArray* arr = NULL;
    size_t size;
    size_t pos;
    Elem v;

    while (gets(line))
    {
        switch (line[0])
        {
        case 'C': // 创建数组
            arr = DynamicArray_new();
            break;
        case 'D': // 删除数组
            DynamicArray_delete(arr);
            arr = NULL;
            break;
        case 'R': // resize
            sscanf(line + 2, "%lu", &size);
            DynamicArray_resize(arr, size);
            break;
        case 'c': // 输出capacity
            printf("%lu\n", DynamicArray_capacity(arr));
            break;
        case 's': // 输出size
            printf("%lu\n", DynamicArray_size(arr));
            break;
        case 'i': // 插入元素
            sscanf(line + 2, "%lu %d", &pos, &v);
            DynamicArray_insert(arr, pos, &v);
            break;
        case 'd': // 删除元素并输出
            sscanf(line + 2, "%lu", &pos);
            printf("%d\n", DynamicArray_erase(arr, pos));
            break;
        case 'G': // get元素并输出
            sscanf(line + 2, "%lu", &pos);
            printf("%d\n", DynamicArray_get(arr, pos));
            break;
        case 'S': // set元素
            sscanf(line + 2, "%lu %d", &pos, &v);
            DynamicArray_set(arr, pos, &v);
            break;
        case 'e': // 输出数组是否为empty
            printf("%s\n", DynamicArray_empty(arr) == true ? "true" : "false");
            break;
        default:
            break;
        }
    }

    return 0;
}

/* 请在这里填写答案 */

输入输出介绍:

输入输出均由裁判程序完成,作答时通常不需要关注。

以下为裁判程序的输入输出介绍,以单个字母表示命令,它们是大小写敏感的,随后跟随若干个参数,命令列表如下:

命令含义示例示例含义
C创建数组C
D删除数组D
R $0重设数组元素个数为$0R 20重设数组元素个数为20
c输出数组容量capacityc
s输出数组元素个数sizes
i 01在位置0前插入元素1i 10 5在第10个位置前插入5
d $0删除位置$0的元素并输出d 10删除位置为10的元素并输出
G $0获取位置$0的元素并输出G 5获取位置为5的元素并输出
S 01将位置0的元素赋值为1S 5 10将数组位置为5的元素设为10
e输出数组是否为空,为空输出true,反之输出falsee

更具体的信息请参考裁判测试程序样例。

可以保证,同一时间,程序中至多只有一个被创建的数组。在创建数组(C)后,每一个操作均在刚刚创建的数组中进行。当数组被删除且没有新的数组被创建时,不会有除创建(C)外的指令。

输入样例:

C
R 10
s
c
S 9 10
i 0 -1
G 0
G 10
d 0
G 9
e
c
R 20
s
c
R 60
s
c
R 5
s
c
D
结尾无空行

输出格式:

10
16
-1
10
-1
10
false
16
20
32
60
64
5
16
结尾无空行

代码:

DynamicArray* DynamicArray_new()
{
    DynamicArray* vec;
    vec = (DynamicArray*)malloc(sizeof(struct DynamicArray));
    vec->capacity = 16 ;
    vec->data = (int *)malloc(sizeof(int) * vec->capacity);
    vec->size = 0 ;
    return vec ;
}
void DynamicArray_insert(DynamicArray* this, size_t pos, const Elem* v){
    if(this == NULL)
        return ;
    if(this->size == this->capacity)
    {
        this->capacity *= 2 ;
        this->data = (int *)realloc(this->data,sizeof(int)* this->capacity);
    }
    int  t = pos ;
    for(int i = this->size-1 ; i >= t ; i-- )
        this->data[i+1] = this->data[i];
    this->data[t] = *v ;
    this->size ++;
}
Elem DynamicArray_erase(DynamicArray* this, size_t pos){
     if(this == NULL)
        return 0;
    if(pos < 0 || pos > this->size)
        return 0;
    Elem t =this->data[pos];
    for(size_t i = pos ; i < this->size-1 ; i ++ )
        this->data[i] = this->data[i+1];
    this->size -- ;
     while(this->size < this->capacity / 4 && this->capacity > 16)
            this->capacity /= 2 ;
    this->data = (int *)realloc(this->data,sizeof(int)* this->capacity);
    return t ;
}
Elem DynamicArray_get(const DynamicArray* this, size_t pos)
{
    return this->data[pos];
}
void DynamicArray_set(DynamicArray* this, size_t pos, const Elem* v) 
{
    this->data[pos] = *v ;
}
size_t DynamicArray_capacity(const DynamicArray* this) 
{
    return this->capacity;
}
size_t DynamicArray_size(const DynamicArray* this) 
{
    return this->size;
}
bool DynamicArray_empty(const DynamicArray* this)
{
    if(this->size == 0)
        return true;
    else
        return false;
}
void DynamicArray_resize(DynamicArray* this, size_t new_size){
     
    if(new_size > this->capacity)
    {
        while(new_size > this->capacity)
            this->capacity *= 2;
        this->data = (int *)realloc(this->data,sizeof(int)* this->capacity);
        this->size = new_size;
    }
    else
    {
        while(new_size < this->capacity / 4 && this->capacity > 16)
            this->capacity /= 2 ;
        this->data = (int *)realloc(this->data,sizeof(int)* this->capacity);
        this->size = new_size;
    }
}
void DynamicArray_delete(DynamicArray* this){
    free(this->data);
    free(this);
}

提交结果: