02142 <数据结构导论>第三章 栈,队列和数组

文章目录

  • 3.1 栈(stack)
    • 3.1.1 基本概念
    • 3.1.2 栈的顺序实现
    • 3.1.3 栈的链接实现
    • 3.1.4 递归
  • 3.2 队列(queue)
    • 3.2.1 队列的基本概念
    • 3.2.2 队列的顺序实现
    • 3.2.3 队列的链接实现
  • 3.3 数组
    • 3.3.1 逻辑结构和基本运算
    • 3.3.2 数组的存储结构
    • 3.3.3 矩阵的压缩存储

3.1 栈(stack)

3.1.1 基本概念

  • 栈(Stack)运算受限的线性表, 这种线性表的插入和删除运算限定在表的某一端进行, 允许进行插入和删除的一端称为栈顶, 另一端为栈底. 不含任何元素的栈称为空栈, 处于栈顶位置的元素称为栈顶元素.
  • 栈的修改原则是后进先出(Last In First Out, LIFO), 栈又称为后进先出表, 栈的插入和的删除运算称为进栈和出栈
  • 栈的基本运算:
  • InitStack(S): 构造一个空栈S
  • EmptyStack(S): 判断栈S是否为空, 若空返回1, 若非空返回0
  • Push(S,x): 进栈, 将元素x插入S中, 使x为S的栈顶元素
  • Pop(S):出栈,删除栈顶元素
  • 取栈顶 GetTop(S): 返回栈顶元素.

3.1.2 栈的顺序实现

为防止数据丢失, 在进栈操作之前应判断是否栈满
顺序栈使用C定义如下:

const int maxsize =6;
typedef struct seqstack {
     
	DataType data[maxsize];
	int top;
} seqStk;

maxsize 为顺序栈的容量
data[maxsize] 存储栈中数据元素的数组
top 标志栈顶位置的变量, 范围为0到maxsize-1

  1. 初始化
int InitStack(SeqStk *stk){
     
	stk->top =0;
	return 1;
}
  1. 判栈空
int EmptyStack(SeqStk *stk){
     
	if (stk->top ==0) return  1;
	else return 0;
}
  1. 进栈
int Push(SeqStk *stk, DateType x){
     
	if (stk->top == maxsize-1)
	{
     error("栈满"); return 0'}
	else{
     
		stk->top++;
		stk->data[stk->top] =x;
		return 1;
}
}
  1. 出栈
int Pop(SeqStk *stk){
     
	if (stk->top ==0){
     
		error("栈空");
		return 0;
	}
	else{
     
	stk->top--;
	return 1;
}
}
  1. 取栈顶元素
DataType GetTop(SeqStk *stk){
     
	if (EmptyStack(stk)) return NULLData;
	else return stk->data[stk->top]
}

3.1.3 栈的链接实现

栈的链接实现称为链栈, 链栈可以用带头结点的单链表实现
LS指向链表的头结点, 首结点即是栈顶结点, LS->next指向栈顶结点, 尾结点为栈底结点. 链栈不用考虑容量的大小.
C语言定义如下:

typedef struct node {
     
	DataType data;
	struct node *next;
}LkStk;
  1. 初始化
void InitStack(LkStk *LS){
     
	LS = (LkStk *)malloc(sizeof(LkStk));
	LS->next = NULL; // 建立一个空栈
}
  1. 判栈空
int EmptyStack(LkStk *LS){
     
	if (LS->next==NULL) return 1;
	else return 0;
}
  1. 进栈
void Push(LkStk *LS, DataType x){
     
	LkStk *temp;
	temp = (LkStk *)malloc(sizeof(LkStk));  // 指向申请的新结点
	temp->data = x; // 新结点的值赋为x
	temp->next=LS->next; // 新结点的next域指向原来的栈顶结点
	LS->next = temp; // 指向新的栈顶结点
}
  1. 出栈
int Pop(LkStk *LS){
     
	LkStk *temp;
	if (EmptyStack(LS)) return 0;
	else {
     
	temp = LS->next;
	LS->next = temp->next;
	free(temp);
	return 1;
	}
}

这里多想一点:
为什么不使用LS->next=LS->next->next这样的方式直接将栈顶指向次栈顶结点呢?
若使用此种方式, 原栈顶结点还是存在的,且还占用内存空间, 因为没有释放它,
所以这里引入 temp 指针, 第一步: temp指向栈顶, 第二步: 将原栈顶结点指向次栈顶, 第三步,释放temp.
5. 取栈顶元素

DataType GetTop(LsStk *LS){
     
	if (EmptyStack(LS)) return NULLData;
	else {
     
		return LS->next->data;  // 返回栈顶元素
	}
}

3.1.4 递归

递归: 如果一个函数或数据结构的定义中又应用了它自身, 那么这个函数或数据结构称为递归定义的, 简称递归的.
递归定义不能是"循环定义", 必须以下两个条件:

  1. 被定义项在定义中的应用具有更新小的"规模"(有限递归)
  2. 被定义项在最小规模上的定义是非递归的, 这是递归的结束条件.

3.2 队列(queue)

3.2.1 队列的基本概念

  • 队列是有限个同类型数据元素的线性序列, 是一种先进先出(First In First Out, FIFO)的线性表, 新加入的元素在队列尾端, 出队列的数据元素在队列首部被删除.
  • 排队的规则不允许"插队", 新加入的元素只能在队尾,而且全体成员只能按照入队列的顺序离开队列.
  • 基本运算:
  • 队列初始化: InitQueue(Q): 设置一个空队列
  • 判队列空: EmptyQueue(Q): 若队列为空,返回1, 否则返回0
  • 入队列: EnQueue(Q, x): 将数据元素x从队尾一端插入队列, 使其成为队列的新的尾元素.
  • 出队列: OutQueue(Q): 删除队首元素
  • 取出队首元素: GetHead(Q): 返回队列首元素的值.

3.2.2 队列的顺序实现

const int maxsize=20;
typedef struct seqqueue{
     
	DataType Data[maxsize];  //一维数组, 存储队列中的数据元素
	int front, rear; // front指向<重要: 队首元素的前一个单元>, rear指向实际的队尾元素单元
} SeqQue;
SeqQue SQ;

Data: 一维数组, 存储队列中的数据元素
front: 指向<重要: 队首元素的前一个单元>
rear: 指向实际的队尾元素单元
为什么不使用线性表, 而使用循环线性表?
参考p72页,

循环队列的基本运算如下:

  1. 队列的初始化
void InitQueue(CycQue CQ){
     
	CQ.front = 0;
	CQ.rear = 0;
}
  1. 判队列空
int EmptyQueue(CycQue CQ){
     
	if (CQ.front == CQ.rear) return 1;
	else return 0;
}
  1. 入队列
void EnQueue(CycQue CQ, DataTye x){
     
	if ((CQ.rear+1)%maxsize == CQ.front) {
     
	error("队列已满");
	return 0;
	}
	else {
     
	CQ.rear = (CQ.rear+1) % maxsize;
	CQ.data[CQ.rear] = x;
	return 1;
	}
}
  1. 出队列
int OutQueue(CycQue CQ){
     
 	if (EmptyQueue(CQ)) {
     
		error("队列为空了");
		return 0;
	}
	else{
     
		CQ.front = (CQ.front+1)%maxsize;  //出队列成功
		return 1;
	}
}
  1. 取队列首元素
DataType GetHead(CycQue CQ){
     
	if (EmptyQueue(CQ) return NULLData;
	else{
     
	return CQ.data[(CQ.front+1)%maxsize];
}
}

3.2.3 队列的链接实现

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

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

LkQue LQ;
  1. 队列的初始化
void InitQueue(LkQue *LQ){
     
	LkQueNode *temp;
	temp = (LkQueNode *)malloc(sizeof(LkQueNode));
	LQ->front = temp;
	LQ->rear = temp;
	(LQ->front)->next = NULL;
}
  1. 判队列空
int EmptyQueue(LkQue *LQ){
     
	if (LQ.front == LQ.rear) return 1;
	else return 0;
}
  1. 入队列
void EnQueue(LkQue *LQ, DataType x){
     
	LkQueNode *temp;
	temp = (LkQueNode *).malloc(sizeof(LkQueNode));
	temp->data = x;
	temp->next = NULL;
	(LQ->rear)->next = temp; // 新结点入队列
	LQ->rear = temp; // 置新的队列队尾结点
}
  1. 出队列
int OutQueue(LkQue *LQ){
     
	LkQueNode *temp;
	if (EmptyQueue(LQ)){
     
		error("队列为空");
		return 0;
	}
	else{
     
		temp = LQ->front->next;  // temp 指向首结点  
		LQ->front->next = temp->next; // 修改头结点的指针,使其指向新的首结点
		if (temp->next ==NULL); 
		LQ->rear = 	LQ->front;// 无首结点时, front和rear都指向头结点
		free(temp);
		return 1;
	}
}
  1. 取队列首元素
DataType GetHead(LkQue LQ){
     
	LkQueNode *temp;
	if (EmptyQueue(LQ)) return NULLData;
	else {
     
		temp = LQ.front->next;
		return temp->data;  //返回队列首结点元素
	}
}

3.3 数组

3.3.1 逻辑结构和基本运算

  • 数组可以看成线性表的一种推广, 一维数量又称向量, 它由一组具有相同类型的数据元素组成, 并存储在一组连续的存储单元中. 一般地, 一个N维数据可以看成元素为N-1维数组的线性表.
  • 基本运算:
  • 读: 给定一组下标, 返回该位置的元素内容;
  • 写: 给定一组下标, 修改该位置的元素内容;

3.3.2 数组的存储结构

一维数据元素在内存单元地址是连续的, 二维数据可以有种存储方法; 一种以行为主序存储,另一种以列为主序存储. 在类C语言中, 数组采用以行序为主序的存储方法.

数组元素的存储位置是下标的线性函数.(p82)
loc[i, j] = loc[0,0] + (n*i +j)*k

3.3.3 矩阵的压缩存储

在数值分析中,经常出现一些高阶矩阵, 这些高阶矩阵中有许多值是相同的元素或零元素, 为了节省存储空间, 对这类矩阵采用多个值相同的元素只分配一个存储空间, 零元素不存储的策略, 这一方法被称为矩阵的压缩存储.
如果值相同的元素或零元素在矩阵中分布有一定规律, 称此类矩阵为特殊矩阵
矩阵的非零元素个数很少的矩阵称为稀疏矩阵
设有a[9,9]
那么a[4,5]的存储位置k怎么计算:
因为数组连续存储在内存单元中, 因此只要知道了首元素的位置, 又是采用行为主序存储的方式,
因此, 算法就是loc[0,0]的位置 + 4(在第4行)*9(每行有9个元素)+5(第5个元素)
假设loc[0, 0]位置为0, 则 a[4, 5]位置为: 41

  1. 特殊矩阵之对称矩阵(p82)
    若一个N阶方阵A中元素满足aij = aji 且i>=0, j<=n-1, 则A称为三角矩阵
    个人的理解 :
    这个就像99乘法表一样的对称矩阵
    设有: a[9, 9],
    则有 a[2,3] = a[3, 2]
    则可以将其存入9*(9+1)/2 = 45个存储单元中,
    实际验证下哈: 原本是需要 9*9=81个存储单元, 对角线如a11, a22, a33之类的, 共需要9个单元, 剩下的都是对称单元, 因此还需要 (81-9)/2=36单元, 再加上对角线的9个单元, 36+9=45个存储单元,
    给出的对称矩阵只需要n(N+1)/2, 公式是正确滴 哈哈
    这个公式并不适用 a[9, 8]这样的结构
    假设loc[0,0]的位置是0, 那么a[4, 5]的位置即是 (4+1)*4/2 + 5 = 15,
    那么a[5, 4]元素的位置K怎么计算?
    这就简单了呀, 因为a[5, 4] = a[4, 5], 所以位置也是15了, 哈哈

  2. 三角矩阵(p83)
    以主对角线为界的上(下)半部分是一个固定值C或零, 这样的矩阵就是上(下)三角矩阵.

  3. 稀疏矩阵
    假设M行N列的矩阵有T个非零元素, 且T<=M*N, 则矩阵称为稀疏矩阵.
    说白了只记录值不为零的行列号
    如: 使用三元组表((0, 1, 5), (2, 1, -1))
    意思即为: a[0,1]=5, a[2, 1] = -1,其它均是0值,

你可能感兴趣的:(02142,数据结构导论,自考,栈,队列,数组)