3.2线性表定义

  线性表(List):零个或多个数据元素的有限序列

  例 (a1,a2,......an)   a2是a1的唯一直接后继元素   an-1是an的唯一直接前驱元素

3.4线性表顺序存储结构

  指用一段地址连续的存储单元依次存储线性表的数据元素

  顺序存储结构代码:

    #define MAXSIZE 20

    typedef int ElemType;

    typedef struct

    { 

      ElemType data [MAXSIZE]; //最大容量

      int length;//当前长度

    }SqList;

  简便定义

  int A[maxsize];

  int n;

  三要素:存储空间起始位置data,最大存储容量MAXSIZE,当前长度length

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

  3.5.1获取元素索引代码

int findElem(SqList L,int x)
{
  int i;
  for(i=0;i<L.length;++i)
  {
    if(x==L.data[i]){
      return i;
    }
  }
  return -1;
}

  3.5.2插入操作

Status ListInsert(Sqlist &L, int p,int e)
{
  int i;
  if(L.length == MAXSIZE)//队满
    return 0;
  if(p<0 || p>L.length)//索引值错误
    return 0;
  for(i=L.length-1;i>p-1;i--){

    L.data[k+1] = L.data[k];//插队位置之后的人往后移动  

  }

  L.data[p]=e;//插入
  L.length++;
  return 1;
}

  3.5.3删除操作

Status ListDelete(SqList &L, int i)
{
  int k;
  if(L.length==0)//队空
    return 0;
  if(i<0 || i>L.length-1)
    return 0;
  for(k=i;k<L.length-1;k++){
    L.data[k]=L.data[k+1];//插队的人走了 后面的人往前移动

  }
  L.length--;
  return 1;
}

  3.5.4顺序存储结构优缺点

  优点:简单逻辑关系、快速读取、能够随机访问

  缺点:插入与删除操作麻烦、空间易有碎片 难以确定容量

3.6线性表链式存储结构

  结点=数据域+指针域(p->data  p->next)

  链表中第一个结点存储位置叫做头指针(有头结点指向头结点,链表为空也存在头指针),最后一个结点指向NULL

  头结点(可选)存储公共信息(例如长度),在第一元素之前

typedef int ElemType;
typedef struct Node
{
  ElemType data;
  struct Node *next;
}Node;
typedef struct Node *LinkList;

3.7单链表读取

Status GetElem(LinkList L,int i,ElemType *e)
{
  int j;
  LinkList p;//声明一指针p
  p = L->next;
  j=1;
  while(p && j<i)
  {
    p=p->next;
    ++j;
  }
  if(!p || j>i)
    return 0;
  *e = p->data;
  return 1;
}

3.8单链表插入与删除

Status ListInsert(LinkList *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)
    return 0;
  s = (LinkList)malloc(sizeof(Node));//申请结点
  s->data = e;
  s->next = p->next;//s的指针被赋值
  p->next = s;
  return 1;
}

 

Status ListDelete(LinkList *L, int i, ElemType *e)
{
  int j=1;
  LinkList p,q;
  p = *L;
  while(p->next && j<i)
  {
    p = p->next;
    ++j;
  }
  if(!(p->next) || j>i)//第i个结点不存在
    return 0;
  q = p->next;//新指针q
  p->next=q->next;
  *e = q->data;
  free(q);//释放(删除)结点
  return 1;
}

3.9单链表整表创建

1、头插法

void CreateListHead(LNode *&L, int n, int a[])
{
  LNode *s,*r;
  int i;
  C = (LNode *) malloc (sizeof(LNode));//建立带头结点的链表
  C->next = NULL;
  for(i=0;i<n;i++)
  {
    s=(LNode *) malloc (sizeof(LNode));//新结点
    s->data = a[i];
    s->next = C->next;
    C->next = s;
  }
}

2、尾插法

void CreateListTail(LinkList *&L,int n,int a[])
{
  LNode *s,*r;
  int i;
  C = (LNode *) malloc (sizeof(LNode));//建立带头结点的链表
  C->next=NULL;

  r=C
  for(i=0;i<n;i++)
  {
    s = (LNode *) malloc (sizeof(Node));//新结点
    s->data = a[i];
    r->next=s;
    r=r->next;//尾指针向后移动
  }
  r->next = NULL;
}

3.10单链表整表删除

Status ClearList(LinkList *L)
{
  LinkList p,q;
  p=(*L->next);
  while(p)//没到表尾
  {
    q=p->next;
    free(p);
    p=q;
  }
  (*L)->next=NULL;//头结点指针域为空
  return 1;
}

3.11单链表与顺序存储对比优缺点

1、存储方式

  顺序存储用一段连续的存储单元依次存储线性表数据元素

  单链表用一组任意的存储单元存放线性表元素

2、时间性能

  查找:顺序表O(1)、链表O(n)

  插入与删除:顺序表需要平均移动表长一半的元素O(n)、链表在找到元素后仅为O(1)

3、空间性能

  顺序表需要预分配空间,链表不需要

3.12静态链表

  用数组描述的链表,有两个数据域data与cur,cur描述next的位置

  数组第一个元素的cur用来存放备用链表第一个节点的下标

  数组最后一个元素的cur用来存储第一个插入元素的下标,相当于头结点

Status InitList(StaticLinkList space)
{
  int i;
  for(i =0;i<MAXSIZE-1;i++)
  {
    space[i].cur=i+1;
  }
  space[MAXSIZE-1].cur=0;
  return 1;
}

  

3.12.1静态链表初始化插入与删除

   

int Malloc_SLL(StaticLinkList sapce)
{
  int i = space[0].cur;//第一个备用空间下标

  if(space[0].cur)
    space[0].cur = space[i].cur;//更新备用空间位置
  return i;
}


Status ListInsert(StaticLinkList L, int i,ElemType e)
{
  int j,k,l;
  k = MAX_SIZE-1;
  if(i<1||i>ListLength(L)+1)
    return 0;
  j = Malloc_SSL(L);
  if(j)
  {
    L(j).data = e;
    for(l=1;l<=i-1;l++)
    k = L[k].cur;
    L[j].cur = L[k].cur; //把第i个元素之前的cur赋值给新元素cur
    L[k].cur = j;//把新元素下标赋值给第i个元素之前元素的cur
    return 1;
  }
  return 0;
}

Status ListDelete(StaticLinkList L, int i)
{
  int j, k;
  if(i < 1||i>ListLength(L))
    return 0;
  k =MAX_SIZE - 1;
  for(j = 1;j<=i-1;j++)
  k=L[k].cur;
  j = L[k].cur;
  L[k].cur = L[j].cur;
  Free_SSL(L,j);
  return 1;
}

3.12.3静态链表优缺点

优点:在插入和删除操作时只需要修改游标,不需要移动元素

缺点:不能解决表长难以确定的问题,不能够随机存取

意义:静态链表是为了给没有指针的高级语言一种实现单链表的方法

——————————————————————————————————————

3.13循环链表

  将单链表中的终端结点指针端由空指针改为指向头结点,这种头尾相接的单链表称为循环链表

3.14双向链表

  双向链表是在单链表的每个结点,再设置一个指向其前驱结点的指针域


typedef struct DulNode
{
  ElemType data;
  struct DuLNode *prior;
  struct DulNode *next;
}DulNode, *DuLinkList;

 

3.14.1双向链表的插入与删除

  

 _______________________________________________________________________________

备注:

1、结点与指针

  结点是内存中一片由用户分配的存储空间,只有一个地址来表示存在,没有显式的名称。因此我们会在分配链表结点空间的时候,同时定义一个指针,来存储这片空间的地址,这个过程称为指针指向结点,并且通常用这个指针的名称来作为结点的名称。

  例如:LNode *A=(LNode *)malloc(sizeof(LNode));

  用户分配了一片LNode型空间,也就是构造了一个LNode型结点,这时定义一个名字为A的指针来指向这个结点,同时我们把A也当做这个结点的名字,即A既是这个新申请的结点,又是指向这个结点的指针。

  若出现描述“p指向q”,此时p指代指针,因为结点不能指向结点。若出现描述“用函数free()释放p的空间”,此时p指代结点,因为指针变量存储空间由系统分配,无需用户释放。

 2、例题:A和B是两个单链表,其中元素递增有序,设计算法将A和B归并成一个元素非递减有序的链表C

  

void merge(LNode *A,LNode *B,LNode *&C)
{
  LNode *p = A->next;
  LNode *q = B->next;
  LNode *r;//r指向C的终端结点
  C=A;//A的头结点来作为C的头结点
  C->next=NULL;
  free(B);
  r=C;
  while(p!=NULL&&q!=NULL)
  {
    if(p->data<=q->data)
    {
      r->next = p;
      p=p->next;
      r=r->next;
    }
    else
    {
      r->next = q;
      q=q->next;
      r=r->next;
    }
  }
  r->next=NULL;
  if(p!=NULL) r->next =p;
  if(q!=NULL) r->next =q;
}

 例题:查找链表C中是否存在一个值为X的结点 存在则删除

int findAndDelete(LNode *C,int x)
{
  LNode *p,*q;
  p=C;
  while(p->next!=NULL)
  {
    if(p->next->data==x)
    {
      break;
    }
    p=p->next;
  }
  if(p->next==NULL)
    return 0;
  else
  {
    q=p->next;
    p->next=p->next->next;
    free(q);
    return 1;
  }
}