数据结构———线性表之顺序表

1.线性表

1.1线性表的定义:

线性表(linear list)是具有相同特性元素的一个有限序列。

线性表是⼀种在实际中⼴泛使⽤的数据结构,常⻅的线性表:顺序表、链表、栈、队列、字符串…
线性表在逻辑上是线性结构,也就说是连续的⼀条直线。但是在物理结构上并不⼀定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。

1.2线性表的特性:

1.有穷性:线性表中的元素个数是有限的。
2.一致性:一个线性表中所有元素的性质相同,所有元素具有相同的数据类型。
3.序列性: 一个线性表中所有元素之间的相对位置是线性的,即存在唯一的开始元素和终端元素,除此之外,每个元素只有唯一的前驱元素和后继元素。各元素在线性表中的位置取决于他们的序号,所以在一个线性表中可以存在两个值相同的元素。

1.3线性表的抽象数据类型的具体描述。
ADT List
{
   数据对象:
   D={ai|1<=i<=n,n>=0,ai为Elemtype类型}    //Elemtype类型为自定义类型标识符
   数据关系:
   R={<ai,ai+1>|ai,ai+1属于D,I=1.....,n-1)
   基本运算:
   InitList(&L):初始化线性表,构造一个空的线性表L。
   DestroyList(&L):销毁线性表,释放为线性表L分配的内存空间。
   ListEmpty(L):判断线性表是否为空表,若L为空表,返回真,否则返回假。
   ListLength(L):求线性表的长度,返回L中的元素的个数。
   DisList(L):   输出线性表,当线性表L不为空时,顺序输出L中各元素的值。
   GetElem(L,i,&e):按序号求线性表中元素,用e返回L中第i (1,n)个元素的值。
   LocateElem(L,i,&e):按元素值查找,返回L中第一个值为e相等的元素序号。
   ListInsert(&L,i,e):插入元素,在L的第i (1,n+1)个元素的位置插入一个新的元素e.
   ListDelete(&L,i,&e):删除元素,删除,在L的第i (1,n)个元素,并用e返回该元素值。
}

在这里插入图片描述

2.线性表之顺序表

2.1顺序表概念:

顺序表是存储在一片连续的存储区间内的一组数据元素,每个数据元素都有一个唯一的线性编号,称为下标。顺序表的最大优点是在访问任何一个元素时都只需一次存取,因此随机存取速度非常快,是一种十分常用和重要的数据结构。

顺序表是一种静态数据结构,其容量是在创建时确定的,在使用过程中,如果数据元素个数变化较大,就需要及时地调整容量大小,否则就会造成空间浪费或空间不够,不能插入新元素的情况。

分析:
线性表中的第一个元素a1,存储在对应数组的起始位置,即下标为0的位置上,第二个元素a2:存储在下标为1的位置上,以此类推。假设线性表L存储在数组A中,A的起始存储位置为LOC(A),则L所对应的顺序表如图2.3所示。需要注意的是,顺序表采用数组来实现,但不能将任何一个数组都当作一个顺序表,二者的运算是不同的,数组只有存元素取元素运算。
在这里插入图片描述
数组大小Maxsize一般定义为一个整型常量。如果估计一个线性表不会超过50个元素。

#define  MaxSize 50

在声明线性表的顺序存储类型,定义一个data数组来存储线性表中的所有元素,定义一个整型变量length来存储线性表的实际长度,并采用结构体类型SqList 表示如下:

typedef struct 
{
   Elemtype  data[MaxSize];   //存放线性表中的元素
   int length;                //存放线性表的长度
}SqList;                      //顺序表类型
2.2顺序表的基本实现:

采用顺序表指针方式建立和使用顺序表:如图:a , 也可以接使用顺序表Q,如图:b

在这里插入图片描述

顺序表指针L和顺序表Q都可以标识一个顺序表,但前者是通过指针L间接的标识顺序表,其定义方式是Sqlist *L,后者是直接地标识顺序表,其定义方式是Sqlist Q,前者引用length域的方式为L->length,后者为Q.length。之所以采用顺序表指针,主要是为了方便顺序表的释放算法设计,并且在函数之间传递顺序表指针,会减少形参分配的空间。

3.顺序表主要学习内容:

3.1建立顺序表:

这里介绍整体创建顺序表: 即,由数组元素a[0…n-1]创建顺序表L,其方法是将数组a中的元素依次放入顺序表,并将n赋值给顺序表的长度域。

void  CreateList(SqList * &L,ElemType a[],int n)    //由a中的n 个元素建立顺序表
{
  int i=0,k=0;                              //k表示L中元素的个数,初始值为0
  L=(SqList *)malloc (sizeof(SqList));     //分配存放线性表的空间
  while(i<n)            //i扫描数组a中的元素
  {
    L->data[k]=a[i];    //将元素a[i]放到L中
    k++;i++;
    L->Length=k;    //设置L的长度为k
  }

当调用上述算法创建好L所指的顺序表后,需要回传给对应的实参,也就是说L是输
出型参数,所以在形参L的前面需要加上引用符“&”。

3.2初始化线性表 InitList(&L)

功能:构造空的线性表L,分配好空间,将length置为0;

void  InitList(SqList * &L)
{
  L= (SqList*) malloc (sizeof(SqList));
  L->Length=0;
}

本算法的时间复杂度为:O(1)

3.3销毁线性表

该运算的功能是释放线性表L占用的内存空间,算法如下:

void  DestroyList(SqList * &L)
{
  free(L);
}

凡是用malloc 申请的内存,用完务必要释放内存空间。

本算法的时间复杂度为:O(1)

3.4判断线性表是否为空:

该运算返回一个布尔值表示L是否为空表,若L为空表,返回true,否则返回false,算法如下:

bool ListEmpty(SqList *L)
{  
   return (L->length ==0);
}
  

本算法的时间复杂度为:O(1)

3.5求线性表的长度: ListLength(L)

该运算返回顺序表L的长度,实际上只需返回length域的值即可,算法如下:

int ListLength(SqList*L)
{
   return (L->length);
}
3.6输出线性表: DispList(L)

该运算依次输出L中各元素的值。算法如下:

void  DisList(SqList *L)
{
    for(int i =0;i<L->length;i++)
    printf("%d ",L->data[i]);  
    printf("\n");
}

本算法中的基本操作为for循环中的printf语句,它执行n次,故时间复杂度为O(n);
其中n为顺序表中元素的个数。

3.7按序号求线性表中的元素。
bool GetElem(SqList * L,int i,ElemType &e)
{
   if(i<1||i>L->length)
   return false;    //参数i错误时返回false
   e=L->data[i-1];  //取元素的值
   return true;     //成功找到元素时返回true
}

本算法的时间复杂度为:O(1)

3.8按元素查找: LocateElem(L,e)

该运算顺序查找第一个值为e的元素的逻辑序号,若这样的元素不存在,则返回值为0.算法如下:

int   LocateElem(Sqlist *L,ElemType e)
{  
    int i=0;
    while(i<L->length&&L->data[i]!=e)
    i++;                    //查找元素e
    if(i>=L->length)        //未找到返回0
    return 0;
    else                    //找到返回其逻辑序号
    return i+1;
 }

本算法中的基本操作为while 循环中的i++语句,其平均执行(n+1)/2次,故算法复杂度为
O(n),其中n为顺序表中元素的个数。

3.9插入数据元素: ListInsert(&L,i,e)

该运算在顺序表L的第i [1,n+1]范围内个位置上插入新元素e,如果i值不正确,返回flase;否则将顺序表原来的第i个元素及以后的元素均后移一个位置,并从最后一个元素an开始移动。如图,腾出一个位置插入新元素,最后顺序表的长度增1并返回true,算法如下:

在这里插入图片描述

bool ListInsert(SqList *&L,int i,ElemType e)
{
   int j;
   if(i<1||i>L->length+1||L->length==Maxsize)
   return false;  //参数i错误是返回
   i--;           //将顺序表中的逻辑信号转为物理信号
   for(j=L->length;j>i;j++)  //将data[i]及后面的数据后移一个位置。
   L->data[j]=L->data[j-1];
   L->data[i]=e;      //插入元素e
   L->length++;       //顺序表的长度增1
   return true;       // 成功插入返回true
   

算法复杂度:
在这里插入图片描述

3.10删除数据元素:ListDelete(&L,i,&e)

该运算删除顺序表L的第i [1,n]个元素,如果i值不正确,返回false否现将线性表第i个元素以后的元素均向前移动一个位置,并从元素a i+1开始移动,如图,这样覆盖了原来的第i个元素,达到了删除该元素的目的,最后顺序表的长度减1并返回true。算法如下:在这里插入图片描述

bool ListDelete(SqList *&L,int i,ElemType &e)
{
   int j;
   if(i<1||i>L->length)       //参数i错误时返回
    return false;
   i--;                       //将顺序表的逻辑序号转为物理序号
   e=L->data[i];             
   for(j=i;j<L->length-1;j++)  //将data[i]之后的前移一个位置
    L->data[j]=l->data[j+1];
    L->length--;            //顺序表的长度减1
    return true;            //成功删除返回1
      

对于本算法来说,元素的移动的次数也与表长n=L->length和位置i有关,共有n个元素可以被删除,当i=n时,删除末尾元素,移动次数为0,i=1,删除首元素,移动过次数为n-1,一般将ai+1~an的元素均前移一个位置,移动次数为n-(i+1)+1=n-i,假设pi是删除第i个位置上元素的概率,在等概率的情况下,pi=1/n,则在计算为n的线性表中,删除一个数据元素所需要的平均次数为:
在这里插入图片描述

4顺序表的应用举例:

例1:
假设一个线性表采用顺序表表示,设计一个算法,删除其中所有值等于X的元素,要求算法的时间复杂度O(n),空间复杂度O(1).

算法1:整体建表法:

void delnodel(SqList *&L,ElemType x)
{
   int k=0,i;      // k记录不等于x的元素的个数,即保留的元素的个数
   for(int i=0;i<L->length;i++)
   {
        if(L->data[i]!=x)      //若当前不为x,将其插入L中
        {
           L->data[k]=L->data[i];
           k++;                //插入一个元素加1
        }
   }
      L->length=k;             //顺序表L的长度等于k
}

算法2:元素移动法

void  delnode2(SqList *&L,ElemType x)
{
   int k=0,i=0;  //k记录不等于x的元素的个数,即保留的元素的个数
   while(i<L->length)
   {
     if(L->data[i]==x)   //当元素为x时,k加1
       k++;
     else                //当前元素不为x时将其前移k个位置
       L->length[i-k]=L->data[i];  
     i++;
   }
   L->length-=k;      //顺序表长度
}

在这里插入图片描述
例2:有一个顺序表L,假设元素类型ElemType为整型,设计一个尽可能高效的算法,以第一个元素为分界线(基准),将所有小于或等于它的元素移到该基准的前面,将所有大于它的元素移到它的后面。

解:使用元素交换法求解。
法1:

int  partitionl(SqList *&L)
{
 int i=0;j=L->length-1;
 ElemType base=L->data[0];   //以data[0]为基准
 while(i<j)                 //从两端向中间遍历
 {
  while(i<j&&L->data[j]>base)
  j--;          //从右向左遍历,找一个小2,7于或等于base的元素
  while(i<j&&L->data[j]<=base)
  i++;          //从左向右遍历,找一个大于base的元素
  if(i<j)
  swap(L->data[i],L->data[j]);  //将L->data[i]和data[j]交换

例如:L(3,8,2,7,1,5,3,4,6,0),执行。
在这里插入图片描述
法二:

void partition2(Sqlist*&L)
{  
   int i=0,j=L->length-1;  //以data[0]为基准
   ElemType base =L->data[0]; //从顺序表的两端交替向中间遍历,直到i=j为止
   while(i<j)
   {
     while(j>i&&L->data[j]>base)
     j--;    //从右向左遍历,找到一个小于或等于base的data[j]
     L->data[i]=L->data[j]; //找到这样的data[j],放入data[i]处
     while(i<j&&L->data[i]<=base)
     i++;   //从左向右遍历,找到一个小于或等于base的data[i]
     L->data[j]=L->data[i];  //找到这样的data[i],放入data[j]处
   }
   L->data[i]=base;

在这里插入图片描述
尽管对于同一个数据序列这两个执行结果不完全相同,但都能满足题目所需要求,他们的时间复杂度为O(n),空间复杂度为O(1),都属于高效算法。
本例采用快速排序算法。
例2:有一个顺序表L,假设元素类型ElemType为整型,设计一个尽可能高的算法,将所有奇数移动到偶数前面。
法一:
元素交换法:

void movel(SqList *&L)
{
   int i=0;j=L->length-1;
   while(i<j)
   {
      while(i<j&&L->data[j]%2==0)
      j--;
      while(i<j&&L->data[i]%2==1)
      i++;
      if(i<j)
      swap(L->data[i],L->data[j]);
   }
}

法2:区间划分法:

void move2(Sqlist *&L)
{
   int i=-1,j;
   for(j=0;j<=L->length-1;j++)
   {
      if(L->data[j]%2==1)
      {
         i++;
         if(i!=j)
         swap(L->data[i],L->data[j]);
      }
   }
}   

上述算法的时间复杂度为O(n),空间复杂度为O(1),都属于高效的算法。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值