IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 栈和队列能熟练使用么?&怎么存储矩阵呢?(更少的引用,更多的思考) -> 正文阅读

[数据结构与算法]栈和队列能熟练使用么?&怎么存储矩阵呢?(更少的引用,更多的思考)

一、前言

? ? ?在上一篇《数据结构内功修炼之线性表》中,采用了中规中矩的方法。文章是很长的,但是认真看下来你会发现——真的长!不过也难怪嘛,线性表里面顺序表和链表本来就是一切的基石,所以很多基本问题自然是要仔仔细细看的,当然看严蔚敏老师的书也更好。所以这一片博客,让我们在规矩的结构上,大胆各种发挥一波。???

? ? ?按照惯例给出本章的知识结构吧!

? ? ?你可能会对一些名词奇怪,例如什么是共享栈?什么是双端队列,这里不是说栈和队列吗,为什么谈到了数组?——别担心,让我们一起往下思考


二、栈

? ? ?定义:栈(Stack)是限定在表尾进行插入或删除操作的限定表。表尾端称为栈顶,表头端称为栈底(就是允许插入删除的那一端称为栈顶,不允许的那一端称为栈底,空栈就是不含任何元素的空表)。

? ? ?逻辑结构:操作受限的线性表

? ? ?存储结构:有顺序存储也有链式存储

? ? ?运算:初始化、判断非空、出栈、入栈、读栈顶元素、销毁栈。

1、顺序栈的一些操作思考。

(1)关于初始化栈顶指针的问题?

? ? ?你有没有想过,利用顺序表来操作栈的话肯定是要有栈顶指针来执行栈顶元素的,方便我们实现出栈、入栈、判空等重要的操作。假如顺序栈的引用设为S,那么初始化栈头指针是S.top=-1还是S.top=0呢?

? ? ?如果这两者有其中之一比较好,那就不会两个都存在,但不管是S.top=-1还是S.top=0,性质都是一样的。可是呢,不同的定义往往会影响操作。那么我们来看看,对于两者到底有什么区别呢?

  • 初始化

? ? ?在严蔚敏老师的课本中,S.top表示栈顶元素在顺序栈中的位置,并且她让栈顶元素初始化为S.top=0。为了用C语言描述时更加方便,所以也引入了S.base指针,也就是初始化就指向栈底,之后一直不变的指针。这样判空就为S.top=S.base。而初始化的代码为:

Status InitStack(SqStack &S)
{
    S.base = new SElemType[MAXSIZE];
    if(!S.base) exit(OVERFLOW);
    S.top = S.base;
    S.stacksize = MAXSIZE;
    return 0;
}

? ? ?而当我们初始化S.top=-1时,初始化代码可以简单的写出这样(省略了S = new SElemType[MAXSIZE]这样的代码):

void InitStack(SqStack &S)
{
    S.top = -1;
}

? ? ?从这里是看不出区别的,但是我们往下看:

  • 入栈

? ? ?S.top=0的情况

Status Push(SqStack &S,SElemType e)
{
    if(S.top-S.base==S.stacksize) return ERROR;//栈满
    *S.top++=e;//先将元素压入栈,再栈顶指针加一
    return OK;
}

? ? ?S.top=-1的情况

bool Push(SqStack &S,ElemType x) {
    if(S.top==MaxSize-1)//栈满
        return false;
    S.data[++S.top]=x;//指针先加一,再入栈
    return true;
}
    

? ? ?我们发现当S.top=0时,我们在入栈的时候直接先赋值,栈顶指针再加一;而S.top=-1时,我们先栈顶指针加一,再赋值。难道这有什么规律吗?——

? ? ?不!没有任何规律,你只要画画图就能够明白,这个问题毫无价值。

  • 出栈

? ? ?那么出栈呢?当S.top=0时,出栈不就是先让栈顶指针减一,然后把值拿出去;当S.top=-1时,出栈不就是直接拿出去,然后栈顶指针再减一吗?有图有真相:

  • 取栈顶元素

? ? ?取栈顶元素指针当然不变啦,不同的是因为初始化栈顶指针的不同,所以对于S.top=0这样的初始化来说,x=S.data[S.top-1]就好,而对于S.top=-1这样的初始化来说,x=S.data[S.top]就好。

2、共享栈

? ? ? ? ? 利用栈底位置相对不变的特性,可以让两个顺序栈共享一个一维数组空间,将两个栈的栈底分别设置在共享空间的两端,两个栈顶向共享空间中间延伸。

? ? ?两个栈的栈顶指针都指向栈顶元素,top0=-1时0号栈为空,top1=MaxSize时1号栈为空(这里我们初始赋为-1类似的情况);仅当两个栈顶指针相邻(top1-top0=1)时,判断为栈满(例如0号栈到了2,1号栈到了3,不就满了吗?)。当0号栈进栈时top0先加一在赋值,1号栈进站时top1先减1在赋值;出站时刚好相反。

3、关于链栈的一些思考

? ? ?这里我们采用没有头结点的链表来表示栈,入栈和出栈操作都在链表的表头进行。那么我们需要注意什么呢?

(1)链栈的出栈

? ? ?如果你熟悉链表的操作的话,你会知道入栈也就是在表头添加结点是很简单的(如果你还记得我招小弟的说法,也就是新插入的结点指向L,然后这里是将L的指针移到插入的结点即可),但是不管是从表头删除结点还是从其它的位置删除,我们都需要注意一件事情,你猜是什么?

? ? ?当然是保存要要出结点的指针,以备释放啦,所以会有类似出栈的代码如下:

Status Pop(LinkStack &S,SElemType &e)
{
    if(S==NULL) return ERROR;
    e=S->data;
    p=S;
    S=S->next;
    delete p;
    return OK;
}

? ? ?解读一下上面的代码,不就是先判断是否为空,然后得到值,利用P指针保存当前栈顶,将下一个结点变为首元结点,然后释放原栈顶元素的空间即可。


二、队列

? ? ?队列(Queue)简称队,也是一种操作受限的线性表,只允许在表的一端进行插入,而在表的另一端进行删除。向队列中插入元素成为入队或进队;删除元素称为出队或离队(先进先出First In First Out,FIFO)。

逻辑结构:操作受限的线性表

物理结构:可以用顺序表实现,也可以用链表实现

运算:初始化队列、判队列空、入队、出队、读队头元素

? ? ?好的,现在我们手里有两个工具,数组和链表。需要实现上面特点的数据结构,你会怎么做呢?

1、队列的顺序存储

? ? ?如果要利用顺序表去实现这个功能,那么队头和队尾肯定需要有指针来指示操作的,并且我们需要设定初始情况,入队、出队以及判空,判断是否队列已满等情况的代码。给出顺序存储结构的表如下:

#define MAXQSIZE 100
typedef struct
{
    QElemType *base;
    int front;
    int rear;
}SqQueue;

? ? ?拥有了这样的结构,那么我们初始化front=rear=0,然后入队的时候只需要插入新元素,然后尾指针加一;出队的时候只需要将头指针front加一。因此在非空队列中,头指针始终指向队头元素,而尾指针始终指向队尾元素的下一个位置。但是会出现这样的情况!!!

? ? ? 我们发现如果再次添加就满了,判断是否满了如果用rear=MAXSIZE是可以组织上面的情况发生的(假溢出),但很明显我们空间还有很多,所以,我们肯定要想办法把空间利用起来,比如让rear指针添加的时候到第0个位置不就好了吗,那肯定是取模的方法可以办到。也就是说入队的时候我们指针变化有rear = (rear+1)%MAXSIZE;这样上面的情况就是rear = (3+1)%4 = 0,尾指针就到下面来了。

? ? ?所以对于运算指针的变化依旧有:

  • 初始时Q.front = Q.rear = 0;
  • 入队Q.rear = (Q.rear+1)%MAXSIZE;
  • 出队Q.font = (Q.front+1)%MAXSIZE;
  • 队列长度(Q.rear-Q.front+MAXSIZE)%MAXSIZE;(长度本来就可以用rear-front,但是当我们采用循环的时候可能减出来是负值,所以加一个MAXSIZE上去,在取余)
  • 判队列满???

? ? ?(1)我们用增设表示元素个数的数据成员,当Q.size=MAXSIZE就满了。

? ? ?(2)但是大多采用的都是牺牲一个单元来区分队空和队满,入队时少用一个队列单元,约定“队头指针在队尾指针的下一位置作为队列满的标志”。这里很好理解,你想啊,现在我们的队尾指针一直都是指向队尾元素的下一个,也就是说它当前的位置是空的,而下一个位置如果是队头指针的话,当然为空啦!

? ? ?所以判断的表达式不就是(rear+1)%MAXSIZE = front用来判断队满吗,而队空一直没有变,就是front=rear。

2、链式队列

? ? ?顺序的实现了,你想来试试链式队列???

? ? ?队列的链式表示成为链队列,实际上是一个同时带有队头指针和队尾指针的单链表。头指针指向队头结点,尾指针指向队尾结点,即单链表的最后一个结点。

? ? ?我们在设置链栈时,采用的是不设置头结点的链表形式,这里我们需要采用带头结点的链表来实现,为什么呢?你想啊,我们总是在头结点删除,如果不设置头结点的话,总是需要去找到首元结点(也就是当前要出队删除的结点的下一个结点记住它的指针来实现),而有了头结点,直接头结点指向下一个结点就实现了,多方便呀)。所以关于链队,没有啥说的,但一定要注意链表的插入和删除操作,必要时画图帮助理解。

3、双端队列

? ? ?双端队列是指允许两端都可以进行入队和出队操作的队列。其元素的逻辑结构仍然是线性结构,队列的两端分别成为前端和后盾,两端都可以入队和出队。

? ? ?这里我们需要了解两个概念即可,其它的,画图都是可以解决的。

? ? ?输出受限的双端队列:允许在一端进行插入和删除,但在另一端只允许删除的双端队列称为输出受限的双端队列。

? ? ?输入受限的双端队列:允许在一端进行插入和删除 ,但在另一端只允许删除的双端队列称为输入受限的双端队列。


三、数组(特殊矩阵的压缩存储)

? ? ?在高级语言中,数组我们在熟悉不过。这里可以给出一个简短的定义:

? ? ?数组是由n(n>=1)个相同类型的数据元素构成的优先序列,每个元素称为一个数组元素,每个元素在n个线性关系中的序号称为该元素的下标,下标的取值范围称为元素的维界。

? ? ?为什么在这里要提到数组,因为它其实也是线性表的推广。一维数组可以视为一个线性表;二维数组可以视为其元素也是定长线性表的线性表。

? ? ?那,我们这里会介绍关于数据的小标存储关系吗?不会,这里要知道的是特殊举证的存储。

? ? ?矩阵充满了现在计算机的应用,而矩阵的存储当然十分的重要。现在我们思考,除了一般的矩阵我们需要一一存储,特殊的矩阵如果在一一存储的话不就浪费了巨大的空间吗?所以我们需要了解如何去压缩存储。

1、对称矩阵

? ? ?用线性代数的方法来说,对称矩阵就是矩阵转置之后等于本身的矩阵。但这显得抽象,而我们给出对一个n阶方阵A[1...n][1...n]中的任意一个元素aij都有aij = aji(1<=i,j<=n),则称为对称矩阵。这个时候你的脑中应该有一条对角线,线的两侧的元素都是对称的(例如y=x线对称)。

? ? ?Why?对于对称矩阵来说,如果我们直接用二维矩阵存储元素,那么会浪费几乎一般的空间(大概是(n^2-n)/2空间)。所以我们可以选择只存储对角线及其上半部分或者下半部分的元素,这样看空间就节省下来了,那么怎么存储呢?

? ? ?首先我们需要一个一位数组空间B[n(n+1)/2]即可。

(1)存

? ? ?存的时候我们可以直接利用双重的for循环把数据存到一位数组空间B中,例如代码:

for(i=1;i<n;i++)
    for(j=1;j<i+1;j++)
        B[k++] = a[i][j]

? ? ?这样我们就将原本存储在二维数组a中的数据存到了B中,但是要怎么取呢??

(2)取

? ? ?如果要从B中取a[i][j]这个元素,我们会发现有这样的下标关系:

? ? ?所以只需要带入i,j的值到B下标关系中,就可以取到相应的元素值(只需要推到一侧的,因为是对称矩阵,所以i<j时情况一致)。如果你自己手动的推到一下就会发现,这只是简单的等差数列求和的公式,即使记不住,现场推导也是可以的。

2、三角矩阵

? ? ?定义:三角矩阵有下三角矩阵和上三角矩阵。

1)下三角矩阵

? ? ?(1)存

? ? ? ? ? 存的时候我们依旧可以利用上面的方法,将二维数组里面的元素存到一维数组B中。这里二维数组指的形式,不是真的先存到二维数组中再去转存到一维数组,这样空间节约在哪里呢?而是说我们有形如这样的二维数组,然后存到一维数组中就好,这样就压缩存储了。取出来的时候根据下标关系就可以了,也就实现了节省空间的做法。

? ? ?(2)取

? ? ?这里我省略推到的过程,因为下三角矩阵的推到关系和对称矩阵一致,不同的是之前在对称矩阵中我们申请的是B[n(n+1)/2]的数组,而下三角矩阵需要多一个来存储同一常量(例如0啥的),所以就有了上式。

2)上三角矩阵

? ? ?(1)存:~~~~~~~~~~~~~~~~~~~~~·存进去即可,存到一个一维数组节省空间。

? ? ?(2)取:

? ? ?这里我们可以一起来推到一下去aij的关系式情况,我们仅仅需要知道aij是在B数组中第几个就好,按行存储,所以:

当i<=j时,也就是上面的右上角部分的关系如下:

第一行:n个元素

第2行:n-1个元素

...

第i-1行:n-i+2个元素

第i行:j-i个元素

所以等差数列求和不就有(i-1)(2n-i+2)/2+(j-i)。而i>j时,最后一个位置存放即可。

3、三对角矩阵

? ? ?定义:对角矩阵也称为带状矩阵。对于n阶方阵A中的任一元素aij,当|i-j|<1时,有aij=0(1<=i,j<=n),则称为三对角矩阵,上图!

? ? ?(1)存:~同上

? ? ?(2)取

? ? ? ? ? 三对角矩阵取的话,我没有找到很好的规律,所以就给出结果吧,但有趣的是反取。也就是知道B数组中的位置,去找aij。

4、稀疏矩阵

? ? ?至于稀疏矩阵的存储,我们可以采用三元组的方式,也可以采用十字链表法存储。这里不再赘述,给出一张三元组的图片就知道有多么便捷了。


总结:

? ? ? 栈、队列和数组及其的重要,我们要熟悉它们的逻辑结构、存储结构和运算,冲冲冲!

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2021-07-15 23:50:43  更:2021-07-15 23:50:56 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 16:34:39-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码