第三章 栈、队列和数组
栈和队列可以看作是特殊的线性表,它们是 运算受限的线性表。
栈
栈的基本概念
栈是 只能在表的一端(表尾) 进行插入和删除的线性表。
允许插入及删除的一端(表尾)称为 栈顶。
另一端(表头)称为 栈底。
当表中没有元素的时候称为 空栈。
进栈:在栈顶插入一个元素。
出栈:在栈顶删除一个元素。
栈的特点:后进先出。所以栈被称为 后进先出线性表。
栈的用途:常用于 暂时保存有待处理的数据。
栈的基本运算:
- 初始化栈:
InitStack(S)
- 判栈空:
EmptyStack(S)
- 进栈:
Push(S,x)
- 出栈:
Pop(S)
- 取栈顶:
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];
};
双栈
栈的链接实现
栈的链接存储结构称为链栈,它是运算受限的单链表,插入和删除操作仅限制在表头位置上进行。栈顶指针就是链表的头指针。
链栈的类型定义
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)。
队列的基本操作:
- 初始化:
InitQueue(Q)
; - 判空:
EmptyQueue(Q)
; - 入队列:
EnQueue(Q,x)
; - 出队列:
OutQueue(Q)
; - 取队首:
GetHead(Q)
;
队列的顺序实现 - 循环队列
用一维数组作为多列的存储结构。
队列容量,maxsize:队列中可存放的最大元素个数。
队列指针,front:指向实际队头元素的前一个位置。
队尾指针,rear:指向实际队尾元素。
循环队列的类型定义
const int maxsize = 20;
typedef struct CycQueue
{
DataType data[maxsize];
int front, rear;
} CycQue;
循环队列CQ,约定:
- 下溢条件(队列空):CQ.front == CQ.rear;
- 上溢条件(队列满):(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];
};
队列的链式实现 - 链队列
链式队列的定义
链式队列:用链表表示的队列,即它式限制尽在 表头删除 和 表尾插入 的单链表。
附设两指针:
- 头指针,front:指向表头结点,队首元素结点为 front -> next;
- 尾指针,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 个列向量组成的向量。
数组一旦被定义,它的维数和维界就不再改变。因此,除了结构的初始化和销毁之外,数组通常只有 两种基本运算:
- 读:给定一组下标,读取 相应的数据元素;
- 写:给定一组下标,修改 相应的数据元素;
数组的存储结构 - 顺序存储结构
由于计算机的 内存结构是一维的,因此用一维内存来表示多维数组,就必须按某种次序将数组元素拍成一列序列,然后将这个线性序列存放在存储器中。
又由于对数组一般不做插入和删除操作,也就是说,数组一旦建立,结构中的元素个数和元素间的关系就不再发生变化。因此,一般都采用 顺序存储 的方法来表示数组。
寻址公式(以行为主存放):
例如:二维数组 按 “行优先排序” 存储在内存中,假设每个元素占用k个存储单位。
元素 的存储地址应是数组的基地址加上排在 前面的元素所占用的单元数。所以可得:
矩阵的压缩存储
特殊矩阵
指非零元素或零元素的分布有一定规律的矩阵。
对称矩阵
在一个n阶方阵A中,若元素满足下述性质:
且
如下,便是一个 5阶对称矩阵:
对称矩阵中的元素 关于主对角线对称,故 只要存储矩阵中上三角或下三角中的元素,让每两个堆成的元素共享一个存储空间,这样,能解决近一半的存储空间。
不失一般性,我们按 “行优先顺序” 存储主对角线(包括对角线)以下的元素。
元素总数为:
下标变换公式:(以下三角表示)
用 i 代表第几行,用 j 代表第几列
若 i >= j,则 是在 下三角形 中,因此:
若 i < j,则 是在 上三角形 中,因为 ,因此:
三角矩阵
以主对角线划分,三角矩阵有:(在大多数情况下,矩阵常数为零)
- 上三角:下三角中的元素均为常数;
- 下三角:上三角中的元素均为常数;
稀疏矩阵
设矩阵A中有s个非零元素,若s远远小于矩阵元素的总数,则称A为稀疏矩阵。
稀疏矩阵的压缩存储的目的:节省存储空间。
由于非零元素的分布一般是没有规律的,因此在存储非零元素的同时,还必须同时记下它所在的行和列的位置 。反之,一个三元组 唯一确定了矩阵的一个非零元素。因此,稀疏矩阵可由表示非零元素的三元组及其行列数唯一确定。