数据结构 第3章:栈与队列

文章目录

  • 1. 栈
    • 1.1 栈的基本概念
    • 1.2 栈的基本操作
    • 1.3 栈的顺序存储实现
    • 1.4 栈的链式存储实现
  • 2. 队列
    • 2.1 队列的基本概念
    • 2.2 队列的基本操作
    • 2.3. 队列的顺序存储实现
    • 2.4 队列的链式存储实现
    • 2.5 双端队列
  • 3. 栈与队列的应用
    • 3.1 栈在括号匹配中的应用
    • 3.2 栈在表达式求值中的应用
    • 3.3 栈在递归中的应用
    • 3.4 队列的应用
  • 4. 特殊矩阵的压缩存储

1. 栈

1.1 栈的基本概念

  1. 栈是特殊的线性表:只允许在一端进行插入或删除操作,其逻辑结构与普通线性表相同。
  2. 栈顶:允许进行插入和删除的一端 (最上面的为栈顶元素)。
  3. 栈底:不允许进行插入和删除的一端 (最下面的为栈底元素)。
  4. 空栈:不含任何元素的空表。

特点:后进先出(后进栈的元素先出栈)、LIFO(Last In First Out)。
缺点:栈的大小不可变,解决方法:共享栈。

在这里插入图片描述

1.2 栈的基本操作

  1. InitStack(&S):初始化栈。构造一个空栈 S,分配内存空间。
  2. DestroyStack(&S):销毁栈。销毁并释放栈 S 所占用的内存空间。
  3. Push(&S, x):进栈。若栈 S 未满,则将 x 加入使其成为新的栈顶元素。
  4. Pop(&S, &x):出栈。若栈 S 非空,则弹出(删除)栈顶元素,并用 x 返回。
  5. GetTop(S, &x):读取栈顶元素。若栈 S 非空,则用 x 返回栈顶元素。
  6. StackEmpty(S):判空。断一个栈 S 是否为空,若 S 为空,则返回 true,否则返回 false。

1.3 栈的顺序存储实现

顺序栈的定义:

#define MaxSize 10         //定义栈中元素的最大个数
typedef struct{    
    ElemType data[MaxSize];       //静态数组存放栈中元素    
    int top;                      //栈顶元素
}SqStack;

void testStack(){    
    SqStack S;       //声明一个顺序栈(分配空间)
}

顺序栈的初始化:

#define MaxSize 10
typedef struct{   
	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 &x, ElemType &x){    // 判断栈是否为空    
    if(S.top == -1)        
        return false;    
    x = S.data[S.top--];    
    return true;
}

读取栈顶元素:

// 读栈顶元素
bool GetTop(SqStack S, ElemType &x){        
    if(S.top == -1)                
        return false;        
    x = S.data[S.top];        
    return true; 
}

共享栈(两个栈共享同一片空间):

#define MaxSize 10         //定义栈中元素的最大个数
typedef struct{       
    ElemType data[MaxSize];       //静态数组存放栈中元素  
    int top0;                     //0号栈栈顶指针  
    int top1;                     //1号栈栈顶指针
}ShStack;

// 初始化栈
void InitSqStack(ShStack &S){    
    S.top0 = -1;      
    S.top1 = MaxSize;   
}

1.4 栈的链式存储实现

链栈的定义:

typedef struct Linknode{        
    ElemType data;        //数据域    
    Linknode *next;       //指针域
}Linknode,*LiStack;

void testStack(){   
    LiStack L;            //声明一个链栈
}

链栈的初始化:

typedef struct Linknode{       
    ElemType data;      
    Linknode *next;
}Linknode,*LiStack;

// 初始化栈
bool InitStack(LiStack &L){    
    L = (Linknode *)malloc(sizeof(Linknode));   
    if(L == NULL)             
        return false;   
    L->next = NULL;    
    return true;
}

// 判断栈是否为空
bool isEmpty(LiStack &L){    
    if(L->next == NULL)      
        return true;   
    else           
        return false;
}

入栈出栈:

// 新元素入栈
bool pushStack(LiStack &L,ElemType x){  
    Linknode *s = (Linknode *)malloc(sizeof(Linknode));  
    if(s == NULL)         
        return false;   
    s->data = x;     
    // 头插法      
    s->next = L->next;  
    L->next = s;     
    return true;
}

// 出栈
bool popStack(LiStack &L, int &x){     
    // 栈空不能出栈  
    if(L->next == NULL)     
        return false;    
    Linknode *s = L->next;  
    x = s->data;       
    L->next = s->next;
    free(s);       
    return true;
}

2. 队列

2.1 队列的基本概念

  1. 队列是操作受限的线性表:只允许在一端进行插入 (入队),另一端进行删除 (出队)。
  2. 队头:允许删除的一端。
  3. 队尾:允许插入的一端。
  4. 空队列:不含任何元素的空表。

特点:先进先出(先入队的元素先出队)、FIFO(First In First Out)
在这里插入图片描述

2.2 队列的基本操作

  1. InitQueue(&Q):初始化队列。构造一个空队列 Q。
  2. DestroyQueue(&Q):销毁队列。销毁并释放队列 Q 所占用的内存空间。
  3. EnQueue(&Q, x):入队。若队列 Q 未满,将 x 加入,使之成为新的队尾。
  4. DeQueue(&Q, &x):出队。若队列 Q 非空,删除队头元素,并用 x 返回。
  5. GetHead(Q,&x):读队头元素。若队列 Q 非空,则将队头元素赋值给 x。
  6. QueueEmpty(Q):判空。若队列 Q 为空,则返回 true。

2.3. 队列的顺序存储实现

顺序队列的定义:

#define MaxSize 10;     //定义队列中元素的最大个数

typedef struct{     
    ElemType data[MaxSize];   //用静态数组存放队列元素     
    int front, rear;          //队头指针和队尾指针
}SqQueue;

void test{     
    SqQueue Q;                //声明一个队列
}

顺序队列的初始化:

#define MaxSize 10;
typedef struct{   
    ElemType data[MaxSize];  
    int front, rear;
}SqQueue;

// 初始化队列
void InitQueue(SqQueue &Q){    
    // 初始化时,队头、队尾指针指向0   
    // 队尾指针指向的是即将插入数据的数组下标  
    // 队头指针指向的是队头元素的数组下标
    Q.rear = Q.front = 0;
}

// 判断队列是否为空
bool QueueEmpty(SqQueue 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; 
    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;
}

获得队头元素:

// 获取队头元素并存入x
bool GetHead(SqQueue &Q, ElemType &x){
    if(Q.rear == Q.front)      
        return false;
    x = Q.data[Q.front];  
    return true;
}

注意:循环队列不能使用Q.rear == Q.front作为判空的条件,因为当队列已满时也符合该条件,会与判空发生冲突!

  • 解决方法一:牺牲一个单元来区分队空和队满,即将(Q.rear+1)%MaxSize == Q.front作为判断队列是否已满的条件。(主流方法)
  • 解决方法二:设置 size 变量记录队列长度。
#define MaxSize 10; 

typedef struct{   
    ElemType data[MaxSize]; 
    int front, rear;    
    int size;
}SqQueue;

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

// 判断队列是否为空
bool QueueEmpty(SqQueue 0){     
    if(Q.size == 0)      
        return true;   
    else       
        return false;
}

// 新元素入队
bool EnQueue(SqQueue &Q, ElemType x){ 
    if(Q.size == MaxSize)    
        return false;
    Q.size++; 
    Q.data[Q.rear] = x; 
    Q.rear = (Q.rear+1)%MaxSize;  
    return true;
}

// 出队
bool DeQueue(SqQueue &Q, ElemType &x){   
    if(Q.size == 0)        
        return false;
    Q.size--;
    x = Q.data[Q.front]; 
    Q.front = (Q.front+1)%MaxSize; 
    return true;
}
  • 解决方法三:设置 tag 变量记录队列最近的操作。(tag=0:最近进行的是删除操作;tag=1 :最近进行的是插入操作)
#define MaxSize 10;   

typedef struct{    
    ElemType data[MaxSize]; 
    int front, rear;        
    int tag;
}SqQueue;

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

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

// 新元素入队
bool EnQueue(SqQueue &Q, ElemType x){
    if(Q.rear == Q.front && tag == 1)     
        return false;     
    Q.data[Q.rear] = x; 
    Q.rear = (Q.rear+1)%MaxSize;  
    Q.tag = 1;  
    return true;
}

// 出队
bool DeQueue(SqQueue &Q, ElemType &x){
    if(Q.rear == Q.front && tag == 0)  
        return false;   
    x = Q.data[Q.front];
    Q.front = (Q.front+1)%MaxSize; 
    Q.tag = 0;     
    return true;
}

2.4 队列的链式存储实现

链队列的定义:

// 链式队列结点
typedef struct LinkNode{  
    ElemType data;    
    struct LinkNode *next;
}

// 链式队列
typedef struct{       
    // 头指针和尾指针  
    LinkNode *front, *rear;
}LinkQueue;

链队列的初始化(带头结点):
在这里插入图片描述

typedef struct LinkNode{    
    ElemType data;     
    struct LinkNode *next;
}LinkNode;

typedef struct{    
    LinkNode *front, *rear;
}LinkQueue;

// 初始化队列
void InitQueue(LinkQueue &Q){   
    // 初始化时,front、rear都指向头结点 
    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){ 
    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; 
    // 如果p是最后一个结点,则将队头指针也指向NULL  
    if(Q.rear == p)          
        Q.rear = Q.front;   
    free(p);     
    return true;
}

以上是带头结点的链队列,下面是不带头结点的操作:

typedef struct LinkNode{   
    ElemType data;  
    struct LinkNode *next;
}LinkNode;

typedef struct{   
    LinkNode *front, *rear;
}LinkQueue;

// 初始化队列
void InitQueue(LinkQueue &Q){ 
    // 不带头结点的链队列初始化,头指针和尾指针都指向NULL
    Q.front = NULL;   
    Q.rear = NULL;
}

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

// 新元素入队
void EnQueue(LinkQueue &Q, ElemType x){ 
    LinkNode *s = (LinkNode *)malloc(sizeof(LinkNode));  
    s->data = x;   
    s->next = NULL; 
    // 第一个元素入队时需要特别处理   
    if(Q.front == NULL){
        Q.front = s;    
        Q.rear = s; 
    }else{
        Q.rear->next = s;
        Q.rear = s;
    }
}

//队头元素出队
bool DeQueue(LinkQueue &Q, ElemType &x){
    if(Q.front == NULL)
        return false;
    LinkNode *s = Q.front;
    x = s->data;
    if(Q.front == Q.rear){
        Q.front = Q.rear = NULL;
    }else{
        Q.front = Q.front->next;
    }
    free(s);
    return true;
}

2.5 双端队列

定义:

  1. 双端队列是允许从两端插入、两端删除的线性表。
  2. 如果只使用其中一端的插入、删除操作,则等同于栈。
  3. 输入受限的双端队列:允许一端插入,两端删除的线性表。
  4. 输出受限的双端队列:允许两端插入,一端删除的线性表。
    考点:判断输出序列的合法化

例:数据元素输入序列为 1,2,3,4,判断 4! = 24 个输出序列的合法性

栈中合法的序列,双端队列中一定也合法ZS

输入受限的双端队列输出受限的双端队列
14个合法只有4213和4231不合法只有4231和4132不合法

3. 栈与队列的应用

3.1 栈在括号匹配中的应用

  1. 用栈实现括号匹配:
    • 最后出现的左括号最先被匹配 (栈的特性——LIFO)。
    • 遇到左括号就入栈。
    • 遇到右括号,就“消耗”一个左括号(出栈)。
  2. 匹配失败情况:
    • 扫描到右括号且栈空,则该右括号单身。
    • 扫描完所有括号后,栈非空,则该左括号单身。
    • 左右括号不匹配。
      在这里插入图片描述
#define MaxSize 10 
typedef struct{    
    char data[MaxSize];   
    int top;
}SqStack;

void InitStack(SqStack &S);
bool StackEmpty(SqStack &S);
bool Push(SqStack &S, char x);
bool Pop(SqStack &S, char &x);

// 判断长度为length的字符串str中的括号是否匹配
bool bracketCheck(char str[], int length){ 
    SqStack S;      
    InitStack(S); 
    // 遍历str    
    for(int i=0; i<length; i++){   
        // 扫描到左括号,入栈     
        if(str[i] == '(' || str[i] == '[' || str[i] == '{'){    
            Push(S, str[i]);        
        }else{              
            // 扫描到右括号且栈空直接返回   
            if(StackEmpty(S))      
                return false;       
            char topElem;          
            // 用topElem接收栈顶元素   
            Pop(S, topElem);          
            // 括号不匹配           
            if(str[i] == ')' && topElem != '(' ) 
                return false;           
            if(str[i] == ']' && topElem != '[' )  
                return false;   
            if(str[i] == '}' && topElem != '{' )   
                return false;              }   
    }  
    // 扫描完毕若栈空则说明字符串str中括号匹配    
    return StackEmpty(S);
}

3.2 栈在表达式求值中的应用

  1. 中缀表达式:中缀表达式是一种通用的算术或逻辑公式表示方法,运算符以中缀形式处于操作数的中间。对于计算机来说中缀表达式是很复杂的,因此计算表达式的值时,通常需要先将中缀表达式转换为前缀或后缀表达式,然后再进行求值。
  2. 前缀表达式(波兰表达式):前缀表达式的运算符位于两个操作数之前。
  3. 后缀表达式(逆波兰表达式):后缀表达式的运算符位于两个操作数之后。
  4. 中缀转后缀的手算方法:
    确定中缀表达式中各个运算符的运算顺序。
    选择下一个运算符,按照左操作数 右操作数 运算符的方式组合成一个新的操作数。
    如果还有运算符没被处理,就继续执行。
    1. 确定中缀表达式中各个运算符的运算顺序。
    2. 选择下一个运算符,按照==「左操作数 右操作数 运算符」==的方式组合成一个新的操作数。
    3. 如果还有运算符没被处理,就继续执行。

中缀转后缀要遵循“左优先”原则:只要左边的运算符能先计算,就优先计算左边的。

  1. 后缀表达式的手算方法: 从左往右扫描,每遇到一个运算符,就让运算符前面最近的两个操作数执行对应运算, 合体为一个操作数。
  2. 后缀表达式的机算方法:
    1. 从左往右扫描下一个元素,直到处理完所有元素。
    2. 若扫描到操作数则压入栈,并回到第一步;否则执行第三步。
    3. 若扫描到运算符,则弹出两个栈顶元素,执行相应运算,运算结果压回栈顶,回到第一步。

弹出栈顶元素时,先出栈的是“右操作数”。

在这里插入图片描述

  1. 中缀转前缀的手算方法:
    1. 确定中缀表达式中各个运算符的运算顺序。
    2. 选择下一个运算符,按照==「运算符 左操作数 右操作数」==的方式组合成一个新的操作数。
    3. 如果还有运算符没被处理,就继续执行第二步。

中缀转前缀遵循“右优先”原则:只要右边的运算符能先计算,就优先算右边的。

  1. 前缀表达式的计算方法:
    1. 从右往左扫描下一个元素,直到处理完所有元素。
    2. 若扫描到操作数则压入栈,并回到第一步;否则执行第三步。
    3. 若扫描到运算符,则弹出两个栈顶元素,执行相应运算,运算结果压回栈顶,回到第一步。
  2. 中缀转后缀的机算方法: 初始化一个栈,用于保存暂时还不能确定运算顺序的运算符。从左到右处理各个元素,直到末尾。可能遇到三种情况:
    1. 遇到操作数:直接加入后缀表达式。
    2. 遇到界限符:遇到“(”直接入栈;遇到“)”则依次弹出栈内运算符并加入后缀表达式,直到 弹出“(”为止。注意:“(”不加入后缀表达式。
    3. 遇到运算符:依次弹出栈中优先级高于或等于当前运算符的所有运算符,并加入后缀表达式, 若碰到“(” 或栈空则停止。之后再把当前运算符入栈。
#define MaxSize 40 
typedef struct{     
    char data[MaxSize];   
    int top;
}SqStack;

typedef struct{  
    char data[MaxSize];  
    int front,rear;
}SqQueue;

void InitStack(SqStack &S);
bool StackEmpty(SqStack S);
bool Push(SqStack &S, char x);
bool Pop(SqStack &S, char &x);
void InitQueue(SqQueue &Q);
bool EnQueue(LQueue &Q, char x);
bool DeQueue(LQueue &Q, char &x);
bool QueueEmpty(SqQueue Q);

// 判断元素ch是否入栈
int JudgeEnStack(SqStack &S, char ch){
    char tp = S.data[S->top];   
    // 如果ch是a~z则返回-1    
    if(ch >= 'a' && ch <= 'z')   
        return -1;    
    // 如果ch是+、-、*、/且栈顶元素优先级大于等于ch则返回0  
    else if(ch == '+' && (tp == '+' || tp == '-' || tp == '*' || tp == '/'))   
        return 0;     
    else if(ch == '-' && (tp == '+' || tp == '-' || tp == '*' || tp == '/'))   
        return 0;  
    else if(ch == '*' && (tp == '*' || tp == '/'))  
        return 0;    
    else if(ch == '/' && (tp == '*' || tp == '/'))     
        return 0;    
    // 如果ch是右括号则返回2   
    else if(ch == ')')      
        return 2;     
    // 其他情况ch入栈,返回1   
    else return 1;
}

// 中缀表达式转后缀表达式
int main(int argc, char const *argv[]) {  
    SqStack S;     
    SqQueue Q;	 
    InitStack(S); 
    InitQueue(Q);  
    char ch;	  
    printf("请输入表达式,以“#”结束:");  
    scanf("%c", &ch);   
    while (ch != '#'){  
        // 当栈为空时     
        if(StackEmpty(&S)){ 
            // 如果输入的是数即a~z,直接入队 
            if(ch >= 'a' && ch <= 'z')               
                EnQueue(Q, ch);      	
            // 如果输入的是运算符,直接入栈    
            else                      
                Puch(S, ch);       
        }else{                
            // 当栈非空时,判断ch是否需要入栈 
            int n = JudgeEnStack(S, ch);     
            // 当输入是数字时直接入队      	
            if(n == -1){        	    
                EnQueue(Q, ch);        
            }else if(n == 0){       
                // 当输入是运算符且运算符优先级不高于栈顶元素时    
                while (1){         
                    // 取栈顶元素入队    
                    char tp;        
                    Pop(S, tp);      
                    EnQueue(Q, tp);         
                    // 再次判断是否需要入栈     
                    n = JudgeEnStack(S, ch);
                    // 当栈头优先级低于输入运算符或者栈头为‘)’时,入栈并跳出循环  
                    if(n != 0){           
                        EnStack(S, ch);           
                        break;              
                    }                   
                }            
            }else if(n == 2){  
                // 当出现‘)’时 将()中间的运算符全部出栈入队   
                while(1){                
                    char tp;                
                    Pop(S, tp);             
                    if(tp == '(')          
                        break;        
                    else            
                        EnQueue(Q, tp);    
                }             
            }else{        
                // 当运算符优先级高于栈顶元素或出现‘(’时直接入栈     
                Push(S, ch);         
            }          
        }         
        scanf("%c", &ch);   
    }     
    // 将最后栈中剩余的运算符出栈入队 
    while (!StackEmpty(S)){	  
        char tp;            
        Pop(S, tp);      
        EnQueue(Q, tp);  
    }      
    // 输出队中元素 
    while (!QueueEmpety(Q)){    
        printf("%c ", DeQueue(Q));  
    }    
    return 0;
}

  1. 中缀表达式的机算方法: 中缀转后缀 + 后缀表达式的求值(两个算法的结合)

初始化两个栈,(操作数栈和运算符栈),若扫描到操作数,压入操作数栈;若扫描到运算符或界限符,则按照“中缀转后缀”相同的逻辑压入运算符栈(期间也会弹出运算符,每当弹出一个运算符时,就需要再弹出两个操作数栈的栈顶元素并执行相应运算, 运算结果再压回操作数栈)。

3.3 栈在递归中的应用

  1. 函数调用的特点:最后被调用的函数最先执行结束(LIFO)。

  2. 函数调用时,需要用一个“函数调用栈” 存储:

    1. 调用返回地址
    2. 实参
    3. 局部变量
  3. 递归调用时,函数调用栈可称为“递归工作栈” 。每进入一层递归,就将递归调用所需信息压入栈顶;每退出一层递归,就从栈顶弹出相应信息。

  4. 缺点: 效率低,太多层递归可能会导致栈溢出;可能包含很多重复计算。

  5. 可以自定义栈将递归算法改造成非递归算法。

3.4 队列的应用

  1. 树的层次遍历
  2. 图的广度优先遍历
  3. 操作系统中多个进程争抢着使用有限的系统资源时,先来先服务算法(First Come First Service)是是一种常用策略。

4. 特殊矩阵的压缩存储

除非题目特别说明,否则数组下标默认从0开始。

  1. 一维数组的存储:各数组元素大小相同,且物理上连续存放。设起始地址为 LOC,则数组元素 a [ i ] a[i]a[i] 的存放地址 = LOC + i * sizeof(ElemType) (0≤i<10)

  2. 二维数组的存储:

    1. M 行 N 列的二维数组 b [ M ] [ N ] 中,设起始地址为 LOC,若按行优先存储,则 b [ i ] [ j ] 的存储地址 = LOC + (iN + j) * sizeof(ElemType)
      在这里插入图片描述
      2. M行N列的二维数组 b [ M ] [ N ] 中,设起始地址为 LOC,若按列优先存储,则 b [ i ] [ j ] 的存储地址 = LOC + (j
      M + i) * sizeof(ElemType) 在这里插入图片描述
  3. 对称矩阵的压缩存储

  4. 三角矩阵的压缩存储
    1. 下三角矩阵:处理主对角线和下三角区,其余元素都相同
    2. 上三角矩阵:处理主对角线和上三角区,其余元素都相同
    3. 压缩存储策略:按行优先原则将主对角线+下三角区存入一维数组中,并在最后一个位置存储常量。

  5. 三对角矩阵的压缩存储: 三对角矩阵,又称带状矩阵

  6. 稀疏矩阵的压缩存储: 稀疏矩阵的非零元素远远少于矩阵元素的个数。压缩存储策略:

  • 顺序存储:三元组 <行,列,值>
  • 链式存储:十字链表法

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/461013.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

聚观早报 | 微博2023年Q4营收;宝马集团2023财年营收

聚观早报每日整理最值得关注的行业重点事件&#xff0c;帮助大家及时了解最新行业动态&#xff0c;每日读报&#xff0c;就读聚观365资讯简报。 整理丨Cutie 3月16日消息 微博2023年Q4营收 宝马集团2023财年营收 海信发布中文大模型 岚图梦想家新增两款车型 vivo X Fold…

Transformer中注意力层和位置感知前馈层的分工与合作

1. 注意力层和位置感知前馈层的分工与合作 在Transformer模型中&#xff0c;注意力层&#xff08;自注意力机制&#xff09;和位置感知前馈层&#xff08;Position-wise Feed-Forward Networks, FFNs&#xff09;分别承担着不同的任务&#xff1a; 注意力层&#xff08;自注意…

【Godot4自学手册】第二十四节利用DirectionalLight2D节点实现夜幕降临

根据我们的游戏情节&#xff0c;今天我们将要实现夜晚的来临&#xff0c;主要是用DirectionalLight2D节点来实现&#xff0c;当与NPC对完话后&#xff0c;整改场景逐渐变得黑暗。 一、添加DirectionalLight2D节点 切换到主场景main&#xff0c;选择根目录&#xff0c;单击添加…

Linux下的多线程编程:原理、工具及应用(2)

&#x1f3ac;慕斯主页&#xff1a;修仙—别有洞天 ♈️今日夜电波&#xff1a;Flower of Life—陽花 0:34━━━━━━️&#x1f49f;──────── 4:46 &#x1f504; ◀️ ⏸ ▶️ ☰ …

Acwing.1262 鱼塘钓鱼(多路归并)

题目 有 N个鱼塘排成一排&#xff0c;每个鱼塘中有一定数量的鱼&#xff0c;例如&#xff1a;N5时&#xff0c;如下表&#xff1a; 即&#xff1a;在第 1个鱼塘中钓鱼第 1分钟内可钓到 10条鱼&#xff0c;第 2分钟内只能钓到 8条鱼&#xff0c;……&#xff0c;第 5分钟以后再…

Vite为什么比Webpack快

一、引言 主流的前端构建工具包括以下几种&#xff1a; Webpack&#xff1a;当下最热门的前端资源模块化管理和打包工具。它能够将许多松散的模块按照依赖和规则打包成符合生产环境部署的前端资源。同时&#xff0c;Webpack还支持代码分割&#xff0c;可以按需加载模块&#…

webshell隐藏哥斯拉流量修改sqlmap改ua

webshell隐藏 windows 1.隐藏shell attrib "文件名" s h attrib "文件名" -s -h 2.利用系统代号隐藏shell 创建文件夹名为Computer.{20D04FE0-3AEA-1069-A2D8-08002B30309D}&#xff0c;此时文件夹将变成我的电脑&#xff0c;无法看到里面的东西&…

【快捷部署】002_Flink(1.17.2)

&#x1f4e3;【快捷部署系列】002期信息 编号选型版本操作系统部署形式部署模式002Flink1.17.2CentOS 7.Xtgz包单机 &#x1f449; 演示视频 Flink一键安装&#xff08;本地模式&#xff09; install-flink.sh 脚本内容 #!/bin/bash ####变量 ###执行脚本的当前目录 mydir$…

《Learning Hierarchical Modular Networks for Video Captioning》论文笔记

论文信息 原文链接&#xff1a; Learning Hierarchical Modular Networks for Video Captioning | IEEE Journals & Magazine | IEEE Xplore 原文代码 GitHub - MarcusNerva/HMN: [CVPR2022] Official code for Hierarchical Modular Network for Video Captioning. Ou…

HarmonyOS应用开发-Stage模型开发概述

基本概念 UI框架 HarmonyOS提供了一套UI开发框架&#xff0c;即方舟开发框架&#xff08;ArkUI框架&#xff09;。提供了应用UI开发所必需的能力&#xff1a;多种组件、布局计算、动画能力、UI交互、绘制。 方舟开发框架针对开发者提供了两种开发范式&#xff1a; 基于ArkTS…

springboot换日志框架后爆SLF4J: Class path contains multiple SLF4J bindings的解决办法

sringboot原本使用的是logback日志框架&#xff0c;将它去掉&#xff0c;修改为log4j2日志框架后&#xff0c;往往会出现以下错误&#xff1a; SLF4J: Class path contains multiple SLF4J bindings. SLF4J: Found binding in [jar:file:/C:/Users/admin/.m2/repository/ch/qos…

java虚拟机的堆核心知识介绍

Java虚拟机&#xff08;JVM&#xff09;的堆&#xff08;Heap&#xff09;是Java内存模型中一个至关重要的部分。它是运行时数据区&#xff0c;用于存储Java对象实例。堆是垃圾收集器工作的地方&#xff0c;也是Java应用程序内存管理的关键区域。在本教程中&#xff0c;我们将深…

uniapp h5 部署

uniapp 配置 服务器文件路径 打包文件结构 //nginx 配置 server {listen 8300;server_name bfqcwebsiteapp;charset utf-8;#允许跨域请求的域&#xff0c;* 代表所有add_header Access-Control-Allow-Origin *;#允许带上cookie请求add_header Access-Control-Allow-C…

【SQL Server】实验四 数据更新

1 实验目的 掌握SQL数据更新语句的基本使用方法&#xff0c;如UPDATE、DELETE、INSERT。掌握更新语句条件中的嵌套查询使用方法。 2 实验内容 2.1 掌握SQL更新语句的基本使用方法 INSERT基本语句。UPDATE基本语句。DELETE基本语句。 2.2 掌握SQL更新语句的高级使用方法 …

汽车电子零部件(3):ADAS前视感知系统FLC

前言: 比如车道保持和车道改变这种场景,如何进行车道的识别,如何进行周围车辆的识别,这算是ADAS中的一个场景,其中就会用到FLC前视感知系统。还有比如前向物体识别,前向车辆识别等。 再往大里说那就是车联网了: 除了前向也可能有其他部位

【计算机网络篇】计算机网络的性能指标

文章目录 &#x1f354;计算机网络的性能指标&#x1f5c3;️常见的计算机网络性能指标⭐速率⭐带宽⭐吞吐量⭐时延⭐时延带宽积⭐往返时间⭐利用率⭐丢包率 &#x1f50e;总结 &#x1f354;计算机网络的性能指标 计算机网络的性能指标被用来从不同方面度量计算机网络的性能 …

如何通过做自己喜欢的事来赚钱?

今天想要跟大家分享一本我今年反复读过最多次的一本书《The Almanack of Naval Ravikant 纳瓦尔宝典》。我之前也有介绍过Naval Ravikant,他是硅谷创业界的一位传奇人物,创办了知名的天使投资平台AngelList。早期他还投资超过了200家科技公司,其中很多都成为了今天的科技巨头…

SpringBoot集成Redisson实现接口限流

系列文章目录 文章目录 系列文章目录前言前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看懂了就去分享给你的码吧。 Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Dat…

MIT 6.S081---Lab: locks

Memory allocator (moderate) 修改kernel/kalloc.c&#xff0c;修改kmem声明并定义结构体数组&#xff1a; 修改kernel/kalloc.c中的kinit函数&#xff0c;对kmemList进行初始化&#xff1a; 修改kernel/kalloc.c中的kfree函数&#xff0c;获取当前的cpuid并将释放的内存添加到…

Ubuntu Linux - Primavera P6 EPPM 安装及分享

引言 根据计划&#xff0c;近日我制作了基于Ubuntu Linux 的P6虚拟机环境&#xff0c;同样里面包含了全套P6 最新版应用服务 此虚拟机仅用于演示、培训和测试目的。如您在生产环境中使用此虚拟机&#xff0c;请先与Oracle Primavera销售代表取得联系&#xff0c;以获取所需的应…