美文网首页
数据结构与算法学习 (02)线性表

数据结构与算法学习 (02)线性表

作者: 暱稱已被使用 | 来源:发表于2020-04-01 18:22 被阅读0次

    1.1线性表的概念

    满足数据元素不同,但是在同一个线性表中的元素必定具有相同的特点,即属于同一数据对象, 相邻数据元素之间存在这个序偶关系. 诸如此类由(n>=0)个数据特性相同的元素构成的有限序列称为"线性表"。
    即将具有“一对一”关系的数据“线性”地存储到物理空间中,这种存储结构就称为线性存储结构(简称线性表)。

    线性表中的元素的个数n定义为线性表的长度,如果n = 0则称为空表。

    使用线性表存储的数据,如同向数组中存储数据那样,要求数据类型必须一致,也就是说,线性表存储的数据,要么全不都是整形,要么全部都是字符串。一半是整形,另一半是字符串的一组数据无法使用线性表存储。
    非空的线性表和线性结构具有以下特点:
    存在唯一的一个被称作"第一个"的数据元素
    存在唯一的一个呗称作"最后一个"的数据元素
    除了第一个之外,结构中的每个数据元素均有一个前驱
    除了最后一个之外,结构中的每个数据元素都有一个后继.

    1.2线性表的顺序存储实现

    1定义

    #define MAXSIZE 100
    #define OK 1
    #define ERROR 0
    #define TRUE 1
    #define FALSE 0
    
    /* ElemType类型根据实际情况而定,这里假设为int */
    typedef int ElemType;
    /* Status是函数的类型,其值是函数结果状态代码,如OK等 */
    typedef int Status;
    
    /*线性结构使用顺序表的方式存储*/
    
    //顺序表结构设计
    typedef struct {
        ElemType *data;//指向一块连续的内存空间来存储数据
        int length;//表的长度
    }Sqlist;
    
    

    2.顺序表的初始化
    为顺序表L动态分配一个预定义大小的数组空间,使elem 指向这段空间的基地址;
    将表的当前长度设置为0;

    Status InitList(Sqlist *L){
        //为顺序表分配一个大小为MAXSIZE 的数组空间
        L->data =  malloc(sizeof(ElemType) * MAXSIZE);
        //存储分配失败退出
        if(!L->data) exit(ERROR);
        //空表长度为0
        L->length = 0;
        return OK;
    }
    
    int main(int argc, const char * argv[]) {
        // insert code here...
        printf("Hello, Data Structure!\n");
        
        Sqlist L;
        Sqlist Lb;
        ElemType e;
        Status iStatus;
        int j,k;
        
        iStatus = InitList(&L);
        printf("初始化L后: L.Length = %d\n", L.length);
        
        
        return 0;
    }
    

    3.顺序表的插入
    判断插入位置i是否合法(i值的合法范围1<=i<n+1),若不合法则返回ERROR;
    判断顺序表的存储空间是否已满,若满则返回ERROR;
    将第n个至第i个位置的元素依次向后移动一个位置,空出第i个位置(i = n+1)时无需要移动;
    将要插入的新元素e放入第i个位置;
    表长累积1.

    //1.2 顺序表的插入
    /*
     初始条件:顺序线性表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;
        
    }
    int main(int argc, const char * argv[]) {
        ...
       //1.2 顺序表数据插入
        for(int j=1; j <= 5;j++){
            iStatus = ListInsert(&L, 1, j);
        }
        printf("插入数据L长度: %d\n",L.length);
        
      }
    
    

    4.顺序表的取值

    //1.3 顺序表的取值
    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 main(int argc, const char * argv[]) {
        ...
       //1.3 顺序表取值
        GetElem(L, 5, &e);
        printf("顺序表L第5个元素的值为:%d\n",e);
        
      }
    

    5.顺序表的删除

    //1.4 顺序表删除
    /*
     初始条件:顺序线性表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;
        
    }
    int main(int argc, const char * argv[]) {
        ...
       //1.4 顺序表删除第2个元素
        ListDelete(&L, 2);
        printf("顺序表删除第%d元素,长度为%d\n",2,L.length);
        
      }
    

    6.清空顺序表

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

    7.判断顺序表清空

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

    8.获取顺序表长度

    //1.7 获取顺序表长度ListEmpty元素个数 */
    int ListLength(Sqlist L)
    {
        return L.length;
    }
    

    9.输出顺序表

    //1.8 顺序输出List
    /* 初始条件:顺序线性表L已存在 */
    /* 操作结果:依次对L的每个数据元素输出 */
    Status TraverseList(Sqlist L)
    {
        int i;
        for(i=0;i<L.length;i++)
            printf("%d\n",L.data[i]);
        printf("\n");
        return OK;
    }
    

    10.查找元素

    //1.9 顺序表查找元素并返回位置
    /* 初始条件:顺序线性表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;
    }
    

    总结:顺序表的优缺点。

    (1)优点:无须关心表中元素之间的关系,所以不用增加额外的存储空间;可以快速地取表中任意位置的元素。

    (2)缺点:插入和删除操作需要移动大量元素。使用前需事先分配好内存空间,当线性表长度变化较大时,难以确定存储空间的容量。分配空间过大会造成存储空间的巨大浪费,分配的空间过小,难以适应问题的需求。

    1.3线性表的链式存储实现

    1.3.0.定义

    #define ERROR 0
    #define TRUE 1
    #define FALSE 0
    #define OK 1
    
    #define MAXSIZE 20 /* 存储空间初始分配量 */
    
    typedef int Status;/* Status是函数的类型,其值是函数结果状态代码,如OK等 */
    typedef int ElemType;/* ElemType类型根据实际情况而定,这里假设为int */
    
    //定义结点
    typedef struct Node{
        ElemType data;
        struct Node *next;
    }Node;
    
    typedef struct Node * LinkList;
    
    

    1.、初始化:线性表在链式存储时需要额外的指针域来记录结点间的关系;初始化需要注意的是我们常常在首元结点前添加头结点。这样可以便于操作首元结点,也便于空表和非空表的统一操作。

    //2.1 初始化单链表线性表
    Status InitList(LinkList *L){
        
        //产生头结点,并使用L指向此头结点
        *L = (LinkList)malloc(sizeof(Node));
        //存储空间分配失败
        if(*L == NULL) return ERROR;
        //将头结点的指针域置空
        (*L)->next = NULL;
        
        return OK;
    }
    
    

    2.插入:

    //2.2 单链表插入
    /*
     初始条件:顺序线性表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;
    }
    
    

    3.取值

    //2.3 单链表取值
    /*
     初始条件: 顺序线性表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;
        
        
    }
    

    4.删除元素

    //2.4 单链表删除元素
    /*
     初始条件:顺序线性表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;
    }
    

    5.输出元素

    /* 初始条件:顺序线性表L已存在 */
    /* 操作结果:依次对L的每个数据元素输出 */
    Status ListTraverse(LinkList L)
    {
        LinkList p=L->next;
        while(p)
        {
            printf("%d\n",p->data);
            p=p->next;
        }
        printf("\n");
        return OK;
    }
    

    6.清空

    /* 初始条件:顺序线性表L已存在。操作结果:将L重置为空表 */
    Status ClearList(LinkList *L)
    {
        LinkList p,q;
        p=(*L)->next;           /*  p指向第一个结点 */
        while(p)                /*  没到表尾 */
        {
            q=p->next;
            free(p);
            p=q;
        }
        (*L)->next=NULL;        /* 头结点指针域为空 */
        return OK;
    }
    
    1. 前插法
    //3.1 单链表前插入法
    /* 随机产生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;
            
        }
    }
    

    8.后插法

    //3.2 单链表后插入法
    /* 随机产生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;
        
    }
    

    9.调用

    int main(int argc, const char * argv[]) {
        // insert code here...
        printf("Hello, World!\n");
        
        Status iStatus;
        LinkList L1,L;
        struct Node *L2;
        ElemType e;
        
    //    L1 =(LinkList) malloc(sizeof(Node));
    //    L2 =(LinkList) malloc(sizeof(Node));
    //
    //    L1->data = 1;
    //    L2->data = 2;
    //    printf("L1.data=%d,L2.data=%d\n",L1->data,L2->data);
        
        //2.1 单链表初始化
        iStatus = InitList(&L);
        printf("L 是否初始化成功?(0:失败,1:成功) %d\n",iStatus);
        
        //2.2 单链表插入数据
        for(int j = 1;j<=10;j++)
        {
            iStatus = ListInsert(&L, 1, j);
        }
        printf("L 插入后\n");
        ListTraverse(L);
        
        //2.3 单链表获取元素
        GetElem(L,5,&e);
        printf("第5个元素的值为:%d\n",e);
        
        //2.4 删除第5个元素
        iStatus = ListDelete(&L, 5, &e);
        printf("删除第5个元素值为:%d\n",e);
        ListTraverse(L);
        
        //3.1 前插法整理创建链表L
        iStatus = ClearList(&L);
        CreateListHead(&L, 20);
        printf("整理创建L的元素(前插法):\n");
        ListTraverse(L);
        
        //3.2 后插法整理创建链表L
        iStatus = ClearList(&L);
        CreateListTail(&L, 20);
        printf("整理创建L的元素(后插法):\n");
        ListTraverse(L);
     }
    

    四、单链表与顺序表的对比

    (1)存储方式:顺序表用一组连续的存储单元依次存储线性表的数据元素;而单链表用一组任意的存储单元存放线性表的数据元素。

    (2)时间性能:采用循序存储结构时查找的时间复杂度为O(1),插入和删除需要移动平均一半的数据元素,时间复杂度为O(n)。采用单链表存储结构的查找时间复杂度为O(n),插入和删除不需要移动元素,时间复杂度仅为O(1)。

    (3)空间性能:采用顺序存储结构时需要预先分配存储空间,分配空间过大会造成浪费,过小会造成问题。采用单链表存储结构时,可根据需要进行临时分配,不需要估计问题的规模大小,只要内存够就可以分配,还可以用于一些特殊情况,如一元多项的表示。

    相关文章

      网友评论

          本文标题:数据结构与算法学习 (02)线性表

          本文链接:https://www.haomeiwen.com/subject/xpbauhtx.html