为了账号安全,请及时绑定邮箱和手机立即绑定

「数据结构与算法」什么是数组,如何用C实现一个简单的动态数组

标签:
数据结构

如果你用过R语言,那么一定用过向量,如果用Python,那么一定用过列表。那么问题来了,这两类数据结构有什么区别呢?

为什么Python的列表,支持存放不同类型的数据,而R语言的向量只能放同一个类型的数据呢?还有,为什么R语言的向量化运算函数(如sum, nchar)速度会显著性高于R的循环呢?

对于下面的R代码,那种循环更好呢?

# loop 1a <- c()for (i in 1:1000){
   a <- c(a,i)
}# loop 2a <- vector(length=1000)for (i in 1:1000){
    a[i] = i
}

要想解答这些问题,就必须得学习一下数组。数组是一种线性表数据结构,他在一段连续内存空间中存储相同大小的元素。这里有两个关键点,第一是线性表,也就是说数组里面的元素只有前后关系,同样属于线性表数据结构的还有链表、队列和栈,与之相反的是非线性表数据结构,例如树和图。

webp

线性表和非线性表

第二是连续内存,且里面的元素大小相同,这样子当知道数组的第一个元素的内存位置时,就可以迅速计算出第n个元素的内存地址,并获取该地址的存储内容。

webp

随机访问

有利就有弊。由于数组要占用一组连续的内存空间,当你要存储的数据要占据非常大的空间时,就会面临内存中找不到位置的尴尬情形。此外,为了保证数据存储的连续性,那么在你插入和删除数据的时候都要进行额外的数据移动操作,这些操作的时间复杂度是0(n)。如果数据已经满了,那么加入新的数据时就需要在内存中申请新的空间,同时将现有数据迁移过去。

webp

插入操作

尽管如此,数组依旧是最常用的数据结构,毕竟数组和CPU和缓存机制非常契合,在数据处理上效率较高。

在C语言中,数组的声明方式有如下方法:

float averages[200]; //在内存中预留200个位置char word[] = { 'H', 'e', 'l', 'l', 'o', '!'}; // 让C自动确定数组的大小char *words[] = {"hello", "world"}; 字符串数组

此外,指针与数组的关系十分密切,一般能用数组下标实现的操作,都能用数组完成。过去的时候,指针操作的速度会快于数组下标操作,但是随着编译器的优化,基本上两者的性能持平了。考虑指针实现的程序理解比较困难,因此更推荐用数组。示例:

int a[10]; //声明一个长度为10的存放整型的数组int *pa;  //声明一个指向整型的指针pa = &a[0]; // 将数组a的起始地址赋值给指针//等价于 pa = a;

那么a[i]  等价于 *(pa + i ) ,  无论数组a中元素的类型或数组长度是什么,该结论始终成立。 ”指针加1“就意味着pa + 1 指向pa所指向对象的下一个对象。简而言之,一个通过数组和下标实现的表达式可等价地通过指针和偏移量实现

但是,指针和数组还是有区别的,指针是一个变量,数组名不是变量。在函数定义中,形式参数char s[];char *s 是等价的,这是因为把数组名传递给函数时,实际传递的时该数组的第一个元素的地址。

R语言的向量和Python的列表还不是普通的数组,因为他们的大小可变,同样特点的还有C++的标准库类型vector, 还有Java的ArrayList和Vector类,都支持动态进行扩容。举个C++的例子

#include <vector>#include <iostream>using std::cin;using std::vector;string word;vector<string>  text;while (cin >> word){
  text
}

当然C语言本身并没有这个功能,所以我就尝试着自己写了一个非常简单的实现,依旧分为两个头文件和C文件。

darray.h如下:

#ifndef _DARRAY_H                                           #define _DARRAY_H                                           
                                                            typedef unsigned int position;                              
                                                            
/* define the data struct */                                typedef struct _cell cell;                                  typedef struct _darray darray;                              
                                                            /* define the manipulate function of darray */              
                                                            darray *dCreate(char *strings[], int ssize);                
darray *dInsert(darray *d, position pos, char *string);     
darray *dExpand(darray *d, int ssize);                      
void dDestroy(darray *d);                                   
void dRemove(darray *d, position pos);                      
void dPrint(darray *d);                                     
                                                            
#endif

darray.c:

#include <stdio.h>#include <stdlib.h>#include "dbg.h"#include "darray.h"// 用deleted标注删除,不做实际的搬移操作enum status {empty, deleted, legitimate };typedef struct _cell {
    enum status info;    char *string ;
} cell;typedef struct _darray{
    int size;    int load;
    cell *cells;
} darray;// 根据初始大小申请内存darray *dCreate(char *strings[], int ssize){
    darray *d;
    d = malloc(sizeof(darray));
    d->size = 2 * ssize;
    d->load = 0;
    d->cells = calloc(d->size, sizeof(cell));    
    // initialize the array
    int i;    for (i = 0; i < ssize; i++){
        d->cells[i].info = legitimate;
        d->cells[i].string = strings[i];    
        d->load ++;
    }
    debug("i shoule be %d", i);    for (; i < d->size; i++){
        d->cells[i].info = empty;   
    }    return d;
}//进行扩容darray *dExpand(darray *d, int ssize){
    darray *newArray;
    newArray = malloc(sizeof(darray));
    newArray->size = 2 * ssize;
    newArray->cells = calloc(newArray->size, sizeof(cell));
    newArray->load = 0;    int i;    int j = 0;        //复制元素
    for (i = 0; i < d->size; i++){        if( d->cells[i].info == legitimate){
            newArray->cells[j].string = d->cells[i].string; 
            debug("new arrys cell %d is %s", j, newArray->cells[j].string);
            newArray->cells[j].info = legitimate;
            newArray->load++ ;
            j++ ;
        }
    }    for (; j < newArray->size; j++){
        newArray->cells[j].info = empty;
    }        //释放原来的内存
    dDestroy(d);    return newArray;
}//插入操作darray *dInsert(darray *d, position pos, char *string){    if (d->load + 1 > d->size ){
        d = dExpand(d, d->size);
    }    if (pos > d->size ){
        d = dExpand(d, pos);
    }
    debug("size of darray is %d", d->size);    if ( d->cells[pos].info == deleted ||
            d->cells[pos].info == empty){
        d->cells[pos].info = legitimate;
        d->cells[pos].string = string;
    } else{        int i = d->size;
        d->cells[i].info = legitimate;        for (; i > pos; i--){
            d->cells[i].string = d->cells[i-1].string;
        }
        d->cells[pos].string = string;
    }    return d;

}//删除操作void dRemove(darray *d, position pos){
    d->cells[pos].info = deleted;
}//输出元素void dPrint(darray *d){    int i = 0;    for (i = 0; i < d->size; i++){        if ( d->cells[i].info == legitimate)            printf("%d \t %s \n", i, d->cells[i].string);
    }    putchar('\n');
}void dDestroy(darray *d){    free(d->cells);    free(d);
}int main(int argc, char *argv[]){
    darray *test;    char *input[] = {"hello", "my", "world","!"} ;
    test = dCreate(input, 4);
    dPrint(test);    char *h = "hello";
    test = dInsert(test, 10, h);
    dPrint(test);
    dRemove(test,1);
    dPrint(test);
    dDestroy(test);    return 0;

}

目前代码还存在一些问题,因为我只是将元素标记了删除,那么后续删除同一个位置时需要向后移动才行。同样插入操作也会存在bug。但是能这样子写代码对之前只能hello world的我已经是很大进步了。

解答开篇: Python的列表中存放的是元素的引用,并非元素本身,因此可以放任意类型的数据,其实和R语言的list更加对应。而R语言的向量则更加接近数组结构。

当你使用a <c()的结果是在内存中申请了一块固定大小的空间。之后每次的a<- c(a, i)的效果就是在内存不断申请新空间,加入元素,因此时间消耗会很明显。所以,事先声明足够大的空间然后进行赋值操作才会比较经济。



作者:hoptop
链接:https://www.jianshu.com/p/ec5f727e5650


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消