我考研时候的数据结构笔记
常用语法:
- 申请内存,并转换成Person类
Person *p = (Person *) malloc(sizeof(Person));
- 定义结构体
typedef struct {
int age;
char *name;
}Person, *PersonP,Persons[20];
如果定义的时候就是类,那么想表示指针就得这么写:Person *p
如果定义的时候就是指针,那么想表示指针就得这么写: Personp p
- 赋值运算
Person p;
P.age = 12;
PersonP p1 = &p;
p1 -> age = 12;
- 定义常量
#define MAX_VALUE 255;
- 入参中 &l
表示对l进行的修改可以拿到外面去
- 创建数组
int *p = (int *)malloc(5 * sizeof(int));
第1 章 绪论
考试内容:
数据结构在程序设计中的作用;数据结构的主要内容;数据结构的基本概念;算法及算法分析。
考试要求:
1. 理解数据结构的基本概念;算法设计;掌握算法的时间和空间复杂度。
2. 掌握数据结构的定义;算法的描述方法。
数据结构包括:逻辑结构、存储结构、数据运算
算法描述方式:自然语言描述、伪代码、程序设计语言、流程图、SD图
时间复杂度:语句被执行的次数
空间复杂度:算法消耗的存储空间
第2章 线性表
考试内容:
线性表的逻辑结构;线性表的顺序存储结构及实现;线性表的链接存储结构及实现;顺序表和链表的比较。
考试要求:
1. 掌握线性表的概念;线性表的顺序存储结构、链式存储结构及其常用算法。
2. 掌握线性表的逻辑结构;线性表的存储结构及操作的实现;链式存储结构及其常用算法;双向循环链表。
常用方法:
initList(&l);
length(l);
locateElem(l,e);
getElem(l,i);
listInsert(&l,i,e);
listDelete(&l,i,&e);
printList(l);
empty(l);
destroyList(&l);
线性表包括:顺序表和链表
#define MAX_SIZE 50
typedef struct{
ElementType data[MAX_SIZE];
int length;
}SqList;
看15页的图
typedef struct LNode{
ElementType data;
struct LNode *next;
}LNode,*LinkList;
看29页的图
顺序表和链表的比较:
1.顺序表可以顺序存取,也可以随机存取;链表只能顺序存取
2.顺序表的底层是数组,链表的底层是链表。相邻元素顺序表物理存储位置相邻,链表存储位置不相邻;
3.按值查找,顺序表无序时,顺序表和链表都是o(n)
顺序表有序时,顺序表为o(log2n),链表为o(n)
按位置查找,顺序表o(1),链表o(n)
- 空间分配: 链表多了指针的位置,所以存储密度小
双向循环链表
看35页的图
第3章 栈和队列
考试内容: 基本运算。
2. 掌握递归的编程实现;循环队列和链队列的基本运算。
栈的名字Stack 后进先出
initStack(&s);
stackEmpty(s);
push(&s,x)
pop(&s,&x)
getTop(s,&x)
destroyStack(&s)
#define MAX_SIZE 50
typedef struct{
ElementType data[MAX_SIZE];
int top;
}SqStack;
top是栈顶的指针,初始值是-1,满栈条件是s.top == MAX_SIZE-1;
入栈:top先+1,然后s.data[s.top] = x
出栈:x = s.data[s.top],s.top再-1
看66页的图
栈的表达式求值: 一棵树的中置表示(人类计算方式)、后置表示(计算机计算方式)
中置表示如何转变成后置表示?
当前是数字就进栈,当前是符号就出栈两个,并进行计算,结果进栈
递归可以用栈来转换
队列的名字Queue 先进先出
initQueue(&q)
queueEmpty(q)
enQueue(&q)
deQuque(&q)
getHead(q)
#define MAX_SIZE 50
typedef struct{
ElementType data[MAX_SIZE];
int front;
int rear;
}SqQueue;
初始时front和rear都是0,队尾进元素,队首出元素
入栈,rear位置添加,rear+1
出栈,front位置出值,front+1
看78页的图
循环队列:使用取余%实现的
初始:front = rear = 0
添加一个元素 rear = (rear + 1)%MAX_SIZE
取出一个元素 front = (front + 1)%MAX_SIZE
队列长度:(rear-front + MAX_SIZE)%MAX_SIZE
判断队列空还是满?1.牺牲一个队列单元;2.增加元素个数字段;3.增加tag标识上一步是添加元素还是取出元素
看79页的图
链队列
typedef struct{
ElementType data;
LNode *next;
}LinkNode;
typedef struct LinkQuquq{
LinkNode *front;
LinkNode *rear;
}LinkQueue;
看81页的图
第4章 字符串和多维数组
考试内容:
字符串;多维数组;矩阵的压缩存储
考试要求:
1. 了解串的逻辑结构,存储结构。
2.掌握串定义和存储方法;串的操作。
字符串的模式匹配KMP算法
- 根据模求next数组
寻找当前位数前面的字符,首尾匹配,获得匹配数+1
如果前面的字符为空,则填写0
2.用模和被匹配字符串进行匹配,不匹配了就去找next数组对应的值,从这一位继续匹配
第5章 树和二叉树
考试内容
树的逻辑结构;树的存储结构;二叉树的逻辑结构;二叉树的存储结构及实现; 二叉树遍历的非递归算法;树、森林与二叉树的转换。
考试要求:
1.了解树的基本概念;
2.理解二叉树的性质和存储结构;掌握遍历、构造二叉树和线索二叉树;
3.理解树的存储结构和遍历;掌握集合的一种表示方法;
4.掌握哈夫曼树及其应用;
树的术语:祖先、子孙、孩子、双亲、堂兄弟、度、叶子结点、终端结点、分支结点、非终端结点、根结点、深度、高度、有序树、无序树
二叉树的形态:空二叉树、只有根结点、只有左子树、只有右子树、左右子树都有
特殊的二叉树:满二叉树、完全二叉树、二叉排序树
计算:n0 = n2 + 1;度为0的结点 = 度为2的结点 + 1
存储结构:顺序存储和链式存储,顺序存储浪费空间,一般为链式存储
typedef struct BiTNode{
ElementType data;
BiTNode *left;
BiTNode *right;
}BiTNode,*BiTree;
二叉树的遍历:前序遍历、中序遍历、后序遍历、层序遍历
遍历方式需要为: 递归和非递归两种
void preOrder(BiTree b){
if(b){
visit(b);
preOrder(b->lchild);
preOrder(b->rchild);
}
}
前序遍历:构造栈,栈顶就是需要被访问的元素
void preOrder(BiTree b){
initStack(s);
BiTree p =b;
while(p || !emptyStack(s)){
If(p){
visit(p);
push(s,p);
p = p->lchild;
}else{
pop(s,p);
p = p->rchild;
}
}
}
中序遍历
void inOrder(BiTree b){
initStack(s);
BiTree p = b;
While(p || !stackEmpty(s)){
If(p){
push(s,p);
p = p->lchild;
}else{
pop(s,p);
visit(p);
p = p->rchild;
}
}
}
后续遍历
void postOrder(BiTree b){
initStack(s);
BiTree p = b;
BiTree r = NULL;
while(p || !emptyStack(s)){
if(p){
#有左子树就直接进栈
push(s,p);
p = p->lchild;
}else{
getTop(s,p)
If(p->rchild && r != p->rchild){
#存在右子树且右子树没有访问过
p = p->rchild;
push(p);
p = p->lchild;
}else{
#不存在右子树,或者右子树已经被访问过
pop(s,p);
visit(p);
r = p;
p = NULL;
}
}
}
}
层序遍历
void levelOrder(BiTree b){
initQueue(q);
enQueue(q,b);
while(!emptyQueue(q)){
BiTree p = deQueue(q);
visit(p);
If(p->lchild){
enQueue(q,p->lchild);
}
If(p->rchild){
enQueue(q,p->rchild)
}
}
}
树和森林和二叉树的转化:左孩子,右兄弟
构造二叉树的方式:
构造二叉树的意思是说:根据两种遍历的结果,获得二叉树的逻辑结构
- 先序遍历+中序遍历
- 后序遍历+中序遍历
- 层序遍历+中序遍历
查看142的案例
线索二叉树:如果当前结点没有左孩子,左孩子变成直接前驱;如果当前结点没有右孩子,右孩子变成直接后继
ltag=0,lchild表示左孩子
ltag =1,lchild指针表示前驱
rtag=0,rchild指针表示右孩子
rtag=1,rchild指针表示后继
typedef struct ThreadNode{
ElementType data;
int ltag;
int rtag;
struct ThreadNode *lchild;
struct ThreadNode *rchild;
}ThreadNode,*ThreadTree;
前序线索二叉树、中序线索二叉树、后序线索二叉树
如何画出中序线索二叉树?
- 中序排序
- 寻找空指针,指向直接前驱或者直接后继
代码实现中序线索二叉树(1.线索化;2.遍历)
void inThread(ThreadTree &p,ThreadTree &pre){
if(p!=NULL){
inThread(p->lchild,pre);
if(p->lchild == NULL){
#第一个的左结点也被处理了
p->ltag = 1;
p->lchild = pre;
}
if(pre!=NULL && pre->rchild == NULL){
pre->rtag = 1;
pre->rchild = p;
}
pre = p;
inThread(p->rchild,pre);
}
}
//然后最后一个结点进行特殊处理
void createInThread(ThreadThree t){
ThreadTree pre = NULL;
If(t!=NULL){
inThread(T,pre);
pre->rchild = NULL;
pre->rtag = 1;
}
}
//中序线索二叉树的遍历
求中序遍历的第一个结点
ThreadNode * firstNode(ThreadNode *p){
while(p->ltag == 0){
p = p->lchild;
}
return p;
}
ThreadNode *nextNode(ThreadNode *p){
if(p->rtag == 0){
return firstNode(p->rchild)
}else{
return p->rchild;
}
}
void inOrder(ThreadNode *t){
for(ThreadNode *p = firstNode(t) ; p!=NULL ; p=nextNode(p)){
visit(p);
}
}
哈夫曼树:带权路径长度最短的树
- 将结点和权排在一起
- 选择权值最小的两个结点,连接起来,生成一个新的结点和权值
- 重复上面的步骤
查看190页的图
哈夫曼编码:用哈夫曼树的编码(可变长度)
左0右1
查看190页的图
集合:并查集
S表示森林,Root1表示以Root1为根的树,Root2表示以Roo2为根的树
并:将Root1和Root2合并变为Root1:Root2作为Root1的孩子
查:找到元素x的根是谁
初始化:让S中所有的元素各自为一个子集
void init(int s[]){
for(int i=0;i<max_size;i++){
s[i] = -1;
}
}
void find(int s[],int x){
while(s[x] >=0){
x = s[x];
}
return x;
}
void union(int s[],int root1,int root2){
s[root2] = root[1]
}
第6章 图
考试内容:
图的逻辑结构;图的存储结构及实现;最小生成树;最短路径;有向无环图及其应用。
考试要求:
1.理解图的基本概念;图的存储结构;
2.掌握图的遍历及应用{最小生成树,最短路径等};拓扑排序和关键路径。
图G=(V,E);V表示顶点,E表示边
V={1,2,3}表示有三个顶点
E={<1,2>,<2,1>}表示有1到2,2到1的有向边
E={(1,2)}表示1到2的无向边
连通、连通图、连通分量的概念:
顶点A和顶点B之间有路径存在,则称A和B连通;
图中所有的顶点都是连通的,则称图为连通图;
一个图中的极大连通子图,叫做连通分量。
图的存储结构:临接矩阵法、临接表法、十字链表、临接多重表
邻接矩阵
typedef struct {
vertexType vex[MAX_SIZE];
edgeType edge[MAX_SIZE][MAX_SIZE];
int vexnum;
int arcnum;
}MGraph;
临接表法
边结点
typedef struct ArcNode{
int adjvex;
struct ArcNode *next;
}ArcNode;
顶点
Typedef struct VNode{
VertexType data;
ArcNode *first;
}VNode,AdjList[MAX_SIZE];
图
Typedef struct {
AdjList vertexList;
int vexNum;
int arcNum;
}AlGraph;
十字链表:(有方向图)
VNode有data、in、out
ArcNode有inData、outData、in、out
临接多重表:(无方向图)
VNode有data、next
ArcNode有inData、in、outData、out
图的基本操作:
getEdgeValue(G,x,y);
setEdgeValue(G,x,y);
insertVertex(G,x);
deleteVertex(G,x);
addEdge(G,x,y);
removeEdge(G,x,y);
neighbors(G,x);
firstNeighbor(G,x)
nextNeighbor(G,x,y);
adjacent(G,x,y);
图的遍历:广度优先和深度优先
下面是广度优先的算法,需要一个visited[]数组和一个queue来做辅助
bool visited[MAX_NUM] = false;
void bfsMain(Graph g){
for(int i=0;i<g.vertexNum;i++){
visited[i] = false;
}
for(i=0;i<g.vertexNum;i++){
if(!visited[i]){
bfs(g,i)
}
}
}
void bfs(Graph g,int v){
initQueue(q);
visit(v);
visited[v]=true;
enqueue(v);
while(!emptyQueue(q)){
dequeue(q,v);
for(int i = firstNeighbor(g,v);i>0;i=nextNeighbor(g,v,i)){
if(!visited(i)){
visit(i);
visited[i]=true;
enQueue(i);
}
}
}
}
广度优先算法分析:
时间复杂度是O(V+E)或者O(V平方);
空间复杂度是O(V)
深度优先:
bool visited[MAX_SIZE];
void dfsMain(Graph g){
for(int i=0;i<g.vertexNum;i++){
visited[i] = false;
for(i=0;i<g.vertexNum;i++){
dfs(g,i);
}
}
}
void dfs(Graph g,int v){
visit(v);
visited[v]=true;
for(int w = firstNeighbor(g,v);w>0;w=nextNeighbor(g,v,w)){
if(!visited[w]){
dfs(g,v);
}
}
}
深度优先算法分析:
时间复杂度是O(V+E)或者O(V平方);
空间复杂度是O(V)
最小生成树的算法:普里姆和克鲁斯卡尔
普里姆:1.任选一点;2.寻找当前图形的权最短的边的未连接的点,加入进来;无限循环
克鲁斯卡尔:每次都找最小权值的边,查看当前的边的两端点是否在树内,不在则加入进来
看书239页
最短路径的算法:迪杰斯特拉和弗洛伊德
迪杰斯特拉P241页
求顶点1到其余顶点的距离:
做表,纵坐标是顶点2,3,4,5;集合
横左边是第一轮,第二轮,第三轮。。。
每次把上一轮确定进入集合的点加入到已知路径的中间;求得最短路径并加入集合
弗洛伊德看总结的案例
迪杰斯特拉和弗洛伊德的算法都是贪心算法,需要先求出当前的路径中经过的点,再把下一个点加入进来,需要整合计算
拓扑结构
给一个有向的树形结构,依次写出没有指向它的顶点。
关键路径
有四个量:事件最早发生时间、事件最迟发生时间、活动最早开始时间、活动最迟结束时间。
- 画图,圆圈和方块
- 圆圈画出事件的最早发生时间和最迟发生时间
- 方块画出活动的最早开始时间和最迟结束时间
- 做表
第7章 查找技术
考试内容
查找的基本概念、查找算法的性能;线性表的查找技术;树表的查找技术;散列表的查找技术
考试要求
1掌握顺序查找、折半查找和索引查找的方法
2.掌握二叉排序树的构造方法和二叉平衡树的建立方法
3.掌握哈希表的构造方法,哈希表在查找不成功时的平均查找长度的计算方法
顺序查找: 一般线性表的顺序查找、有序表的顺序查找、有序表的二分查找
typedef struct SSTable{
ElementType *data;
int length;
}SSTable;
int search(SSTable ss,ElementType key){
ss.data[0] = key;
for(int i = ss.length;ss.data[i]!=key;i--){
//哨兵 + 从后往前找
}
return i;
}
二分查找
int binarySearch(SeqList l,ElementType key){
int low = 0;int high = l.length-1;
int middle;
while(high >= low){
middle = ( low + high ) / 2;
if(l[middle] == key){
return middle;
}
if(l[middle] > key){
high = middle -1;
}else{
low = middle + 1;
}
}
return -1;
}
索引查找又叫做分块查找
散列表:包括hash函数和处理冲突的方式
常用的散列函数:直接地址法、除留余数法、数字分析法、平方取中法
处理冲突的方法:开放地址法,拉链法
散列表的性能比较:ASL和α
求ASL:1.先写出具体的数组;2.写出某个数查询的次数;3.ASL等于平均查找次数
α装填因子 = 表中记录数/数组的长度
二叉排序树和二叉平衡树
二叉排序树:中序排序,左子树均小于父结点,右子树均大于父结点
int search(BiTree t,ElementType key){
while(t!=null && t->data!=key){
if(t->data < key){
t = t->rchild;
}else{
t = t->rchild;
}
}
return t;
}
int insertBSTNode(BiTree &t,ElementType key){
if(t == NULL){
t = (BiTree *)malloc(sizeof(BiTree));
t->data = key;
t>lchild = NULL;
t->rchild = NULL;
return 1;
}
if(key == t->data){
return 0;
}else if(key > t->data){
return insertBSTNode(t->rchild,key);
}else if(key < t->data){
return insertBSTNode(t->lchild,key);
}
}
二叉排序树的删除操作:
1.如果删除叶子结点,则直接删除
2.如果删除的结点只有1个左孩子或者右孩子,则删除该结点后孩子顶替
3.如果删除的结点有左孩子和右孩子,则将直接前驱结点或者直接后继结点顶替
平衡二叉树
概念:结点的左子树和右子树高度差不超过1
插入:LL LR RL RR四种情况
P186页 RL和LR都是以RL和LR为父结点,LL和RR是以L和R为父节点
第8章 排序技术
考试内容:
排序的基本概念、排序算法的性能;插入排序;交换排序;选择排序;归并排序; 分配排序;各种排序方法的比较。
考试要求:
1.掌握各类排序的原理和特征;
2. 掌握排序的各种算法实现和应用
排序的口诀:
选择插入归交基(排序的种类分为选择、插入、交换、归并、基数)
插入里有直半希(插入排序包括:直接插入、折半、希尔)
交换冒泡快排好(交换排序包括:冒泡、快排)
选出一堆简单栗(选择排序包括:简单选择、堆排)
稳定:
稳稳的幸福,鸡毛插归壳(插入、归并)
额外的存储:
有序插,无序快
O(nlog2n):
快以nlog2n归堆(快排,归并、堆排)
插入排序:将数分成三部分:已经排好序的、正在排序的、未排序的
快排:在一堆成绩里面第一次排序就可以得到张硕士的成绩排名,需要i,j,pivot三个指针,i在最左边,pivot在最左边,j在最有边。从j开始运动,j向i的方向运动,i向j的方向运动。当j碰到比pivot小的数就停止运动,且将该值放到i上,i碰到比pivot大的数就停止运动,且将该值放到j上。一直运动直到i和j位置相等,此时将pivot赋值到ij位置上。Pivot的排名得以确定。
空间复杂度O(log2n)
堆排序:
堆排序分为大根堆和小根堆,大根堆就是父节点比子节点大
1.建堆
只处理分支结点的逆层序排序 O(n)
2输出堆结点,然后将最底层的结点放到根节点,然后建堆
3新增结点放到最底层,然后逆序排序
归并排序:
1先把两两一组排序
2.再把四个一组排序
3.再把八个一组排序
空间复杂度O(n)
基数排序:面向数字进行排序
比如n个三位数字的排序:
需要三次分配三次收集
每次分配需要n次,每次收集需要d(10)次