栈、队列和数组

第三章 栈、队列和数组

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

栈的示意图.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;
};

数组

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

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

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

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

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

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

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

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

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

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

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

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

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

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

矩阵的压缩存储

特殊矩阵

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

对称矩阵

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

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

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

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

元素总数为:

对称矩阵例题.png

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

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

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

若 i < j,则 是在 上三角形 中,因为 ,因此:

三角矩阵

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

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

稀疏矩阵

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

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

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

稀疏矩阵的三元组.png

你可能感兴趣的:(栈、队列和数组)