数据结构
数据结构总结
数据结构
什么是数据结构?
数据元素:由数据项组成
- 数据结构:
- 数据之间的逻辑结构
- 数据的存储结构
数据之间的逻辑结构
集合结构(各个元素项之间没有关系)
线性结构(排序问题)
- 线性表
非线性结构
树(传达命令,数据继承)
树
二叉树
二叉搜索树

- 图、网络(人际交往图,路由网络图)(网络:带权重的图)

数据元素的储存形式
顺序存储
链接存储

索引存储
散列存储
数据的运算
- 创建
- 清除:删除数据结构中的全部元素
- 插入:在数据结构的指定位置上插入一个新元素;
- 删除运算:将数据结构中的某个元素删除
算法
对特定问题的描述步骤,有穷的指令集合
程序=数据结构+算法
特点:
- 输入:\(\ge0\)
- 输出:\(\ge 1\)
- 确定性:没有二义性
- 可执行性(大象塞进冰箱没有可执行性)
- 有穷性
算法的时间复杂度和空间复杂度
程序步

时间复杂度,大\(O\)标记法,常见的时间复杂度
线性表
定义:除了头结点和尾节点之外,其他结点有且仅有一个前驱和一个后继称为线性表
顺序表(顺序存取)
实现方式:数组
主要注意操作:
删除操作\(O(n)\)
删除操作是删除表的第i (\(1\le i\le n+1\))个位置的数据主要实现方式如下(需要将后面的位置向前移动):
1 | for(int j=i;j<length;j++) |
插入操作\(O(n)\)
插入操作是在表的第i (\(1\le i\le n+1\))个位置插入一个新元素\(x\),主要实现方式如下:
1 | for(int j=length;j>=i;j--) |
缺点:
- 插入和删除的复杂度太高,都达到了\(O(n)\)
- 容量不好确定
- 必须要连续的存储空间
链式存储(顺序存取)
单链表(带头结点)
插入操作(p结点之后)(\(O(n)\))
说明:时间复杂度尾\(O(n)\),主要花在了找查的上面,实际时间只需\(O(1)\).
1 | Node * insert= new Node; |
删除操作(p结点之后)(\(O(n)\))
说明:时间复杂度尾\(O(n)\),主要花在了找查的上面,实际时间只需\(O(1)\).
1 | Node * q = p->next; |
- 初始化带头结点的单链表
1 | first = new Node<DateType>; |
- 两种插入新元素的方式
头插法(\(O(1)\))
尾插法(\(O(n)\))
说明:时间复杂度尾\(O(n)\),主要花在了找查的上面,实际时间只需\(O(1)\).
1 | p = first ; |
双链表
- 插入操作(在结点p后面插入一个新结点s)
1 | s->prior = p; |
- 删除操作(删除p结点)
1 | //这个地方没有前后顺序 |
循环链表
循环链表一般使用尾指针rear,在循环链表的遍历中,为了判断其是否遍历完成,所以可以用p==rear来判断
两种储存方式的比较
顺序表:
- 查找:\(O(1)\)
- 插入:\(O(n)\)
- 删除:\(O(n)\)
链表:
- 查找:\(O(n)\)
- 插入:\(O(1)\)
- 删除:\(O(n)\)
线性表应用
静态链表
约瑟夫环
问题描述:
0,1,···,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字(删除后从下一个数字开始计数)。求出这个圆圈里剩下的最后一个数字。
例如,0、1、2、3、4这5个数字组成一个圆圈,从数字0开始每次删除第3个数字,则删除的前4个数字依次是2、0、4、1,因此最后剩下的数字是3。
抽象数据类型:
构造函数:环的长度n和密码m
第一步:构造一个长度为n的约瑟夫环
第二步:使用密码m进行求解约瑟夫环的答案
1 | //使用密码求解环的答案 |
一元多项式求和
问题:多项式\(A(x)=a_0+a_1x+a_2x^2+..a_nx^n\)和\(B(x)=b_0+b_1x+b_2x^2+..b_nx^n\)合并
分析:合并的时候,有一些项数是都存在,有一些项数只是A或者B存在,那么,这种情况下我们要假设A,B都已经按照从小到大的幂次排列好,这时,我们只需要将一项一项进行比较,观察他们是否相等,相等则合并,不相等则不合并
抽象数据类型:
元素:长度足够的数组data 每一项:系数xishu,幂次mici
操作:合并A,B,运算符重载
1 | void add(Poly B) |
栈和队列
栈结构(Stack)
顺序栈(数组实现)
栈空判断:top==-1
栈的长度StackSize
链栈
单链表的表头为栈顶(头插法)
问题:当进入栈的顺序为1,2,3,4,5...n时,请问可能出栈的序列有多少种?
结果:\(\frac{1}{n+1}C_{2n}^{n}\)
元素个数变化较大的时候用链栈,否则用顺序栈
队列
循环队列(数组实现)
顺序队列的存储结构(数组),入队出队时间复杂化度为\(O(1)\),空间利用率为n-1
解决假溢出:rear=(rear+1)%QueueSize
循环队列判断是否满的判断:(rear+1)%QueueSize=front
链队列
- 队头:头指针,时间复杂度为\(O(1)\)
- 入队:尾插法
- 出队,头出发(不要删除头结点)
1 | //出队代码 |
应用举例
- 两栈共享空间

括号匹配问题
使用栈来匹配括号,查看代码是否是多了括号或者少了括号
算法:Match(str)
1 | top = -1; |
表达式求值
设运算符有+,-,*,/,#,其中#号是界定符,求出给的表达式的结果:假设给的表达式为 \[
3\times 3+(6/5+7)*4-9
\]
一个表达式的输出一共有两种情况,一种是数字,一种是符号,我们假设有两个栈,一个是符号栈,一个是数据栈,首先要判断进来的这个是不是一个符号,其次他的优先级是否高于现在栈中栈顶元素的符号,如果高于,那么这个时候我们让它进栈,如果不高于,那么,我们让栈中栈顶元素出栈,,对数据进行运算,直到其优先级小于入栈符号的优先级,然后新的符号压入栈中.
1 | SymbolStack.push('#') |
递归
递归的主要组成部分
- 基础情况
- 递归部分
汉诺塔求解

解决方法:
:one: 首先假设样本数量比较小,例如只有一个盘子的时候,那么我们需要做的是:A--->C
如果是两个盘子:第一步:将上面的盘子移到B,将A的盘子移到C,再把B上面的移动到C:A--->B,A--->c,B--->c
如果是三个盘子:第一步,想办法把A上面2个盘子移到到B,再把A上的移动到C,最后想办法把B上的移动到C:A(2)--->B,A--->C,B(2)--->C
:two:现在考虑更加朴实的情况:n个盘子:则算法的思路为:A(n-1)--->B,A--->C,B--->C
:three: 好!现在算上想清楚了,基础条件:n=1:直接移动,如果n!=1的时候,就借助B,再移动到C
1 | //Move函数:A通过借助B移动到C |