数据结构
绪论
数据结构的基本概念
基本概念和术语
数据:是信息的
数据对象:是具有相同性质的数据元素的集合,是数据的一个子集
数据结构三要素
- 逻辑结构
- 线性结构
- 线性结构
- 树形结构
- 图形结构
- 数据运算
- 存储结构
- 顺序存储
- 链式存储
- 索引存储
- 散列存储
总结:
- 数据的逻辑结构独立于其存储结构
- 数据的存储结构是逻辑结构在计算机上的映射,它不能独立于逻辑结构而存在
算法
算法是问题求解步骤的描述
程序 = 数据结 + 算法
算法五个特性:
- 有穷性
- 确定性
- 可行性
- 零个或者多个输入
- 一个或者多个输出
算法的效率
- 空间
- 时间
算法时间复杂度
记忆公式:常对幂指阶
计算上述算法的时间复杂度T(n):
设最深层循环的语句频度(总共循环的次数)为x,则
由循环条件可知,循环结束时刚好满足在2^x>n
最好情况:元素n在第一个位置 –最好时间复杂度T(n)=0(1)
最坏情况:元素n在最后一个位置 –最坏时间复杂度T(n)=O(n)
平均情况:假设元素n在任意一个位置的概率相同为1/n –平均时间复杂度T(n)=O(n)
空间复杂度
空间规模与n没有关系的话S(n) = O(1) 算法原地工作——算法所需内存空间为常量
线性表
线性表是具有相同数据类型的n (n≥0) 个数据元素的有限序列,其中n为表长,当n= 0时线
性表是一个空表。
顺序表
顺序表的定义
线性表的顺序存储称为顺序表。把逻辑上相邻的元素存储在物理位置上也相邻的存储单元中,元素之间的关
系由存储单元的邻接关系来体现。
c语言求数据元素大小?
1 | sizeof(ElemType) //eg:sizeof(int) = 4B |
- 静态分配
1 |
|
- 一维数组在静态分配时,由于数字的大小和空间已经固定,一旦空间占满,再加入新的数据就会产生溢出,进而导致程序崩溃。
动态分配
1
2
3
4
5
6
7
typedef struct{
int *data//动态分配数组指针
int length;//顺序表的当前长度
int Maxsize;//最大长度
}SeqList;//顺序表的类型定义
顺序表的特点
- 随机访问
- 存储密度高
- 拓展容量不方便
- 插入、删除数据元素不方便
顺序表的基本操作
初始化
IintList(&L)
静态初始
1
2
3
4
5void InitList(SqList &L){
for(int i=0; i<MaxSize; i++)
L.data[i]=0;//将所有数据元素设置为默认初始值
L.length=0; //顺序表初始长度为0
}动态初始
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16void InitList(SqList &L){
// 用malloc函数申请一片连续的存储空间
L.data = (int *)malloc(InitSize*sizeof(int));
L.length = 0;
L.MaxSize = InitSize;
}
//增加动态数组的长度
Void IncreaseSize(SeqList &L,int len){
int *p = L.data;
L.data = (int *)malloc((L.MaxSize+len)*sizeof(int));
for(int i =0;i<L.length;i++){
L.data[i]=p[i]; // 将数据复制到新区域
}
L.MaxSize = L.MaxSize+len; //顺序表最大长度增加len
free(p); // 释放原来的空间
}
插入
- bool ListInsert(SqList &L,int i,int e)
1 | bool ListInsert(SqList &L,int i,int e){ |
时间复杂度: O(n)
平均情况:假设新元素插入到任何一个位置的概率相同,即i= 1,2,3, … length+1 的概率都是p=
1/(n+1),i=1,循环n次; i=2时,循环n-1次; i=3, 循环n-2次….. i=n+1时,循环0次
平均循环次数
删除
bool ListDelete(SqList &L,int i,int &e)
1
2
3
4
5
6
7
8
9
10bool ListDelete(SqList &L,int i,int &e){
if(i<1||i>L.length)
return false;
e = L.data[i-1];
for(int j =i;j<L.length;j++){
L.data[j-1] = L.data[j];
}
L.length--;
return true;
}- 时间复杂度: O(n)
- 平均情况:(n-1)/2
链表
链表的定义
1 | typedef struct LNode{ |
表示一个单链表时,声明一个头指针L,指向单链表的第一个节点LNode *L;或者LinkList L;
强调这是一个单链表——LinkList
强调这是一个节点 ——LNode *
链表的基本操作
初始化
不带头节点
1
2
3
4bool InitList(LinkList &L){
L = NULL;
return true;
}带头节点
1
2
3
4
5
6
7bool InitList(LinkList &L){
L = (LNode *)malloc(sizeof(LNode));
if(L==NULL) // 内存不足分配失败
return false;
L->next = NULL;
return true;
}空表判断:不带头节点:L==NULL
带头节点:L->next ==NULL
插入
在第i个位置插入元素e
带头节点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19// 在第i个位置插入元素e
bool ListInsert(LinkList &L,int i,ElemType e){
if(i<1)
return false;
LNode *p; // 指针p指向当前扫描到的节点
int j =0; // 当前p指向是第几个节点
p =L;
while(p!=NULL && j<i-1){
p =p->next;
j++;
}
if(p==NULL) // i值不合法
return false;
LNode *s = (LNode *)malloc(sizeof(LNode));
s->data = e;
s->next = p->next;
p->next = s;
return true;
}不带头节点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26// 在第i个位置插入元素e
bool ListInsert(LinkList &L,int i,ElemType e){
if(i<1)
return false;
if(i==1){
LNode *s = (LNode *)malloc(sizeof(LNode));
s->data = e;
s->next = L;
L = s;
retuen true;
}
LNode *p; // 指针p指向当前扫描到的节点
int j =1; // 当前p指向是第几个节点
p =L;
while(p!=NULL && j<i-1){
p =p->next;
j++;
}
if(p==NULL) // i值不合法
return false;
LNode *s = (LNode *)malloc(sizeof(LNode));
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
在p节点之前插入元素e
1 | bool InsertPriorNode(LNode *p,ElemType e){ |
删除
按位序删除
1 | bool ListDelete(LinkList &L,int i,ElemType &e){ |
删除指定节点 (移动值)
1 | bool DeleteNode(LNode *p){ |
注意:如果p为最后一个节点会出现空指针异常
单链表的建立
尾插法
1 | LinkList List_Taillnsert(LinkList &L){ |
头插法
逆置
1 | LinkList List_Headlnsert(LinkList &L){ |
双链表
定义
1 | typedef struct DNode{ |
插入
1 | //p节点之后插入s节点 |
删除
1 | // 删除p节点的后继节点 |
循环链表
初始化
1 | //初始化一个循环链表 |
栈
栈的定义
栈(Stack)是只允许在一端进行插入或删除操作的线性表
栈的特点
后进先出表
n个不同元素进栈,出栈元素不同排列的个数为
栈的基本操作
顺序栈
使用静态数组实现,并且需要记录栈顶指针
1 |
|
顺序存储:给各个数据元素分配连续的存储空间,大小为MaxSize * sizeof(ElemType)
初始
1 | void InitStack(SqStack &s){ |
进栈
1 | bool Push(SqStack &S,ElemType x){ |
出栈
1 | bool Pop(SqStack &S,ElemType &x){ |
链栈
1 | typedef struct LinkNode{ |
初始
1 | void InitStack(LinkStNode *&s){ |
入栈
1 | bool Pup(LinkStNode *&s,int x){ |
出栈
1 | bool Push(LinkStNode *&s,int &x){ |
栈的应用
括号匹配
思路分析
graph TD; A[开始]-->B{还有未处理括号?}; B-->|Yes|C[扫描下一个括号a]; B-->|No|S{栈空?}; S-->|No|G; S-->|Yes|su[匹配成功]; su-->结束; C-->D{a是左括号?}; D-->|Yes|E[a压入栈顶]; E-->B; D-->|No|F{栈空}; F-->|No|H[弹出栈顶元素b]; H-->T{b与a匹配?}; T-->|Yes|B; T-->|No|G; F-->|Yes|G[匹配失败]; G-->结束;
算法实现
1 | bool brackCheck(char str[],int length){ |
表达式求值
中缀表达式 | 后缀表达式 | 前缀表达式 |
---|---|---|
运算符在两个操作数中间 | 运算符在两个操作数后面 | 运算符在两个操作数前面 |
a+b | ab+ | +ab |
a+b-c | ab+c- | -+abc |
a+b-c*d | ab+cd*- | -+ab*cd |
中缀转化为后缀
初始化一个栈,用于保存暂时还不能确定运算顺序的运算符。
从左到右处理各个元素,直到末尾。可能遇到三种情况:
①遇到操作数。直接加入后缀表达式。
②遇到界限符。遇到“(”直接入栈;遇到“)”则依次弹出栈内运算符并加入后缀表达式,直到
弹出“(”为止。注意:“(” 不加入后缀表达式。
③遇到运算符。依次弹出栈中优先级高于或等于当前运算符的所有运算符,并加入后缀表达式,
若碰到“(”或栈空则停止。之后再把当前运算符入栈。
按上述方法处理完所有字符后,将栈中剩余运算符依次弹出,并加入后缀表达式。
队列
队列的定义
队列(Queue) 是只允许在一端进行插入,在另一端删除的线性表
队列的特点
先进先出表
队列的基本操作
循环队列
1 |
|
判断队列已满条件
(Q.rear+1)%MaxSize == Q.front 对尾指针下一个就是对头
:warning:会牺牲一片存储空间
解决方法:
- 添加元素size判断个数
- 添加操作变量。删除时候tag=1,添加tag=0;队空 front=rear&&tag=1;
队列元素个数
(rear+MaxSize-front)%MaxSIze
初始
1 | void InitQueue(SqQueue &Q){ |
入队
1 | bool EnQueue(SqQueue &Q,ElemType x){ |
出队
1 | bool DeQueue(SqQueue &Q,ElemType &x){ |
链式队列
1 | typedef struct LinKNode { //链式队列节点 |
初始
1 | void InitQueue(LinkQueue &Q){ |
入队
带头节点
1
2
3
4
5
6
7void EnQueue(LinkQueue &Q,ElemType x){
LinkNode *s=(LinkNode *)malloc(sizeof(LinkNode));
s->data = x;
s->next = NULL;
Q.rear->next = s; //新节点插入到rear之后
Q.rear = s; //修改尾指针
}不带头节点
1
2
3
4
5
6
7
8
9
10
11
12void 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; //新节点插入到rear之后
Q.rear = s; //修改尾指针
}
}
出队
带头节点
1
2
3
4
5
6
7
8
9
10
11bool DeQueue(LinkNode &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; //修改rear指针
free(p);
return true;
}不带头节点
1
2
3
4
5
6
7
8
9
10
11
12
13bool DeQueue(LinkNode &Q,ElemType &x){
if(Q.front=Q.rear)
return false; //空队
LinkNode *p = Q.front;
x=p->data;
Q.front->next = p->next;
if(Q.rear == p){
Q.front = NULL;
Q.rear = NULL;
} // 此次是最后一个节点出队
free(p);
return true;
}
特殊矩阵压缩存储
一维数组的存储结构
一维数组元素的存放地址
起始地址 LOC
数组元素a[i] =LOC+i*sizeof(ElemType)
二维数组的存储结构
- 按行优先
二维数组按行优先,元素的存放地址
二维数组b[M] [N]]中起始地址 LOC
*数组元素b[i] [j]=LOC+(iN+j)sizeof(ElemType)*
- 按列优先
二维数组按列优先,元素的存放地址
二维数组b[M] [N]]中起始地址 LOC
*数组元素b[i] [j]=LOC+(jM+i)sizeof(ElemType)*
- 标题: 数据结构
- 作者: 小徐
- 创建于 : 2023-08-18 18:23:36
- 更新于 : 2023-09-08 15:04:09
- 链接: https://xiaoxua18.gitee.io/2023/08/18/数据结构/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。