栈。只允许在一端进行插入或删除操作的线性表。
栈顶。线性表允许进行插入和删除的那一端。
栈底。固定的,不允许进行插入和删除的另一端。
空栈。不含任何元素的空表。
initStack(&S):初始化一个空栈。
stackEmpty(S):判断一个栈是否为空。
push(&S, x):进栈。
pop(&S, &x):出栈,若栈非空,则弹出栈顶元素。
getTop(S, &x):读栈顶元素。
destoryStack(&S):销毁栈,并释放栈S所占用的空间。
采用顺序存储的栈称为顺序栈,它利用一组地址连续的存储单元存放自栈底到栈顶的数据元素。同时附设一个指针(top)指示当前栈顶的位置。
#define MaxSize 50
typedef struct SqStack{
ElemType data[MaxSize];
int top;
}SqStack;
由于顺序栈的入栈操作受数组上界的约束,当对栈的最大使用空间估计不足时,有可能发生栈上溢。
栈和后面提到的队列的判空和判满条件,会因为实际给的条件不同而变化,下面给出栈顶指针设定的条件下的相应方法,而其他情况需要具体情况具体分析。
void initStack(SqStack &S){
s.top = -1;
}
bool stackEmpty(SqStack S){
if(S.top == -1)
return true;
else
return false;
}
bool push(SqStack &S, ElemType x){
if(S.top == MaxSize - 1)
return false;
S.data[++S.top] = x;
return true;
}
bool pop(SqStack &S, ElemType &x){
if(S.top == -1)
return false;
x = S.data[S.top--]; //先出栈,指针再减1
return true;
}
bool getTop(SqStack S, ElemType &x){
if(S.top == -1)
return false;
x = S.data[S.top];
return true;
}
**栈顶指针做++、–操作的次序视初始化时栈顶的大小而定。**即若栈顶指针初始化为S.top = 0
,即入栈操作变为S.data[S.top++] = x
,出栈操作变为x = S.data[--S.top]
。相应的栈空、栈满条件也会发生变化。
利用栈底位置相对不变的特性,可让两个顺序栈共享一个一维数组空间,将两个栈的栈底分别设置在共享空间的两端,两个栈顶向共享空间的中间延伸。两个栈的栈顶指针都指向各自的栈顶元素。
采用链式存储的栈称为链栈,其优点是便于多个栈共享存储空间和提高效率,且不存在栈满上溢的情况。通常采用单链表实现,并规定所有操作都是在单链表的表头进行的。这里规定链栈没有头结点,Lhead指向栈顶元素。
typedef struct LinkNode{
ElemType data;
struct LinkNode * next;
}LinkNode, *LiStack;
采用链式存储,便于结点的插入与删除。值得注意的是,对于带头结点和不带头结点的链栈,在具体的实现方面有所不同。
对于n个不同元素进栈,出栈序列的个数为:
1 n + 1 C 2 n n = 1 n + 1 ( 2 n ! ) n ! ∗ n ! \frac{1}{n+1} C^n_{2n} = \frac{1}{n+1}\frac{(2n!)}{n!*n!} n+11C2nn=n+11n!∗n!(2n!)
队列。也是一种操作受限的线性表,直允许在表的一端进行插入,而在表的另一端进行删除。
initQueue(&Q):初始化队列,构造一个空队列Q。
queEmpty(Q):判队列空。
enQueue(&Q, x):入队。
deQueue(&Q, &x):出队,若队列Q非空,删除队头元素,并用x返回。
getHead(Q, &x):读队头元素。
队列的顺序实现是指分配一块连续的存储单元存放队列中的元素,并附设两个指针front和rear分别指示队头元素和队尾元素的位置。设队头指针指向队头元素,队尾指针指向队尾元素的下一个位置(也可以让rear指向队尾元素,front指向队头元素的前一个位置)。
#define MaxSize 50
typedef struct SqQueue{
EleType data[MaxSize];
int front, rear;
}
初始状态:Q.front = Q.rear = 0
进队操作:队不满时,先送值到队尾元素,再将队尾指针加1.
出队操作:队不空时,先取队头元素的值,再将队头指针加1.
Q.rear == Q.front
作为队列满的条件。将顺序队列臆造为一个环状的空间,即把存储队列元素的表从逻辑上视为一个环,称为循环队列。当队首指针Q.front = MaxSize - 1后,再前进一个位置就自动到0,这可以利用除法取余(%)来实现。
队首指针进1:Q.front = (Q.front + 1) % MaxSize;
队尾指针进1:Q.rear = (Q.rear + 1) % MaxSize;
队列长度:length = (Q.rear + MaxSize - Q.front) % MaxSize;
队空:Q.front == Q.rear;
队满
(Q.rear + 1) % MaxSize == Q.front;
Q.front == Q.rear;
Q.front == Q.rear;
Q.size == 0;
Q.size == MaxSize;
Q.front == Q.rear
,则为队空Q.front == Q.rear
,则为队满void initQueue(SqQueue &Q){
Q.rear = Q.front = 0;
}
bool queEmpty(SqQueue Q){
if(Q.rear == Q.front)
return ture;
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;
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;
return true;
}
链队列,实际上是一个同时带有队头指针和队尾指针的单链表。头指针指向队头结点,尾指针指向队尾结点,即单链表的最后一个结点(队列顺序存储结构中的尾指针指向队尾元素的下一个位置)
typedef struct LinkNode{
ElemType data;
struct LNode * next;
}LinkNode;
typedef struct{
LNode *front, *rear;
}LinkQueue;
void initQueue(LinkQueue &Q){
Q.front = Q.rear = (LinkNode*)malloc(sizeof(LinkNode));
Q.front->next = NULL; //初始为空,看出队操作
}
bool queEmpty(LinkNode Q){
if(Q.front == Q.rear)
return true;
else
return false;
}
void enQueue(LinkQueue &Q, ElemType x){
LinkNode *s = (LinkNode *)malloc(sizeof(LinkNode));
s->data = x;
s->next = NULL;
Q.rear->next = s;
Q.rear = s;
}
bool deQueue(LinkQueue &Q, ElemType &x){
if(Q.front == Q.rear)
return false;
LinkNode *p = Q.front->next;
x = p->data;
Q.front->next = p->next;
if(Q.rear == p)
Q.rear = Q.front;
free(p);
return true;
}
双端队列,允许两端都可以进行入队和出队操作的队列。其元素的逻辑结构仍是线性结构。将队列的两端分别称为前端和后端,两端都可以入队和出队。
输出受限的双端队列,允许在一端进行插入和删除,但在另一端只允许插入。
输入受限的双端队列,允许在一端进行插入和删除,但在另一端只允许删除。
设有一个双端队列,输入序列为1, 2, 3, 4,试分别求出以下条件的输出序列
1)能由输入受限的双端队列得到,但不能由输出受限的双端队列得到的输出序列;
2)能由输出受限的双端队列得到,但不能由输入受限的双端队列得到的输出序列;
3)既不能由输入受限的双端队列得到,又不能由输出受限的双端队列得到的输出序列;
答:1)4,1,3,2;
2)4,2,1,3;
3)4,2,3,1。
中缀表达式不仅依赖运算符的优先级,而且还要处理括号。后缀表达式的运算符在操作数后面,在后缀表达式中已经考虑了运算符的优先级,没有括号。例如:中缀表达式 A+B*(C-D)-E/F(表达式树中序遍历)所对应的后缀表达式是 ABCD-*+EF-(表达式树后序遍历)。
通过后缀表示计算表达式值的过程为:顺序扫描表达式的每一项,然后根据它的类型做如下相应操作:若该项是操作树,则将其压入栈中;若该项是操作符
若一个函数、过程或数据结构的定义中又应用了它自身,则这个函数、过程或数据结构称为是递归定义的,简称递归。
它通常把一个大型的复杂问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的代码就可以描述出解题过程所需要的多次重复计算,大大减少了程序的代码量,但在通常情况下,它的效率并不高。
递归模型不能是循环定义的,其必须满足下面两个条件:
int Fib(int n){
if(n == 0)
return 0;
else if(n == 1)
return 1;
else
return Fib(n - 1) + Fib(n - 2);
}
在递归调用的过程中,系统为每一层的返回点、局部变量、传入实参等开辟了递归工作栈来进行数据存储,递归次数过多容易造成栈溢出等。其效率不高的原因是递归调用过程中包含很多重复的计算。可以将递归算法转换为非递归算法。
层次遍历二叉树的过程:
在数据结构中考虑的是如何用最少的内存空间来存储同样的一组数据。
数组是由n(n≥1)个相同类型的数据元素构成的有限序列。
数组是线性表的推广。一维数组可视为一个线性表,二位数组可视为元素是线性表的线性表。数组一旦被定义,其维数和维界就不再改变。因此,除结构的初始化和销毁外,数组只会有存取元素和修改元素的操作。
一个数组的所有元素在内存中占用一段连续的存储空间。
对于多维数组,有两种映射方法:按行优先和按列优先。对于数组A2*3,按行存储在内存中的顺序为:a00,a01,a02,a10,a11,a12;按列存储在内存中的顺序为:a00,a10,a01,a11,a02,a12。
对于n阶对称矩阵,将其存放在一维数组B[n(n+1)/2]中,只存放主对角线和下三角区的元素。
k = { i ( i − 1 ) 2 + j − 1 , i > = j (下三角区和主对角线元素) j ( j − 1 ) 2 + i − 1 , i < j (上三角区元素 a i j = a j i ) k = \begin{cases} \frac{i(i-1)}{2}+j-1, &\text{$i>=j$(下三角区和主对角线元素)} \\\\ \frac{j(j-1)}{2}+i-1, &\text{$i
下三角矩阵:存储完下三角区和主对角线上的元素之后,紧接着存储对角线上方的常量一次,故可以压缩存储在B[n(n+1)/2+1]中。
k = { i ( i − 1 ) 2 + j − 1 , i > = j (下三角区和主对角线元素) n ( n − 1 ) 2 , i < j (上三角区元素) k= \begin{cases} \frac{i(i-1)}{2}+j-1, &\text{$i>=j$(下三角区和主对角线元素)} \\\\ \frac{n(n-1)}{2}, &\text{$i
类似的,上三角矩阵只存储主对角线、上三角区上的元素和下三角区的常量一次。
k = { ( i − 1 ) ( 2 n − i + 2 ) 2 + ( j − i ) , i < = j (上三角区和主对角线元素) n ( n + 1 ) 2 , i > j (下三角区元素) k= \begin{cases} \frac{(i-1)(2n-i+2)}{2}+(j-i), &\text{$i<=j$(上三角区和主对角线元素)} \\\\ \frac{n(n+1)}{2}, &\text{$i>j$(下三角区元素)} \end{cases} k=⎩⎪⎨⎪⎧2(i−1)(2n−i+2)+(j−i),2n(n+1),i<=j(上三角区和主对角线元素)i>j(下三角区元素)
以上推到均假设数组的下标从0开始,若题设有具体要求,则应该灵活应对。
对于n阶方阵A中的任一元素aij,当|i-j|>1时,有aij = 0(1≤i, j≤n),则称为三对角矩阵。所有非零元素都集中在以主对角线为中心的3条对角线的区域,其他元素都为0。三对角矩阵的压缩存储是将3条对角线上的元素按行优先方式存放在一维数组B中。
k = 2 i + j − 3 k=2i+j-3 k=2i+j−3
矩阵元素个数s相对于矩阵中非零元素的个数t来说非常多,这样的矩阵称为稀疏矩阵。
通常,稀疏矩阵非零元素的分布没有规律,因此将非零元素及其相应的行和列构成一个三元组(行标,列表,值)A[t+1][3],然后再按照某种规律存储这些三元组。稀疏矩阵压缩存储后便失去了随机存取特性。
本小节有关下标的计算问题要注意题目给定数组的起始下标为0还是1,通常可以利用特殊下标值验证公式,用归纳法推出下标值。
十字链表将行单链表和列单链表结合起来存储稀疏矩阵;
邻接矩阵空间复杂度为O(n2),不适合存储稀疏矩阵;
二叉链表又名左孩子右兄弟表示法,可用于表示树或森林。