zoukankan      html  css  js  c++  java
  • 大话数据结构笔记_线性表

    线性表的定义 :

      简而言之 : 0 个 或 多个元素(类型相同)的有限序列( 有顺序 ) , 第一个元素无前驱 , 最后一个元素无后继 , 其他元素 与有唯一的前驱 和 唯一的后继
      数学语言定义 : 若将线性表记为 ( a1 , a2 , ..... , ai - 1 , ai , ai+1 , ... , an) , 则表中 ai-1 领先于 ai , ai 领先于 ai+1
               称 ai-1 是 ai 的前驱 , ai+1 是 ai 的后继元素 . 当 i = 1 , 2 ... , n - 1 时候 , ai 有且仅有 一个 直接后
             继 , 当 i = 2 ,......., n 时候 , ai 有且仅有一个直接的前驱。
      线性表的长度 : 线性表的元素的个数 n( n>=0 ) , 当 n = 0 时候 我们称之为空表


    线性表的ADT :

      注意 :  这只是 抽象 层面 来说 , 也就是说实现方式尽管 有 顺序表连表等实现方式 , ADT仅仅抽取他们的相同
          处 且 基本具有的操作 , 具体实现时候 , 可以添加Data 数据 , 可以 扩充 操作 或者添加 辅助方法( 抽取
          相同代码 )。 对于实际问题中更复杂的操作 , 可以使用ADT中的基本操作来组合完成。

           定义如下:
        

    ADT 线形表 ( List ) 
    Data : 下一层数据类型 + 描述
    线性表的数据对象的序列 {a1,a2,a3,......, an} , 每个元素均为DataType , 其中 , 除了元素 a1 外 , 每一个    
    元素都只有唯一的前驱元素 , 除了an外 , 每个元素都只有唯一的后继元素 。 元素之间的关系是一对一的关 
    系。
    Operation: 操作 + 描述
    (规定 : 提前说明 , 我们使用的编号 均从 0 开始 , 操作返回 0 代表成功 , -1 代表失败)
    
    InitList(*L) : 初始化操作 , 给L 指针指向的List类型开辟能容纳序列的空间
    ListEmpty(L) : 若线性表为空 , 返回true else false
    ClearList(*L) : 将线性能表清空 
    GetElem(L , i ,*e) : 将线性表L中的第i个元素通过传出参数e取出 , 返回值 0 代表的 成功 , -1 代表错误
    LocateElem(L,e ) : 将线形表L 中查找 第一个匹配到的e , 成功返回 编号位置 , 失败返回-1
    ListInsert(*L , i , e) : 在线性表第 i 个元素 的前面 插入一个元素 , 成功返回 0 失败返回 -1 
    ListDelete(*L , i ,*e ): 删除线性表中的第i个位置的元素 , 并用e返回其值
    ListLength(L) : 返回线性表中的元素的个数 : 
    endADT

    线形表的顺序存储结构:

      ADT -> 物理层面 -> 语言实现( C 语言实现):

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

       

    sqList 结构体 定义在头文件当中:

    #ifndef __SEQ_LIST_H
    #define __SEQ_LIST_H
    
    #define MAXSIZE 20     /*假设顺序表的容量为20*/
    #define TRUE     0     /*我们只使用TRUE 和 FALSE 两个量表示成功和失败就行了 , 但是注意TRUE是0*/
    #define FALSE   -1   
     
    typedef int ElemType;  /*假设ElemType 类型为int*/
    typedef int BOOL;      /*BOOL 表示两个量 : TRUE | FALSE */
     
    /*结构体的声明放在*/
    typedef struct
    {
         ElemType data[MAXSIZE];
         int length;        /*扩充了ATD的DATA,其表示指向序列最后一个元素的下一个位置*/
    }sqList;
      
    /*函数的声明放在这个位置 */
    extern void InitList(sqList * L);
    extern BOOL ListEmpty(sqList L);
    extern void ClearList(sqList * L);
    extern int LocateElem(sqList L , ElemType e);
    extern int  ListLength(sqList L);
    extern BOOL ListInsert(sqList * L , int i , ElemType e);
    extern BOOL ListDelete(sqList * L , int i , ElemType *e);
    extern int GetElem(sqList L , int i  , ElemType * e); 
    
    #endif
    sqList 结构体定义 以及 操作接口声明

    sqList 操作定义在 .c 文件当中 :

    /*************************************************
    Function: InitList 
    Description: 初始化顺序表的长度为 0 
    Output: *L : 线性表发生变动
    Return: 空 TRUE | 非空FALSE (TRUE :0  FALSE:-1)
    *************************************************/
    void InitList(sqList * L)
    {
         L->length = 0;
    }
    
    /*************************************************
    Function: LisEempty
    Description: 返回顺序表是否为空 , O(1)
                  
                  判断 L->length 是否为0即可
     
    Input:       L : 线性表结构 
    Return: 空 TRUE | 非空FALSE (TRUE :0  FALSE:-1)
    *************************************************/
    BOOL ListEmpty(sqList L)
    {   
        return L.length ? FALSE : TRUE;
    }
    
    /*************************************************
    Function: ClearList 
    Description:  将顺序表清空 O(1) 
                  
                  将L->length 置为0即可
    Output: *L : 线性能表发生变动
    Return: void
    *************************************************/
    void ClearList(sqList * L)
    {
        L->length = 0;
    }
    
    /*************************************************
    Function: LocateElem
    Description: 获取第一次出现指定元素的位置 O(n)
                
                 开始遍历判断是否相等 
                 返回下下标位置
    Input:  
            L : 顺序表结构
            e : 指定的元素 
    Return: int 具体位置 , 没找到返回-1 
    *************************************************/
    int LocateElem(sqList L , ElemType e)
    {
        int i = 0;
        for(;i < L.length ; ++i)
        {
            if(L.data[i] == e)
                return i;
        }
        return -1;
    }
    
    /*************************************************
    Function: ListLength 
    Description:  获取顺序表的长度 O(1)
                    
                  直接返回 L.length即可
    Input:  L 表结构
    Return: int 顺序表的长度
    *************************************************/
    int ListLength(sqList L)
    {
        return L.length;
    }
    
    /*************************************************
    Function: ListInsert
    Description:  
                在顺序表的指定位置插入元素 : 时间复杂度O(n) : 时间浪费在移动上面 : 
    
                插入位置不合理  , return FALSE
                如果 length + 1 > MAXSIZE  return FALSE
                需要将 i 到 length-1的元素向后移动一个位置
                将元素插入指定位置后 , 表的长度 + 1
    Input: 
                i : i位置元素的前面进行插入
                e : 被插入的元素
    Output:     
                *L: 传出参数,表示线性表会被修改
    Return: 成功 TRUE | 失败 FALSE
    *************************************************/
    BOOL ListInsert(sqList * L , int i  , ElemType  e)
    {
        int j = 0;
        if( 0>i || i > L->length || L->length + 1 > MAXSIZE)
            return FALSE;
    
        for(j = i+1 ; j <=L->length ; ++j)
            L->data[j] = L->data[j-1];
    
        L->data[i] = e;
        L->length+=1;
    
        return TRUE;
    }
    
    /*************************************************
    Function: ListDelete
    Description: 删除指定位置的元素 : O(n) 时间也是浪费在移动数据上面 :
    
                 如果删除的位置不合适: return FALSE
                 取出删除的元素
                 将 i 位置之后的元素都向前移动一个元素:
                 表的长度需要减去 1 
    Input:  
            i : 删除元素的位置 :  0<=i<=L->length-1
    Output:
            L : 表会发生变动
            e : 输出被删除的元素
    Return: 删除成功 TRUE | 失败 FALSE
    *************************************************/
    BOOL ListDelete(sqList * L , int i , ElemType * e)
    {
        int j = 0;
        if(0 == L->length || 0 > i || i >= L->length)
                return FALSE;
    
        *e = L->data[i];
    
        for(j = i+1 ; j <=L->length ; ++j)
        {
            L->data[j-1] = L->data[j];
        }
    
        L->length-=1;
        return TRUE;
    }
    
    
    /*************************************************
    Function: GetElem
    Description: 获取顺序表中指定位置的元素 , 时间复杂度O(n) , 时间浪费在遍历上面:
                
                 i位置不合理 return FALSE
                 取得i位置的元素
            
    Input: 
          L  : 顺序表结构
          i  : 获取元素的位置 ,  0<= i <= L.length-1 
    Output: 
          *e : 输出类型参数 , 获取 i 指向的元素  
    Return: 成功TRUE | 失败 FALSE
    *************************************************/
    BOOL GetElem(sqList L , int i  , ElemType * e)
    {
        /* i 是从0 开始计算的*/
        if(0 == L.length || i < 0 || i > L.length-1)
            return FALSE;
        *e = L.data[i];
        return TRUE;
    }
    sqList 操作的实现

    宗上看出:  

      主要操作:
        插入 , 删除 O(n)
        搜寻 , 修改 O(1)

    线性能表存储结构的优点和缺点:

    • 优点:
      • 无需为表中元素之间的逻辑关系增加额外空间(比如连表 , 需要增加一个指针域)
      • 可以快速的存取表中 任意元素的位置
    • 缺点:
      • 插入 和 删除操作需要移动大量元素
      • 当线性表的长度变化较大时 , 难以确定存储空间的容量
      • 造成存储空间的碎片 [ 可能开辟的空间大小 > 顺序表的大小造成 空间的浪费 ]

    线性表的链式存储结构:

        为了解决链表的使用顺序结构存储的实现方式导致的插入 和 删除元素时候的效率问题 , 使 

      用链式来解决 , 但是链式存储结构的随机访问效率降低(不能根据第一个元素的位置直接得出指
      定位置元素的地址 , 只能通过遍历来实现)

      ADT-> 链式存储结构->语言层次实现( C语言 ):

      带上头结点 , 就能让在链表的头位置插入元素/删除元素变得统一

    Node 结构体的定义: 放置在LinkList.h 的头文件当中

    #ifndef _LINK_LIST_H
    #define _LINK_LIST_H
    #define TRUE 0
    #define FALSE -1
    
    typedef int ElemType;
    typedef int BOOL;
    
    typedef struct Node
    {
        ElemType data;
        struct Node * next;
    }Node;
    
    typedef struct Node * LinkList;
    
    /*基于上述链表结构体的操作的声明:*/
    /*ADT 中的实现*/;
    extern BOOL ListInit(LinkList *L);
    extern BOOL ListEmpty(LinkList L);
    extern void ClearList(LinkList *L);
    extern BOOL GetElem(LinkList *L, int i, ElemType * e);
    extern int LocateElem(LinkList *L,ElemType e);
    extern BOOL ListInsert(LinkList *L, int i, ElemType e);
    extern BOOL ListDelete(LinkList *L, int i, ElemType *e);
    extern int ListLength(LinkList L);
    /*ADT上的操作的扩展*/
    
    #endif
    LinkList.h

    单链表的操作的实现 , LinkList.c 文件:

    /**
     * File name: LinkList.c
     * Author:MusaGeek    Version: 1.0       Date: 2018-11-21
     * Description:  单链表的操作的实现的.c文件
     * Function List:  // 主要函数列表,每条记录应包括函数名及功能简要说明
    
        BOOL ListInit(LinkList *L);                     初始化链表
        BOOL ListEmpty(LinkList L);                     判断链表是否为空
        void ClearList(LinkList *L);                     清空链表
        BOOL GetElem(LinkList *L, int i, ElemType * e); 获取链表指定位置的元素
        int LocateElem(LinkList *L,ElemType e);         定位元素的位置
        BOOL ListInsert(LinkList *L, int i, ElemType e);插入元素
        BOOL ListDelete(LinkList *L, int i, ElemType *e);删除元素
        int ListLength(LinkList L);                         获取链表的长度
    */
    
    #include <stdio.h>
    #include <stdlib.h>
    #include "LinkList.h"
    
    /*************************************************
    Function: ListInit
    Description: 初始化单链表中的数据 
                 1.为指向链表的头指针分配头结点空间
                 2.将头结点的指针域设置为NULL
    Input: 
          L  : 指向链表头结点的指针
    Return: 成功TRUE | 失败 FALSE
    *************************************************/
    BOOL ListInit(LinkList *L)
    {
        Node *head = (Node *)malloc(sizeof(Node));
        if(!head)
            return FALSE;
        *L = head;
        head->next = NULL;
        return TRUE;
    }
    
    
    /*************************************************
    Function: ListInit
    Description: 初始化单链表中的数据 
                 1.为指向链表的头指针分配头结点空间
                 2.将头结点的指针域设置为NULL
    Input: 
          L  : 指向链表头结点的指针
    Return: 成功TRUE | 失败 FALSE
    *************************************************/
    BOOL ListEmpty(LinkList L)
    {
        return ( (L->next) ? FALSE : TRUE);
    }
    
    
    /*************************************************
    Function:  ClearList
    Description: 因为 LinkList 中的结点均是malloc而来的 , 因此当整个链表不使用了时候 需要手动释放掉,避免消耗内存
                 但是保留头结点
                 1.创建 指针 p = (*L)->next , 指针 q;
                 2.循环 :当 p 不为 NULL 时候 ; q = p->next;释放 p; p = q;
    Input: 
          L  : 指向链表头结点的指针  
    Return: void
    *************************************************/
    void ClearList(LinkList *L)
    
    {
        Node *p = (*L) = (*L)->next, *q = NULL;
        while(p)
        {
            q = p->next;
            free(p);
            p = q;
        }
        (*L)->next = NULL; /*头结点不释放 ,指针域置为NULL*/
    }
    
    
    /*************************************************
    Function: GetElem
    Description: 获取顺序表中指定位置的元素 , 时间复杂度O(n) , 时间浪费在遍历上面:
                1.声明一个结点p指向链表的第一个结点 , 初始化 j 从1开始
                2.当 j < i 时 , 就遍历链表 , 让指针p不断的向后移动 , j+=1
                3.若最终 NULL == p 则 返回FALSE
                4.若 NULL != p 则 将data域数据传出 返回TRUE , 
    Input: 
          L  : 指向链表头结点的指针
          i  : 获取元素的位置  
    Output: 
          *e : 输出类型参数 , 获取 i 指向的元素  
    Return: 成功TRUE | 失败 FALSE
    *************************************************/
    BOOL GetElem(LinkList *L, int i, ElemType * e)
    {
        Node * p = (*L)->next;
        int j = 1;
        while(p && j < i)
        {
            p = p->next;
            j++;
        }
        *e = p->next->data;
        return (p ? TRUE : FALSE);  
    }
    
    
    /*************************************************
    Function: LocateElem
    Description: 定位e在链表中的位置, 时间复杂度O(n), 时间浪费在遍历上面:
                 遍历找到第一个匹配的元素即可   
    Input: 
          L: 指向链表头结点的指针的指针
          e: 需要定位的元素值
    Return: int 元素在链表中的位置,没有匹配到为-1
    *************************************************/
    int LocateElem(LinkList *L,ElemType e)
    {
        Node *p = (*L)->next;
        int j =  1;
        while(p && p->data != e)
        {
            p = p->next;
            j++;
        }
        return p ? j : -1;
    }
    
    
    /*************************************************
    Function:  ListInsert
    Description: 在指定 i 的位置插入元素 , O(n) , 浪费的时间在遍历上面 , 但是没有移动元素造成的复制开销
                
                  1.声明一个结点 p 指向链表的头结结点 , 初始化 j 从 1 开始
                  2.当 j < i 遍历链表 , 让p指针不断后移 , j+=1
                  3.若 遍历结束 , NULL == p , 返回FALSE
                  4.否则 当 j == i 时候 , p 已经指向的了 第i个元素之前的元素
                5.创建结点s(malloc) , s->next = p->next; p->next = s;
                6.返回TRUE 
    Input: 
          L  : 指向链表头结点的指针
          i  : 获取元素的位置  
          e  : 插入的元素值  
    Output: 
         
    Return: 成功TRUE | 失败 FALSE
    *************************************************/
    BOOL ListInsert(LinkList *L, int i, ElemType e)
    {
        Node *p = *L , *s = NULL;
        int j = 1;
        while(p && j<i)                           //如果 i 有效的话 ,那么p最终会指向第i个结点前面的那个结点
        {
            p = p->next;
            j++;
        }
        
        if(p)                                
        {
            s = (struct Node *)malloc(sizeof(Node));    //给新插入的结点分配空间
            s->data = e;
            s->next = p->next;                                   //下面两步骤不能反了
            p->next = s;
            return TRUE;
        }
        return FALSE;
    }
    
    
    /*************************************************
    Function:  ListDelete
    Description: 删除指定的第 i 个位置的元素 , O(n) 但是没有移动元素复制元素的开销
                
                1.定义指针p指向第一个结点 , 定义 j = 1;
                2.在 j < i 且 p !=NULL 的情况下 让指针p向后移动 , j++
                3.当 j >= i 时 若 NULL = p 那么 返回FALSE
                4.否则 临时指针变量 t 保存 p->next;p->next = p->next->next; 再释放t
                5.返回TRUE       
    Input: 
          L  : 链表头指针
          i  : 获取元素的位置  
    Output: 
          *e : 输出类型参数 , 获取 i 指向的元素  
    Return: 成功TRUE | 失败 FALSE
    *************************************************/
    BOOL ListDelete(LinkList *L, int i, ElemType *e)
    {
        Node *p = *L, *t = NULL;
        int j = 1;
        while(p && j < i)     //如果 i 有效的话 ,那么p最终会指向第i个结点前面的那个结点
        {
            p = p->next;
            j++;
        }
        if(p)
        {
            t = p->next;
            p->next = t->next;  //将要删除结点剔除链表
            *e = t->data;
            free(t);
            return TRUE;
        }
        return FALSE;
    }
    
    
    /*************************************************
    Function:  ListLength
    Description: 获取链表的长度
                 直接遍历链表获得
    Input: 
          L  : 链表头指针
    Return: int 链表的长度
    *************************************************/
    int ListLength(LinkList L)
    {
        Node *p = L->next;
        int len = 0;
        while(p)
        {
            len++;
            p = p->next;
        }
        return len;
    }
    LinkList.c

     

        单链表虽然在 插入 , 删除元素上面的操作也是O(n) , 但是只是时间花费在了遍历的开销上面了 , 而不是元素移动上 , 且如果元素的类型越复杂 , 移动上面造成复制的开销将更大 , 因此单链表 比 顺序表更适合频繁的元素插入的和删除。

      单链表的创建又分为 头插法 和 尾插法 两种创建方式 

      顺序表 和 链表的对比 :

      • 频繁查找 , 很少进行删除 和 插入时 适用顺序表
      • 线性表的元素变化较大 , 频繁的进行插入和删除 ,使用链表
      • 线性表不知道元素的个数的多少的时候 , 就使用链表

         


     静态链表:

      对于早期没有指针的编程语言来说, 实现单链表可以使用静态链表方式来实现线性链表;

           使用定长的数组来模仿一个链表 , 且数组中的每个元素 包含 一个 data 和 一个指向其他元素的数组下标的cur , 这样就能实现在一个连续存储空间上面的链表了。

      细节注意:

         将整个数组分为两个部分 : 备用链表 和 已经使用的链表 。 第一个元素的cur指向的是备用 链表的开始位置。最后一个元素的cur指向的是 已经使用的链表的第一个元素下标 [为 空时候指向的是 0 ]

         

      静态链表的优缺点:

      • 优点:
        • 在插入 和 删除操作的时候只需要修改游标 , 不需要移动元素。
      • 缺点:
        • 表长度不能确定。
        • 虽然物理内存空间连续 , 但是失去了像顺序表那样的随机访问的能力。

    循环链表

      将单链表的中的尾结点的指针域指向头结点即可 , 头尾相接的链表的即为循环链表。

      解决问题 :
        从其中某个结点出发依然能够遍历整个链表。

        改进方式 :  

          将指向链表的指针指向循环的链表的尾巴元素 , 这样 不光 访问最后一个元素可以由O(n) 优化到O(1) , 并且对于两个链表的合并 也可以省去遍历到最后一个元素的操作 ,由O(n) 优化到O(1)

     


    双向链表:

        一个的Node中包含两个指针域 , 一个指向前驱元素(prior) , 一个指向后继元素即可(next)。初始化时候的头结点的 prior 指向自己 , next 也指向自己。
        注意 : 删除元素 和 插入元素时候 涉及元素的 指针值修改的顺序需要特别的注意。

      双向链表 的 插入 和 删除 :


    总结:

    • 线性表 : 0个或多个具有相同的类型的数据元素构成有限序列
    • 顺序结构 :
      • 顺序表 : 适合频繁随机访问 , 不适合频繁插入和删除
      • 静态链表 : 使用顺序结构模仿 链表 : 失去了随机访问能力 , 且空间不能自动增长 , 但是改进了顺序表的移动开销
    • 链式结构 :
      • 单链表 : 适合 频繁 插入 和 删除 , 不适合 频繁的随机访问
      • 循环链表 : 能从任意结点开始遍历整个链表 , 尾巴针的方式 改进了访问直接访问最后一个元素的时间复杂度 , 同时也改进了访问两个链表合并的时间复杂度要特别的注意。
      • 双向链表 : 多增加了一个指向 前驱元素的指针域 , 插入/删除操作的顺序需要注意
  • 相关阅读:
    服务限流原理及算法
    Kafka 与 RabbitMQ 如何选择使用哪个?
    分布式事务之最终一致性实现方案
    如何在Ubuntu 14.04中使用systemctl?
    postgresql13 for window 安装及备份还原数据
    手把手教大家在mac上用VMWare虚拟机装Ubuntu
    在Mac平台上使用Multipass安装Ubuntu虚拟机
    如何在markdown中插入js和css
    HTML5标签
    linux
  • 原文地址:https://www.cnblogs.com/Geek-Z/p/9925475.html
Copyright © 2011-2022 走看看