数据结构2-1:数据表及其实现

多项式的表示:

对于这2001个分量,我们可以看到,只有2项是非0的,其余全是0,这会造成很大的空间浪费。而且做加法的时候,怎么也要做一个循环,从0开始一直做到2000,实际上还有很多是在加无效的0,所以虽然方便但是也有很大的问题。

只表示非0项——结构数组:

这样的一种方法显然是用指数、系数构成的结构存储在数组里面,来表示非零项。这种方法是一种节省空间的方法,而且操作效率也不算差。同时表示非零项的这种方法,不一定非要用数组来做,也可以用另外一种方法:链表

上面是按照指数系数从大到小排列的。

什么是线性表?

线性表是由同类型的数据元素构成有序序列的线性结构,注意这里说的一定是同类型的。

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

假设说:线性表类型叫List,对其中的一个具体的线性表叫L。它里面的元素X有个类型叫ElementType,这个类型可以是整型,也可以是实型,也可以是个结构,在这个地方统称为:ElementType。

线性表的顺序存储实现:(用数组来实现)

线性表的顺序存储实现就是用数组的方法来进行实现 。

因为数组可以放不同长度的线性表,它是连续存放的,那他最后一个元素在哪里?所以我们要有一个指针Last来指示在这个数组里面存放的、线性表的最后个元素所在的位置。


数组,我们这儿叫Data。定义个数组,它的分量类型是ElementType。
再定义一个Last,代表线性表的最后一个元素。
酱构成一个结构, 这个结构就可以抽象地实现一个线性表
那么利用线性表这个类型,我们可以定义一个变量L,还有个PtrL

因为Last的值代表位置,因为我们是从0开始,所以长度是Last+1

如果按照顺序存储这样一种方法, 放在数组里面,它相应的操作是怎么实现的?
首先一个,初始化,MakeEmpty。
MakeEmpty的实现方法,显然是比较简单的。
前面提到过,我们表的表示呢,是用结构来表示。
它包含了数组,还有代表最后一个元素的Last。
所以我们首先就想要去申请这样的一个结构
所以建表的第一个是通过malloc这个函数,申请酱一个结构
然后把结构的Last设为-1,因为Last代表最后一个元素
所以Last (为) 0是代表这个表里有一个元素放在第一个位置,没元素就设为-1
然后把这个结构的指针返回回来
这是我们第一个函数的实现:初始化,MakeEmpty




Find中,它是告诉你一个PtrL,它是一个线性表结构的指针,通过这个结构,我们知道这个数组是谁,Last 是谁,然后我们要在这个线性表里面找X它所在的位置

1就插在线性表的头上; n+1是插在线性表的最后.那么线性表是在数组里表示的,下标是从0开始的。所以实际上就意味着,我们要把这个元素放在哪个位置呢?
放到i-1这个位置。
你要把新插入的元素X放到i-1这个位置,那怎么办?
它是连续存放的,所以你首先要做的一个事情,是要把i-1之后的这些元素全部往后挪1位,腾出i-1这个位子来,然后再往里边放。
每一个元素都往后挪,显然有个循环就可以做了。
显然应该是从后面开始挪,把最后一个往后挪,倒数第二个往后挪……,否则你从前往后挪,这个算法是不对的。

这里插入的i表示:插入在第i个元素的位置上,在数组下标中就是下标为i-1的位置上。

首先判断表空间是否满了,即maxsize-1表示的是数组中最后一个元素的下标。


一定是从后往前一个一个慢慢挪。
否则会出现下面的情况:

这里删除的i表示:删除在第i个元素所在位置的元素,在数组下标中就是下标为i-1的位置上的元素。

删除元素是从左往右挪动的,比如说删除下标第i的元素,那么就先把下标为i+1的元素移动到下标为i的位置上,然后后面的i+2移动到i+1上,依次类推,把要删除的元素覆盖掉,并且把空出来的位置移到最后。

线性表的链式存储实现:

一个线性表用数组存储的时候,两个相邻的元素不仅逻辑上是相邻的,而且物理上也是租邻的。我们前面看到了,插入、删除都要把数组后面的元素往前挪或者往后挪。而在链里面,只要轻易地修改链就行了,不需要对很多元素进行挪动。

链表的每个结点都是一个结构。这个结构里面至少有两个分量。

ElementType Data代表结点所对应的数据。
List Next代表下一个结点的位置,就是Next这样一个指针。

在数组里面,我们知道,要找到序号为i的元素是很简单的,a[i-1]就可以了。那么我们要求线性表的长度,有个last指针,很快也能求出来。


但是如果在一个链表里面,我们光知道一个链表头,我们怎么知道第i个元素在哪里?链表的长度是多少?所以显然,链式的存储方法跟数组相比,在这两个问题上面就要复杂了。


在这里插入图片描述

链表遍历的方式求表长。
循环到最后,p的值就是null了,循环退出。

链式查找也是类似求表长一样,使用遍历的方法。

那么在链表的插入呢,一定要知道插的结点的前面一个是谁,这样我们才能够把这个结点接上去。

注意:先将p->next赋值给s->next,再将s赋值给p->next。
否则:

删除后一个结点后很重要的一步就是:释放(free)。这样的话,内存空间才不会泄漏

广义表

原来这个位置(a\b\c)相当于都是常量的位置,现在变成了指针了,指向了另外一个一元多项式,这种表我们就称为广义表。

所以在广义表构造的时候,我们会碰到一个问题,就是一个域有可能就是不能分解的单元,有可能是一个指针,那么这个问题怎么处理呢?

C语言提供了一种手段,叫做联合union ,union可以把不同类型的数据组合在一起,可以把这个空间理解成某种类型,也可以理解成另外一种类型,那么怎么区分不同的类型呢?

往往采用的方法(是)再弄个标记,类似刚才讲的二元多项式的酱表示的问题。就是说在某些域里面,它可能有不同的理解,一般的一种方法,就是设一个标记,然后用union把它组在一起。

根据这个标记,假如说这个标记等于0,就代表单元素,1是代表一个指针,指向另外一张广义表。

所以它的结构指向通过Tag来区分后面的这块空间:,到底是Data还是Sublist

多重链表:

实际上,刚刚看到的广义表就是一个多重链表。

多重链表是指的它里面的这个链表的结点,可能同时隶属于多个链表,也就是说这里面结点的指针会有多个。

所谓“稀疏矩阵”,就是里面的0很多。

每个结点要包括行坐标、列坐标、数值。

每一个结点它是同时又属于某一行,同时也属于某一列。

这里的结点一共有两种类型,第一种类型是Term类型,Term类型它是有两个指针,一个是指向同一行的,一个是指向同一列的。同一行的指针它把同一行同一列,都设计成一个叫循环链表。

所以每个结点属于某一行也属于某一列.
形成了这样的十字结构,所以我们叫十字链表


我们还有另一个结点叫head。

这个Head干什么?
是作为行这个链表的头结点,也作为这个列链表的头结点。


我们还发现一个特殊的结点:

因为Term我们]说是代表稀疏矩阵里面的非零的项,现在左上角这个Term干啥用?
它是整个稀疏矩阵的一个入口,这里的行值、列值跟Value值是什么呢?
这里大家看到了4、5、7,
4代表这个稀疏矩阵总共有4行,总共有5列,非零项个数总共有7项。
所以它的结构跟Tem结构是一样的。

通过指针,这个指针,它就可以找到所有列的头结点,所有行的头结点,所以这个是整个矩阵的一个入口结点。
在这里插入图片描述
一个是Head的结构,一个Term的结构,这两个结构明显地不一样,但是又有共性,都有两个指针,一个行方向,一个列方向,但是里面的内容是不一样的

我们可以建立一个union,把这两项内容union在一起,形成一个统一的结构(即上图中的a)。

代码:

typedef int Position;
typedef struct LNode *List;
struct LNode {
    ElementType Data[MAXSIZE];
    Position Last;
};

/* 初始化 */
List MakeEmpty()
{
    List L;

    L = (List)malloc(sizeof(struct LNode));
    L->Last = -1;

    return L;
}

/* 查找 */
#define ERROR -1

Position Find( List L, ElementType X )
{
    Position i = 0;

    while( i <= L->Last && L->Data[i]!= X )
        i++;
    if ( i > L->Last )  return ERROR; /* 如果没找到,返回错误信息 */
    else  return i;  /* 找到后返回的是存储位置 */
}

/* 插入 */
/*注意:在插入位置参数P上与课程视频有所不同,课程视频中i是序列位序(从1开始),这里P是存储下标位置(从0开始),两者差1*/
bool Insert( List L, ElementType X, Position P ) 
{ /* 在L的指定位置P前插入一个新元素X */
    Position i;

    if ( L->Last == MAXSIZE-1) {
        /* 表空间已满,不能插入 */
        printf("表满"); 
        return false; 
    }  
    if ( P<0 || P>L->Last+1 ) { /* 检查插入位置的合法性 */
        printf("位置不合法");
        return false; 
    } 
    for( i=L->Last; i>=P; i-- )
        L->Data[i+1] = L->Data[i]; /* 将位置P及以后的元素顺序向后移动 */
    L->Data[P] = X;  /* 新元素插入 */
    L->Last++;       /* Last仍指向最后元素 */
    return true; 
} 

/* 删除 */
/*注意:在删除位置参数P上与课程视频有所不同,课程视频中i是序列位序(从1开始),这里P是存储下标位置(从0开始),两者差1*/
bool Delete( List L, Position P )
{ /* 从L中删除指定位置P的元素 */
    Position i;

    if( P<0 || P>L->Last ) { /* 检查空表及删除位置的合法性 */
        printf("位置%d不存在元素", P ); 
        return false; 
    }
    for( i=P+1; i<=L->Last; i++ )
        L->Data[i-1] = L->Data[i]; /* 将位置P+1及以后的元素顺序向前移动 */
    L->Last--; /* Last仍指向最后元素 */
    return true;   
}

代码:

typedef struct LNode *PtrToLNode;
struct LNode {
    ElementType Data;
    PtrToLNode Next;
};
typedef PtrToLNode Position;
typedef PtrToLNode List;

/* 查找 */
#define ERROR NULL

Position Find( List L, ElementType X )
{
    Position p = L; /* p指向L的第1个结点 */

    while ( p && p->Data!=X )
        p = p->Next;

    /* 下列语句可以用 return p; 替换 */
    if ( p )
        return p;
    else
        return ERROR;
}

/* 带头结点的插入 */
/*注意:在插入位置参数P上与课程视频有所不同,课程视频中i是序列位序(从1开始),这里P是链表结点指针,在P之前插入新结点 */
bool Insert( List L, ElementType X, Position P )
{ /* 这里默认L有头结点 */
    Position tmp, pre;

    /* 查找P的前一个结点 */        
    for ( pre=L; pre&&pre->Next!=P; pre=pre->Next ) ;            
    if ( pre==NULL ) { /* P所指的结点不在L中 */
        printf("插入位置参数错误\n");
        return false;
    }
    else { /* 找到了P的前一个结点pre */
        /* 在P前插入新结点 */
        tmp = (Position)malloc(sizeof(struct LNode)); /* 申请、填装结点 */
        tmp->Data = X; 
        tmp->Next = P;
        pre->Next = tmp;
        return true;
    }
}

/* 带头结点的删除 */
/*注意:在删除位置参数P上与课程视频有所不同,课程视频中i是序列位序(从1开始),这里P是拟删除结点指针 */
bool Delete( List L, Position P )
{ /* 这里默认L有头结点 */
    Position pre;

    /* 查找P的前一个结点 */        
    for ( pre=L; pre&&pre->Next!=P; pre=pre->Next ) ;            
    if ( pre==NULL || P==NULL) { /* P所指的结点不在L中 */
        printf("删除位置参数错误\n");
        return false;
    }
    else { /* 找到了P的前一个结点pre */
        /* 将P位置的结点删除 */
        pre->Next = P->Next;
        free(P);
        return true;
    }
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱睡觉的小馨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值