栈、队列和数组

第三章 栈、队列和数组

栈和队列可以看作是特殊的线性表,它们是 运算受限的线性表

栈的示意图.png

栈的基本概念

栈是 只能在表的一端(表尾) 进行插入和删除的线性表。

允许插入及删除的一端(表尾)称为 栈顶

另一端(表头)称为 栈底

当表中没有元素的时候称为 空栈

进栈:在栈顶插入一个元素。

出栈:在栈顶删除一个元素。

栈的特点:后进先出。所以栈被称为 后进先出线性表

栈的用途:常用于 暂时保存有待处理的数据

栈的基本运算:

  1. 初始化栈:InitStack(S)
  2. 判栈空:EmptyStack(S)
  3. 进栈:Push(S,x)
  4. 出栈:Pop(S)
  5. 取栈顶:GetTop(S)

栈的顺序实现

顺序栈及常用名称

顺序栈:栈的顺序实现。

栈容量:栈中可存放的最大元素个数。

栈顶指针,top:指示当前栈顶元素在栈中的位置。

栈空:栈中无元素时,表示栈空。

栈满:数组空间已被占满,称栈满。

下溢:当栈空时,再要求做出栈运算,则称“下溢”。

上溢:当栈空时,再要求做进栈运算,则称“上溢”。

顺序栈的类型定义

const int maxsize = 6;

typedef struct seqStack
{
  DataType data[maxsize];
  int top;
} SeqStk;

// 定义一个顺序栈
SeqStk *s;

// 约定栈的第一个元素放在 data[1] 中

// 代表顺序栈s为空
s -> top == 0;
// 代表顺序栈s为满
s -> top == maxsize - 1;

栈的第一个元素放在 data[1] 中,从索引 1 开始。

顺序栈的运算

初始化

int InitStack(SeqStk *stk)
{
  stk -> top = 0;
  return 1;
};

判栈空

int EmptyStack(SeqStk *stk)
{
  if (stk -> top == 0) return 1;
  else return 0;
};

进栈

int Push(SeqStk *stk, DataType x)
{
  if (stk -> top == maxsize - 1) {
    error("栈满");
    return 0;
  } else {
    stk -> top ++;
    stk -> data[stk -> top] = x;
    return 1;
  }
};

出栈

int Pop(SeqStk *stk)
{
  if (stk -> top == 0) {
    error("栈空");
    return 0;
  } else {
    stk -> top --;
    return 1;
  }
};

取栈顶元素

DataType GetTop(SeqStk *stk)
{
  if (EmptyStack(stk)) return NULLData;
  else return stk -> data[stk -> top];
};

双栈

双栈.png

栈的链接实现

栈的链接存储结构称为链栈,它是运算受限的单链表,插入和删除操作仅限制在表头位置上进行。栈顶指针就是链表的头指针。

链栈的类型定义

typedef struct node {
  DataType data;
  struct node *next;
} LkStk;

// 下溢条件
LS -> next == NULL;

链栈的运算

初始化

void InitStack(LkStk *LS)
{
  // 最初的内存分配
  LS = (LkStk *) malloc(sizeof(LkStk));
  LS -> next = NULL;
};

判栈空

int EmptyStack(LkStk *LS)
{
  if (LS -> next == NULL) return 1;
  else return 0;
};

进栈

void Push(LkStk *LS, DataType x)
{
  LkStk *temp = (LkStk *) malloc(sizeof(LkStk));
  temp -> data = x;
  temp -> next = LS -> next;
  LS -> next = temp;
};

出栈

int Pop(LkStk * LS)
{
  if (!EmptyStack(LS))
  {
    LkStk *temp = LS -> next;
    LS -> next = temp -> next;
    free(temp);
    return 1;
  }
  else return 0;
};

取栈顶元素

DataType GetTop(LkStk *LS)
{
  if (!EmptyStack(LS)) retrun LS -> next -> data;
  else return NULLData;
};

队列

队列的基本概念

队列(Queue)也是一种运算受限的线性表。

队列是 只允许在表的一端进行插入,而且另一端进行删除 的线性表。

对头,front:允许删除的一端。

对尾,rear:允许插入的另一端。

队列的特点:先进先出(FIFO)

队列的基本操作:

  1. 初始化:InitQueue(Q)
  2. 判空:EmptyQueue(Q)
  3. 入队列:EnQueue(Q,x)
  4. 出队列:OutQueue(Q)
  5. 取队首:GetHead(Q)

队列的顺序实现 - 循环队列

用一维数组作为多列的存储结构。

队列容量,maxsize:队列中可存放的最大元素个数。

队列指针,front:指向实际队头元素的前一个位置。

队尾指针,rear:指向实际队尾元素。

循环队列的类型定义

const int maxsize = 20;
typedef struct CycQueue
{
  DataType data[maxsize];
  int front, rear;
} CycQue;

循环队列CQ,约定:

  1. 下溢条件(队列空):CQ.front == CQ.rear;
  2. 上溢条件(队列满):(CQ.rear + 1) % maxsize == CQ.front;

浪费一个空间,队满时实际队列容量 = maxsize - 1

循环队列的运算

初始化

void InitQueue(CycQue CQ)
{
  CQ.front = 0;
  CQ.rear = 0;
};

判队空

int EmptyQueue(CycQue CQ)
{
  if (CQ.front == CQ.rear) return 0;
  else return 0;
};

入队列

int EnQueue(CycQue CQ, DataType x)
{
  if ((CQ.rear + 1) % maxsize == CQ.front)
  {
    error('队列满');
    retrun 0;
  }
  else
  {
    CQ.rear = (CQ.rear + 1) % maxsize;
    CQ.data[CQ.rear] = x;
    return 1;
  }
};

出队列

int OutQueue(CycQue CQ)
{
  if (EmptyQueue(CQ))
  {
    error('队列空');
    return 0;
  }
  else
  {
    CQ.front = (CQ.front + 1) % maxsize;
    retrun 1;
  }
};

取队首

DataType GetHead(CycQue CQ)
{
  if (EmptyQueue(CQ)) return NULLData;
  else retrun CQ.data[(CQ.front + 1) % maxsize];
};

队列的链式实现 - 链队列

链式队列的定义

链式队列:用链表表示的队列,即它式限制尽在 表头删除表尾插入 的单链表。

附设两指针:

  1. 头指针,front:指向表头结点,队首元素结点为 front -> next;
  2. 尾指针,rear:指向链表的最后一个结点(即队尾结点);

链队列的类型定义

typedef struct LinkQueueNode
{
  DataType data;
  struct LinkQueueNode *next;
} LkQueNode;

typedef struct LkQueue
{
  LkQueue *front, *rear;
} LkQue;

规定:链队列空时,令rear指针也指向表头结点

链队列下溢条件(链为空):

LQ.front == LQ.rear 或 LQ.front -> next == NULL

链队列的运算

初始化

void InitQueue(LkQue *LQ)
{
  LkQueNode *temp = (LkQueNode *) malloc(sizeof(LkQueNode));
  temp -> next = NULL;
  LQ -> front = temp;
  LQ -> rear = temp;
};

判队空

int EmptyQueue(LkQue LQ)
{
  if (LQ.rear == LQ.front) return 1;
  else return 0;
};

入队列

void EnQueue(LkQue *LQ, DataType x)
{
  LkQueNode *temp = (LkQueNode *) malloc(sizeof(LkQueNode));
  temp -> data = x;
  temp -> next = NULL;
  (LQ -> rear) -> next = temp;
  LQ -> rear = temp;
};

出队列

int OutQueue(LkQue *LQ)
{
  if (EmptyQueue(LQ))
  {
    error('队空');
    return 0;
  }
  else
  {
    LkQueNode *temp = (LQ -> front) -> next;
    (LQ -> front) -> next = temp -> next;
    if (temp -> next == NULL) LQ -> rear = LQ -> front;
    free(temp);
    return 1;
  }
};

取队首

DataType GetHead(LkQue LQ)
{
  if (EmptyQueue(LQ)) retrun NULLData;
  else return (LQ.front -> next) -> data;
};

数组

数组可以看成是一种特殊的线性表,其特殊在于,表中的数组元素本身也是一种线性表。

数组的逻辑结构和基本运算

数组是 线性表的推广,其每个元素由一个值和一个组下表组成,其中下标个数称为 数组的维数

数组中各元素具有统一的类型,并且数组元素的下标一般具有固定的上界和下界。如二维数组 A_{ mn }

\begin{bmatrix} {a_{11}}&{a_{12}}&{\cdots}&{a_{1n}}\\ {a_{21}}&{a_{22}}&{\cdots}&{a_{2n}}\\ {\vdots}&{\vdots}&{\ddots}&{\vdots}\\ {a_{m1}}&{a_{m2}}&{\cdots}&{a_{mn}}\\ \end{bmatrix}

二维数组 A_{ mn } 可以看成是由 m 个行向量组成的向量,也可以看成是由 n 个列向量组成的向量。

数组一旦被定义,它的维数和维界就不再改变。因此,除了结构的初始化和销毁之外,数组通常只有 两种基本运算

  1. :给定一组下标,读取 相应的数据元素;
  2. :给定一组下标,修改 相应的数据元素;

数组的存储结构 - 顺序存储结构

由于计算机的 内存结构是一维的,因此用一维内存来表示多维数组,就必须按某种次序将数组元素拍成一列序列,然后将这个线性序列存放在存储器中。

又由于对数组一般不做插入和删除操作,也就是说,数组一旦建立,结构中的元素个数和元素间的关系就不再发生变化。因此,一般都采用 顺序存储 的方法来表示数组。

寻址公式(以行为主存放)

例如:二维数组 A_{ mn } 按 “行优先排序” 存储在内存中,假设每个元素占用k个存储单位。

元素 a_{ ij } 的存储地址应是数组的基地址加上排在 a_{ ij } 前面的元素所占用的单元数。所以可得:

LOC(a_{ ij }) = LOC(a_{ 00 }) + (i * n + j) k

数组例题1.png
数组例题2.png

矩阵的压缩存储

特殊矩阵

指非零元素或零元素的分布有一定规律的矩阵。

对称矩阵

在一个n阶方阵A中,若元素满足下述性质:

a_{ ij } = a_{ ji }0 <= i, j <= n - 1

如下,便是一个 5阶对称矩阵

\begin{bmatrix} {1}&{5}&{1}&{3}&{7}\\ {5}&{0}&{8}&{0}&{0}\\ {1}&{8}&{9}&{2}&{6}\\ {3}&{0}&{2}&{5}&{1}\\ {7}&{0}&{6}&{1}&{3}\\ \end{bmatrix}

对称矩阵中的元素 关于主对角线对称,故 只要存储矩阵中上三角或下三角中的元素,让每两个堆成的元素共享一个存储空间,这样,能解决近一半的存储空间。

不失一般性,我们按 “行优先顺序” 存储主对角线(包括对角线)以下的元素。

元素总数为:\sum(i) = \frac{ n (n + 1) }{ 2 }

对称矩阵例题.png

下标变换公式:(以下三角表示)

用 i 代表第几行,用 j 代表第几列

若 i >= j,则 a_{ ij } 是在 下三角形 中,因此:

a_{ ij } = \frac{ i * (i + 1) }{ 2 } + j

若 i < j,则 a_{ ij } 是在 上三角形 中,因为 a_{ ij } = a_{ ji },因此:

a_{ ij } = a_{ ji } = \frac{ j * (j + 1) }{ 2 } + i

三角矩阵

以主对角线划分,三角矩阵有:(在大多数情况下,矩阵常数为零)

  1. 上三角:下三角中的元素均为常数;
  2. 下三角:上三角中的元素均为常数;

稀疏矩阵

设矩阵A中有s个非零元素,若s远远小于矩阵元素的总数,则称A为稀疏矩阵。

稀疏矩阵的压缩存储的目的:节省存储空间

由于非零元素的分布一般是没有规律的,因此在存储非零元素的同时,还必须同时记下它所在的行和列的位置 (i, j)。反之,一个三元组 (i, j, a_{ ij }) 唯一确定了矩阵的一个非零元素。因此,稀疏矩阵可由表示非零元素的三元组及其行列数唯一确定。

稀疏矩阵的三元组.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容