线性表基础

线性表是最常用最简单的一种数据结构。它是n个元素的有序序列。其中的元素可能是一个数值,也可能是一个由若干数值组成的结构,也可能是其它任意的结构。 

一、线性表的特点

线性结构的特点是:在数据元素的非空有限集中

 

  • 存在唯一一个被称为“第一个”的数据元素;
  • 存在唯一的一个被称为“最后一个”的数据元素
  • 除第一个元素之外,集合中每个数据元素均只有一个前驱(循环链表是特例)
  • 除最后一个元素之外,集合中的每个数据元素只有一个后继 (循环链表是特例)

线性表中的元素可以是各种各样的,但是它们必定有相同的性质。

线性表中元素的个数n称为线性表的长度,当n=0时称为空表。在非空表中每个元素都有一个确定的位置。

 

二、线性表的表示

线性表可以使用顺序表示或者链式表示,即采用顺序映像或者非顺序映像。

1.  线性表的顺序表示

线性表的顺序表示即采用顺序映像(存储),指的是使用一组地址连续的存储单元一次存储线性表的数据元素。

如果线性表中每个元素的长度为l,那么顺序表中第i个元素的位置为:

    Location(ai)=Location(a1)+  ( i – 1 ) * l

因而我们可以随机的访问线性表中的任何一个元素。

1)添加元素

添加元素指在线性表的第i个元素和第i-1个元素之间插入新的元素。由于顺序表示中,元素之间的逻辑关系(即相对关系)是用物理位置表示的,因而除非i = n + 1,否则必须移动元素才能反映这个逻辑关系的变化。

例如有往一个线性表:

    (1,3,4,6,7,10)

现在要在4和6之间插入一个新的元素5,那么插入后的线性表应该是

    (1,3,4,5,6,7,10)

很显然在插入后的表中第4个元素从6变成了5,而原线性表中的元素6,7,10在新表中的位置与旧表中的位置相比都往后挪了一个元素的位置,这就意味着为了插入新的元素5,从6开始的所有元素都往后挪动了一个位置。

即如果要在第i个元素之前插入一个新的元素,则需依次将第n至第i个元素都往后移动一个位置。

2)删除元素

删除元素指将线性表中第i个元素从线性表中删除,同添加元素类似,除非i=n,否则必须移动元素来反映这个逻辑关系的变化。

例如有一个线性表

    (1,3,4,5,6,7,10)

现在要将其中的第一个元素即元素1删除,则删除后的线性表应该是:

    (3,4,5,6,7,10)

很显然在删除元素1后,原线性表中在元素1之后的所有元素的位置都往前移动了一个位置。

即如果要删除一个元素i,则需依次将第i+1至第n个元素都往前移动一个位置。

3)添加和删除元素的效率分析

假设在第i个元素之前插入一个元素的概率为pi,则在长度为n的线性表中插入一个元素时所需移动元素的期望值(平均次数)为:

    p1 * ( n – 1 +1) + p2 * (n -2 + 1) + … + pn * ( n – n + 1) + p(n+1) * 0

当随机插入的时候,插入到每个位置的概率是相同的,因此

    pi = 1/(n+1)

    p1 * ( n – 1 +1) + p2 * (n -2 + 1) + … + pn * ( n – n + 1) + p(n+1) * 0=1/(n+1) * (n + n-1 +n-2 + … + 1) = n/2

因而我们得到添加其复杂度为: f(n) = n/2= O(n) 

假设删除第i个元素的概率为pi,则在长度为n的线性表中删除一个元素时所需移动元素的期望值(平均次数)为:

    p1 * ( n – 1 ) +p2 * (n -2 ) + … + pn * ( n – n )

当随机删除的时候,插入到每个位置的概率是相同的,因此

    pi = 1/n

    p1 * ( n – 1 +1) + p2 * (n -2 + 1) + … + pn * ( n – n + 1) + p(n+1) * 0=1/n * (n-1 + n-2 + …+ 1) = (n – 1)/2

因而我们得到其复杂度为: f(n) = (n – 1)/2=O(n) 

2.线性表的链式表示

线性表的链式表示即采用非顺序映像,指的是用一组任意的存储单元存储线性表的数据元素,这组存储单元可以在物理上连续也可以不连续,元素之间的关系用指针表示。 

连续存储的线性表实际中比较少用到,因此如果不特别说明的话,链式表示的讨论都是针对物理上不连续的存储单元的。

在链式表示方法中,每个数据元素除了要存储自己本身的信息外,还要存储一个指示其直接后继(直接前驱)的信息。每个数据元素中包含的数据元素本身的信息是它的数据域;而存储直接后继(直接前驱)信息的域称为指针域,该信息称为指针。如果某个元素没有直接后继(直接前驱)则要将指针域设置为“空”表示它没有直接后继(直接前驱),一般用NULL表示。 由数据域和指针域组成的数据元素的存储映像通常称为结点。

采用链式表示得到的数据结构也称为链表。在链表中,数据元素之间的逻辑关系是用指针表示的。 

1)链表的分类

  • 单链表:在链表中,如果数据元素中只包含一个指针,那么这个链表称为单链表
  • 双向链表:如果数据元素中即包含指向直接前驱的指针,也包含指向直接后继的指针,则这个链表称为双向链表
  • 单向循环链表:如果单链表的最后一个数据元素的指针指向链表的第一个数据元素,那么该链表形成一个环,称为单向循环链表
  • 双向循环链表:如果双向链表的最后一个数据元素的后继指针指向链表的第一个数据元素,第一个元素的前驱指向最后一个元素,那么该双向链表形成一个环,称为双向循环链表

2)添加元素

添加元素指在线性表的第i个元素和第i-1个元素之间插入新的元素。由于链式表示中,元素之间的逻辑关系(即相对关系)是用指针表示的,因而只需修改相应的指针域即可。 

例如要在数据元素a和b之间插入一个新的元素new(假设a,b都不为空),如果是单向链表,则相应的语句为

    new->next =a->next;

    a->next=new;

如果是双向链表,则相应的语句为:

    new->next=a->next;

    new->prev=a;

    a->next=new;

    b->prev=new;

与顺序表示相比,链式表示不需要移动元素,只需要修改相应的指针域即可。

3)删除元素

删除元素指将线性表中第i个元素从线性表中删除,同添加元素类似只需要修改相应的指针域即可。 

例如要将元素b从链表中删除,如果是单链表,假设a为它的直接前驱且不为空,则相应的语句为:

    a-> next = b->next;

    b->next =NULL;

    free(b);

如果是双链表,则相应的语句为(假设b的前驱和后继都不为空):

    b->prev->next= b->next;

    b->next->prev= b->prev;

    b->next =NULL;

    b->prev =NULL;

    free(b);

与顺序表示相比,链式表示不需要移动元素,只需要修改相应的指针域即可。

4)添加和删除元素的效率分析

对于链式表示法,无论是删除还是添加,都要先找到添加的位置或者所要删除的元素。因此需要先分析在链式表示法中查找一个元素的位置的复杂度。

由于在链式存储中,只能通过数据元素中的指针找到它的下一个(或前一个)元素,因此不能随机的访问其中的元素,如果要找到一个元素,则必须从第一个元素开始遍历链表,直到找到所要的元素或者遍历完所有的元素都没有发现所要找的元素,因而找一个元素最少要读取链表中的一个元素进行比较,最多要读取链表中所有的元素即读取n次进行比较,假设元素在链表中位置是随机的,不难得到所要读取的元素的平均次数为n/2.

在找到要添加元素的位置或者要删除的元素后,对于指针的更新可以在常数时间内完成,因而添加和删除元素的复杂度都等于查找元素的复杂度,即:f(n) = O(n)

3.线性映射和非线性映射的比较

1)空间比较

线性映射只需要保存元素本身的信息,而非线性映射除了要保存元素本身的信息外,还需要保存元素之间的关系信息,即需要存储空间来保存指针信息,因而非线性映射需要更多的存储空间。

但是从另一方面来说,顺序映射的线性表需要连续的内存,对使用的内存区域有要求,当初始申请的内存空间不能满足新的要求时,就要申请新的更大的内存,并复制原有内容到新的存储区域;如果初始申请的内存区域足够大,则又可能出现存储区域使用率不够,导致内存浪费的情况,因而在内存使用处理上不是很灵活,不适用内存寻求变化较大的情形;而链式映射的线性表则不要求内存是连续的,比较灵活,可以充分利用存储空间。

2)时间比较

根据前边的分析,对于非线性映射,其添加和删除元素的时间复杂度都是O(n)。而对于非线性映射我们只给出了添加和删除元素时移动元素的时间复杂度,而没给出找到所要添加的位置或者找到要删除的元素的复杂度分析,如果线性表有序,则顺序映射(存储)结构中,可以采用折半查找,查找位置的时间复杂度为O(log n),如果线性表无序,则其查找位置的时间复杂度也为O(n)。

因而仅从理论分析上来说,顺序映射添加和删除元素的时间复杂度为“查找的复杂度+O(n)”,因而链式存储的添加和删除的时间复杂度更优。但是顺序映射的优势在于它支持随机访问,可以在常数时间内随机访问任一下标的元素,这对于其它操作极其有利(比如顺序映射上的折半查找可以极大地优化查找性能,但是折半查找无法应用到链式存储结构中);其次,在实际的计算机中,指针访问属于间接存取,可能会出现cache miss从而加大实际的访存时间;最后链式存储在添加和删除结点时需要申请获释放结点内存,增加了存储管理的复杂度。

 通过分析比较可以发现,两种结构各有优劣,需要根据实际的需求决定使用哪种结构:

  • 如果添加和删除并不是很频繁,线性表的容量大小变化也不大,则就可以使用顺序映射结构
  • 如果添加和删除很频繁,线性表容量不大,则就可以考虑使用非顺序映射结构(当线性表容量很大的时候,即便添加和删除很频繁,也不能简单的使用非顺序映射,因为使用一个数据结构绝不仅仅是为了构造、销毁它,而是要做其它的操作,而做其它操作时查找是一个基础操作,因而查找的性能非常关键,这个时候就要考虑两种方式相结合了。)
  • 如果添加和删除很频繁,但是对查找等其它操性能要求也很高,可以考虑将两种方式结合起来,使用哈希的方式,将具有相同key的放入一个链表中(哈希术语是用链式方式来解决冲突 ^-^)

具体的场景太多了,还是要根据具体的应用场景分析该用哪种结构。。。 

三、.线性表的简单应用—多项式

数学上的一元多项式的结构一般为:a1 + a2 * x + a3 * x2 + … + an * xn
其每一项都有一个常数项和x的n次幂组成,利用线性表,用一个结点表示其中的一项,每个结点的内容包括常数、变元及其幂即可
比如可以用如下的方式表示上述多项式:


 
由于该多项式只有一个变元,所以可以只记录其幂即可。

原文地址:https://www.cnblogs.com/vijozsoft/p/7125374.html