顺序表

354 阅读5分钟

线性表的定义和特点

定义:线性表是具有相同特性的数据结构元素(由n[n>=0]个数据元素(结点)a1,a2……an组成的)的一个有限序列。

  • 其中数据原始的个数n定义为表的长度
  • 当n = 0时称为空表
  • 将非空线性表(n > 0)记作:(a1,a2……an)
  • 这里的数据袁术ai只是一个丑行的符号其具体含义在不同情况下可以不同
  • 同一线性表的原始必定具有相同特性,数据元素之间是线性关系

线性表类型定义

抽象数据类型线性表的定义: image.png 有以下操作:

Ooeration:
    InitList(*L/*&L*)//初始化操作,建立一个新的线性表L
    GetLength(Sqlist L);//返回线性表的长度
    ListEmpty(L)//线性表已存在,若线性表为空表,则返回TURE,否则返回FALSE
    DestroyList(*L/*&L*)//线性表已存在,销毁线性表
    ClearList(*L)//线性表已存在,清空线性表(置为空表)
    GetElem(L,i,&e)//线性表已存在,用e返回表中第i个元素
    LocateElem(L,e,cmp())//线性表已存在,cmp()是数据元素的判定函数(= < >),返回L中第一个满足cmp()的数据元素的序号。若不此女子这样的数据元素则返回0
    PriorElem(L,cur,pre)//线性表已存在,若cur是L的数据元素且不是第一个,则用pre返回cur的前驱,否则返回失败(pre无意义)
    NextElem(L,cur,next)//线性表已存在。若cur是L的数据元素且不是最后一个,则用next返回cur的前驱,否则返回失败(next无意义)
    ListInsert(*L/*&L*,i,e)//线性表已存在,且1<=i<=len_s+1。在L的第i个位置之前插入新的数据元素e,L的长的(len_s)加一
    ListDelete(*L/*&L*,i,&e)//线性表已存在,且1<=i<=len_s。删除L的第i个数据元素,并用e返回其值,L长度减一
    ListTraverse(L,visited())//线性表已存在。一次对线性表中每个元素用visited()[遍历之后,输出,修改之类~~]
    

发现上面各函数的传入的参数各有不同,其中有一点差别的是表头的传入,一些是L,一些是L,那么什么时候传入什么呢,其实如果函数会改变指针L的值(改变表的地址),而你希望函数结束调用后保存L的值,那你就要用LinkList L,这样,向函数传递的就是指针的地址,结束调用后,自然就可以去改变指针的值; 而如果函数只会修改指针所指向的内容,而不会更改指针的值,那么用LinkList L就行了; 举个例子:清空线性表,函数调用完毕后,链表被清空,L会指向一个空的链表,即会改变指针的值,所以要用*L,而插入元素这种,不会改变链表的地址,所以传入L就好!!

线性表的储存结构

计算机中,线性表有两种基本的粗存结构:顺序储存结构和链式储存结构

线性表的顺序表示和实现

顺序储存定义把逻辑上相邻的数据元素储存在物理上相邻的储存单元中的储存结构。

如下顺序储存结构占据一片连续的空间[地址连续](知道某个元素的储存位置就可以计算其他元素的储存位置):

image.png 线性表中第一个数据元素a1的储存位置,作为线性表的起始位置或基地址。

顺序表是利用元素的储存位置表示线性表中相邻元素之前的前后关系,即顺序表(线性表)的逻辑结构和存储结构一致。我们用一维数组表示顺序表,但是数组长度是不能动态定义的(数组长度不等于线性表长度),我们就用一个变量表示顺序表的长度属性: image.png 然后我们来进行函数的实现:

函数

我们提前定义一下Status为函数的类型(返回值),ElemType是表中元素的值。还有一些其他定义下面再说

//初始化
Status InitList(Sqlist *L/*Sqlist &L*){
    L->elem = (ElemType *)malloc(MAXSIZE * sizeof(ElemType));//得到全部空间
    if(!L->elem) return false;//分配空间失败
    L->len = 0;//线性表长度为0
    return OK;//创建成功
}

 }
//删除表
void DestroyList(Sqlist *L/*Sqlist &L*){
    if(L->elem) free(L->elem);//释放空间
    L->elem = NULL;
    L->len = 0;//
}
//清空表
void ClearList(Sqlist *L/*Sqlist &L*){
    L->len = 0;//直接清空
}
//得到线性表长度
int GetLength(Sqlist L) {
    return L.len;
}
//判断是否空表
int listEmpty(Sqlist L) {
    if(L.len == 0) return true;
    else return false;
}
////根据位置i得到对应位置数据元素的值
int GetElem(Sqlist L,int i, ElemType &e) {
    if(i < 1 || i > L.len) return false;
    e = L.elem[i - 1];//第i-1个位置储存这第i个数据,从0开始
    return true;
}

//按值查找(找相同的,查找成功返回序号,失败返回false)
int LocateElem(Sqlist L, ElemType e) {
    for(int i = 0; i < L.len; i++)
        if(L.elem[i] == e) return i+1;//查找成功,返回序号

    return false;//查找失败
}

平均查找长度 ASL(Average Search Length):为了确定记录在表中的位置,需要与给定值进行比较关键字的个数的期望值叫做查找算法的平均查找长度 ASL=ΣPiCi[i个元素被查找的概率;找到第i个记录需要比较的次数]ASL = ΣPiCi[第i个元素被查找的概率;找到第i个记录需要比较的次数]

插入函数,插入到哪个位置,一个表有N个元素可以插入到0~N+1的位置。若是元素已经满了,那就溢出了。 image.png

/*
 * 1.判断插入位置i是否合法
 * 2.判断顺序表的储存空间是否已满,若已满返回ERROR
 * 3.将第n至第i位的元素一次向后移动一个位置,空出第i个位置
 * 4.将要插入的新元素e放入第i个位置
 * 5.表长加一,返回OK
 */
int ListInsert(Sqlist *L/*Sqlist &L*/,int i,ElemType e) {
    if(i < 1 || i > L->len + 1) return false;
    if(L->len == MAXSIZE) return  false;
    for(int j = L->len - 1; j >= i - 1; j--){
        L->elem[j + 1] = L->elem[j];//向后移
    }
    L->elem[i - 1] = e;
    L->len++;
    return true;
}
//删除元素,删除位置,最前面,中间,最后
/*
 * 1.判断删除位置i是否合法(1~n)
 * 2.将欲删除的元素保留在e中
 * 3.将第i+1至第n位元素一次向前移动一个位置
 * 4.表长减一
 */
int ListDelete(Sqlist *L/*Sqlist &L*,int i,ElemType *e){
    if(i < 1 || i > L->len) return false;
    *e = L->elem[i - 1];//删除的值给到e
    for(int j = i; j <= L->len - 1; j++){
        L->elem[j - 1] = L->elem[j];//依次前移
    }
    L->len--;
    return true;
}
//删除,插入的复杂度都是O(n);

还有其他的函数比如遍历就不写了。

某些顺序`表操作

顺序表合并`

void unionList(Sqlist &La, Sqlist Lb) {
    int La_len = GetLength(La);
    int Lb_len = GetLength(Lb);
    for (int i = 1; i <= Lb_len; ++i) {//查看链表b中是否存在链表a中的元素
        //因为查找的元素是序号,不是下标,从1开始
        ElemType e;
        GetElem(Lb,i,&e);//得到b链表中的第i个值
        if (!LocateElem(La,e)) { ListInsert(&La, ++La_len, e); }//a当最找到了e,那么加到a表尾,表长加一
    }
}

有序表的合并: 两种都可以用,原理都相同,第二中再函数内申请了Lc的空间

void MergeList_One(Sqlist La, Sqlist Lb, Sqlist &Lc){
    int i=0,j=0,k=0;//分别表示三个表到了哪个位置
    while((i < La.len) && (j < Lb.len)){
        if(La.elem[i] <= Lb.elem[j])
            Lc.elem[k++]=La.elem[i++];
        else
            Lc.elem[k++] = Lb.elem[j++];
    }
    while(i < La.len){
        Lc.elem[k++] = La.elem[i++];
    }
    while(j < Lb.len){
        Lc.elem[k++]=Lb.elem[j++];
    }
    Lc.len = k;
}
void MergeList_Two(Sqlist La, Sqlist Lb, Sqlist &Lc){
    ElemType *pa = La.elem;
    ElemType *pb = Lb.elem;//pa,pa分别指向La,Lb第一个元素
    Lc.len = La.len + Lb.len;//Lc的长度
    Lc.elem = (ElemType *)malloc(MAXSIZE * sizeof(ElemType));//得到全部空间
    ElemType *pc = Lc.elem;
    ElemType *paLast = La.elem + La.len - 1;
    ElemType *pbLast = Lb.elem + Lb.len - 1;//指向最后一个元素的指针
    while (pa <= paLast && pb <= pbLast){
        //依次拿比较小的
        if(*pa <= *pb)
            *pc++ = *pa++;
        else
            *pc++ = *pb++;
    }
    //La还没拿完
    while (pa <= paLast)
        *pc++ = *pa++;
    //Lb还没拿完
    while (pb <= pbLast)
        *pc++ = *pb++;
}

代码会传到我的github上,还有一个引用版本的,更简单

特点

优点:

  • 存储密度大(节点本身所占存储量/结点结构所占存储量)
  • 可以随机存取表中任意元素 缺点:
  • 在插入,删除某一元素时,需要移动大量元素
  • 浪费储存空间
  • 属于静态储存形式,数据元素的个数不能自由扩充

客服缺点,就使用链表。