线性表的基础归纳

来源:互联网 发布:数据分析师适合女性 编辑:程序博客网 时间:2024/04/29 08:42

线性结构包括:线性表、队列和数组。

 

下面讨论线性表:

 

一个线性表的基本操作如下:

       InitList( &L): 初始化一个表,构造一个空的线性链表.

       Length( L) :求表的长度。返回线性表L的长度(即L中数据元素的个数)。

       LocateElem(& L , e):     按值查找操作。在L表中查找具有给定关键字值的元素位置。

       ListInsert(&L , i , e): 插入操作。在L中指定位置i处插入e。

       ListDelete( &L, i): 删除操作。删除L中的第i个位置的元素。

       GetElem( L,i) : 按位查找操作。获得表L第i个位置处的元素。

       PrintList( L)   : 输出操作。按前后顺序输出线性表L的所有元素。

       Empty( L)      :判断空操作。如果为空 则返回true,否者false。

       Destroy( &L)  : 销毁操作。销毁线性表。释放所占的空间。

 

注意说明: &这个符号不是取地址符号。是C++语言中的引用调用符号。在纯C的编译中是无法通过的。怎么解决C环境? 可以采取指针的形势。C语言的指针可以相当于引用。

 

 

下面讨论线性表里面的存储结构:

       总的来说线性表里面存储结构分为:顺序存储 、链式存储。

顺序存储分配的时候: 可以是静态分配存储空间,还可以是动态的分配空间。

 

链式存储: 单链表、 双链表 、 循环链表  、静态链表。

 

下面我们介绍顺序存储:

       概念: 逻辑上相邻的两个元素在物理位置上也相邻(即逻辑顺序与物理顺序相同)。

 

 

注意说明:数组中的元素下表是从0开始的,而顺序表元素位序是从1开始的。

 

 

顺序存储结构类型如下:

 

静态分配:

#define MaxSize 50

typedef struct {

 

       ElemTypedata [MaxSize];

       int  length;

 

}Sqlist;

 

 

动态分配:

#define InitSize 100

typedef struct {

       ElemType*data ;

       int  length,MaxSize;

}Sqlist;

 

C语言下动态分配:

L.data =(ElemType)malloc(sizeof(ElemType)*InitSize);

 

注意说明:动态分配不是链式存储,同样还是属于顺序存储结构,其物理结构没有变化,依然是随机存取方式,只是分配的空间大小是在运行时决定。

 

 

顺序表的特点:

       优点:可以进行随机随机存取,(通过首地址和元素的序号可以在0(1)时间内可以找到元素)。

       缺点: 存储密度高,每个结点只能存储数据,还有顺序表的逻辑上相邻的物理上也相邻,那么在删除和插入的过程中需要移动大量的元素。

 

顺序存储算法归纳

 

1.插入算法(ListInsert

/******************************************

 | 顺序标的插入算法

 | 注意顺序表的位序数组的区别

 | 静态分配顺序存储

*******************************************/

bool ListInsert(Sqlist &L , int i, ElemType e){

 

       /********************

       1.判断条件是否有效

       ********************/

      

       if(i < 1 || i > L.length +1)

              return false;

       if(L.length >= MaxSize)

              return false;

      

       /*******************

       2.移动i之后的数据,插入数据

       ********************/

      

       for(int j = L.length; j >= i ; j--)

              L.data[j] = L.data[j-1];

             

       L.data[i-1] = e;

       L.length ++;

      

       return true;

             

}

 

注意说明:区别 顺序表的序位和数组的下表;理解为什么if里面的是length +1 ;还有for语句中的length。(if语句里面的length+1就说明了可以在这个顺序表的最后面加上一个数据,那么就不需要移动!,for语句的里面是取的是数组下标,序位length比下标大1所以….

 

 

该算法的时间复杂度分析:

最好情况:在表的尾部插入(i=n+1)元素移动语句不执行,时间复杂度是0(1).

最坏情况:在表的头部插入(I= 1) 元素要移动n下,时间复杂度是0(n).

 

平均情况是: 一共可以在1到n+1个位置插入元素。Pi(pi = 1/(n+1))是在i个位置增加元素的概率。则移动的平均次数是 n/2。 那么时间的复杂度是:O(n).

 

 


2. 删除操作算法(ListDelete):

/*******************************************

 | 根据序列删除顺序表L中的第i个位置的元素

********************************************/

 

boolListDelete(Sqlist &L, int i){

      

       /********************

       1.判断条件是否符合

       *********************/

      

       if(i < 1 || i > L.length)

              return false;

             

       /********************

       2.删除操作

       *********************/

      

       for(int j = i; j < L.length; j++)

              L.data[j-1] = L.data[j];

       L.length --;

      

       return true;

             

}

 

算法世间法复杂度分析:
最好情况:删除表尾元素(即i = n ),无需移动元素,时间复杂度是O(1)。

最坏情况: 删除表头元素(即 i = 1),移动(n-1)个元素,时间复杂度是O(n)。

平均情况: 一共可以删除n个元素,那么每个元素被删除的概率是 1/n, 则在长度为n的线性表中删除一个结点所需要移动的结点的平均次数是: (n-1)/2。 所以线性表删除算法的平均时间复杂度是O(n)。

 

       3.按值查找(顺序查找)算法:

 

/***************************************

 | 按值查找,在顺序表中查找值为e的元素。

 | 查找成功,返回元素序位,否者返回 0

 ***************************************/

 

 int LocateElem(Sqlist L, ElemTYpe e){

 

      inti;

     

      for(i= 0; i < L.length; i++)

             if(e== L.data[i])

                    returni+1;      //把下表转换成位序

      return0;

     

 }

 

算法时间复杂度分析:

最好情况: 查找的元素就在表头,那么仅需要一次,时间复杂度O(1)。

最坏情况: 查找的元素在表尾,那么需要n次,时间复杂度是O(n)。

平均情况: 一共有n中查找,那么每个元素被查到的概率是pi(1/n);则在长度为n的线性表中的查找值为e的元素的平均查找次数是: (n+1)/2。 因此线性表的所需要的平均时间复杂度是O(n)。

 

 

 

 

 

链式存储算法分析:

1.单链表算法分析:

概念: 链式存储的线性表又称为单链表。

特点:

       优点: 单链表的元素是离散地分布在存储空间的,所以单链表是非随机存储的存储结构。

       缺点: 由于链表附加了指针域,也带来了浪费存储空间。

 

单链表的结构类型如下:

       typedef struct {

       ElemType data;

       struct LNode *next;

      

}LNode , *LinkList;

 

单链接里面还引入了头指针的优点

1、  由于开始结点位置被存放在头结点的指针域中,所以链表的第一开始位置上的操作和在表的其他位置上的操作一致,无需进行其他的处理。

2、无论链表是否为,其头指针是指向头结点的非空指针(空表中的头结点的指针域空),因此空表和非空标的处理就得到了统一。

 

 

单链表的创建

       1.头插法

概念: 头插法是从一个空表开始,生成新的结点,并将读取的数据存放到新结点的数据域中,然后将新结点插入到当前链表的表头,即头结点之后。

 

 

头插法算法:

 

/********************************

| 从表尾到表头逆向建立单链表la  |

| 每次均在头结点之后插入元素    |

|*********************************/

 

LinkList CreatList(LinkList &la){

 

       int x;

       /*********************

       | 建立一个空表(头结点)

       |********************/

      

       la = (LNode*)malloc(sizeof(LNode));

       la -> next = NULL;

      

       /********************

       | 创建链表(头插入法)

       *********************/

      

       scanf("%d",&x);

      

       while(x ! = 1314) //输入1314表示输入结束

       {

              LNode * s =(LNode*)malloc(sizeof(LNode));

              s ->data =x;

             

              s -> next =la ->next;

              la -> next= s;

             

              scanf("%d",&x);

       }

      

       return la;

}

 

 

注意说明:   

       采用头结点法建立的链表,读入数据的顺序和生成的链表的顺序是相反的(好比是一个栈),时间复杂度是O(n)

 

 

       2.尾插法

概念: 将新结点插入到当前链表的表尾,为此必须增加一个尾指针r,使其始终指向当前链表的尾结点。

       尾插法算法:

/************************************

| 尾插法建立单链表la

| 每次均在表尾插入元素

*************************************/

 

 

LinkList CreatList2(LinkList &ls){

      

       int x;

       /*********************

       | 建立一个空表(头结点)

       |********************/

       la =(LinkList)malloc(sizeof(LNode));

       la -> next =NULL;

      

       LinkList r,s;

       r = la;

      

       scanf("%d",&x);   

       while( x != 1314 )

       {

              s =(LinkList)malloc(sizeof(LNode));

              s -> data= x;

              s -> next= r ->next;

              r -> next= s;

              r = s;

             

              scanf("%d",&x);

       }

       r ->next = NULL;//特别注意最后一个结点指针域是NULL

       return la

      

      

}

说明: 因为附加了一个尾指针,故尾插法的时间复杂度和头插法相同。

 

 

单链表按序查找

       按序查找算法

/********************************

| 单链表按序查找

*********************************/

LNode* GetElem(LinkList L, int i){

       int j =1;

       LNode *p = L->next;

      

       if(i <1 || i >GetLength(L

))

              return NULL;

       while(p && j< i)

       {

              p =p->next;

              j++;

       }

       return p;

}

 

按序号查找的时间复杂度是O(n)

 

单链表按值查找

按值查找算法:

/*********************************

| 单链表按值查找

| 找到数据等于e的指针,否者返回NULL

*********************************/

LNode* LocateElem(LinkList L, ElemType e){

      

       LNode *p = L->next;

      

       while(p !== NULL&& p->data != e)

              p = p->next;

             

       return p;

             

}

时间复杂度是O(n)

 

 

单链表插入结点操作

       插入值算法分析:

代码片段分析(后插法)

       目的: 插入操作时在单链表i位置处插入一个值e。

       1.首先检查i的插入位置是否合法。

       2.找到带插入结点的前驱结点,即第i-1个结点,再在其后插入新结点。

代码片段:

p = GetElem(L,i-1);

s ->next = p ->next;

p - >next = s;

 

拓展(前插法):

       前插法是:在某结点的前面插入一个新的结点,与后插法的定义刚好相反。

注意: 在单链表插入算法中,通常都是采用后插法。

1.可以采用前面讲解的后插法,找到该插入结点的前驱结点,然后执行后插法。

2.另外一种方法是:将其转化成后插法进行。设带插入结点为*s,将*S插入到*p的前面,我们仍然将*s插入到*p的后面

  然后将p ->data与s->data交换即可。

 

代码片段:

 s->next = p ->next;

 p ->next = s;

 temp = p->next;

 p ->data = s ->data;

 s ->data = temp;

 

 

删除结点操作

代码片段:

 1.删除结点代码片段(给出的是位置i):

      p = GetElem(L,i-1);

      q = p ->next;  //q是要删除的结点

      p ->next = q ->next;

      free(q);

     

2.删除结点代码片段(给出的是指针q)

p = q ->next;

      temp = p ->data;

      p -> data = q ->data;

      q ->data = temp;

      free(q);

 

 

 

求链表的长度:

概念:求表长操作时计算链表中的数据结点(不含头结点)的个数,需要从第一个结点开始依次访问每一个结点,自己定义一个计数器的变量。直到访问到空结点为止。

注意:因为单链表的长度是不包括头结点的,因此不带头结点和带头结点的和带头结点的单链表在求解的过程中会略有不同。对于不带头结点的的单链表,当链表为空的时候要单独处理。

 

 

 

   2.双链表的算法分析

/*****************

 | 双链表的结构体

 *****************/

 typedef struct{

      ElemType data;

      struct DNode *prior,*next;

 }DNode,*DLinkList;

 

1.      双链表的插入操作

算法片段分析:

双链表的插入算法片段分析:

      在双链表中的p所指向的结点之后插入一个结点*s

 代码片段:

 //插入结点后继结点相连

 s ->next = p ->next;

 p ->next ->prior = s;

 //插入结点前继结点相连

 p ->next = s;

 s ->prior = p

 

2.      双链表的删除操作:

算法片段分析:

双链表的删除算法片段分析:

       删除结点*p的后继结点*q,

删除操作的代码片段:

p -> next = q ->next;

q ->next -> prior = p;

free(q);

 

思考:如果是删除*q的前驱结点咋办?(提示:前面讲解过类似…)

 

3. 循环链表:

概念:循环链表和单链表的区别在于,表的最后是一个结点的指针域不是空,而是指向了头结点,从而形成了一个环状。

在循环链表中最后一个结点指针r指向了头结点,故表中没有一个结点的指针域是空。因此,循环链表中的判断空的条件不是头结点的指针域是否为NULL,而是它是否等于头指针。

有时候对单链表做的操作是在表头和表尾进行的,此时可对循环单链表不设头指针而设置尾指针,从而使得操作效率更加高。

 

4.循环双链表

概念:由循环单链表的定义不难推出循环双链表,不同的是头结点的prior指针还要指向尾结点。

在循环双链表L中,某结点*p为尾结点时,p->next = L; L ->prior = q; 当循环双链表为空表的时候,其结点的prior和next 域都等于L。

 

 

 

 5.静态链表

概念:静态链表是借助数组来描述线性的链式结构,结点也有数据域data和指针域next,与前面讲解的链表不同的是,这里的指针域next是结点的相对地址(数值的下标)可以叫做游标。

 

静态链表结构类型

#define MaxSize 100

typedef strurct {

       ElemType data;

       int next;

}SLinkList[MaxSize]

 

静态链表以next = 0 作为结束标志。静态链表的插入、删除操作与动态链表相同,只需要修改指针,不需要移动元素。

但是总的来说,静态链表没有单链表使用起来方便。但是可以使用在一个高级语言中(比如Basic)

 

 

0 0
原创粉丝点击