文章导读
【线性表还有另外一种存储结构,就是链式存储结构。链式存储结构对逻辑上相邻的线性表元素不要求物理位置也相邻,相邻元素之间通过指针链接。本文主要讨论线性表的链式存储结构及其运算】
前面研究了线性表的顺序存储结构,它的特点是逻辑关系上相邻的两个元素在物理位置上也相邻,因此,可以随机存取表中的任一元素。然而,从另一方面来看,这个特点也造成了顺序存储结构的三个弱点:其一,线性表在作插入或删除元素时,需要移动大量元素。其二,当线性表的长度变化比较大时,需按最大的空间分配,存储空间得不到充分利用。其三,线性表扩充容量时,需要移动大量元素。
线性表还有另外一种存储结构,该结构不要求逻辑上相邻的两个元素在物理位置上也相邻,因此它没有顺序存储结构的弱点,这种结构被称为链式存储结构。
1、 线型链表
链式存储结构是用一组任意的存储单元存储线性表的元素,存储单元可以是连续的,也可以是不连续的。由于线性表各元素的存储单元不再是连续的存储空间,无法在物理位置上表示元素间的逻辑关系。因此,对于元素a来说,除了存储元素本身的信息之外,还需要存储一个指示其直接后继的信息(即直接后继的存储位置)。这两部分信息组成数据元素a的存储映象,称为节点。它包括两个域,一个是存储元素本身信息的域称作数据域名;一个是存储直接后继元素存储位置的域称作指针域,指针域中存储的信息称作指针或链。n个节点链接成一个链表,称为线性链表,当节点只包含一个指针域时,称为线性链表,也称为单链表。
例如,如图1所示为线性表
{北京,济南,成都,西安,上海,昆明}
线性链表存储结构。整个链表的存取必须从头指针开始,头指针指示链表中第一个节点的存储位置。由于最后一个数据元素没有直接后继,因此线性链表中最后一个结点的指针为空(NULL)。
用线性链表表示线性表时,数据元素之间的逻辑关系是由结点中的指针指示的,逻辑上相邻的两个元素其物理存储位置不要求紧邻,因此这种结构称为非顺序存储结构或链式存储结构。
图1 性链表示例
用图示的方法表示线性单链表时,一般将链表画成用箭头相链接的结点的序列,结点之间的箭头表示链域中的指针。如图1的线性链表可画成如图2所示的形式。
图 2 线性链表的逻辑状态
下面通过一个项目案例,编写LinkList类,实现线性单链表元素的添加、插入和删除操作。
定义Node结点
在Java语言中,线性表结点元素可以采用下面的类结构描述:
Node结点采用泛型,可以存储不同的数据类型。结点有泛型属性item,item是数据域,用于存储数据;属性next是结点的指针域,其值指向链表的下一个Node结点,在线性表中指向其后继结点。
定义头指针
LinkList类除了定义Node结点外,还定义了链表的头指针first,它指向表中第一个结点。若该指针为空,则所表示的线性表为“空”表,其长度为“零”。
从线性单链表中查询第i个元素
在线性表的顺序存储结构中,由于逻辑上相邻的两个元素在物理位置上紧邻,则每个元素的存储位置都可以通过线性表的起始位置计算得到。而在线性链表中,任何两个元素的存储位置之间没有固定的联系,每个元素的存储位置都包含在其直接前趋结点的信息之中。例如,头指针first指向链表的第一个结点,则first.next指向第二个结点,first.next.next指向第三个结点,以此类推。下面给出从链表中查询第i个元素的实现代码。
查询算法的核心是按指针遍历链表,首先将头指针first赋值给root指针,在while循环语句中不断修改root指针,直至查询到第i个元素或到达链表尾部。while循环语句的频度为i,和被查找的数据元素在线性表中的位置有关。因此,对长度为n的线性链表而言,查询算法的复杂度为O(n)。
线性单链表的插入运算
线性链表的插入运算是在两个元素之间插入一个新的元素,为了插入新的元素,首先需要生成一个新的结点,并将该结点插入到链表中。例如,要在元素a和b之间插入一个新的元素p,则需要生成新结点p,并将a的指针域指向p元素,p元素的指针域指向b元素,从而实现了元素的插入。
插入算法的核心是遍历链表,查找新元素插入的位置,虽然无需移动元素,但仍需执行第i趟运算,i和要插入的数据位置有关。因此,对长度为n的线性链表而言,插入算法的复杂度为O(n)。
2、双向链表
前面讨论的线性单链表中的结点中只有一个指示直接后继的指针域,由此,从某个结点出发只能顺指针往后寻查其它结点。若要寻找结点的直接前驱,则需从表头指针出发。在线性单链表中,查询元素的后继结点的执行时间为O(1),而查询元素的前趋结点的执行时间为O(n)。
单向链表有一个指针域,而双向链表有两个指针域,其一指向直接后继,另一指向直接前趋。双向链表的结点结构描述如下:
在双向链表中,查询、获取长度等运算仅需涉及一个方向的指针,则它们的算法描述和线性单链表的运算相同,但在插入、删除时有很大的不同,在双向链表中需要同时修改两个方向的指针。双向链表的算法请自行给出。
文章小结
线性链表的元素由两部分组成,一部分用于存储数据元素值,称为数据域;另一部分用于存储该元素直接前驱或直接后继元素的地址,称为指针域。把对应一个数据元素的存储快称为结点。
只有一个指针的结点形成的链表称为单向链表,也称为线性链表,存储的指针一般指向该结点的直接后继结点,当该结点为表的最后一个元素时,指针为空;存储两个指针的结点形成的链表称为双向链表,一个指针指向该结点的直接后继,另一个指针指向该结点的直接前趋。双向链表的优点是从一个结点出发,既可以向前遍历表中元素,也可以向后遍历表中元素。