大话数据结构——3线性表

线性表

线性表的定义

        定义:零个或多个数据元素的有限序列。
        一个有顺序的序列。第一个元素无前驱,最后一个元素无后继。其他每个元素都只有一个前驱和一个后继。
        线性表的元素个数n定义为线性表的长度n,当n=0的时候为空表。在非空的数据表中每个元素的位置都是固定的。

线性表的抽象数据类型

ADT 线性表 抽象数据类型

DATA:线性表的数据对象的集合是a1,a2,…,an,每个数据的数据类型是DataType.
Operation
InitList(*L): 初始化操作,建立一个空的线性表。
ListEmpty(L): 若线性表为空返回true否则就返回false
ClearList(*L):将线性表清空
GetElem(L,i,*e):把数据表中第i个元素返回给e
LocateElem(L,e):在线性表中查找与给定的e相等的元素,如果查找成功返回该元素在表中的序列号;否则返回0表示失败
ListInsert(*L,i,e):在线性表中的第i个位置插入新元素e
ListDelete(*L,i,e):删除线性表中的第i个位置元素,并用e返回其值
ListLength(L):返回线性表L的元素个数

下面举一个简单的例子:
把两个线性表进行合并把仅存在集合B的元素插入到集合A:

void unionL(List *La,List Lb)
{
 int La_len,Lb_len,i;
 ElemType e;
 La_len=ListLength(*La);
 Lb_len=ListLength(Lb);
   for(i=1;<Lb_len;i++)
  {
   GetElem(Lb,i,&e);//依次取出元素存入e
   if(!LocateElem(*La,e));//如果有与e相同的元素
   ListInsert(La,++La_len,e);//在La的末尾插入此相同元素
  }
}

3.4线性表的顺序存储结构

3.4.2顺序存储的定义

线性表有两种物理存储结构,一种是顺序存储,一种是链式存储结构;
    顺序存储结构就是用一串地址连续的存储单元依次存放线性表的数据元素。

3.4.2顺序存储方式

顺序结构的存储方式就是在内存中找了一块区域把一串相同数据类型的数据元素存在这块空地里面。
顺序表存储结构代码:

#define MAXSIZE 20
typedef int ElemType ;
typedef struct{
  ElemType data[MAXSIZE];
  int length;
}SqList;

这存储空间有三个属性


  1. 存储空间的其实位置:数组data
  2. 存储空间的最大长度:MAXSIZE
  3. 线性表的当前长度:length

3.4.3数据长度与线性表的长度区别·

数组的长度和线性表的长度,其中数组的长度在一开始就被定义了,是一个不变的量,而线性表的长度随着数据的插入和删除会相应的增加和减小。一般来说线性表的长度要小于等于数组的长度。

3.4.4地址的计算方法

线性表的顺序是从一开始的,但是我们定义的数组是从0开始的下标。就是说现线性表种的第i个元素对应的是数组下标为i-1的。在存储器中每个存储单元都有自己的编号,这个编号就叫做地址。这举个例子来说明如何算当前的地址:
假设每个数据元素占c个字节那么线性表中第i个数据元素的地址和第i+1个数据元素地址之间有如下关系:(LOC表示获取当前存储位置的函数)

                                                            LOC(ai+1)=LOC(ai)+c

那么第i个元素的位置就可以由第一个公式推算出:

                                                          LOC(ai)=LOC(a1)+(i-1)*c

由最后这个公式你可以很快的算出任何一个元素的地址,不管是第一个还是最后一个,都是相同的时间,所以对线性表的每个元素经行读取和删除操作的时候,时间是一样的。在算法中的概念就是算法的时间复杂度是O[1]这一存储结构由被称为随机存储结构

3.5顺序存储结构的插入与删除

3.5.1获取元素操作

对于线性表的顺序存储结构我们进行GetElem操作:把顺序表中的第i个元素返回。在c语言中就是把数组下标为i-1的元素返回:

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
 typedef int Status;
 Status GetElem(SqList L,int i,ElemType *e)
 {
 if((L.length==0)||i<1||i>L.length)
 return ERROR;
 *e =L.data[i-1];
 reuturn OK;
 }
 

3.5.2插入操作

实现向线性表中插入一个元素,就是ListInsert(*L,i,e),向线性表的第i个位置插入元素e。
插入的思路:

  1. 如果插入位置不合理,就抛出异常
  2. 如果线性表长度大于数组长度就抛出异常或者动态增加内存
  3. 从最后一个位置向前遍历到第i个位置,把他们都向后移动一个位
  4. 将要插入的元素放入第i个位置
  5. 表长加一
Status ListInsert(Sqlist *L,int i, ElemType e)
{
  int k;
  if(L->length==MAXSIZE)
  return ERROR;
  if (i<1||I>L->length-1)
  return ERROR;
  if(i<=L->length)
  {
  for(k=L=>length-1;k>=i-1;k--)
   {
    L->data[k+1]=L->data[k];
   }
   L->data[i-1]=e;
   L->length++;
   return OK;
  }
}

3.5.3删除操作

删除的算法思路:

  1. 如果删除位置不合理就抛出异常
  2. 取出删除元素
  3. 从删除的元素后每个元素向前加一
  4. 表长减一

具体代码如下:



Status ListDelete(Sqlist *L,int i,ElemType *e)
{
    int k;
    if(L->length==0)
    rreturn ERROR;c
    if(i<1||i>L->length)
    return ERROR;
    *e=L->data[i-1];//取出被删除的元素
    if(i<L->length)
    {
    for(k=i;k<L->length;k++)
      L->data[k-1]=L->data[k];//向前挪动一位
    }
    L->length--;
    return OK;
}

线性存储的优缺点

线性表的顺序存储结构的优点与缺点:

  • 优点
    可以快速的存取表种任意位置的元素
    无需位表示表中元素之间的存储关系增加额外的空间

  • 缺点
    插入和删除操纵要移动大量的元素
    当线性表长度发生变化时难以确定存储空间的容量
    容易造成存储空间的碎片化

线性表的链式存储结构

线性表的链式存储结构定义

定义:用一组任意的存储空间来存储线性表的数据元素,存储单元可以是线性的,也可以是不线性的。(这些数据元素可以存在任意未被分配的内存空间中)
    为了表示每个元素与其后继元素之间的关系除了存储数据本身的信息外,还需要一个存储一个指示其直接后继元素的信息。
存储元素信息的域成为数据域
存储直接后继位置的域称为指针域
指针域中存储的信息叫做指针或者链
这两部分的信息组成数据元素ai的存储映像,叫做结点

n个这样的结点链接成链表,成为线性表的链式存储结构,由于每个结点的指针域中只包含一个指针信息,所以称为单链表
对线性表来说肯定有头和尾
头:我们把链表中第一个结点的存储地址当作头指针
尾:我们规定线性表的最后一个结点的指针位NULL空指针。

为了表示方便,我们常常在第一个结点前加上一个头结点,头结点不包含任何的数据,指针域包含一个头指针指向第一个结点

3.6.4线性表的链式存储结构的代码实现

单链表,在c语言中用结构体就能进行很好的构建:

typedef struct Node
{
  ElemType data;
  struct Node *next;
}Node;
typedef struct Node *LinkList;//定义LinkList

可以清楚的看到,结点由存放数据元素的数据与存放下一个结点地址的指针与构成。
假设p是指向线性表的第i个元素的指针,则该节点的的数据可以用p->data来表示
而下一个节点的数据表示为:p->next->data;

3.7单链表的读取

在线性表的顺序存储结构中,由于元素是按顺序存在数组里面的所以很容易知道任意一个元素在线性表中位置。但是当线性表是链式存储结构,要想访问第i个元素就必须从一开始查找。算法思路:

  1. 申明一个指针p指向链表的第一个结点,初始化j从第1开始
  2. j小于i的时候指针就不断向后遍历
  3. 若到链表末尾p为空,则说明第i个结点不存在
  4. 否则查找成功,并且返回p指针的数据
    具体实现代码如下:
//用e返回L中第i个元素的值
Status GetElem (LinkList L,int e,ElemType *e)
{
  int j;
  LinkList  p;
  p=L->next;
  j=1;
   while(p&&j<i)//指针不为空且j始终小于i
   {
    p=p-next;
    j++;
   }
   if(!p||j>1)
   return ERROR;
   *e =p->data;
   return OK;
}

此算法的时间复杂度取决于i的大小,最坏情况时间复杂度为O[n],核心为工作指针后移

3.8单链表的插入与删除

3.8.1单链表的插入

单链表的插入,假设要,存储单元e的结点为s,要实现结点p,p->next和s之间的逻辑关系变化,只需要把s结点插入到p和p->next中,不许需要改变其他结点。
s->next=p->next;p->next=s;
让p的后继结点改成s的后继结点再把结点s变成p的后继结点。

  1. 指针p指向链表头结点,初始化j从i开始
  2. 当j’
  3. 若链表末尾p为空则说明第i个结点不存在
  4. 否则查找成功,在系统中生成一个空结点s
  5. 将数据元素e赋值给s->data
  6. 单链表的插入标准语句s->next=p->next,p->next=s;
  7. 返回成功
    代码实现如下:
Status ListInsert(ListType *L,int i,ElemType e)
{
 int j;
 LinkList p,s;
 p=*L;
 j=1;
 while(p&&j<i)
  {
   p=p->next;
   ++j;
  }
  if(!p||j>i)
  {
  s=(LinkList)malloc(sizeof(Node))//为新生成的结点申请内存空间
  s->data=e;
  s->next=p->next;
  p->next=s;
  return OK;
  }
}

3.8.2单链表的删除

这里要做的就是把p->next=p->next->next现在用q代表p->next;
q=p->next;p->next=q->next;
就是把p的后继结点变成p的后继结点的后继结点;

  1. 申明指针p指向表头指针,j初始化为1
  2. j
  3. 若p指向空,则第i个结点不存在
  4. 否则查找成功,将预删除的结点p->next赋值给q
  5. 单链表删除元素标准语句p->next=q->next;
  6. 把q结点中的数据赋值给e作为返回
  7. 释放q结点
  8. 返回成功
    代码如下:

 Status ListDelete()
 {
  int j;
  LinkList p,q;
  p=*L;
  j=1;
  while(p->next&&j<i)
  {
   p=p->next;
   j++;
  }
  if(!(p->next)||j>i)
  return ERROR;
  q=p->next;
  p->next=q->next;
  *e =q->data;
  free(q);
  return OK;
 }

由此可以看出,如果对插入删除操作比较多的时候,用链表结构会比较有又是,如果只是查找某个元素的话,线性表的顺序存储结构会方便很多。

3.9单链表的整体创建

回顾之前的可得:线性表的顺序存储结构就是数组的创建,为线性表的链式存储结构就是一种动态结构,对于每个链表来说所占据的空间大小和位置是不需要预先划分的,根据需要即使生成。单链表创立的整体思路如下:

  1. 声明一个指针p和计数器标量i
  2. 初始化一个空链表
  3. 让L的头结点指向NULL,建立一个代头结点的空链表
  4. 循环:
    生成一新结点赋值给p
    随机生成一数字赋值给p的数据域p->data
    将p插入到头节点与前一新节点之间
    具体实现代码如下:
void CreateListHead(LinkList *L,int n)
{
  LinkList p;
  int i;
  srand(time(0));
  *L=(LinkList)malloc(sizeof(Node));
  (*L)->next =NULL;
  for(i-0;idata =rand()%100+1;//随机生成数字
  p->next =(*L)->next;
  (*L)->next=p;
  }
}

你可能感兴趣的:(大话数据结构)