考研数据结构-栈、队列和数组(超详细)

3. 栈、队列和数组

请添加图片描述

3.1 栈

3.1.1 栈的基本概念
  1. 栈的定义:栈(Stack)是只允许在一端进行插入或删除操作的线性表

  2. 栈的基本操作:

    InitStack(&S):初始化栈。构造一个空栈 S,分配内存空间。

    DestroyStack(&S):销毁栈。销毁并释放栈 S 所占用的内存空间。

    Push(&S,x):进栈,若栈S未满,则将x加入使之成为新栈顶。

    Pop(&S,&x):出栈,若栈S非空,则弹出栈顶元素,并用x返回。

    GetTop(S, &x):读栈顶元素。若栈 S 非空,则用 x 返回栈顶元素

    其他常用操作:

    StackEmpty(S):判断一个栈 S 是否为空。若S为空,则返回true,否则返回false

  3. 卡特兰数
    请添加图片描述

3.1.2 栈的顺序存储实现
  1. 顺序栈

    #include <stdio.h>
    #define true 1
    #define false 0
    #define MAXSIZE 10
    typedef int bool;
    typedef int ElemType;
    typedef struct {
    	ElemType data[MAXSIZE]; // 数据域
    	int top; // 栈顶指针
    } SqStack;
    /*初始化栈*/
    bool InitStack(SqStack* S)
    {
    	S->top= -1; // 让栈顶指针指向 -1,表示当前为空栈
    	return true;
    }
    /*判断栈是否为空*/
    bool isEmpty(SqStack S)
    {
    	if (S.top == -1)
    		return true; // 栈空
    	return	false; // 非空
    }
    /*进栈操作*/
    bool Push(SqStack* S, ElemType e)
    {
    	if (S->top == MAXSIZE - 1)
    		return false; // 栈已满
    	S->top++; // 栈顶指针加一
    	S->data[S->top] = e; // 将值赋值给新的栈顶元素
    	return true;
    }
    /*出栈操作*/
    bool Pop(SqStack* S, ElemType* x)
    {
    	if (isEmpty(*S))
    		return false; // 栈空,无出栈元素
    	*x = S->data[S->top];
    	S->top--; // 栈顶指针减一
    	return true;
    }
    /*获取栈顶元素*/
    bool GetTop(SqStack S, ElemType* x)
    {
    	if (isEmpty(S))
    		return false; // 栈空
    	*x = S.data[S.top];
    	return true;
    }
    int main()
    {
    	SqStack S; // 定义一个栈
    	InitStack(&S); // 初始化一个栈
    	Push(&S, 666);
    	Push(&S, 777);
    	// 判断栈是否为空
    	isEmpty(S) ? printf("The Stack is NULL\n") : printf("The Stack not is NULL\n"); 
    	ElemType x;
    	Pop(&S, &x) ? printf("The pop element is %d\n", x) : printf("Pop failure\n");
    	GetTop(S, &x) ? printf("Get top element is %d\n", x) : printf("The Stack is NULL\n");
    	return 0;
    }
    
  2. 共享栈

    #include <stdio.h>
    #define true 1
    #define false 0
    #define MAXSIZE 10
    typedef int bool;
    typedef int ElemType;
    typedef struct {
    	ElemType data[MAXSIZE];
    	int top0; // 0号栈栈顶指针
    	int top1; // 1号栈栈顶指针
    } ShStack;
    /*初始化栈*/
    bool InitStack(ShStack* S)
    {
    	S->top0 = -1; // 0号栈栈顶指针指向 -1
    	S->top1 = MAXSIZE; // 1号栈栈顶指针指向 MaxSize
    }
    /*判断栈是否为空*/
    bool isEmpty(ShStack S)
    {
    	if (S.top0 == -1 && S.top1 == MAXSIZE)
    		return true;
    	return false;
    }
    /*入栈 c=0代表操作0号栈,c=1代表操作1号栈*/
    bool Push(ShStack* S, ElemType e, int c)
    {
    	if (c == 0)// 操作0号栈
    	{
    		if (S->top0 + 1 == S->top1)
    			return false; // 栈满
    		S->data[++S->top0] = e; // 将top0加一,并填入新元素
    		return true;
    	}
    	else if (c == 1) // 操作1号栈
    	{
    		if (S->top1 - 1 == S->top0)
    			return false; // 栈满
    		S->data[--S->top1] = e; // 将top1减一,并填入新元素
    		return true;
    	}
    	return false; // c参数输入不合法
    }
    /*弹栈 c=0代表操作0号栈,c=1代表操作1号栈*/
    bool Pop(ShStack* S, ElemType* x, int c)
    {
    	if (c == 0)// 操作0号栈
    	{
    		if (S->top0 == -1)
    			return false; // 栈空
    		*x = S->data[S->top0--]; // 出栈,栈顶指针减一
    		return true;
    	}
    	else if (c == 1) // 操作1号栈
    	{
    		if (S->top1 == MAXSIZE)
    			return false; // 栈空
    		*x = S->data[S->top1++]; // 出栈,栈顶指针加一
    		return true;
    	}
    	return false; // c参数输入不合法
    }
    int main()
    {
    	ShStack S;
    	InitStack(&S); // 初始化栈
    	isEmpty(S); // 判断栈是否为空
    	Push(&S, 985, 0); // 0号栈入栈
    	Push(&S, 211, 1); // 1号栈入栈
    	ElemType x;
    	Pop(&S, &x, 0); // 0号栈弹栈
    	Pop(&S, &x, 1); // 1号栈弹栈
    	return 0;
    }
    
3.1.3 栈的链式存储实现
  1. 不带头结点

    #include <stdio.h>
    #include <stdlib.h>
    #define true 1
    #define false 0
    typedef int bool;
    typedef int ElemType;
    
    typedef struct LinkNode { // 定义链栈的结构体
    	ElemType data; // 数据域
    	struct LinkNode* next; // 指针域
    }LinkNode,*LiStack;
    /*初始化链栈*/
    bool InitStack(LiStack* S)
    {
    	*S = NULL;
    	return true;
    }
    /*判空*/
    bool isEmpty(LiStack S)
    {
    	return S == NULL;
    }
    /*入栈*/
    bool Push(LiStack* S, ElemType e)
    {
    	if (isEmpty(*S)) // 当前链栈为空
    	{
    		*S = (LinkNode*)malloc(sizeof(LinkNode));
    		if (*S == NULL) // 内存不足,分配失败
    			return false;
    		(*S)->data = e;
    		(*S)->next = NULL;
    		return true;
    	}
    	// 链栈不为空时
    	LinkNode* p = (LinkNode*)malloc(sizeof(LinkNode));
    	if (p == NULL)
    		return false;
    	p->data = e;
    	p->next = *S; // 让新节点指向原来的第一个节点
    	*S = p; // 让头指针指向新节点
    	return true;
    }
    /*弹栈*/
    bool Pop(LiStack* S, ElemType* x)
    {
    	if (isEmpty(*S)) // 链栈为空
    		return false;
    	if ((*S)->next == NULL) // 链栈中只有一个节点
    	{
    		*x = (*S)->data; // 把仅有的一个节点的数据赋值给x
    		free(*S); // 把仅有的那个节点空间释放掉
    		*S = NULL;
    		return true;
    	}
    	// 链栈中不只有一个节点
    	LinkNode* p = (*S)->next;
    	*x = (*S)->data;
    	free(*S); // 释放第一个节点
    	*S = p; // 让栈指针指向原来位序为2的节点
    	return true;
    }
    /*获取栈顶元素*/
    bool GetTop(LiStack S, ElemType* x)
    {
    	if (isEmpty(S))
    		return false;
    	*x = S->data;
    }
    int main()
    {
    	LiStack S; 
    	InitStack(&S);
    	isEmpty(S);
    	Push(&S, 666);
    	Push(&S, 777);
    	ElemType x;
    	Pop(&S, &x);
    	GetTop(S, &x);
    	Pop(&S, &x);
    	Pop(&S, &x);
    	return 0;
    }
    
  2. 带头结点

    #include <stdio.h>
    #include <stdlib.h>
    #define true 1
    #define false 0
    typedef int bool;
    typedef int ElemType;
    
    typedef struct LinkNode { // 定义链栈的结构体
    	ElemType data; // 数据域
    	struct LinkNode* next; // 指针域
    }LinkNode, * LiStack;
    /*初始化链栈*/
    bool InitStack(LiStack* S)
    {
    	*S = (LinkNode*)malloc(sizeof(LinkNode));
    	if (*S == NULL)
    		return false;
    	(*S)->next = NULL;
    }
    /*判空*/
    bool isEmpty(LiStack S)
    {
    	return S->next == NULL;
    }
    /*入栈*/
    bool Push(LiStack S, ElemType e)
    {
    	LinkNode* p = (LinkNode*)malloc(sizeof(LinkNode));
    	if (p == NULL)
    		return false;
    	p->data = e;
    	p->next = S->next; // 让新节点指向头节点的下一个节点
    	S->next = p; // 让头节点指向 新节点
    	return true;
    }
    /*弹栈*/
    bool Pop(LiStack S, ElemType* x)
    {
    	if (isEmpty(S))
    		return false;
    	LinkNode* p = S->next; // 让p指向即将被删除的节点
    	*x = p->data;
    	S->next = p->next; // 断链。让头节点的next指向被删除节点的下一个节点
    	free(p); // 释放被删除节点的空间
    	return true;
    }
    bool Pop(LiStack* S, ElemType* x);
    /*获取栈顶元素*/
    bool GetTop(LiStack S, ElemType* x)
    {
    	if (isEmpty(S))
    		return false;
    	*x = S->next->data;
    	return true;
    }
    int main()
    {
    	LiStack S;
    	InitStack(&S);
    	Push(S, 666);
    	Push(S, 777);
    	isEmpty(S);
    	ElemType x;
    	GetTop(S, &x);
    	Pop(S, &x);
    	Pop(S, &x);
    	Pop(S, &x);
    	return 0;
    }
    

3.2 队列

3.2.1 队列的基本概念
  1. 队列的定义:队列(Queue)是只允许在一端进行插入,在另一端删除的线性表

  2. 队列的基本操作:

    InitQueue(&Q):初始化队列,构造一个空队列Q。

    DestroyQueue(&Q):销毁队列。销毁并释放队列Q所占用的内存空间。

    EnQueue(&Q,x):入队,若队列Q未满,将x加入,使之成为新的队尾。

    DeQueue(&Q,&x):出队,若队列Q非空,删除队头元素,并用x返回。

    GetHead(Q,&x):读队头元素,若队列Q非空,则将队头元素赋值给x。

    其他常用操作:

    QueueEmpty(Q):判队列空,若队列Q为空返回true,否则返回false。

3.2.2 队列的顺序实现(循环队列)
  1. 顺序实现的队列我们只讨论循环队列,因为不能循环得队列没意义

  2. 在这里插入图片描述

  3. 循环队列(初始化Q.front=0; Q.rear=0)

    #include <stdio.h>
    #include <stdlib.h>
    #define true 1
    #define false 0
    #define MAXSIZE 10
    typedef int bool;
    typedef int ElemType;
    typedef struct {
    	ElemType data[MAXSIZE];
    	int front, rear; // 队头指针和队尾指针(头出尾进)
    } SqQueue;
    /*初始化队列*/
    void InitQueue(SqQueue* Q)
    {
    	// 初始时对头指针和队尾指针都指向 0
    	(*Q).front = (*Q).rear = 0;
    }
    /*判断队列是否为空*/
    bool isEmpty(SqQueue Q)
    {
    	return Q.front == Q.rear;
    }
    /*入队*/
    bool EnQueue(SqQueue* Q, ElemType e)
    {
    	if (((*Q).rear + 1) % MAXSIZE == (*Q).front)
    		return false; // 队满
    	(*Q).data[(*Q).rear] = e; // 新元素插入队尾
    	(*Q).rear = ((*Q).rear + 1) % MAXSIZE; // 队尾指针加 1 取模
    	return true;
    }
    /*出队*/
    bool DeQueue(SqQueue* Q, ElemType* x)
    {
    	if (isEmpty(*Q))
    		return false; // 队列为空
    	*x = (*Q).data[(*Q).front];
    	(*Q).front = ((*Q).front + 1) % MAXSIZE; // 队头指针后移
    	return true;
    }
    /*获取队头元素值*/
    bool GetHead(SqQueue Q, ElemType* x)
    {
    	if (isEmpty(Q))
    		return false;
    	*x = Q.data[Q.front];
    	return true;
    }
    int main()
    {
    	SqQueue Q;
    	InitQueue(&Q);
    	EnQueue(&Q, 666);
    	ElemType x;
    	GetHead(Q, &x);
    	DeQueue(&Q, &x);
    	DeQueue(&Q, &x);
    	return 0;
    }
    
  4. 判断队列已满/已空

    初始化时rear=front=0

    1. 队空的条件:Q.front == Q.rear
    2. 队满的条件:牺牲一个存储位置(Q.rear + 1) % MAXSIZE == Q.front

    在结构体添加一个size属性记录当前队列的长度

    1. 队空条件:Q.size == 0
    2. 队满的条件:Q.size == MAXSIZE

    在结构体添加一个tag属性每次删除操作成功时,都令tag=0;每次插入操作成功时,都令tag=1;

    1. 队空条件:Q.front == Q.rear && tag == 0
    2. 队满的条件:Q.front == Q.rear && tag == 1
  5. 还有其他出题方法,比如出题人初始时front仍然指向0不变,但是rear要求实时指向当前队列中的元素,那么我们最好将rear的初始值定为 MAXSIZE - 1。这样的话判队空/满的条件也就都发生变化了,所以学东西一定要灵活,重在理解。

🧡💚💛3.2.3 队列的链式实现
  1. 带头结点

    #include <stdio.h>
    #include <stdlib.h>
    #define true 1
    #define false 0
    typedef int bool;
    typedef int ElemType;
    
    // ★★★★★ 之前从来没有定义过两个结构体
    typedef struct LinkNode { // 链式队列节点
    	ElemType data;
    	struct LinkNode *next;
    }LinkNode;
    typedef struct { // 链式队列
    	LinkNode *front, *rear; // 队列的队头指针和队尾指针
    }QueueNode, *LinkQueue;
    /*初始化队列*/
    bool InitQueue(LinkQueue* Q)
    {
    	// ★★★★★ 之前写过的数据结构没这样写过
    	*Q = (LinkQueue)malloc(sizeof(QueueNode)); // 先给队列Q申请空间
    	if (*Q == NULL)
    		return false;
    	// 申请头节点
    	LinkNode *s = (LinkNode*)malloc(sizeof(LinkNode));
    	if (s == NULL)
    		return false;
    	s->next = NULL;
    	(*Q)->front = (*Q)->rear = s; // Q的头指针和尾指针都指向头节点
    }
    /*判断队列是否为空*/
    bool isEmpty(LinkQueue Q)
    {
    	return Q->front == Q->rear;
    }
    
    /*入队*/
    bool EnQueue(LinkQueue Q, ElemType e)
    {
    	LinkNode* s = (LinkNode*)malloc(sizeof(LinkNode));
    	if (s == NULL)
    		return false;
    	s->data = e;
    	s->next = NULL;
    	Q->rear->next = s; // 当前队尾节点指向新插入的节点
    	Q->rear = s; // 保证队列的rear指针一直指向队尾元素
    	return true;
    }
    /*出队*/
    bool DeQueue(LinkQueue Q, ElemType* x)
    {
    	if (isEmpty(Q))
    		return false; // 队列为空
    	LinkNode* s = Q->front->next; // 让s指向队列链表中的除头节点的第一个节点
    	*x = s->data;
    	Q->front->next = s->next; // 让头节点指向出队节点的下一个节点
    	if (s == Q->rear) // 当前出队的是队中的最后一个节点(★★★★★)
    		Q->rear = Q->front; // 修改 rear 的指针
    	return true;
    }
    int main()
    {
    	LinkQueue Q;
    	LinkNode* L;
    	InitQueue(&Q);
    	free(Q);
    	EnQueue(Q, 111);
    	EnQueue(Q, 222);
    	ElemType x;
    	DeQueue(Q, &x);
    	DeQueue(Q, &x);
    	DeQueue(Q, &x);
    	return 0;
    }
    
  2. 不带头结点

    #include <stdio.h>
    #include <stdlib.h>
    #define true 1
    #define false 0
    typedef int bool;
    typedef int ElemType;
    
    typedef struct LinkNode { // 链式队列的节点
    	ElemType data;
    	struct LinkNode* next;
    } LinkNode;
    
    typedef struct { // 链式队列
    	LinkNode *front, *rear;
    }QueueNode, *LinkQueue;
    
    /*初始化队列*/
    bool InitQueue(LinkQueue* Q)
    {
    	*Q = (QueueNode*)malloc(sizeof(QueueNode));
    	if (*Q == NULL)
    		return false;
    	(*Q)->front = (*Q)->rear = NULL; // 头指针和尾指针都指向NULL
    	return true;
    }
    /*判空*/
    bool isEmpty(LinkQueue Q)
    {
    	return Q->front == NULL;
    }
    
    /*入队*/
    bool EnQueue(LinkQueue Q, ElemType e)
    {
    	LinkNode* s = (LinkNode*)malloc(sizeof(LinkNode));
    	if (s == NULL)
    		return false;
    	s->data = e;
    	s->next = NULL;
    	if (Q->front == NULL) // 在空队列中插入第一个元素
    	{
    		Q->front = Q->rear = s; // 修改队头队尾指针
    		return true;
    	}
    	Q->rear->next = s; // 将新节点插入到 rear 节点之后
    	Q->rear = s; // 修改 rear指针
    	return true;
    }
    /*出队*/
    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; // front 和 rear都指向NULL
    	Q->front = s->next; // 修改front 指针
    	free(s);
    	return true;
    }
    
    int main()
    {
    	LinkQueue Q;
    	InitQueue(&Q);
    	EnQueue(Q, 111);
    	EnQueue(Q, 222);
    	ElemType x;
    	DeQueue(Q, &x);
    	DeQueue(Q, &x);
    	DeQueue(Q, &x);
    	return 0;
    }
    
3.2.4 双端队列

3.3 栈和队列的应用

3.3.1 栈在括号匹配中的应用
  1. 流程图

    在这里插入图片描述

  2. 处理流程:

    1. 遇到左括号 ([{ :进栈
    2. 遇到右括号 )]} :弹栈 & 判断是否匹配
    3. 遇到非括号:数据输入不合法,匹配失败(){}[]
  3. 三种失败情况:

    1. 在处理过程中括号不匹配,失败。如:(){]
    2. 在处理过程中,遇到右括号,需要弹栈,但是栈空,失败。如:())
    3. 括号处理完之后,栈不为空,说明括号元素有多余,失败。如:{()
/*括号是否匹配*/
bool bracketCheck(char str[], int length) 
{
	// 注意:length接收的长度是实际 字符串长度 + 1,因为有'\0'
	SqStack S;
	InitStack(&S); // 初始化一个栈
	for (int i = 0; i < length - 1; i++)
	{
		if (str[i] == '(' || str[i] == '[' || str[i] == '{') { // 扫描到左括号
			Push(&S, str[i]); // 扫描到左括号元素入栈
		} else if(str[i] == ')' || str[i] == ']' || str[i] == '}') { // 扫描到右括号
			if (isEmpty(S)) { // 栈为空
				return false; // 匹配失败
			} else { // 栈不为空
				ElemType topElem;
				Pop(&S, &topElem); // 栈顶元素出栈
				if (str[i] == ')' && topElem != '(')
					return false;
				if (str[i] == ']' && topElem != '[')
					return false;
				if (str[i] == '}' && topElem != '{')
					return false;
			}
		} else{
			return false; // 表示输入数据不合法
		}
	}
	return isEmpty(S); // 若此时栈为空则说明完全匹配,且无剩余,表示匹配成功;
}
int main()
{
	char str[] = "{()";
	bool flag = bracketCheck(str, sizeof(str) / sizeof(char));
	flag ? printf("匹配成功\n") : printf("匹配失败\n");
	return 0;
}
3.3.2 栈在表达式求值中的应用
  1. 中缀转后缀(手算)
    (1)确定中缀表达式各个运算符的运算顺序(采用“左优先原则”)
    (2)选择下一个运算符,按照<左操作数 右操作数 运算符>的方式组合成一个新的操作数
    (3)如果还有运算符没被处理,就继续(2)

    eg: A+B*(C-D)-E/F
    运算次序:3 2 1 5 4
    ABCD-*+EF/-
    123 45

    eg: A+B-CD/E+F
    运算次序: 1 4 2 3 5
    AB+CD
    E/-F+
    1 2 34 5


  1. 后缀表达式的运算(手算)
    从左往右扫描,每遇到一个运算符,就让运算符前面最近的两个操作数执行对应运算,合体为一个操作数

  1. 后缀表达式的运算(机算)【这也正是后缀转中缀的算法】
    用栈实现后缀表达式的计算:
    (1)从左往右扫描,直到处理完所有元素
    (2)若扫描到操作数则压栈,并且返回(1),否则执行(3)
    (3)若扫描到运算符,则弹出两个栈顶元素,执行相应运算,运算结果压回栈顶,回到(1)
    ———若表达式合法,则最后栈中只会留下一个元素,就是最终结果
    注意:先出栈的是“右操作数”

  1. 中缀转前缀(手算)
    (1)确定中缀表达式各个运算符的运算顺序(采用“右优先原则”)
    (2)选择下一个运算符,按照<运算符 左操作数 右操作数>的方式组合成一个新的操作数
    (3)如果还有运算符没被处理,就继续(2)

    A+B*(C-D)–E/F
    5 3 2 4 1
    +A-*B-CD/EF
    5 43 2 1
    +A-*B-CD/EF


  1. 前缀表达式的计算(手算)【这也正是前缀转中缀的算法】
    用栈实现前缀表达式的计算:
    (1)从右往左扫描下一个元素,直到处理完所有元素
    (2)若扫描到操作数则压入栈,并回到(1);否则执行(3)
    (3)若扫描到运算符,则弹出两个栈顶元素,执行相应运算,运算结果压回栈顶,回到(1)
    注意:先取出的是左操作数

  1. 中缀表达式转后缀表达式(机算)
    初始化一个栈,用于暂时保存还不能确定运算顺序的操作符。
    从左至右处理各个元素,直到末尾。可能遇到三种情况:
    (1)遇到操作数。直接加入后缀表达式。
    (2)遇到界限符。遇到’(‘直接入栈;遇到’)‘则依次弹出栈内运算符并加入后缀表达式,直到
    弹出’(‘为止。注意’(‘不加入后缀表达式。
    (3)遇到运算符。依次弹出栈中优先级高于或等于当前运算符的所有运算符,并加入后缀表达式,
    若碰到’('或栈空则停止。之后再把当前运算符入栈。
    按上述方法处理完所有字符后,将栈中剩余运算符依次弹出,并加入后缀表达式。

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

3.3.3 栈在递归中的应用
  1. 函数调用的特点:最后被调用的函数最先执行结束(LIFO)

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

​ ① 调用返回地址

​ ② 实参

​ ③ 局部变量

  1. 递归调用时,函数调用栈可称为“递归工作栈”每进入一层递归,就将递归调用所需信息压入栈顶每退出一层递归,就从栈顶弹出相应信息。
  2. 缺点:效率低,太多层递归可能会导致栈溢出;可能包含很多重复计算
3.3.4 队列的应用
  1. 树的层序遍历
  2. 图的广度优先遍历
  3. 队列在操作系统中的应用

3.4 数组和特殊矩阵

####3.4.1 对称矩阵

特点:对方阵中的任意一个元素都有ai,j = aj,i
压缩:只存储主对角线+下三角区(或主对角线+上三角区)

3.4.2 三角矩阵

特点:上三角区全为常量(下三角矩阵);或下三角区全为常量(上三角矩阵)
压缩:按行/列优先规则依次存储非常量区域,并在最后一个元素存储常量c

####3.4.3 三对角矩阵

特点:当|i - j| > 1时,有ai,j = 0(1<=j, j<=n)
压缩:按行/列优先规则依次存储带状区域

####3.4.4 稀疏矩阵

特点:非零元素远小于零元素个数
压缩:只存储非零元素
顺序存储:顺序存储三元组<行、列、值>
链式存储:十字链表法

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

别云超

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值