线性表

1.1 定义:

List,由零个或多个数据特性相同的元素构成的有限序列。个数 n 称为线性表的长度,n=0 的时候称为空表。比如说,十二星座就是一个线性表,它的“第一个”和“最后一个”元素都是唯一的,并且中间的元素均只有一个前驱、一个后继。

1.2 抽象数据类型

ADT List{
    Data:.......
    Operation:
        InitList(&L): 初始化,建立一个空的线性表L
        ClearList(&L): 清空线性表L
        GetElem(L,i,&e): 将L中第i个位置元素值返回给e
        LocateElem(L,e): 在L中查找与e相等的元素
        ListInsert(&L,i,e): 在L中第i个位置插入新元素e
        ListDelete(&L,i,&e): 删除L中第i个位置元素,并用e返回其值
        ListLength(L): 返回L长度
} ADT List

线性表的顺序存储结构 / 顺序表

2.1 定义:

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

各个元素的地址之间构成等差数列,因此,知道了某一个元素的地址,就可以随时推导出其它元素的地址,也就是说不管取出还是存入哪一个元素,花费的时间都一样,是一个常数(O(1)),这种特性的存储结构就叫随机存取结构

一维数组可以实现线性表的顺序存储结构,不过要注意数组下标从0开始,而说线性表的时候说的是位序,是从1开始的。

2.2 基本操作

(1) 初始化:

首先定义线性表顺序存储结构:

typedef struct Table{
    int * head;//声明了一个名为head的长度不确定的数组,也叫“动态数组”
    int length;//记录当前顺序表的长度
    int size;//记录顺序表分配的存储容量
}table;

第一个操作是初始化,包括:给head申请足够大小的物理空间;给size和length赋初值:

#define Size 5 //对Size进行宏定义,表示顺序表申请空间的大小
table initTable(){
    table t;
    t.head=(int*)malloc(Size*sizeof(int));//构造一个空的顺序表,动态申请存储空间
    if (!t.head) //如果申请失败,作出提示并直接退出程序
    {
        printf("初始化失败");
        exit(0);
    }
    t.length=0;//空表的长度初始化为0
    t.size=Size;//空表的初始存储空间为Size
    return t;
}

(2) 取值:
找某一个位序的元素,只需要判断位序的合法性,之后返回数组[位序-1]即可。

(3) 查找/修改:
遍历数组元素,与目标元素比较,相等则返回。如果要修改,重新赋值即可。

(4) 删除:
找到目标元素,让后续元素整体前移一个位置,之后长度减一即可。

table delTable(table t,int add){
    if (add>t.length || add<1) {
        printf("被删除元素的位置有误");
        exit(0);
    }
    //删除操作
    for (int i=add; i<t.length; i++) {
        t.head[i-1]=t.head[i];
    }
    t.length--;
    return t;
}

比如说要删除5个元素中的第三个,那么 add 就是 3,而 t.head[3] 就是第四个元素,从这个元素开始依次向前移动并覆盖前面的元素。

(5) 插入:
找到目标位置,将该位置对应元素以及后续元素整体后移一个位置,之后长度加一即可。

//插入函数,其中,elem为插入的元素,add为插入到顺序表的位置
table addTable(table t,int elem,int add)
{
    //判断插入本身是否存在问题
    if (add>t.length+1||add<1) {
        printf("插入位置有问题");
        return t;
    }
    //线性表长度等于数组长度,没有多余空间容纳新插入的元素,此时需要申请
    if (t.length==t.size) {
        t.head=(int *)realloc(t.head, (t.size+1)*sizeof(int));
        if (!t.head) {
            printf("存储分配失败");
            return t;
        }
        t.size+=1;
    } 
    for (int i=t.length-1; i>=add-1; i--) {
        t.head[i+1]=t.head[i];
    }
    t.head[add-1]=elem;
    t.length++;
    return t;
}

  • 首先要注意范围的判断,add 作为目标位置,至少也得等于1,此时是插在最前面;同时,add 可以等于 length+1,此时是插在最后面,但不能比 length+1 更大了,否则会出现空隙。
  • 接着要注意是从后往前遍历的,因为如果是从前往后的话就会发生覆盖。
  • 可以不做“目标位置是否在表尾”的判断,这样会直接绕过for循环,执行赋值。

这里可以发现,线性表的插入和删除元素操作往往需要移动大量的元素。