线性表

抽象数据类型的标准格式:ADT(Abstract Date Type)

ADT 抽象数据类型名
Data    
    数据元素之间逻辑关系的定义
Operation 
    操作
endADT

线性表抽象数据类型格式

ADT 线性表(List) 
Data 
    线性表的数据对象集合为{a1,a2,...,an},每个元素的类型均为DataType。
    其中,除第一个元素a1外,每一个元素有且只有一个直接前驱元素;
    除最后一个元素an外,每一个元素有且只有一个直接后驱元素。
    数据元素之间的关系是一对一的关系。
Operation
    InitList(*L):           初始化操作,建立一个空的线性表L。 
    ListEmpty(L):           若线性表为空,返回true,否则返回false。 
    ClearList(*L):          将线性表清空。
    GetElem(L,i,*e):        将线性表L中的第i个位置元素值返回给e。
    LocateElem(L,e):        在线性表L中查找与定值e相等的元素,如果查找成功,返回该元素在表中序号表示成功;否则,返回0表示失败。
    ListInsert(*L,i,e):     在线性表L中的第i个位置插入新元素e。
    ListDelete(*L,i,*e):    删除线性表L中的第i个位置元素,并用e返回其值。
    ListLength(L):          返回线性表L的元素个数
endADT

上面说到的Operation是最基本的操作(基操基操),对于实际问题中涉及到的关于线性表的复杂操作,完全可以用这些基本操作组合实现。
例如:求A=A∪B(将B中A不存在的元素找出,插入到A中)

/*
    Name: A=A∪B
    Author:Liweidong
    Date: 16/07/18 15:00
    Description:  求集合A与B的并集。
*/ 
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;i

线性表的顺序存储结构(sequence list ->SqList)

线性表的顺序存储的结构代码

#define MAXSIZE 20
typedef int ElemType;   //ElemType类型根据实际情况确定,此处假设为int。
typedef struct
{
    ElemType data[MAXSIZE];
    int length;
}SqList;

GetElem(L,i,*e):

将线性表L中的第i个位置元素值返回给e。

算法思路

  • 只要 i 的数值在数组下标范围内,就返回数组第 i - 1 的值即可
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0

typedef int Status;
/*  
    Status是函数的类型,其值是函数结果状态代码,如OK等。
    初始条件:顺序线性表L已存在,1 ≤ i ≤ ListLength(L)。
    操作结果:用e返回L中第i个数据元素的值
*/

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

ListInsert(*L,i,e):

在线性表L中的第i个位置插入新元素e。

算法思路

  1. 如果插入位置不合理,抛出异常;
  2. 如果线性表长度大于数组长度,则抛出异常或动态增加容量;
  3. 从最后一个元素开始向前遍历到第i个元素,分别将它们都向后移动一个位置;
  4. 将要插入元素填入位置i处;
  5. 表长加1.
/*  
    初始条件:顺序线性表L已存在,1 ≤ i ≤ ListLength(L)。
    操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1
*/

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;
}

ListDelete(L,i,e):

删除线性表L中的第i个位置元素,并用e返回其值。

算法思路

  1. 如果删除位置不合理,抛出异常;
  2. 取出删除元素;
  3. 从删除元素位置开始遍历到最后一个元素位置,分别将它们都向前移动一个位置;
  4. 表长减1。
/*  
    初始条件:顺序线性表L已存在,1 ≤ i ≤ ListLength(L)。
    操作结果:删除线性表L中的第i个位置元素,并用e返回其值,L的长度减1.
*/

Status ListDelete(SqList *L,int i,ElemType e)
{
    int k;
    
    if(L->length == 0)
        return ERROR;
    if(i<1 || i > L->length)
        return ERROR;

    *e=L->data[i-1];

    if(ilength)
    {
        for(k=i;klength;k++)
            L->data[k-1]=L->data[k];
    }

    L->length--;
    return OK;
}

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

优点

  • 无须为表示表中元素之间的逻辑关系而增加额外的存储空间;
  • 可以快速的存取表中任一位置的元素。

缺点

  • 插入和删除操作需要移动大量的数据(参考代码,时间复杂度为n);
  • 当线性表长度变化较大时,难以确定存储空间的容量;
  • 造成存储空间的"碎片"。

顺序表的链式存储结构

一些名词定义:

  • 为了表示每个数据元素ai与其直接后继元素ai+1之间的逻辑关系
  • 对数据元素ai来说,除了存储其本身的信息之外,还需存储一个指示其直接后继的信息(即地址)。
  • 把存储数据元素信息的域称为数据域,把存储直接后继位置的域称为指针域
  • 指针域中存储的信息称作指针
  • 这两部分信息组成数据元素ai的存储映像,称为结点(Node)
  • n个结点(ai的存储映像)链结成一个链表,即为线性表的链式存储结构
  • 因为此链表的每个结点只包含一个指针域,所以叫做单链表

头指针和头结点

头指针:链表中第一个结点的存储位置叫做头指针;
头结点:在单链表的第一个结点前附设一个结点,称为头结点。

异同点

  1. 头指针是指链表指向第一个结点的指针,若链表有头结点,则是指向头结点的指针;
  2. 头指针具有标志作用,所以常用头指针冠以链表的名字;(标志冠名)
    无论链表是否为空,头指针均不为空。头指针是链表的必要元素。(必须存在)
  3. 头结点是为了操作的统一和方便而设立的,放在第一元素的结点之前,其数据域一般无意义;(也可存放链表的长度)
  4. 有了头结点,对在第一元素结点前插入结点和删除第一结点,其操作与其他结点的操作就统一了;(方便插入、删除操作)
  5. 头结点不一定是链表的必须要素。(可有可无)

线性表的链式存储的结构代码(链表:linked list -> LinkList)

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

GetElem(L,i,*e):

将线性表L中的第i个位置元素值返回给e。

算法思路

  1. 声明一个指针p指向链表第一个结点,初始化j从1开始;
  2. 当 j < i 时,就遍历链表,让p的指针向后移动,不断的指向下一结点,j累加1;
  3. 若到链表末尾p为空,则说明第i个结点不存在;
  4. 否则查找成功,返回结点p的数据。
/*  
    初始条件:顺序线性表L已存在,1 ≤ i ≤ ListLength(L)。
    操作结果:用e返回L中第i个数据元素的值。
    核心思想:工作指针后移。
*/
Status GetElem(LinkList L,int i,ElemType *e)
{
    int j;
    LinkList p;
    p = L->next;
    j = 1;
    
    while(p && jnext;
        ++j;
    }
    
    if(!p || j>i)
        return ERROR;
    
    *e = p->data;
    
    return OK;
}

ListInsert(*L,i,e):

在线性表L中的第i个位置插入新元素e。

算法思路

  1. 声明一个指针p指向链表第一个结点,初始化j从1开始;
  2. 当 j < i 时,就遍历链表,让p的指针向后移动,不断的指向下一结点,j累加1
  3. 若到链表末尾p为空,则说明第i个结点不存在;
  4. 否则查找成功,在系统中生成一个空结点s;
  5. 将数据元素e赋值给s->data;
  6. 单链表的插入标准语句 s->next = p->next; p->next = s;
  7. 返回成功。
/*  
    初始条件:顺序线性表L已存在,1 ≤ i ≤ ListLength(L)。
    操作结果:在L中第i个结点位置之前插入新的数据元素e,L的长度加1。
    核心思想:工作指针后移。
*/

Status ListInsert(LinkList *L,int i,ElemType e)
{
    int k;
    LinkList p,s;
    p = *L;
    k = 1;
    
    while (p && knext;
        ++k;
    }
    
    if(!p || k>i)
        return ERROR;
        
    s = (LinkList)malloc(sizeof(Node));
    s->data = e;
    s->next = p->next;
    p->next =s;
    
    return OK;
}

ListDelete(L,i,e):

删除线性表L中的第i个位置元素,并用e返回其值。

算法思路

  1. 声明一个指针p指向链表第一个结点,初始化j从1开始;
  2. 当 j < i 时,就遍历链表,让p的指针向后移动,不断的指向下一结点,j累加1;
  3. 若到链表末尾p为空,则说明第i个结点不存在;
  4. 否则查找成功,将欲删除的结点p->next赋值给q;
  5. 单链表的删除标准语句 p->next = q->next;
  6. 将q结点中的值重新赋值给e,作为返回;
  7. 释放q结点;
  8. 返回成功。
/*  
    初始条件:顺序线性表L已存在,1 ≤ i ≤ ListLength(L)。
    操作结果:删除线性表L中的第i个位置元素,并用e返回其值,L的长度减1。
    核心思想:工作指针后移。
*/

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

单链表的整表创建

算法思路

  1. 声明一指针P和计数器变量i;
  2. 初始化一空链表L;
  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;
    }
}
//  尾插法法创建单链表
void CreateListTail(LinkList *L,int n) {
    LinkList p,r;
    int i;
    srand(time(0));
    
    *L = (LinkList)malloc(sizeof(Node));
    r = *L;
    
    for(i=0; idata = rand()%100+1;
        r->next = p;
        r = p;
    }
    r->next = NULL;
}

单链表的整表删除

算法思路

  1. 声明一结点p和结点q;
  2. 将第一个结点赋值给p;
  3. 循环
    • 将下一节点赋值给q;
    • 释放p;
    • 将q赋值给p;
//  删除单链表(将L置为空表) 
Status ClearList(LinkList *L){
    LinkList p,q;
    p = (*L)->next;
    while(p){
        q = p->next;
        free(p);
        p = q;
    }
    (*L)->next = NULL;
    return OK;
}

单链表和顺序表 存储结构优缺点

存储分配方式:

顺序表用一段连续的存储单元依次存储线性表的数据元素
单链表采用链式存储结构,用一组任意的存储单元存放线性表的元素

时间性能:

查找:
    顺序表:O(1) 
    单链表:O(n) 
插入和删除
    顺序表:平均需移动表长一半的元素,时间为:O(n) 
    单链表:在找出某位置的指针后,插入和删除时间仅为O(1) 

空间性能:

顺序表需预分配存储空间,分大了,浪费;分小了,易发生溢出
单链表不需要分配空间,只要有就可以分配,元素个数也不受限制 

TO BE CONTINUE

静态链表

循环链表

双向链表

你可能感兴趣的:(线性表)