数据结构——关于数据结构中的线性表

1 顺序存储线性表:

    顺序存储线性表结构,指的是用一段地址连续的存储单元依次存储线性表的数据元素。顺序线性表中各个结点元素类别是相同的。
   连续的存储单元,元素类别相同?这个概念和C语言中的数组岂不是相似? 是的,使用一维数组恰可以实现顺序存储结构~
   
<span style="font-size:18px;"><span style="font-size:14px;">#define MAXLENGTH 20

typedef struct sequencelist
{
	int data[MAXLENGTH];
	int length;
}sqList;</span></span>
  描述顺序结构的属性有三:
(1)存储空间起始位置:数组data,假设类型为整型,数组开始位置即为存储空间开始位置。
(2)线性表的最大容量:Maxlength。
(3)线性表当前长度,这是随着线性表的插入和删除操作而改变的。

引入了一维数组概念后,意味着可以方便的使用数组名与指针。
例如在对顺序列表插入的操作中,我们可以对插入函数函数头定义为int insert(sqList *L,int index,int element)
其中L为要操作的线性表,index为我们要插入的位置,elemnt为待插入的元素。

但是,这样一个简单的操作需要注意的有三点:
(1) 插入之间要判断,线性表是否可以被插?插入位置是否合法?
(2) 插入相当于把第index个元素到最后一个元素后移一个存储单元。
(3) 由于数组是从0开始计数,即data[0]是第一个存储单元,所以在插入时,新元素的位置为data[index-1]。
<span style="font-size:18px;"><span style="color:#009900;">//insert operation</span>
int insert(struct sqList *L,int index,int element)
{
	int length =L->length;
	if( index <= 0 || index > length+1 || length == MAXLENGTH)
		return ERROR;
	if(index<=L->length)
       {
          for(int k=L->length-1;k>=index-1;k--)
          L->data[k+1]=L->data[k];
       }
        L->data[index-1]=e;
	L->length++;
	return OK;
}</span>
顺序存储结构在读、存数据时,不管在哪个位置,其时间复杂度都是O(1),而其插入和删除数据的时间复杂度都是O(n)。
这说明顺序存储结构适用于频繁的读写操作,不适合较多删除和插入操作,并且其顺序线性结构在初始化时要给定最大空间,不适用于空间不定的结构。


2 具有链式结构的线性表: 

上面介绍的是一种连续存储的线性表结构,每当我们需要插入或者删除数据时,就要移动大量的元素。
而链式存储结构的线性表是一种带有指针域的,非连续存储的线性表结构。
对于这种链式结构线性表的基本单元,我们可以用结点(Node)来表示,一个结点包括数据域和指针域。
链表的第一个结点叫做头结点,其数据域可以为空,也可以存储线性表长度信息,头结点的指针域指向第一个结点。
指向第一个结点的指针定义为头指针。
头指针是链表的必要元素,但是头结点不是,即可以不存在头结点,但是一定要定义指向第一个结点的头指针。

2.1 单链表;

  每个结点只含有一个指针域称之为单链表。
  单链表是最简单的链表结构,其简单在于单向性,但缺点也在于不可逆性。
  单链表结点元素定义:

<span style="font-size:18px;">// 定义链表节点
typedef struct _LNode
{
	elemType data;
	struct _LNode *Next;
}LNode;
</span>


  对于单链表的操作来说,读取可没有顺序存储链表这么轻松,顺序存储链表无论链表多长,时间复杂度都是O(1)的事,可对于单链表,读取可要从头结点一个个结点读过去,所以时间复杂度和链表长度n有关系,时间复杂度为O(n)。
  但是对于单链表的插入和删除操作来说,那就简单的多了,仅需要对要插入或删除位置的周边结点作些调整就行了。
 
  如链表插入的操作具体如下:
<span style="font-size:18px;">//链表插入
int ListInsert(LNode *L,int i, elemType e)
{
	LNode *p,*q;
	int j=1;
	p=L;
	while(p&&j<i)
	{
		p=p->Next;
		++j;
	}
	if(!p||j>1)
		return ERROR;
	q=(LNode *)malloc(sizeof(LNode));
	q->data=e;
	q->Next=p->Next;
	p->Next=q;
	return OK;
}</span>
显然,对于插入和删除数据越频繁的操作,单链表的效率优势就越是明显。

2.2 循环链表;

   采用循环链表是为了链表两端的访问更加方便。

  想想,如果像普通的单链表那样,想要访问链表的最后一个节点的话需要遍历整个链表,即时间复杂度是O(n)。单链表有一个头指针,指向头结点,同样我们可以定义尾指针指向尾结点。这样,我们想要访问尾结点时那就是分分钟的事情啦。

  加一个尾结点称不上是循环链表,要将链表的首尾连起来。
  head->1->2->3............->n->head
 
  循环链表好像不够酷,最多提供的功能就是浏览到内容底端再返回到首部,最鸡肋的一点是单向遍历性!那么更酷一点的就是双向链表了。

2.3 双向链表; 

  为了克服单向性的缺点,人类发明了双向链表。双向链表是在单向链表的基础上再提供一个前驱结点指针。
  其作用很明显,当我想访问之前的结点时,前驱结点指针就提供了便利。

   双向链表不是一劳永逸的方案,当它给我们带来访问前向结点方便的同时,其他的操作也随之变得复杂。
   在链表的插入操作中,结点指针的改变要注意顺序问题!
   例如,下图中需要在双向结点p和p->next中间添加结点s。
   
   
其顺序应是:
             s->next=p->next;
             s->prior=p;
             p->next=s;
             p->next->prior=s;
即先将要新加入节点的前驱和后项指针搞定,再调整p和p->next的指针域部分。
对于双向链表来说,删除结点同样稍显复杂,但通过上面的增加结点可以举一反三。



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值