数据结构之线性表、多重链表

目录

前言

线性结构

线性表及其实现

线性表的抽象数据类型描述

线性表的顺序存储实现

主要操作的实现

1.初始化(建立空的顺序表)

2.查找

3.插入

4.删除

线性表的链式储存实现

主要操作的实现

1.求表长(链表遍历)

2.查找

3.插入(一定要知道插入结点的前面一个是谁)

4.删除

广义表(Generalized List)

多重链表


前言

本篇是对于学习到的数据结构线性表,广义表,多重链表的总结,内容有点小多。

线性结构

线性结构是数据结构里面最基础也是最简单的一种数据结构类型,其中典型的就是  线性表。

线性表及其实现

同一个问题可以有不同的表示(存储)方法,在数据结构中最常见的有两种方法:数组链表

线性表(Linear List):由同类型数据元素构成有序序列的线性结构

  • 表中元素个数称为线性表的长度
  • 线性表没有元素时,称为空表
  • 表起始位置称表头,表结束位置称表尾

线性表的抽象数据类型描述

类型名称:线性表(List)

抽象数据类型包含两个要素:这个类型所包含的数据对象集;在对象集上进行的操作。

数据对象集:线性表是n(大于等于0)个元素构成的有序序列(a1,a2,a3……)

操作集线性表L属于List,整数i表示位置,元素X属于ElementType(整型、实型、结构),线性表的基本操作主要有:

  1. List MakeEmpty():   初始化一个空线性表L;
  2. ElementTpe FindKth( int K,List L):  根据位序K,返回相应元素;
  3. int Find(ElementType X,List L):  在线性表L中查找X的第一次出现位置;
  4. void Insert( ElementType X,int i,List L):在位序i前插入一个新元素X;
  5. void Delete(int i,List L):删除指定位序i的元素;
  6. int Length(List L): 返回线性表L的长度n。

线性表的顺序存储实现

利用数组连续存储空间顺序存放线性表的各元素

数组可以放不同长度的线性表,是连续存放的。我们需要一个指针Last来指示在数组里存放的线性表中最后一个元素的位置

typedef struct LNode*List;
struct LNosde{
    ElementType Data[MAXSIZE];  //定义一个数组,它的分量类型是ElementType
    int Last;                   //再定义一个Last,代表线性表的最后一个元素
};
struct LNode L;
List PtrL;

这样构成了一个结构,那么这个结构就可以抽象的实现线性表

访问下标为i的元素L.Data[i]  或  PtrL->Data[i]

线性表的长度:L.Last+1  或 PtrL->Last+1

主要操作的实现

1.初始化(建立空的顺序表)

List MakeEmpty()
{
    List PtrL;
    PtrL = (List)malloc(sizeof(struct LNode)); //通过malloc函数申请一个包含数组和 
                                 //代表最后一个元素的Last的结构
    PtrL->Last = -1;  //把结构的Last赋值为-1
    return Ptrl;   //返回结构的指针
}

2.查找

int Find(ElementType X,List PtrL)
{
    int i = 0;
    while(i<=PtrL->Last && PtrL->Data[i]!=X)
        i++;
    if(i>PtrL->Last)
        return -1;  //如果没找到,返回-1
    else return i;  //找到后返回的是存储位置
}

查找成功的平均比较次数为(n+1)/2,若是运气好,一次就找到了,运气不好最后一个找到

平均时间性能为O(n)

3.插入

在线性表的第i(i>=1且i<=n+1)个位置上插入一个值为X的新元素

线性表在数组里表示,下标是从0开始的,所以实际上,我们要把X放在i-1的下标上。因为是连续存放的,所以要把下标为i-1及其之后的元素全部向后挪一位,腾出i-1的位置,然后把X放在里面。移动时,要从最后一个开始挪。

void Insert(ElementType X,int i,List Ptrl)
{
    int j;
    if(PtrL->Last == MAXSIZE-1){  //MAXSIZE是数组的大小。若表空间已满,不能插入
        printf("表满");
        return;
    }
    if(i<1 || i>PtrL->Last+1){ //检查插入位置的合法性
        printf("位置不合法");
        return;
    }
    for(j = PtrL->Last;j>=i-1;j--)
        PtrL->Data[j+1]=PtrL->Data[j]; //将ai~an倒序向后移动
    PtrL->Data[i-1]=X;                 //新元素插入
    PtrL->Last++;                      //Last仍指向最后元素
    return;
}

整个算法的复杂性在for循环里,平均移动次数为n/2;平均时间性能为O(n)

4.删除

删除表的第i个(i>=1且i<=n)个位置上的元素

实际上删除的位置对应的下标为i-1,所以要把下标为i及其之后的元素向前挪。

void Delete(int i,List PtrL)
{
    int j;
    if(i<1||i>PtrL->Last+1){         //检查空表及删除位置的合法性
        printf("不存在第%d个元素",i);
        return;
    }
    for(j=i;j<=PtrL->Last;j++)
        PtrL->Data[j-1]=PtrL->Data[j];//将ai+1~an顺序向前移动
    PtrL->Last--;                     //Last仍指向最后元素
    return;
}

平均移动次数为(n-1)/2,平均时间性能为O(n)

线性表的链式储存实现

不要求逻辑上相邻的两个元素物理上也相邻;通过“链”建立起数据元素之间是逻辑关系。

插入、删除不需要移动数据元素,只需要修改“链”

typedef struct LNode *List;
struct LNode{
    ElementType Data;   //代表结点所对应的数据
    List Next;          //代表下一个结点的位置
};
struct Lnode L;
List PtrL;

主要操作的实现

1.求表长(链表遍历)

int Length(List PtrL)
{
    List p=PtrL;  //p指向表的第一个结点
    int j=0;
    while(p){      //p不等于NULL,就循环下去
        p=p->Next;
        j++;      //当前p指向的是第j个结点
    }
    return j;
}

时间性能为O(n)

2.查找

(1)按序号查找:FindKth;

List FindKth(int K,List PtrL)
{
    List p=PtrL;  //p是临时变量,指向链表的表头
    int i=1;
    while(p!=NULL && i<K){
        p=p->Next;
        i++;
    }
    if(i==K)
        return p;    //找到第K个,返回指针
    else return NULL;//否则返回空
}

(2)按值查找:Find

List Find(ElementType X,List PtrL)
{
    List p=PtrL;
    while(p!=NULL && p->Data!=X)
        p=p->Next;
    return p;   //返回结点所在的地址
}

平均时间性能O(n)

3.插入(一定要知道插入结点的前面一个是谁)

在第i-1(i>=1且i<=n+1)个结点后插入一个值为X的新结点

(1)先构造一个新结点,用s指向;

(2)再找到链表的第i-1个结点,用p指向;

(3)然后修改指针,插入结点(p之后插入新结点是s)

 s->Next = p->Next;

p->Next = s;              //这两个步骤不可交换

List Insert(ElementType X,int i,List PtrL)
{
    List p,s;
    if(i == 1){                              //新结点插入在表头
        s=(List)malloc(sizeof(struct LNode));//申请、填装结点
        s->Data = X;
        s->Next = PtrL;
        return s;                            //返回新表头指针
    }
    p = FindKth(i-1,PtrL);                   //查找第i-1个结点
    if(p == NULL){                           //第i-1个不存在,不能插入
        printf("参数i错");
        return NULL;
    }else{
        s=(List)malloc(sizeof(struct LNode));  //申请、填装结点
        s->Data = X;
        s->Next = p->Next;                  //新结点插入在第i-1个结点后面
        p->Next = s;
        return PtrL;
    }
}

4.删除

删除链表的第i(i>=1且i<=n)个位置上的结点

(1)先找到链表的第i-1个结点,用p指向;

(2)再用指针s指向要被删除的结点(p的下一个结点)

(3)然后修改指针,删除s所指结点;

(4)最后释放s所指结点的空间。

 p->Next = s->Next;

List Delete(int i,List PtrL)
{
    List p,s;
    if(i==1){                         //若要删除的是表的第一个结点
        s=PtrL;                       //s指向第一个结点
        if(PtrL!=NULL)PtrL = PtrL->Next; //从链表中删除
        else return NULL;
        free(s);                    //释放被删除的结点
        return PtrL;
    }
    p=FindKth(i-1,PtrL);          //查找第i-1个结点
    if(p==NULL){
        printf("第%d个结点不存在",i-1); return NULL;
    }else if(p->Next == NULL){
        printf("第%d个结点不存在",i);  return NULL;
    }else{
        s=p->Next;               //s指向第i个结点
        p->Next =s->Next;         //从链表中删除
        free(s);                 //释放被删除结点
        return PtrL;
    }
}

广义表(Generalized List)

【例】给定二元多项式如何表示?

 【分析】可以将上述二元多项式看成关于x的一元多项式

 所以,上述二元多项式可以用“复杂”链表表示为:

  •  广义表是线性表的推广
  • 定义线性表而言,n个元素都是基本的单元素
  • 广义表中,这些元素不仅可以是单元素也可以是另一个广义表
typedef struct GNode *GList;
struct GNode{
    int Tag;  //标志域:0表示结点是单元素,1表示结点是广义表
    union{    //子表指针域Sublist与单元素数据域Data复用,即共用存储空间
      ElementType Data;
      GList SubList;
    }URegion;
    GList Next;   //指向后继结点
};
TagDataNext
SubList

广义表实际上就是多重链表

多重链表

多重链表:链表中的结点可能同时隶属于多个链

  • 多重链表中结点的指针域会有多个,如前面例子包含了Next和SubList两个指针域
  • 但包含两个指针域的链表并不一定是多重链表,比如在双向链表不是多重链表

多重链表有广泛的用途:

基本上如树、图这样相对复杂的数据结构都可以采用多重链表方式实现存储

【例】矩阵可以用二维数组表示,但二维数组表示有两个缺陷:

  • 一是数组的大小需要事先确定
  • 对于“稀疏矩阵”,将造成大量的存储空间浪费

【分析】采用一种典型的多重链表——十字链表来存储稀疏矩阵

  1. 只存储矩阵非0元素项

结点的数据域:行坐标Row、列坐标Col、数值Value

     2.每个结点通过两个指针域,把同行、同列串起来;

  • 行指针(或称为向右指针)Right
  • 列指针(或称为向下指针)Down

1>用一个标识域Tag来区分头结点和非0元素结点;

2>头结点的标志值为“Head”,矩阵非0元素结点的标志值为“Term”

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值