51-数组与链表

原创 2016年04月26日 12:30:20

51-数组与链表

在C语言中,我们可以自定义各种各样的数据结构,用来把很多数据保存在一个变量里面,但是每种数据结构都有自己的优缺点,PHP内核规模如此庞大,是否已经找到了一些非常棒的解决方法呢?

我们在选择各种数据结构时,往往会考虑我们需要处理的数据规模以及需要的性能。下面让我们简要的看一下看C语言中数组和链表的一些事情。

数组

作者这里用的不是Array,而是Vector,可能指的是C++里的Vector,它与数组几乎是完全一样的,唯一的不同便是可以实现动态存储。本节下文都是用数组一词代替之,请各位注意。数组是内存中一块连续的区域,其每一个元素都具有一个唯一的下标值。

int a[3];
a[0]=1;
a[2]=3;

不仅是整数,其它类型的变量也可以保存在数组中,比如我们前面用到的zend_get_parameters_array_ex(),便把很多zval**类型的变量保存到一个数组里,为了使其正常工作,我们提前向系统申请了相应大小的内存空间。

zval ***args = safe_emalloc(ZEND_NUM_ARGS(), sizeof(zval**), 0);

这里我们仍然可以用一个整数来当作下标去数组中取出我们想要的数据,就像var_dump()的实现中通过args[i]来获取参数并把它传递给php_var_dump()函数那样。

使用数组最大的好处便是速度!读写都可以在O(1)内完成,因为它每个元素的大小都是一致的,只要知道下标,便可以瞬间计算出其对应的元素在内存中的位置,从而直接取出或者写入。

链表

链表也是一种经常被使用的一种数据结构。链表中的每一个元素都至少有两个元素,一个指向它的下一个元素,一个用来存放它自己的数据,就像下面定义的那样:

typedef struct _namelist namelist;
struct
{
    struct _namelist *next;
    char *name;
}_namelist;

我们可以声明一个其类型的元素:

static namelist *people;

假设每一个元素都代表一个人,元素中的name属性便是这个人的名字,我们通过这样的语句来得到它:people->name; 第二个属性指向后面的一个元素,那我们便可以这样来访问下一个人的名字:people->next->name, 或者下一个人的下一个人的名字:people->next->next->name,一次类推,直到next的值是NULL,代表结束。

//通过一个循环来遍历这个链表中的所有人~
void name_show(namelist *p)
{
    while (p)
    {
        printf("Name: %s\n", p->name);
        p = p->next;
    }
}

链表可以被用来实现FIFO模式,达到先进者先出的目的!

static namelist *people = NULL, *last_person = NULL;
void name_add(namelist *person)
{
    person->next = NULL;
    if (!last_person) {
        /* No one in the list yet */
        people = last_person = person;
        return;
    }
    /* Append new person to the end of the list */
    last_person->next = person;

    /* Update the list tail */
    last_person = person;
}
namelist *name_pop(void)
{
    namelist *first_person = people;
    if (people) {
        people = people->next;
    }
    return first_person;
}

这样,我们便可以随意的向这个链表中添加或者删除数据,而不向数组那样,谨慎的考虑是否越界等问题。

上面实现的结构的学名叫做单向链表,也有地方叫单链表,反正是比较简单的意思~。它有一个致命的缺点,就是我们在插入或者读取某条数据的时候,都需要从这个链表的开始,一个个元素的向下寻找,直到找到这个元素为止。如果链表中的元素比较多,那它很容易成为我们程序中的CPU消耗大户,进而引起性能问题。为了解决这个问题,先人们发明了双向链表:

typedef struct _namelist namelist;
struct
{
    namelist *next, *prev;
    char *name;
} _namelist;

改动其实不大,就是在每个元素中都添加了一个prev属性,用来指向它的上一个元素。

void name_add(namelist *person)
{
    person->next = NULL;
    if (!last_person)
    {
        /* No one in the list yet */
        people = last_person = person;
        person->prev = NULL;
        return;
    }
    /* Append new person to the end of the list */
    last_person ->next = person;
    person->prev = last_person;

    /* Update the list tail */
    last_person = person;
}

单单通过上面的程序你还体会不到它的好处,但是设想一下,如果现在你有这个链表中其中一个元素的地址,并且想把它从链表中删除,那我们该怎么做呢?如果是单向链表的话,我们只能这样做:

void name_remove(namelist *person)
{
    namelist *p;
    if (person == people) {
        /* Happens to be the first person in the list */
        people = person->next;
        if (last_person == person) {
            /* Also happens to be the last person */
            last_person = NULL;
        }
        return;
    }
    /* Search for prior person */
    p = people;
    while (p) {
        if (p->next == person) {
            /* unlink */
            p->next = person->next;
            if (last_person == person) {
                /* This was the last element */
                last_person = p;
            }
            return;
        }
        p = p->next;
    }
    /* Not found in list */
}

现在让我们来看看双向链表是怎样来处理这个问题的:

void name_remove(namelist *person)
{
    if (people == person) {
        people = person->next;
    }
    if (last_person == person) {
        last_person = person->prev;
    }
    if (person->prev) {

        person->prev->next = person->next;
    }
    if (person->next) {
        person->next->prev = person->prev;
    }
}

对元素的遍历查找不见了,取而代之的是一个O(1)的运算,这将极大的提升我们程序的性能。

王者归来:HashTable才是我们的银弹!

也许你已经非常喜欢使用数组或者链表了,但我还是要向你推荐一种威力极大的数据结构,有了它之后,你可能会立即抛弃前两者,它就是HashTable.

HashTable即具有双向链表的优点,同时具有能与数据匹敌的操作性能,这个数据结构几乎是PHP内核实现的基础,我们在内核代码的任何地方都发现它的痕迹。

前面我们接触过,所有的用户端定义的变量保存在一个符号表里,而这个符号表其实就是一个HashTable,它的每一个元素都是一个zval*类型的变量。不仅如此,保存用户定义的函数、类、资源等的容器都是以HashTable的形式在内核中实现的。

Zend Engine中HashTable的元素其实是指针,对其的这个改进使得HashTable能够包容各种类型的数据,从小小的标量,到复杂的PHP5中实现的类等复合数据。本章接下来的内容,我们将详细的研究如何使用zend内置的API来操作HashTable这个数据结构。

数组和链表的区别

数组结构:        数组结构在通过索引进行查询数据时效率比较高,而对于数组插入和删除操作,则效率会比较低,在第一个位置进行插入数据,其余数据就需要依次向后移动,而第一个数据进行删除,则需要所有数...
  • baoq_v5_java
  • baoq_v5_java
  • 2015年04月10日 17:31
  • 19348

数组与链表的优缺点

数组,在内存上给出了连续的空间.链表,内存地址上可以是不连续的,每个链表的节点包括原来的内存和下一个节点的信息(单向的一个,双向链表的话,会有两个).  数组优于链表的:  1.内存空间占用的少,因...
  • u014082714
  • u014082714
  • 2015年03月14日 14:05
  • 12704

Java 数组和链表的区别以及使用场景

科技优家 2016-12-11 17:11 数组:是将元素在内存中连续存储的;它的优点:因为数据是连续存储的,内存地址连续,所以在查找数据的时候效率比较高;它的缺点:在存储之前,我们需要申请一块...
  • u011277123
  • u011277123
  • 2016年12月28日 14:39
  • 2346

链表的实现与数组

链表与数组是最基础的数据的结构,堆栈.队列等逻辑上的数据结构都是基于这两种数据结构实现的。 链表与数组的区别在于数组的内存分配是连续的(即数组的每一项内存地址是连续的),而链表则可能连续也可能不连续...
  • dragon_cat_han
  • dragon_cat_han
  • 2016年03月02日 11:00
  • 1396

链表与数组的区别

链表和数组都可用来存放指定的数据类型。          首先分别介绍一下链表和数组。          链表的特性是在中间任意位置添加删除元素的都非常的快,不需要移动其它的元素。通常链表每一个元素都...
  • sunjiangangok
  • sunjiangangok
  • 2017年04月10日 10:36
  • 488

数组与链表的区别

首先从逻辑结构上说,两者都是数据结构的一种。 数组是将元素在内存中连续存放,由于每个元素占用内存相同,可以通过下标迅速访问数组中任何元素。但是如果要在数组中增加一个元素,需要移动大量...
  • ZhongGuoZhiChuang
  • ZhongGuoZhiChuang
  • 2016年11月17日 09:44
  • 2803

数组与链表的优缺点和区别

1.数组: 数组是将元素在内存中连续存放,由于每个元素占用内存相同,可以通过下标迅速访问数组中任何元素。但是如果要 在数组中增加一个元素,需要移动大量元素,在内存中空出一个元素的空间,然后将要增加...
  • Mormont
  • Mormont
  • 2016年12月02日 22:32
  • 4676

Java基础--数组和链表的区别

1、数组 2、链表 3、对比 4、综上所述 5、选取
  • u010843114
  • u010843114
  • 2016年08月17日 15:34
  • 1312

数组和链表区别

谈到链表与数组的区别,可以从几个不同的角度来谈, 首先从逻辑结构上说,两者都是数据结构的一种,但存在区别, 数组是申请的一块连续的内存空间,并且是在编译阶段就要确定空间大小的,同时在运行阶段是不允...
  • melody_day
  • melody_day
  • 2016年12月08日 14:20
  • 192

从cpu和内存来理解为什么数组比链表查询快

一个常见的编程问题: 遍历同样大小的数组和链表, 哪个比较快? 如果按照大学教科书上的算法分析方法,你会得出结论,这2者一样快, 因为时间复杂度都是 O(n)。 但是在实践中, 这2者却有极大的差...
  • Islandww
  • Islandww
  • 2017年05月18日 22:58
  • 883
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:51-数组与链表
举报原因:
原因补充:

(最多只允许输入30个字)