408个人复习记录,数据结构强化②

今天8.12开始第三章栈和队列,最近进度协调不好。

目录

第三章 栈、队列和数组

3.1栈

3.1.1栈的基本概念

3.1.2栈的基本操作

3.2 队列

3.2.1队列的基本概念

3.2.2队列的存储结构及基本操作

一、队列的顺序存储结构

二、队列的链式存储结构

3.3 栈和队列的应用

3.4 数组和特殊矩阵(思路,简答)

3.4.1 定义

3.4.2数组的存储结构

 3.4.3 特殊矩阵的压缩存储(重点)        

一、对称矩阵

二、三角矩阵

三、三对角矩阵

3.4.4 稀疏矩阵


第三章 栈、队列和数组

3.1栈

3.1.1栈的基本概念

栈是线性结构,存储结构既有链式存储的链栈,也有顺序存储的栈。

只允许在栈顶插入或删除的线性表,允许为空栈。

栈的操作特性是 后进先出 ( Last In First Out, LIFO)
n 个不同元素进栈,出栈元素不同排列的个数为: \textrm{C}_{2n}^{n}/(n+1)
 
栈和队列具有相同的逻辑结构,栈是限制存取点的线性结构

3.1.2栈的基本操作

1.栈的存储结构Stack(顺序存储结构与链式存储结构)

//栈的顺序存储结构
#define maxsize 20//栈中元素最大个数
typedef struct{
    Elemtype data[maxsize]; //数据域
    int top;                //栈顶指针
}SqStack;

//栈的链式存储结构
#define maxsize 20//栈中元素最大个数
typedef struct Linknode{   //定义了Linknode
    Elemtype data;         //数据域
    sturct Linknode *next; //指针域,指向下一个结点
}Linknode,*LinkStackPtr;   //LinkStackPtr是一个指向Linknode的指针类型。

typedef struct LinkStack{//定义链栈
    LinkStackPtr top;    //指向栈顶元素
    int count;           //栈的大小
}
注:入栈和出栈的操作都在链表的表头进行。
        对于带头结点以及不带头结点的链表实现有所区别。

2.InitStack(&S):初始化一个空栈S

//顺序存储结构
void InitStack(SqStack &S){
    S.top=-l;//直接把指针域赋值为-1表示此时为空栈
}

//链式
void InitStack(LinkStack &S){    
    S->top = NULL;
    S->count = 0;
}

3.判断栈是否为空

//顺序栈
bool IsEmpty(SqStack &S){
    if(S.top == -1){
        return true;
    }
    else 
        return false;
}
//链栈
bool IsEmpty(LinkStack &S){
    if(S->count == 0){
        return true;
    }
    else 
        return false;
}

4.进栈

注:

这里 top 指向的是栈顶元素 所以进栈操作为 S .data [++S . top] =x 出栈操作为 x=S.data[S.top--]
若top指向栈顶元素下一个元素,即指针初始化为S.top=0;则入栈变为S .data [S . top++] =x;
出栈变为x=S.data[--S.top];
//顺序栈
bool Push(SqStack &S,ElemType x){
    if(S.top >= maxsize-1){
        return false;}
    S.data[++S.top] = x;
    return true;
}
//链栈
bool Push(LinkStack &S, ElemType x){
    if(S->count >=maxsize-1){//判断是否已满
        return false;
    }
    LinkStackPtr node = (LinkStackPtr)malloc(sizeof(StackNode));//分配一个指针空间
    node->data = x;        
    node->next = S.top;
    S.top = node;
    S->count++;
}

5.出栈

//顺序栈
bool pop(SqStack &S, Elemtype &x){
    if(S.top == -1){//如果栈为空,则返回false
        return false;
    }
    x=S.data[top--];//先出栈,指针再减一
    return true;
}
//链栈
bool pop(LinkStack &S, ElemType &x){
    if(S.count == 0){
    retrun false;}
    LinkStackptr p = S->top;
    x = p->data;
    S->top = p->next;//栈顶指针后移
    free(p);
    S->count--;
    return true;
}

6.读取栈顶元素

//顺序
bool GetTopElem(SqStack S, ElemType x){
    if(S.top == -1){//如果是空栈就返回false
        return false;
    }
    x = S.data[S.top];
    return true;
}
//链栈
bool GetTopElem(LinkStack S, ElemType x){
    if(S->count == 0){//如果是空栈就返回false
        return false;
    }
    x = S->top->data;
    return true;
}

5.共享栈

栈底位置相对不变,让两个顺序栈共享一个一维数组空间,将两个栈的栈底分别设置在数组的两端,两个栈顶同时向共享空间延伸。

两个栈的栈顶指针都指向栈顶元素top1=0时1号栈为空,top2=maxsize时2号栈为空

        仅当两个栈的栈顶指针相邻top2-top1=1时栈满;

采用共享栈的好处:节省存储空间降低发生上溢的可能

        上溢(栈顶指针超出了最大范围)

//顺序栈
//定义结构类型
typedef struct {
    int data[MAXSIZE];
    int top1;//分别代表两端指针
    int top2;
} SharedStack;

void InitStack(SharedStack &S){
    S.top1 = 0;
    S.top2 = maxsize;
}

bool push(SharedStack &S,int x, int StackNum){//要入栈的元素以及入栈的栈号
    if(S.top1 == S.top2){    //栈满,返回false
        return false;
    }
    if(StackNum==1){
        S.data[++S.top1] = x;
    }else if(StackNum==2){
        S.data[--S.top2] = x;
    }else{return false;}//如果输错了则返回false
    return true;
}

3.2 队列

3.2.1队列的基本概念

队列,是一种操作受限的线性表,只允许在表的一端进行插入,而在表 的另一端进行删除,
其操 作的特性是先进先出 ( First In First Out, FIFO)可以为空队列
队头 ( Front) 允许删除的一端 又称队首。队尾 ( Rear) 允许插入的一端。
栈和队列的主要区别在于插入 删除操作的限定不一样

3.2.2队列的存储结构及基本操作

一、队列的顺序存储结构

        1.队列的顺序存储

队列的顺序实现是指分配一块连续的存储单元存放队列中的元素 ,并附设指向队头元素的队头指针front和指向队尾元素的队尾指针rear。
#define maxsize 20
typedef sturct{
    Elemtype data[maxsize];//数据域
    int front;            //队头指针
    int rear;             //队尾指针
}SqQueue;

void InitQueue(SqQueue &Q){//初始化队列为空
    Q.front = 0;
    Q.rear = 0;
}

bool EnterQueue(SqQueue &Q, int x){//入队
    if(Q.rear == maxsize-1 && Q.front == 0){    //判断队伍是否已满
        return false;}
    Q.data[++Q.rear] = x;        //如果未满,则队尾指针先+1再入队 
    return true;
}

bool DeleteQueue(SqQueue &Q, int &x){    //出队
    if(Q.front==Q.rear){            //判断队伍是否为空
        return false;}
    x = Q.data[Q.front];            //先将队头元素的值赋给x,然后队头元素-1
    Q.front--;
    return true;
}

void GetHead(SqQueue &Q){        //读取队头元素
    return Q.data[Q.front];}

  顺序队列的缺点:当Q.rear==maxsize时不能作为队满的条件,这时候入队会出现假的上溢出,因为对队列中实际上还有存放空间,所以是假溢出

         2.循环队列

为了解决顺序队列的缺点设计了循环队列,将顺序队列改造为一个环状空间。
当队首指针Q.front = maxsize-1后,在前进会自动到0(利用除法取余操作实现)
初始:Q.front= Q.rear=0
入队(队尾指针+1):Q.rear = (Q.rear+1)%maxsize    
出队(队首指针+1):Q.front = (Q.front+1)%maxsize
队列长度即为(Q.rear + maxsize - Q.front)%maxsize
队满条件(Q.rear+1)%maxsize = Q.front
队空条件Q.rear= Q.front
队列中元素个数(即队列长度):(Q.rear-Q.front+maxsize)%maxsize

 基本操作的实现与顺序存储基本相同

二、队列的链式存储结构

        1.链队

队列的链式存储是一个同时带有队头指针和队尾指针的单链表,其头指针指向队头结点,尾指针指向队尾结点。当Q.front==NULLQ.rear==NULL链式队列为空。

用单链表表示的链式队列特别适合于数据元素变动比较 大的情形,
        而且不存在队列满且产生溢出的问题.
typedef sturct LinkNode{   //队列结点
    ElemType data;
    struce LinkNode *next;
}        
typedef sturct{            //链队
    LinkNode *front,*rear; //队头队尾指针
}*LinkNode;

//队列初始化
void InitQueue(LinkQueue &Q){     
    Q.front = Q.rear = (LinkNode*)malloc(sizeof(LinkNode));//建立头结点
    Q.front->next = NULL;        //初试为空
}

//判断队列是否为空
bool IsEmpty(LinkQueue &Q){
    if(Q.rear == Q.front)
        return true;
    else return false;
}

//入队
bool EnterQueue(LinkQueue &Q, ElemType x){
    LinkNode *s = (LinkNode *)malloc(sizeof(LinkNode));//创建新节点
    s->data = x;               //x赋值到s的数据域
    s->next = NULL;            //s的后继结点为空
    Q.rear->next = s;          //队尾指针的后继结点设置为s
    Q.rear = s;                //在把队尾指针指向s
    return true;
}

//出队
bool DeleteQueue(LinkQueue &Q, ElemType &x){
    if(Q.front==Q.rear) return false;//判断队列是否为空
    LinkNode *p = Q.front->next; //Q.front是头结点,不存储内容,
                                 //所以队头结点实际上是Q.front->next
    x = p->data;
    Q.front->next = p->next;    //此时p结点指向的是Q.front->next也就是队头结点
                                //p->next也就是队伍中第二个结点,
                                //以将Q.fornt->next改为第二个结点的方式把队头结点删除
    if(Q.rear == p)
        Q.rear = Q.front; //如果队列中只有一个结点,则删除后变为空队列
    free(p);
    return true;
}

        2.双端队列(重点是了解输入输出顺序,操作实现过程可以不那么注重,只需要修改链队部分代码)

双端队列是指允许两端都可以进行入队和出队操作的队列。
其元素的逻辑结 构仍是线性结构。 将队列的两端分别称为前端和后端 两端都可以入队和出队
输出受限的双端队列:

输入受限的双端队列 允许在一端进行插入和删除 但在另一端只允许删除的双端队列称为 输入受限的双端队列。
        若限定双端队列从某个端点插入的元素只能从该端点删除, 则该双端队列就蜕变为两个栈底相邻接的栈。

8.13日继续以下部分

3.3 栈和队列的应用

一、括号匹配

给定一串括号,要检测这些括号是否互相匹配

算法思想:

(1)初始设置一个空栈 顺序读入左括号
(2 若是遍历过程中遇到一个右括号 则弹出当前栈顶元素,合法情况应该是与该右括号匹配的左括号 或是不合法的情况 括号序列不匹配, 退出程序
(3 若是左括号 则压入栈中 自然使原有的在栈中。
                算法结束时,栈为空,否则括号序列不匹配。
//括号匹配
#include<stdio.h>
#define maxsize 20//栈中元素最大个数

typedef struct{
    char data[maxsize]; //数据域
    int top;            //栈顶指针
}SqStack;

void push(SqStack &s, char c) {
    if (s.top != maxsize - 1) {
        s.data[++s.top] = c;
    } else {
        printf("栈满\n");
    }
}

char pop(SqStack &s) {
    if (s.top != -1) {
        return s.data[s.top--];
    } else {
        printf("栈空\n");
    }
}
bool check(char arr[]){
    SqStack S;
    char x = ' ';
    S.top = -1;//初始化栈
    int i = 0; 
    while(arr[i]){
        if(arr[i]=='('||arr[i]=='{'||arr[i]=='['){//如果是左括号,则入栈
            push(S, arr[i]);}
        else if(arr[i]==')'||arr[i]=='}'||arr[i]==']'){//如果是右括号
            if(S.top == -1){//如果为空,则直接报错
                return false;}
            x = pop(S);//不为空,则出栈
            if((arr[i]=='('&&x == ')' ) || (arr[i]=='{'&&x == '}' ) || (arr[i]=='['&&x == ']' ))
                return true;
            else{return false;}
            } 
        i++;}
    }
int main(){
    char arr[maxsize];
    printf("输入括号序列:");
    scanf("%s", arr);
    
    if(check(arr)){
        printf("括号序列不匹配");
        return 1;}
    else {
        printf("括号序列匹配");
        return 0;}
    return 0;
}

二、栈在表达式求值中的应用

它的实现是栈应用的一个典型范例。

要掌握所有方法的手算以及部分的机算过程

算数表达式:A+B*(C-D)-E/F 操作数:A,B,C,D,E,F  运算符:*,/,+,-     ;界限符:()

中缀表达式:A+B*(C-D)-E/F

(波兰表达式)前缀表达式:-+A*B-CD/EF 

(逆波兰表达式)后缀表达式:ABCD-*+EF/-

中缀表达式就是正常的表达式,把中缀表达式按照运算顺序做成一个二叉树,前缀表达式就是二叉树的先序遍历序列,后缀表达式就是二叉树的后序遍历序列。

(1)由中缀表达式转为后缀表达式:(遵循左优先原则:只要左边运算符能先算,就先算左边的)

(2)由后缀表达式转中缀表达式:从左向右扫描,将与道德运算符最近(前面)的两个操作数执行对应运算。

(3)由中缀表达式转前缀表达式:遵循右优先原则

        用栈实现后缀表达式的计算:①从左到右扫描,数字压入栈中,运算符则执行下一条     ②如果是运算符,则弹出前两个栈顶元素,与扫描到的符号执行对应运算,结果压回栈中。最终栈中只剩下一个元素,就是最终结果。

        用栈实现前缀表达式的计算:①从右向左扫描,数字压入栈中,运算符则执行下一条 ②如果是运算符,则弹出前两个栈顶元素,与扫描到的符号执行对应运算,结果压回栈中。最终栈中只剩下一个元素,就是最终结果。(与上一个几乎完全相同,只是遍历方向变了)

        用栈实现中缀表达式的计算(机算)①初始化两个栈,分别是操作数栈和运算符栈,②所有的操作数压入操作数栈,③扫描到界限符“(”时,(入栈;扫描到界限符“)”时,依次弹出栈内运算符并加入到后缀表达式中直到弹出)为止   ④扫描到运算符时,依次弹出优先级 大于等于 当前运算符的所有运算符,并输出,遇到遇到优先级小于栈顶元素的优先级或栈空时停止,之后再把当前运算符入栈。

        用栈实现中缀表达式转后缀表达式(机算):①遇到操作数时直接后缀表达式中②扫描到界限符“(”时,“(”入栈;扫描到界限符“)”时,依次弹出栈内运算符并输出直到弹出“(”为止   ③扫描到运算符时,依次弹出优先级 大于等于 当前运算符的所有运算符,并加入后缀表达式中,遇到“(”或栈空时停止,之后再把当前运算符入栈,

经过以上处理后再将站内剩余运算符依次弹出并加入后缀式中

#中缀表达式的计算与中缀表达式转后缀表达式

8.14

三、栈在递归中的应用

 递归策略通常只需要用较少的代码量完成多次重复计算,但效率不太高

递归模型不能是循环定义的,一定要满足以下两个条件:

                                递归表达式(递归体);边界条件(递归出口)

递归的精髓在于能否将原始问题转换为属性相同但规模较小的问题

四、队列在层次遍历中的应用

应用于树的层次遍历中,将每层元素按顺序存入队列中

五、队列在计算机系统中的应用

从两个方面来简述队列在计算机系统中的作用:
        第一个方面是解决主机与外部设备之间速度不匹配的问题,
                如:打印数据缓冲区中所存储的数据就是一个队列;
        主机输出数据给打印机打印,输出数据的速度比打印数据的速度要快得多 由于速度不匹配 若直接把输出的 数据送给打印机打印显然是不行的。
        解决的方法是设置一个打印数据缓冲区 主机把要打印输出 的数据依次写入这个缓冲区, 写满后就暂停输出 转去做其他的事情
        打印机就从缓冲区中按照 先进先出的原则依次取出数据并打印, 打印完后再向主机发出请求 主机接到请求后再向缓冲区 写入打印数据。 这样做既保证了打印数据的正确 又使主机提高了效率
        第二个方面是解决由多用户引起的资源竞争问题
        CPU ( 即中央处理器 它包括运算器和控制器 资源的竞争就是一个典型的例子
OS通常会将发出占用CPU请求的程序排成一个队列依次分配。

3.4 数组和特殊矩阵(思路,简答)

3.4.1 定义

        数组是由n(n>= 1) 个相同类型的数据元素构成的有限序列 每个数据元素称为一个数组元素, 每个元素在"个线性关系中的序号称为该元素的下粽 下标的取值范围称为数组的维界
        数组与线性表的关系: 数组是线性表的推广 一维数组可视为一个线性表 二维数组可视为
其元素也是定长线性表的线性表(线性表中每个元素也是一个线性表) 以此类推 数组一旦被定义其维数和维界就不再改变 因此,除结构的初始化和销毁外, 数组只会有存取元素和修改元素的操作。

3.4.2数组的存储结构

对于多维数组两种映射方法:按行优先和按列优先

按行优先存储的基本思想是:先行后列先存储行号较小的元素行号相等先存储列号较小的元素

按列优先存储的基本思想是:先列后行先存储列号较小的元素,列号相等先存储行号较小的元素

行优先:第i行*列数+j

列优先:第j列*行数+i

 3.4.3 特殊矩阵的压缩存储(重点)        

        压缩存储 指为多个值相同的元素只分配一个存储空间 对零元素不分配存储空间 其目的
是节省存储空间
        特殊矩阵 指具有许多相同矩阵元素或零元素 并且这些相同矩阵元素或零元素的分布有一
规律性的矩阵 常见的特殊矩阵有对称矩阵 上(下)三角矩阵 对角矩阵等
        特殊矩阵的压缩存储方法 找出特殊矩阵中值相同的矩阵元素的分布规律 把那些呈现规律
性分布的 值相同的多个矩阵元素压缩存储到一个存储空间中。
一、对称矩阵

若对一个n阶矩阵/中的任意一个元素a i j都有 ai j = aj i 则称其为对称矩阵

其中的元素可以划分为 3 个部分 即上三角区 主对角线和下三角区。

        

        对于n 阶对称矩阵 上三角区的所有元素和下三角区的对应元素相同, 若仍采用二维数组存放 则会浪费几乎一半的空间,
        为此将n阶对称矩阵A存放在一维数组B[n(n+1)/2] 即元素aij 存放在bk中 比如只存放下三角部分(含主对角)的元素.
        在数组B 位于元素ai j (i>=j)前面的元素.个数为

元素下标对应关系!!!

注意 二维数组 A[n] [n] A[0...n-1]  [0...n-1] 的写法是等价的
        如果数组写为 A[1...n] [1...n] , 则说明指定了从下标 1 开始存储元素
二维数组元素写为 a[i][j], 注意数组元素下标 i  和  通常是从 0 开始的
矩阵元素通常写为 ai,j a(i)(j) 注意行号  和列号  是从1 开始的

 ***********************************************************************************

8.15 

二、三角矩阵

        1.上三角矩阵

上三角矩阵中,下三角区的所有元素均为同一常量。只需存储主对角线、上三角区上的元素和下三角区的常量一次即可。可将其压缩存储在B[n(n+1)/2+1]

         2.下三角矩阵

下三角矩阵[见图 3.22 a ] 上三角区的所有元素均为同一常量
其存储思想与对称矩阵类似, 不同之处在于存储完下三角区和主对角线上的元素之后 紧接着存储对角线上方的常量一 次, 故可以将 n 阶下三角矩阵A 压缩存储在 B[n( n+1) /2 +1 ]
三、三对角矩阵
对角矩阵也称带状矩阵
     对于n阶矩阵A中的任意一个元素ai,j ,当| i-j |>1时,有ai,j=0(1<=i,j<=n)则称为三对角矩阵。
在三对角矩阵中 所有非零元素都集中在以主对角线为中心的3 条对角线的区域 其他区域的元素都为零。

 矩阵A3条对角线上的元素ai,j (| i-j |<=1 ,1<=i,j<=n)在一维数组B中存放的下标为k=2i+j-3

反之,一直三对角线矩阵中某元素存放于一维数组B中的第k个位置,则可得i=[(k+1)/3+1]向下取整,j =k-2i+3 (掌握)

3.4.4 稀疏矩阵

矩阵中非零元素的个数t 相对矩阵元素的个数 s  来说非常少 即 s>>t 的矩阵称为稀疏矩阵。
 
       若采用常规的方法存储稀疏矩阵, 则相当浪费存储空间 因此仅存储非零元素 但通常非零 元素的分布没有规律, 所以仅存储非零元素的值是不够的 还要存储它所在的行和列.
因此 将非零元素及其相应的行和列构成一个三元组(行标, 列标 值)
(可采用十字链表法或数组存储)
稀疏矩阵压缩存储后便失去了随机存取特性

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值