线性表专题

线性表

线性表是一种简单的线性结构,特色是在非空的有限集合中,且第一个元素没有直接前驱元素,最后一个元素没有直接后继元素,其余元素都有惟一的前驱和后继元素。线性表有顺序存储结构和链式存储结构。node

线性表的顺序存储方式

指将线性表中的各个元素依次存放在一组地址连续的存储单元中,一般将这种方法存储的线性表称为顺序表。
逻辑相邻,物理存储地址也相邻数组

定义以下:markdown

/* ElemType类型根据实际状况而定,这里假设为int */
typedef int ElemType;
/* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int Status;
/*线性结构使用顺序表的方式存储*/
//顺序表结构设计 
typedef struct {
    ElemType *data;
    int length;
}Sqlist;
复制代码

其中,ElemType表示数据元素类型,data用于存储线性表中的数据元素的首地址,length用来表示线性表中数据元素的个数,Sqlist是结构体类型名。定义一个顺序表代码:Sqlist L; 指向顺序表的指针:Sqlist *L;函数

  • 顺序表初始化
Status InitList(Sqlist *L){
    //为顺序表分配一个大小为MAXSIZE 的数组空间
    L->data =  malloc(sizeof(ElemType) * MAXSIZE);
    // 存储分配失败退出
    if(!L->data) exit(ERROR);
    // 初始化,空表长度为0
    L->length = 0;
    return OK;
}
复制代码
  • 顺序表的插入
/* 初始条件:顺序线性表L已存在,1≤i≤ListLength(L); 操做结果:在L中第i个位置以前插入新的数据元素e,L的长度加1 */
Status ListInsert(Sqlist *L,int i,ElemType e){
    // i值不合法判断
    if((i<1) || (i>L->length+1)) return ERROR;
    // 存储空间已满
    if(L->length == MAXSIZE) return ERROR;
    // 插入数据不在表尾,则先移动出空余位置
    if(i <= L->length){
        for(int j = L->length-1; j>=i-1;j--){
            //插入位置以及以后的位置后移动1位
            L->data[j+1] = L->data[j];
        }
    }
    // 将新元素e 放入第i个位置上
    L->data[i-1] = e;
    // 长度+1;
    ++L->length;
    return OK;
}
复制代码
  • 顺序表的取值
Status GetElem(Sqlist L,int i, ElemType *e){
    //判断i值是否合理, 若不合理,返回ERROR
    if(i<1 || i > L.length) return  ERROR;
    //data[i-1]单元存储第i个数据元素.
    *e = L.data[i-1];
    return OK;
}
复制代码
  • 非空判断
int ListEmpty(Sqlist L){
    if(L.length == 0) {
        return OK;
    }
    return ERROR;
}
复制代码
  • 顺序表删除
/* 初始条件:顺序线性表L已存在,1≤i≤ListLength(L) 操做结果: 删除L的第i个数据元素,L的长度减1 */
Status ListDelete(Sqlist *L,int i){
    //线性表为空
    if(L->length == 0) return ERROR;
    //i值不合法判断
    if((i<1) || (i>L->length+1)) return ERROR;
    for(int j = i; j < L->length;j++){
        //被删除元素以后的元素向前移动
        L->data[j-1] = L->data[j];
    }
    //表长度-1;
    L->length --;
    return OK;
}
复制代码

*清空顺序表性能

/* 初始条件:顺序线性表L已存在。操做结果:将L重置为空表 */
Status ClearList(Sqlist *L) {
    L->length=0;
    return OK;
}
复制代码
  • 顺序输出List
Status TraverseList(Sqlist L) {
    int i;
    for(i=0;i<L.length;i++)
        printf("%d\n",L.data[i]);
    printf("\n");
    return OK;
}
复制代码
  • 顺序表查找元素并返回位置
/* 初始条件:顺序线性表L已存在 */
/* 操做结果:返回L中第1个与e知足关系的数据元素的位序。 */
/* 若这样的数据元素不存在,则返回值为0 */
int LocateElem(Sqlist L,ElemType e) {
    int i;
    if (L.length==0) return 0;
    for(i=0;i<L.length;i++) {
        if (L.data[i]==e)
            break;
    }
    if(i>=L.length) return 0;
    return i+1;
}
复制代码

总结

  • 优势:无须关心表中元素之间的关系,因此不用增长额外的存储空间;能够快速地取表中任意位置的元素
  • 缺点:插入和删除操做须要移动大量元素。使用前需事先分配好内存空间,当线性表长度变化较大时,难以肯定存储空间的容量。分配空间过大会形成存储空间的巨大浪费,分配的空间太小,难以适应问题的需求。

线性表的链式存储

在解决实际问题时,有时并不适合采用线性表的顺序存储结构,例如两个一元多项式的相加、相乘,这就须要另外一种存储结构——链式存储。它是采用一组任意的连续或非连续存储单元存储线性表的元素。为了表示每一个元素ai与其直接后继ai+1的逻辑关系,链式存储不只须要存储元素自己,还要存储一个指向其直接后继元素的地址。这种存储结构被称之为结点(node)。存储元素的叫数据域,存储地址的叫指针域。结点元素的逻辑顺序称之为线性链表或单链表。spa

链式存储最大的特色就是不连续的,因此每一个数据与数据之间的关系是经过指针域来进行链接的。设计

由于第一个结点没有直接前驱结点,所以须要一个头指针L指向它。为了方便操做放在第一个元素结点以前一个结点称之为头结点,头指针变成指向头结点,其数据域能够存放如线表长度等信息,而指针域则存放第一个元素结点的地址信息。若该链表为空,则头结点指针域为空。 最后一个元素没有直接后继元素,因此将其指针域设置为“Null”空。指针

(图片来自逻辑教育) code

为何要使用头结点?

便于首元结点处理和空表和⾮空表的统一处理orm

用C语言描述以下:

  • 定义结点
typedef struct Node{
    ElemType data;
    struct Node *next;
}Node;

typedef struct Node * LinkList;
复制代码
  • 初始化单链表线性表
Status InitList(LinkList *L) {
    //产生头结点,并使用L指向此头结点
    *L = (LinkList)malloc(sizeof(Node));
    
    //存储空间分配失败
    if(*L == NULL) return ERROR;
    
    //将头结点的指针域置空
    (*L)->next = NULL;
    
    return OK;
}
复制代码
  • 单链表插入
/* 初始条件:顺序线性表L已存在,1≤i≤ListLength(L); 操做结果:在L中第i个位置以后插入新的数据元素e,L的长度加1; */
Status ListInsert(LinkList *L,int i,ElemType e){
    int j;
    LinkList p,s;
    p = *L;
    j = 1;
    
    //寻找第i-1个结点
    while (p && j < i) {
        p = p-> next;
        ++j;
    }
    
    //第i个元素不存在
    if(!p || j>i) return ERROR;
    
    //生成新结点s
    s = (LinkList)malloc(sizeof(Node));
    
    //将e赋值给s的数值域
    s->data = e;
    
    //将p的后继结点赋值给s的后继
    s->next = p->next;
    
    //将s赋值给p的后继
    p->next = s;
    
    return OK;
}
复制代码

注意:须要先将p的next赋值给s的next,而后在将p的next指向s,若是操做反了,会形成p的next也就是图中的Hank老师丢失,形成野指针。

  • 单链表取值
/* 初始条件: 顺序线性表L已存在,1≤i≤ListLength(L); 操做结果:用e返回L中第i个数据元素的值 */
Status GetElem(LinkList L,int i,ElemType *e){
    
    // j: 计数.
    int j;
    // 声明结点p;
    LinkList p;
    
    // 将结点p 指向链表L的第一个结点;
    p = L->next;
    // j计算=1;
    j = 1;
    
    
    // p不为空,且计算j不等于i,则循环继续
    while (p && j < i) {
        //p指向下一个结点
        p = p->next;
        ++j;
    }
    
    // 若是p为空或者j>i,则返回error
    if(!p || j > i) return ERROR;
    
    // e = p所指的结点的data
    *e = p->data;
    return OK;
}
复制代码
  • 单链表删除元素
/* 初始条件:顺序线性表L已存在,1≤i≤ListLength(L) 操做结果:删除L的第i个数据元素,并用e返回其值,L的长度减1 */

Status ListDelete(LinkList *L,int i,ElemType *e) {
    int j;
    LinkList p,q;
    p = (*L)->next;
    j = 1;
    
    // 查找第i-1个结点,p指向该结点
    while (p->next && j< (i-1)) {
        p = p->next;
        ++j;
    }
    
    // 当i>n 或者 i<1 时,删除位置不合理
    if (!(p->next) || (j > i-1)) return  ERROR;
    
    // q指向要删除的结点
    q = p->next;
    
    // 将q的后继赋值给p的后继
    p->next = q->next;
    
    // 将q结点中的数据给e
    *e = q->data;
    
    // 让系统回收此结点,释放内存;
    free(q);
    return OK;
}
复制代码
  • 将L重置为空表
Status ClearList(LinkList *L) {
    LinkList p,q;
     /* p指向第一个结点 */
    p = (*L)->next;          
  
   /* 没到表尾 */
    while(p) {
        q=p->next;
        free(p);
        p=q;
    }
    /* 头结点指针域为空 */
     (*L)->next=NULL;       
    return OK;
}
复制代码
  • 单链表前插入法
/* 随机产生n个元素值,创建带表头结点的单链线性表L(前插法)*/
void CreateListHead(LinkList *L, int n){
    
    LinkList p;
    
    // 创建1个带头结点的单链表
    *L = (LinkList)malloc(sizeof(Node));
    (*L)->next = NULL;
    
    // 循环前插入随机数据
    for(int i = 0; i < n;i++) {
        //生成新结点
        p = (LinkList)malloc(sizeof(Node));
       
        //i赋值给新结点的data
        p->data = i;
        
        // p->next = 头结点的L->next
        p->next = (*L)->next;
        
        //将结点P插入到头结点以后;
        (*L)->next = p;
    }
}
复制代码
  • 单链表后插入法

/* 随机产生n个元素值,创建带表头结点的单链线性表L(后插法)*/
void CreateListTail(LinkList *L, int n){
    
    LinkList p,r;
    // 创建1个带头结点的单链表
    *L = (LinkList)malloc(sizeof(Node));
    // r指向尾部的结点
    r = *L;
    
    for (int i=0; i<n; i++) {
        
        // 生成新结点
        p = (Node *)malloc(sizeof(Node));
        p->data = i;
        
        // 将表尾终端结点的指针指向新结点
        r->next = p;
        // 将当前的新结点定义为表尾终端结点
        r = p;
    }
    
    // 将尾指针的next = null
    r->next = NULL;
}
复制代码

单链表与顺序表的对比

  1. 存储方式:顺序表用一组连续的存储单元依次存储线性表的数据元素;而单链表用一组任意的存储单元存放线性表的数据元素。
  2. 时间性能:采用循序存储结构时查找的时间复杂度为O(1),插入和删除须要移动平均一半的数据元素,时间复杂度为O(n)。采用单链表存储结构的查找时间复杂度为O(n),插入和删除不须要移动元素,时间复杂度仅为O(1)。
  3. 空间性能:采用顺序存储结构时须要预先分配存储空间,分配空间过大会形成浪费,太小会形成问题。采用单链表存储结构时,可根据须要进行临时分配,不须要估计问题的规模大小,只要内存够就能够分配,还能够用于一些特殊状况,如一元多项的表示。
相关文章
相关标签/搜索