数据结构学习之路-第二章:线性表的顺序表示与实现

【 声明:版权所有,转载请标明出处,请勿用于商业用途。  联系信箱:[email protected]


前言:

经过了第一章绪论的热身,通过对三元组的实现,我们基本做好了热身,对于本书的编排也应该不会陌生了,那么现在进进入到了第二章的学习,线性表。

这本书用了三章来讲解线性表,第二章(链表),第三章(栈与队列),第四章()

由此可见线性表的重要性,可以说,这是整本数据结构的基础,如果线性表没有学好,那么由线性表延伸的树,森林,图等数据结构学起来也将会很困难。

那么,接下来,让我们进入线性结构的学习吧。

 

注:

本文仅代表博主本人的一些浅显的见解,欢迎大家评论学习,共同创造出一个良好的环境

对于一些问题,博主会尽量为大家解答,但是如果有疑问没有及时回答的,也希望其他热心人心帮忙解决,鄙人不胜感激。

 


线性结构


定义

线性结构是一个有序数据元素的集合。

常用的线性结构有:线性表,栈,队列,双队列,数组,串。

关于广义表,是一种非线性的数据结构。

常见的非线性结构有:二维数组,多维数组,广义表,树(二叉树等),图。

 

特征

1.集合中必存在唯一的一个"第一个元素"

2.集合中必存在唯一的一个"最后的元素"

3.除最后元素之外,其它数据元素均有唯一的"后继"

4.除第一元素之外,其它数据元素均有唯一的"前驱"

数据结构中线性结构指的是数据元素之间存在着“一对一”的线性关系的数据结构。

如(a1,a2,a3,.....,an,a1为第一个元素,an为最后一个元素,此集合即为一个线性结构的集合。

相对应于线性结构,非线性结构的逻辑特征是一个结点元素可能对应多个直接前驱和多个后继。

 

线性表


定义

线性表(亦作顺序表)是最基本、最简单、也是最常用的一种数据结构。线性表中数据元素之间的关系是一对一的关系,即除了第一个和最后一个数据元素之外,其它数据元素都是首尾相接的。线性表的逻辑结构简单,便于实现和操作。因此,线性表这种数据结构在实际应用中是广泛采用的一种数据结构。

 

顺序表的顺序表示和实现

1.存储结构

书本很严格详细的说了很多顺序结构的存储方式是怎么样的一种情况,其实说到底,就是说这个顺序表其实就跟数组一样,必须存储在一段连续的内存之中,不能随意分布。

//线性表的动态分配顺序存储结构
#define LIST_INT_SIZE 100  //线性表存储空间的初始分配量
#define LISTINCREMENT 10   //线性表存储空间的分配增量
typedef struct
{
    ElemType *elem;     //存储空间基址
    int length;         //当前长度
    int listsize;       //当前分配的存储容量(以sizeof(ElemType)为单位)
}SqList;

2.建立空表

在建立空表的时候,首先要给elem动态分配储存空间

代表线性表当前长度的length赋值为0

代表线性表最大容量的listsize赋值为指定的容量值

Status InitList(SqList *L)
{
    //操作结果:构造一个空的顺序线性表
    (*L).elem = (ElemType*)malloc(LIST_INT_SIZE*sizeof(ElemType));
    if(!(*L).elem) exit(OVERFLOW);        //存储分配失败
    (*L).length = 0;                      //空表长度为0
    (*L).listsize = LIST_INT_SIZE;        //初始存储容量
    return OK;
}

3.插入元素

对于一个顺序表而言,查询第i个元素很简单,方法与数组一样

那么对于插入一个元素,我们则需要通过移动插入位置与其之后的元素来实现

例如我要在第i个位置插入一个e,那么我们需要将原本线性表内的i~length-1位置的数都往后移动一个位置,然后再将e赋值到i位置

Status ListInsert(SqList *L,int i,ElemType e)
{
    //初始条件:顺序线性表L已存在,1≤i≤ListLength(L)+1
    //操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1
    ElemType *newbase,*p,*q;
    if(i<1||i>(*L).length+1) return ERROR;    //i值不合法
    if((*L).length>=(*L).listsize)            //当前内存已满,增加内存分配
    {
        newbase = (ElemType*)realloc((*L).elem,((*L).length+LISTINCREMENT)*sizeof(ElemType));
        if(!newbase) exit(OVERFLOW);     //分配失败
        (*L).elem = newbase;             //指向新的分配地址
        (*L).listsize+=LISTINCREMENT;    //储存容量增加
    }
    q = (*L).elem+i-1;                            //插入的位置
    for(p = (*L).elem+(*L).length-1; p>=q; p--)   //插入位置及之后的元素后移
        *(p+1) = *p;
    *q = e;            //插入元素
    (*L).length++;     //长度增加                      
    return OK;
}

4.删除元素

线性表内元素的删除与插入方式类似,对于删除的节点,我们只需要把其后的元素都往前移动一个位置即可

Status ListDelete(SqList *L,int i,ElemType *e)
{
    //初始条件:顺序线性表L已存在,1≤i≤ListLength(L)
    //操作结果:删除L的第i个数据元素,并用e返回其值,L的长度减1
    ElemType *newbase,*p,*q;
    if(i<1||i>(*L).length+1) return ERROR;   //i值不合法
    q = (*L).elem+i-1;                       //被删除元素的位置
    *e = *q;                                 //被删元素的值赋值给e
    p = (*L).elem+(*L).length-1;             //表尾指针
    for(; q<p; q++)                          //元素前移
        *q = *(q+1);
    (*L).length--;                           //长度减少
    return OK;
}

5.元素比较

这是一个标准的函数调用函数的写法,其方法与平常数据类型的变量类似,以函数名作为参数

Status LocateElem(SqList L,ElemType e,Status (*compare)(ElemType,ElemType))
{
    //初始条件:顺序线性表L已存在,compare()是数据元素判定函数(满足为1,否则为0)
    //操作结果:返回L中第1个与e满足关系compare()的数据元素的位序。若这样的数据元素不存在,则返回值为0。
    ElemType* p;
    int i = 1;        //i指向第一个元素的位序
    p = L.elem;       //p获得表头的地址
    while(i<=L.length && !(*compare)(*p++,e))   //寻找满足条件的数的位置
        i++;
    if(i<=L.length) return i;
    return 0;
}

6.双表归并

①.将所有在线性表Lb中但不在La中的数据元素插入到La中,一个个遍历Lb顺序表中的元素,取出并与La中的元素比较,如果La中不包含,则插入到La表的表尾

void Union(SqList *La,SqList Lb)
{
    //操作结果:将所有在线性表Lb中但不在La中的数据元素插入到La中
    ElemType e;
    int La_len,Lb_len;
    int i;
    La_len = (*La).length;   
    Lb_len = Lb.length;
    for(i = 1; i<=Lb.length; i++)
    {
        GetElem(Lb,i,&e);               //取得Lb表的第i个元素赋值给e
        if(!LocateElem(*La,e,equal))    //调用函数判断e元素不在La表中出现才加进去
            ListInsert(La,++La_len,e);
    }
}

②. 书中的这个顺序表合并必须是在La和Lb本来都是有序的前提之下,用线性的时间来合并出一个同样有序的顺序表Lc

关键就是比较La和Lb当前位置的值的大小,选小的放入Lc之中

void MergeList(SqList La,SqList Lb,SqList *Lc)
{
    //初始条件:已知顺序线性表La和Lb的元素按值非递减排列。
    //操作结果:归并La和Lb得到新的顺序线性表Lc,Lc的元素也按值非递减排列
    ElemType *pa,*pa_last,*pb,*pb_last,*pc;
    pa = La.elem;    //La表头指针
    pb = Lb.elem;    //Lb表头指针
    (*Lc).length = (*Lc).listsize = La.length+Lb.length;                     //得到Lc的长度
    pc = (*Lc).elem = (ElemType*)malloc((*Lc).listsize*sizeof(ElemType));    //给Lc分配储存容量
    if(!(*Lc).elem) exit(OVERFLOW);    //分配失败
    pa_last = La.elem+La.length-1;     //La表尾指针
    pb_last = Lb.elem+Lb.length-1;     //Lb表尾指针
    while(pa<=pa_last&&pb<=pb_last)    //La与Lb均未到达尾部
    {
        //归并过程
        if(*pa<=*pb)
            *pc++ = *pa++;
        else
            *pc++ = *pb++;
    }
    while(pa<=pa_last)    //Lb到达尾部而La还没到尾部
        *pc++ = *pa++;
    while(pb<=pb_last)    //La到达尾部而Lb还没到尾部
        *pc++ = *pb++;
}

7.其他操作

前面介绍的都是书本上比较详细描叙的几个实现操作,剩下的就是这些包括清空,测长度等等,具体实现起来也不难

Status DestroyList(SqList *L)
{
    //初始条件:顺序线性表L已存在。
    //操作结果:销毁顺序线性表L
    free((*L).elem);   //释放空间
    (*L).elem = NULL;
    (*L).length = 0;
    (*L).listsize = 0;
    return OK;
}

Status ClearList(SqList *L)
{
    //初始条件:顺序线性表L已存在。
    //操作结果:将L重置为空表
    (*L).length = 0;
    return OK;
}

Status ListEmpty(SqList L)
{
    //初始条件:顺序线性表L已存在。
    //操作结果:若L为空表,则返回TRUE,否则返回FALSE
    return L.length==0;
}

int ListLength(SqList L)
{
    //初始条件:顺序线性表L已存在。
    //操作结果:返回L中数据元素个数
    return L.length;
}

Status GetElem(SqList L,int i,ElemType *e)
{
    //初始条件:顺序线性表L已存在,1≤i≤ListLength(L)
    //操作结果:用e返回L中第i个数据元素的值
    if(i<1 || i>L.length) exit(ERROR);
    (*e) = *(L.elem+i-1);
    return OK;
}

Status PriorElem(SqList L,ElemType cur_e,ElemType *pre_e)
{
    //初始条件:顺序线性表L已存在
    //操作结果:若cur_e是L的数据元素,且不是第一个,则用pre_e返回它的前驱,否则操作失败,pre_e无定义
    ElemType* p = L.elem+1;          //跳过第一个元素,从第二个元素开始
    int i = 2;
    while(i<=L.length && *p!=cur_e)  //找到与cur_e相同的元素地址
    {
        i++;
        p++;
    }
    if(i>L.length) return INFEASIBLE;   //没有找到
    *pre_e = *(--p);    //返回其前驱
    return OK;
}

Status NextElem(SqList L,ElemType cur_e,ElemType *next_e)
{
    //初始条件:顺序线性表L已存在
    //操作结果:若cur_e是L的数据元素,且不是最后一个,则用next_e返回它的后继,否则操作失败,next_e无定义
    ElemType* p = L.elem;
    int i = 1;
    while(i<L.length && *p!=cur_e)     //找到与cur_e相同的元素地址
    {
        i++;
        p++;
    }
    if(i==L.length) return INFEASIBLE; //没有找到
    *next_e = *(++p);    //返回其后继
    return OK;
}

Status ListTraverse(SqList L,void (*vi)(ElemType*))
{
    //初始条件:顺序线性表L已存在
    //操作结果:依次对L的每个数据元素调用函数vi()。一旦vi()失败,则操作失败,vi()的形参加'&',表明可通过调用vi()改变元素的值
    ElemType* p = L.elem;
    int i;
    for(i = 1; i<=L.length; i++)
        vi(p++);
    printf("\n");
    return OK;
}

8.具体测试

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <stack>
#include <queue>
#include <map>
#include <set>
#include <vector>
#include <math.h>
#include <bitset>
#include <algorithm>
#include <climits>
#include <ctype.h>
#include <malloc.h>
#include <limits.h>
#include <stdlib.h>
#include <io.h>
#include <process.h>
using namespace std;

#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1



typedef int ElemType;
typedef int Status;

//线性表的动态分配顺序存储结构
#define LIST_INT_SIZE 100  //线性表存储空间的初始分配量
#define LISTINCREMENT 10   //线性表存储空间的分配增量
typedef struct
{
    ElemType* elem;     //存储空间基址
    int length;         //当前长度
    int listsize;       //当前分配的存储容量(以sizeof(ElemType)为单位)
} SqList;

Status InitList(SqList *L)
{
    //操作结果:构造一个空的顺序线性表
    (*L).elem = (ElemType*)malloc(LIST_INT_SIZE*sizeof(ElemType));
    if(!(*L).elem) exit(OVERFLOW);        //存储分配失败
    (*L).length = 0;                      //空表长度为0
    (*L).listsize = LIST_INT_SIZE;        //初始存储容量
    return OK;
}

Status DestroyList(SqList *L)
{
    //初始条件:顺序线性表L已存在。
    //操作结果:销毁顺序线性表L
    free((*L).elem);   //释放空间
    (*L).elem = NULL;
    (*L).length = 0;
    (*L).listsize = 0;
    return OK;
}

Status ClearList(SqList *L)
{
    //初始条件:顺序线性表L已存在。
    //操作结果:将L重置为空表
    (*L).length = 0;
    return OK;
}

Status ListEmpty(SqList L)
{
    //初始条件:顺序线性表L已存在。
    //操作结果:若L为空表,则返回TRUE,否则返回FALSE
    return L.length==0;
}

int ListLength(SqList L)
{
    //初始条件:顺序线性表L已存在。
    //操作结果:返回L中数据元素个数
    return L.length;
}

Status GetElem(SqList L,int i,ElemType *e)
{
    //初始条件:顺序线性表L已存在,1≤i≤ListLength(L)
    //操作结果:用e返回L中第i个数据元素的值
    if(i<1 || i>L.length) exit(ERROR);
    (*e) = *(L.elem+i-1);
    return OK;
}

Status PriorElem(SqList L,ElemType cur_e,ElemType *pre_e)
{
    //初始条件:顺序线性表L已存在
    //操作结果:若cur_e是L的数据元素,且不是第一个,则用pre_e返回它的前驱,否则操作失败,pre_e无定义
    ElemType* p = L.elem+1;          //跳过第一个元素,从第二个元素开始
    int i = 2;
    while(i<=L.length && *p!=cur_e)  //找到与cur_e相同的元素地址
    {
        i++;
        p++;
    }
    if(i>L.length) return INFEASIBLE;   //没有找到
    *pre_e = *(--p);    //返回其前驱
    return OK;
}

Status NextElem(SqList L,ElemType cur_e,ElemType *next_e)
{
    //初始条件:顺序线性表L已存在
    //操作结果:若cur_e是L的数据元素,且不是最后一个,则用next_e返回它的后继,否则操作失败,next_e无定义
    ElemType* p = L.elem;
    int i = 1;
    while(i<L.length && *p!=cur_e)     //找到与cur_e相同的元素地址
    {
        i++;
        p++;
    }
    if(i==L.length) return INFEASIBLE; //没有找到
    *next_e = *(++p);    //返回其后继
    return OK;
}

Status ListTraverse(SqList L,void (*vi)(ElemType*))
{
    //初始条件:顺序线性表L已存在
    //操作结果:依次对L的每个数据元素调用函数vi()。一旦vi()失败,则操作失败,vi()的形参加'&',表明可通过调用vi()改变元素的值
    ElemType* p = L.elem;
    int i;
    for(i = 1; i<=L.length; i++)
        vi(p++);
    printf("\n");
    return OK;
}

Status ListInsert(SqList *L,int i,ElemType e)
{
    //初始条件:顺序线性表L已存在,1≤i≤ListLength(L)+1
    //操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1
    ElemType *newbase,*p,*q;
    if(i<1||i>(*L).length+1) return ERROR;    //i值不合法
    if((*L).length>=(*L).listsize)            //当前内存已满,增加内存分配
    {
        newbase = (ElemType*)realloc((*L).elem,((*L).length+LISTINCREMENT)*sizeof(ElemType));
        if(!newbase) exit(OVERFLOW);     //分配失败
        (*L).elem = newbase;             //指向新的分配地址
        (*L).listsize+=LISTINCREMENT;    //储存容量增加
    }
    q = (*L).elem+i-1;                            //插入的位置
    for(p = (*L).elem+(*L).length-1; p>=q; p--)   //插入位置及之后的元素后移
        *(p+1) = *p;
    *q = e;            //插入元素
    (*L).length++;     //长度增加
    return OK;
}

Status ListDelete(SqList *L,int i,ElemType *e)
{
    //初始条件:顺序线性表L已存在,1≤i≤ListLength(L)
    //操作结果:删除L的第i个数据元素,并用e返回其值,L的长度减1
    ElemType *newbase,*p,*q;
    if(i<1||i>(*L).length+1) return ERROR;   //i值不合法
    q = (*L).elem+i-1;                       //被删除元素的位置
    *e = *q;                                 //被删元素的值赋值给e
    p = (*L).elem+(*L).length-1;             //表尾指针
    for(; q<p; q++)                          //元素前移
        *q = *(q+1);
    (*L).length--;                           //长度减少
    return OK;
}

Status comp(ElemType a,ElemType b)
{
    //操作结果:判断a是否等于b的平方,是则返回1,否则返回0
    if(a == b*b)
        return TRUE;
    return FALSE;
}

Status equal(ElemType a,ElemType b)
{
    return a==b;
}

void Print(ElemType *e)
{
    printf("%d ",*e);
}

Status LocateElem(SqList L,ElemType e,Status (*compare)(ElemType,ElemType))
{
    //初始条件:顺序线性表L已存在,compare()是数据元素判定函数(满足为1,否则为0)
    //操作结果:返回L中第1个与e满足关系compare()的数据元素的位序。若这样的数据元素不存在,则返回值为0。
    ElemType* p;
    int i = 1;        //i指向第一个元素的位序
    p = L.elem;       //p获得表头的地址
    while(i<=L.length && !(*compare)(*p++,e))   //寻找满足条件的数的位置
        i++;
    if(i<=L.length) return i;
    return 0;
}

void Union(SqList *La,SqList Lb)
{
    //操作结果:将所有在线性表Lb中但不在La中的数据元素插入到La中
    ElemType e;
    int La_len,Lb_len;
    int i;
    La_len = (*La).length;   
    Lb_len = Lb.length;
    for(i = 1; i<=Lb.length; i++)
    {
        GetElem(Lb,i,&e);               //取得Lb表的第i个元素赋值给e
        if(!LocateElem(*La,e,equal))    //调用函数判断e元素不在La表中出现才加进去
            ListInsert(La,++La_len,e);
    }
}

void MergeList(SqList La,SqList Lb,SqList *Lc)
{
    //初始条件:已知顺序线性表La和Lb的元素按值非递减排列。
    //操作结果:归并La和Lb得到新的顺序线性表Lc,Lc的元素也按值非递减排列
    ElemType *pa,*pa_last,*pb,*pb_last,*pc;
    pa = La.elem;    //La表头指针
    pb = Lb.elem;    //Lb表头指针
    (*Lc).length = (*Lc).listsize = La.length+Lb.length;                     //得到Lc的长度
    pc = (*Lc).elem = (ElemType*)malloc((*Lc).listsize*sizeof(ElemType));    //给Lc分配储存容量
    if(!(*Lc).elem) exit(OVERFLOW);    //分配失败
    pa_last = La.elem+La.length-1;     //La表尾指针
    pb_last = Lb.elem+Lb.length-1;     //Lb表尾指针
    while(pa<=pa_last&&pb<=pb_last)    //La与Lb均未到达尾部
    {
        //归并过程
        if(*pa<=*pb)
            *pc++ = *pa++;
        else
            *pc++ = *pb++;
    }
    while(pa<=pa_last)    //Lb到达尾部而La还没到尾部
        *pc++ = *pa++;
    while(pb<=pb_last)    //La到达尾部而Lb还没到尾部
        *pc++ = *pb++;
}

int main()
{
    SqList L,La,Lb,Lc;
    Status i;
    ElemType e,e0;
    int n,k;
    
    //初始化线性表
    i = InitList(&L);
    if(i)
        printf("初始化成功,此时线性表的长度为length=%d,线性表的最大容量为listsize=%d\n",L.length,L.listsize);
    else
    {
        printf("初始化失败\n");
        return 0;
    }
    puts("");

    //插入操作
    printf("请输入想要插入线性表的元素个数:");
    scanf("%d",&n);
    printf("请输入插入线性表的元素:");
    for(k = 1; k<=n; k++)
    {
        scanf("%d",&e);
        ListInsert(&L,k,e);
    }
    printf("此时线性表内的元素是:");
    ListTraverse(L,Print);

    //删除操作
    printf("请输入要删除元素的位置:");
    scanf("%d",&n);
    ListDelete(&L,n,&e);
    printf("删除的元素为:%d\n",e);
    printf("此时线性表内的元素是:");
    ListTraverse(L,Print);

    //元素比较
    printf("寻找线性表内是否有(1~5)的平方:\n");
    for(n = 1; n<=5; n++)
    {
        i = LocateElem(L,n,comp);
        if(i)
            printf("线性表内第%d个元素是%d的平方\n",i,n);
        else
            printf("线性表内没有元素是%d的平方\n",n);
    }
    puts("");

    //线性表合并
    InitList(&La);
    InitList(&Lb);
    for(n = 1; n<=5; n++)
        ListInsert(&La,n,n);
    for(n = 1; n<=5; n++)
        ListInsert(&Lb,n,n*n);
    printf("线性表La内的元素是:\n");
    ListTraverse(La,Print);
    printf("线性表Lb内的元素是:\n");
    ListTraverse(Lb,Print);
    MergeList(La,Lb,&Lc);
    Union(&La,Lb);
    printf("MergeList合并La,Lb之后,Lc内的元素是:\n");
    ListTraverse(Lc,Print);
    printf("Union合并La,Lb之后,La内的元素是:\n");
    ListTraverse(La,Print);

    //其他函数检测
    i = ListEmpty(Lb);
    printf("Lb是否为空:%d(1:是 0:否)\n",i);
    ClearList(&Lb);
    i = ListEmpty(Lb);
    printf("清空Lb之后,Lb是否为空:%d(1:是 0:否)\n",i);
    i = ListLength(La);
    printf("La的长度为:%d\n",i);
    GetElem(La,3,&e);
    printf("La的第3个元素为:%d\n",e);
    printf("对La表前驱与后继的检测:\n");
    for(k = 1; k<=2; k++)
    {
        GetElem(La,k,&e0);
        i = PriorElem(La,e0,&e);
        if(i==INFEASIBLE)
            printf("元素%d无前驱\n",e0);
        else
            printf("元素%d的前驱为%d\n",e0,e);
    }
    for(k = ListLength(La)-1; k<=ListLength(La); k++)
    {
        GetElem(La,k,&e0);
        i = NextElem(La,e0,&e);
        if(i==INFEASIBLE)
            printf("元素%d无后继\n",e0);
        else
            printf("元素%d的后继为%d\n",e0,e);
    }
    i = DestroyList(&La);
    if(i)
        printf("La已经被成功销毁\n");

    return 0;
}


总结:

花了不少的精力来敲这些代码,虽然基本都是按着书上的模板来敲的,但是这代码的长度也决定了这不是一项很容易完成的工程。

这一节主要讲了顺序表的操作与实现,不知道看了我的博客之后对大家是否有所帮助呢?

总体而言,顺序表应该是线性表里面实现起来最简单的了,毕竟它的一些操作与数组几乎是相差无几的,而只要有一定变成基础的同学,对于数组还是不会陌生的。

好了,这次就到这里结束了,下次我们将进行链表的学习。


你可能感兴趣的:(数据结构,线性表)