[TOC]html
该系列博客的目的是为了学习一遍数据结构中经常使用的概念以及经常使用的算法,为笔试准备;主要学习过程参考王道的《2018年-数据结构-考研复习指导》;前端
已总结章节:node
上篇博客《数据结构与算法》-2-线性表中介绍了线性结构中的线性表的定义、基本操做以及线性表的两种存储方式:顺序存储与链式存储;这一篇博客将主要介绍线性结构中的受限线性表:栈、队列, 线性表推广:数组。算法
主要包含的内容有:后端
其知识框架以下图所示: 数组
栈(Stack): 只能在一端执行插入或删除操做的线性表;数据结构
栈顶(Top):框架
线性表容许执行插入或删除操做的一端;学习
栈底(Botton):spa
线性表不容许执行插入或删除操做的一端;
**注意:**根据栈的定义能够获得,栈是先进后出的线性表;
InitStack(&S)
:初始化一个空栈S;
StackEmpty(S)
:判断S是否为空栈;
Push(&S, x)
:进栈操做;若未满,则将x进栈,做为栈顶;
Pop(&S, &x)
:出栈操做;若非空,则将栈S的栈顶元素弹出,并用x返回;
GetTop(S, &x)
:读取栈顶元素;若非空,用x返回栈顶元素;
ClearStack(&S)
:销毁栈,并释放其存储空间;
栈的顺序存储称为顺序栈,它是利用一组地址连续的存储单元存放从栈底到栈顶的全部数据元素,同时附设一个指针top,指向当前栈顶位置。
栈的顺序存储类型描述:
# define MaxSize 50 typedef struct{ ElemType data[MaxSize]; int top; }SqStack;
S.top
,初始时S.top=-1
;S.data[S.top]
;S.top==-1
;S.top == MaxSize -1
;初始化:
void InitStack(&S){ S.top = -1; }
判栈空:
bool StackEmpty(S){ if(S.top == -1) return true; else return false; }
进栈:
bool Push(&S, x){ if(S.top == MaxSize-1) // 判满 return false; else s.top++; S.data[S.top] = x; // 或 S.data[++S.top] = x; return true; }
出栈:
bool Pop(&S, &x){ if(S.top == -1) // 判空 return false; x = S.data[S.top]; S.top--; // 或 x = S.data[S.top--] return true; }
读栈顶元素:
bool GetTop(S, &x){ if(S.top == -1) // 判空 return false; x = S.data[S.top]; return true; }
利用栈底位置不变的特性,可让两个顺序栈共享一个一维数组,以下图所示:
s0.top = -1
表示0号栈栈空;s1.top = MaxSize -1
表示1号栈栈空;s1.top - s0.top = 1
表示栈满;栈的顺序存储称为顺序栈,那么采用链式存储的栈则称为链栈;
**优势:**便于多个栈共享存储空间和提升效率,不存在栈满溢出的状况;
采用单链表来实现,并规定全部操做再单链表表头进行,没有头结点;Lhead指向栈顶元素;如图所示:
栈的链式存储的类型描述:
typedef struct Linknode{ ElemType data; struct Linknode *next; }*LiStack;
队列(Queue):
只容许在表的一端插入,另外一端删除;
队头(Front):
容许删除的一端;
队尾(Rear):
容许插入的一端;
空队列:
不含任何元素的空表;
**注意:**根据队列的定义能够获得,队列是先进先出的线性表;
InitQueue(&Q)
:初始化队列,构造一个空队列Q;
QueueEmpty(Q)
:判空;
EnQueue(&Q, x)
:入队操做;先判满,再入队;
DeQueue(&Q, &x)
:出队操做;先判空,再出队,并用x返回;
GetHead(Q, &x)
:读队头元素,并用x返回;
队列的顺序实现是指:分配一块连续的存储单元存放队列中的元素;
队列的顺序存储类型描述:
#define MaxSize 50 typedef struct{ ElemType data[MaxSize]; int front, rear; }SqQueue;
Q.front == Q.rear == 0
; 上面讲述了队列顺序存储时的判空条件,即Q.front == Q.rear == 0
;那么判满条件呢?是Q.rear == MaxSize -1
吗?显然不是,假如队头已经有出队的元素,这时候是一种“假溢出”;
为解决上述队列顺序存储的缺点,这里有了一种循环队列,即当队头指针Q.front == MaxSize -1
,或队尾指针Q.rear == MaxSize -1
时,再前进一个位置时,会自动到0;
Q.front = Q.rear = 0
;Q.front = (Q.front + 1) % MaxSize
;Q.rear = (Q.rear + 1) % MaxSize
;Q.rear + MaxSize - Q.front) % MaxSize
; 从上图能够看出,初始化时Q.front = Q.rear
;当入队操做多于出队操做时,队尾指针很快就能遇上队头指针,当Q.front = Q.rear
时(图d1),也表明队满;
那么Q.front = Q.rear
便可以表示队空,也能够表示队满?那怎么来区分呢?这里有三种处理方式:
(Q.rear + 1) % MaxSize = Q.front
;Q.front == Q.rear
;Q.rear + MaxSize - Q.front) % MaxSize
;Q.size
;
Q.size = MaxSize -1
;Q.size = 0
;tag=0
,因删除致使Q.front = Q.rear
,则表示队空;tag=1
,因插入致使Q.front = Q.rear
,则表示队满;初始化:
void InitQueue(&Q){ Q.rear = Q.front = 0; }
判队空:
bool IsEmpty(Q){ if(Q.rear == Q.front) return true; else return false; }
入队:
bool EnQueue(SqQueue &Q, ElemType x){ if((Q.rear + 1) % MaxSize == Q.front) // 判队满 return false; Q.data[Q.rear] = x; // 队尾元素赋值 Q.rear = (Q.rear + 1) % MaxSize; // 队尾元素加1 return true; }
出队:
bool DeQueue(SqQueue &Q, ElemType &x){ if (Q.rear == Q.front) // 判队空 return false; x = Q.data[Q.front]; // 取出队头元素 Q.front = (Q.front + 1) % MaxSize; // 队头指针加1 return true; }
队列的链式存储称为链队列;它其实是一个同时带有队头指针和队尾指针的单链表;
队列的链式存储类型描述:
typedef struct{ ElemType data; struct LinkNode *next; }LinkNode; typedef struct{ LinkNode *front, *rear; }LinkQueue;
不带头结点的链式队列:
Q.front == NULL, Q.rear == NULL
时,链式队列为空;带头结点的链式队列:
初始化:
void InitQueue(LinkQueue &Q){ Q.front = Q.rear = (LinkNode*)malloc(sizeof(LinkNode)); // 创建头结点 Q.front -> next = NULL; // 初始为空 }
判队空:
bool isEmpty(LinkQueue Q){ if(Q.front == Q.rear) return true; else return false; }
入队:
void EnQueue(LinkQueue &Q, ElemType x){ s = (LinkNode *)malloc(sizeof(LinkNode)); // 新建结点 s -> data = x; // 对新结点数据域赋值 s -> next = NULL; // 将新结点指针域设空 Q.rear -> next = s; // 链表尾插入 Q.rear = s; // 对尾指针指向队尾元素 }
出队:
void DeQueue(LinkQueue &Q, ElemType &x){ if(Q.front == Q.rear) // 判空 return false; p = Q.front -> next; // 须要出队的结点 x = p -> data; // 赋值 Q.front -> next = p -> next; // 断开p结点 if(Q.rear == p) // 判断原队列是否只有一个元素 Q.rear = Q.front; free(p); return true; }
双端队列指的是两端均可以执行入队、出队操做;
入队:
出队:
即有一端只容许入队;
即有一端只容许出队;
算法流程:
**Step 1:**初始化一个空栈,顺序读入括号;
**Step 2:**如果左括号,则进栈;
**Step 3:**如果右括号,则取出栈顶元素,断定是否匹配;
在后缀表达式中,已经考虑了运算的优先级,没有括号;可使用栈来计算;
**Step 1:**依次读取后缀表达式; **Step 2:**若遇到操做数,则将操做进栈; **Step 3:**若遇到运算符,则前后弹出两个栈内操做数,进行计算,计算结果,从新进栈; **Step 4:**重复上述步骤
将中缀表达式转换称后缀表达式,可使用栈来转换,须要根据运算符在栈内外的优先级来判断出/入栈操做;
操做符 | # | ( | *, / | +, - | ) |
---|---|---|---|---|---|
isp(栈内优先级) | 0 | 1 | 5 | 3 | 6 |
icp(栈外优先级) | 0 | 6 | 4 | 2 | 1 |
**Step 1:**首先,依次读取中缀表达式; **Step 2:**若遇到操做数,则直接输出; **Step 3:**若遇到操做符;
在递归的调用过程当中,系统为每一层的返回点、局部变量、传入实参等开辟了递归工做栈进行数据存储,递归次数过多,容易形成栈溢出。其效率不高的缘由是递归调用的过程当中,包含了须要重复的计算;
举例以下图:
**Step 1:**根节点入队; Step 2:若队空,则结束遍历;不然执行Step 3;
**Step 3:**队列中第一个结点出队,并访问。
数据结构中考虑矩阵是:如何用最小的内存空间来存储一样的一组数据;
定义:数组是由n个相同类型的数据元素构成的有序序列,其中每个数据元素称为一个数组元素;
数组与线性表的关系:
注意:数组一旦定义,维数再也不改变,只有初始化、销毁、存取元素、修改元素的操做;
一个数组的全部元素在内存中占用一段连续的存储空间;
压缩存储: 压缩存储是指多个值相同的元素只分配一个存储空间,对零元素不分配存储空间;
特殊矩阵: 指具备许多相同元素或零元素,而且分布有必定规律的矩阵;对称矩阵、上(下)三角矩阵、对角矩阵;
特殊矩阵的压缩存储:
找出特殊矩阵中相同值的分布规律,而后存入到一个存储空间内;
注意:下面几种特殊矩阵的压缩存储,均采用按行优先存储;
将对称矩阵$A[1\cdots n][1\cdots n]$存储到一维数组$B[n(n+1)/2]$中;
上三角矩阵与主对角线上元素$a_{i,j}(i \geq j)$对应数组的下标位置为: $$ k = 1+2+\cdots+(i-1)+(j-1)=\dfrac{i(i-1)}{2}+j-1 $$ 下三角矩阵与上三角矩阵相反,所以有: $$ k= \begin{cases} \dfrac{i(i-1)}{2}+j-1 & i \geq j (下三角区与主对角线元素)\ \dfrac{j(j-1)}{2}+i-1 & i < j (上三角区元素) \end{cases} $$ 注意:数组下标从0开始;
将上/下三角矩阵$A[1\cdots n][1\cdots n]$存储到一维数组$B[n(n+1)/2+1]$中;
下三角矩阵:
$$ k= \begin{cases} \dfrac{i(i-1)}{2}+j-1 & i \geq j (下三角区与主对角线元素)\ \dfrac{n(n+1)}{2} & i < j (上三角区元素) \end{cases} $$
上三角矩阵:
$$ k = n + (n-1) + (n-2)+\cdots +(n-j+2)+(j-1+1)=\dfrac{(i-1)(2n-i+2)}{2}+j-i $$
$$ k= \begin{cases} \dfrac{(i-1)(2n-i+2)}{2}+j-i & i \leq j (上三角区与主对角线元素)\ \dfrac{n(n+1)}{2} & i > j (下三角区元素) \end{cases} $$
注意:数组下标从0开始;
矩阵A中3条对角线元素$a_{i,j}(1 \leq i, j \leq n, |i-j|\leq 1)$对应数组中的下标为: $$ k = 2i + j -3 $$
存储方式,以下图所示,仅仅只记录其非零元素的位置与值;
总结:
该篇博客主要栈、队列、数组矩阵三部份内容;
有关栈,主要介绍了栈的基本概念、栈的基本操做;并根据其两种存储方式,一种是顺序存储,包括顺序栈、顺序栈的基本操做、共享栈;另外一种是链式存储,包括链式栈;
有关队列,主要介绍了队列的基本概念、队列的基本操做;并根据其两种存储方式,一种是顺序存储,包括队列的顺序存储、循环队列;另外一种是链式存储,包括链队列、链队列的基本操做、双端队列;
第三部分介绍了栈和队列的一些应用,包括:括号匹配、表达式求值、递归、层次遍历、计算机系统;
最后介绍了几种特殊矩阵,以及对应的压缩存储方式;