第一章 概论
数据结构是计算机内中存储、组织数据的方式,精心选择的数据结构可以带来最有效率的算法
解决问题方法的效率:跟数据的组织方式、空间的利用效率、算法的巧妙程度有关
数据:
1.值(定义)
2.关系:<a,b>有序偶,(a,b)无序偶,a->b->c线性,1:n树状,n:n网状图
3.存储:1️⃣顺序(数组)2️⃣链式(指针)
ps.线性、树性、网状图称为数据的逻辑结构,顺序和链式称为数据的存储结构
4.实现
逻辑结构:数据对象的逻辑组织关系,分为线性、树、图
物理结构:数据对象信息在计算机内存中的存储组织关系,分为顺序存储(连续、数组)和链 式存储(指针)
算法是解决某一类问题的步骤描述,至少产生一个输出,需满足确定性、有限性、可行性
具体衡量算法优劣的指标主要有两个:空间复杂度S(n)、时间复杂度T(n)
数据结构DS=(D,R)→D为元素的集合,R是元素之间关系的集合
第二章 实现基础
变量是数据存储的基本单位,变量的类型决定了存储和操作
数组:相同数据类型的有序集合,需在内存中连续存放
类型定义typedef:typedef 原有类型名 新类型名
指针:能对计算机的内存进行分配控制,在函数中使用可以返回多个值
结构:可以是不同类型的数据分量聚合成一个整体,是变量的集合
struct 结构名{
类型名 结构成员1;
类型名 结构成员2;
...
}
共用体:不同元素类型,共享同一内存单元→共用体长度=最长成员的长度
union 共用体名{
类型名 成员名1;
类型名 成员名2;
...
}
链表:由若干个同一结构类型“结点”依次串接而成,分为单向链表、双向链表、循环链表
单向链表:
插入结点 t->next=p->next;p->next=t
删除结点 p->next=t->next;free(t)
遍历 p=head;while(p!=null){...;p=p->next}
第三章 线性结构
线性表:有同一类型的数据元素构成的有序序列的线性结构
表的起始位置称为表头,结束位置称为表尾
直接前驱和直接后继反映了元素之间一对一的邻接逻辑关系
线性表的顺序存储实现:
m=sizeof(ElemType)
顺序表的优缺点:
优点:存储密度大(空间利用率高),可以随机存取任一元素
缺点:在插入、删除某一元素时,需要移动大量元素
线性表的链式存储实现:不要求逻辑上相邻的物理上也相邻,插入和删除不需要移动数据元素
链表的两种形式:是否带头结点
首位相连的链表称为循环链表,有两个指针域的链表称为双链表
循环列表最大的优势在于:从表任意一个结点出发都可找到表中其他结点
链式存储结构的特点:逻辑上相邻的数据元素在物理上不一定相邻
访问时只能通过头指针进入链表,并通过每个结点的指针域向后扫描其余结 点,所以访问第一个结点和最后一个所花费的时间不等。这种存取元素的方法称为顺序存取法。
链表优缺点:
优点:数据元素的个数可以自由扩充, 删除、插入不必移动数据,只需修改链接指针,修改效率高
缺点:需较多内存,存储密度低,算法相对复杂
两个受限制的线性表:栈和队列
中缀表达式:运算符在两个运算数之间
后缀表达式:运算符在两个运算数之后
- 栈堆Stack:具有一定操作约束的线性表,插入和删除都作用在栈顶的端点位置
数据插入称为入栈,数据删除称作出栈,最后入栈的数据最先弹出所以堆栈也称为”后入先出“
操作对象集:一个有0个或多个元素的有穷线性表
操作集:
Stack CreateStack(int max)生成空堆栈
bool IsFull(Stach S)判断S是否已满
bool Push(Stack S,ElementType X)将x压入栈堆
bool IsEmty(Stach S)判断栈是否为空
ElementType Pop(Stach S)删除并返回栈顶元素
栈的顺序存储实现:一个一维数组+一个记录栈顶元素位置变量组成
栈的链式存储:实际上就是一个单链表,叫做链栈
- 队列Queue:具有一定操作约束的线性表,只能在一端插入、另一端删除
插入称为入队列,删除称为出队列
最先入队列的数据最先被弹出,即先来先服务,所以队列也称为”先进先出“表
队列的顺序实现:一个一维数组+记录队列头元素的变量front+一个记录队列尾元素的变量rear
队空:front==rear,队满:(front+1)%M==rear
front所指的是队头元素的前一个位置,且此单元不用,rear指向的是队尾元素真实位置
队列的链式存储:单链表且不带头结点,但front必须指向首结点,rear指向尾结点
第四章 树
查找可分为静态查找和动态查找,手段分为利用比较和利用映射两种思路
静态查找:集合中记录是固定的,不涉及插入和删除
动态查找:记录是动态变化的,即记录时可能插入和删除操作
查找效率主要用”平均查找长度“(ASL)来衡量
n个结点的判定树深度为 []+1
折半查找的算法复杂度为O()
- 树:n个结点构成的有限集合
对于任一棵非空树,有:
1.树中有一个结点称为”根“的特殊结点,用r表示
2.其余结点可分为m个互不相交的有限集,这些树称为原来树的子树。每个子树子树是不相交的
除根节点外,每个结点有且仅有一个父结点
一棵n个结点的树有n-1条边
树的一些基本术语:
1.结点的度:其子树的个数
2.树的度:所有结点中最大的度数
3.叶结点(端结点):度为0的结点
4.父结点:有子树的结点是其子树根结点的父结点
5.子结点(孩子结点):若A结点是B结点的父结点,则B是A的子节点
6.兄弟结点:具有同一父节点的各节点彼此是兄弟结点
7.分支:树中两个相邻结点的连边
8.路径和路径长度:只能从上往下走,长度为路径所包含的边(分支)个数
9.祖先结点:沿树根到某一节点路径上所有结点都是这个节点的祖先结点
10.子孙结点:某一结点的子树中所有结点是这个结点的子孙
11.结点的层次:根节点在1层,其余层数为其父结点层数+1
12.树的高度(深度):树中所有结点中的最大层次
- 二叉树:一个结点最多2个孩子,若不为空则它是由根节点和称其为左子树和右子树的两个不相交的二叉树组成
二叉树结构最简单,规律性最强
所有树都能转为唯一对应二叉树,不失一般性
特殊二叉树:
1.二叉树的深度<=结点数n,平均深度为O()
2.斜二叉树(退化二叉树)
3.完美二叉树(满二叉树)
4.完全二叉树
- 二叉树的重要性质:
一个二叉树第i层最大结点数为
一个深度为k的二叉树的最大结点数为
对于任一非空二叉树,若n0表示叶结点数,n2为度为2的非叶结点个数,则n0=n2+1
n个结点的完全二叉树的深度k=[]+1
- 二叉树的两个存储结构:
1.顺序存储(一维数组存放二叉树)
一般二叉树用这样的结构会造成空间浪费,完全二叉树就不会浪费,更适用于完全二叉树
现有序号为i的非根结点,则其父结点为[i/2],左孩子为2i,右孩子为2i+1
2.链式存储(用指针表示左右孩子)->更适用于存储一般二叉树
- 二叉树抽象数据类型定义
//对于BT属于BinTree
bool IsEmpty(BinTree BT);//若BT为空返回true,否则false
void Traversal(BinTree BT);//二叉树遍历
BinTree CreatBinTree();//创建二叉树
//先序遍历(根节点->左子树->右子树):根节点访问次序在左右子树之前
void PreOrderTraversalBinTree(BinTree BT);
//中序遍历(左子树->根节点->右子树):根节点访问次序在左右子树之间
void InOrderTraversalBinTree(BinTree BT);
//后序遍历(左子树->右子树->根节点):根节点访问次序在左右子树之后
void PostOrderTraversalBinTree(BinTree BT);
//按层从小到大、从左到右遍历
void LevelOrderTraversalBinTree(BinTree BT);
例.中序遍历
void InOrderTraversalBinTree(BinTree BT){
if(T==NULL) return;//根节点空直接返回,不用访问了
else{
InOrderTraversalBinTree(BT->Left);
printf("%d",BT->Data);
InOrderTraversalBinTree(BT->Right);
}
}
- 求二叉树高度
- 求二叉树叶子结点(度为0)的个数
int count(BirTree T){
if(T==NULL) return 0;
if(T->left==NULL&&T->right==NULL) return 1;
return count(T->lett)+count(T->right);
}
- 二叉搜索树(BST):有利于排序和查找,是在查找过程中动态生成的
可以为空,若不为空,则左子树<根节点<右子树,左右子树均为二叉搜索树
若对二叉搜索树进行中序排序,则一定得到一个从小到大的序列
插入:二叉搜索树上本身不存在插入元素,新插入的节点位于叶子节点
中序遍历是有序的
删除:若删除的结点只有一个孩子,则删除之前需要改变其父结点的指针,指向要删除结点的孩子结点。若删除的结点有左右两颗子树,为保持二叉搜索树的有序性,替代被删除元素的位置有两种选择:一种是取其右子树的最小元素,另一个是取其左子树中的最大元素
二叉搜索树性能分析(汇总)
二叉搜索树是一动态查找表
比较次数=结点所在层数<=二叉搜索树深度
对于n个结点的二叉搜索树不是唯一的
中序排列有序的一定是二叉搜索树
最坏情况:若插入次序恰好有序,则得到斜二叉树,此时查找效率=顺序查找
最好情况:二叉查找判定树,为O()
避免出现极端情况,可对插入过程进行修正,使得左右子树深度差不超过2,这种树称为平衡二叉树
- 优先队列:特殊队列,有限集最高的元素优先 出
- 堆:采用完全二叉树存储的优先队列
堆的两个特性:
结构性:用数组表示的完全二叉树(下标1开始存放)
有序性:根节点到任一结点关键字保持非递增(最大堆、大顶堆)或非递减(最小堆、小顶堆)
大顶堆是上面大,小顶堆是下面大
最大堆的插入:从新增的最后一个结点的父结点开始,用插入元素向下过滤上层结点(向上渗透)
最大堆的删除:把最后一个元素移至根,找出其较大的孩子
最大堆的建立:已经存在n个元素按最大堆的要求存放在一个一维数组中,时间代价最大为O()
例.大顶堆的调整
step1.先按表格顺序写出二叉树
step2.倒序检查。从最后一个非叶结点开始,若根的左右孩子任一大于根(若都大则选更大),则孩子与根互换。
step3.若互换之后,与其相比孩子相比小则继续换,直至结束
二叉树是树形结构中最简单、最有规律的一种形态
- 树的存储结构:
1.双亲表示法
2.孩子链表表示:为每一个结点创建一个单链表,叶子结点的链表为空
3.孩子兄弟表示法(二叉树表示法):左孩子右兄弟
指向第一个孩子结点|data|指向下一兄弟结点
- 树与二叉树的转换:
1.树转换成二叉树:转换后的二叉树的根节点没有右子树,只有左子树
2.森林转换成二叉树
例.左孩子右兄弟
第五章 散列查找
散链查找的两项基本工作:
创建散链函数:确定关键词所在存储位置的计算方法
解决冲突:当多个关键词所在存储位置相同时的解决办法
- 散列表的定义:形如”名字-属性“对的集合的符号表,也称为哈希表
散列表的基本思想:以某一个关键字key为自变量,通过一个确定函数关系h,计算出对应函数值h(key),把这个值解释为数据对象的存储地址并按此存放,即存储位置=h(key)
可能将不同关键字映射到同一个散列地址上,这种现象称为”冲突“
通常关键词的值域(允许取值的范围)远远大于表空间的地址集。所以说冲突不可避免,只能尽可能减少。
一个好的散列函数一般应考虑:计算简单+关键词地址空间分布均匀
- 数字关键字散列函数构造:
1.直接定值法:y=ax+b
2.除留余数法:h(key)=key mod p(p为<=表长的最大素数)
3.数字分析法
- 字符关键字散列函数构造
1.ASCII码相加(最简单也最不好)
2.前三个字符移位
3.移位法(好的散列函数)
- 处理冲突的办法
1.开放地址法:一旦产生冲突,就去寻找另外一个空的散列地址
若发生i此冲突则:hi(key)=[h(key)+di] mod TableSize
其中,di决定了不同的解决方法:线性探测(di=i)、二次探测(di=±i**2)、双散列(di=i*h2(key))
一次聚集现象:需要很多次冲突才能找到空位置
2.链地址法(分离链接法)
- 散列表的性能分析:
1.用平均查找长度ASL来度量效率
2.产生冲突的多少(由散列函数是否均匀、处理冲突的方法、装填因子α决定)
- 散列表的查找效率通常是常数,几乎与关键字空间大小n无关
- 散列方法的存储对关键字是随机的
例.
第六章 图
图G可以表示为两个集合:G=(V,E),通常用|V|表示顶点数量,|E|表示边的数量
1.无向图:用”()“表示无向边
2.有向图:用”<>“表示有向边(也称作弧)
3.简单图:没有重边和自回路的图
4.邻接点
5.路径长度=路径长度边的个数
简单路径:路径上的都是不同顶点
回路:起点和终点相同的路径
无环图:不存在任何回路的图
6.无向完全图:顶点数为n,边数最大为n(n-1)/2
7.有向完全图:顶点数为n,边数最大为n(n-1)
8.顶点的度:与顶点关联的边的个数
入度、出度
边个数=所有顶点的度之和/2
9.稠密图、稀疏图
10.权、网络
11.子图
12.无向图的顶点连通、连通图、连通分量
若顶点vi和vj中间有路径,则称他们连通。
若无相图中任意两个顶点都是连通的,则该图是连通图
无向图的极大连通子图称为连通分量
13.有向图的强连通图、连通分量
有向图中任一对顶点,都有两条相反路径,则称其为强连通图
14.树是图的特例:无环无向图
生成树:G包含其全部n个顶点的一个极小连通子图,它必定包含G的n-1条边
生成树有可能不唯一
- 图的存储结构
图是一种复杂的数据结构,即逻辑上任意两个顶点之间都可以存在关系(即存在边或弧)
图的信息可以包括两部分:顶点的信息和边(弧)的信息
常用两种存储结构:
1.邻接矩阵
顶点信息D[n],边的信息A[n][n],时间复杂度O(N**2)
无向图的邻接矩阵一定是对称矩阵,所需存储元素个数为|V|*(|V|-1)/2
2.邻接表
稀疏的情况下,用邻接表表示图比邻接矩阵节省存储空间
- 图的遍历:从某个顶点出发访遍其余顶点,且使每个顶点仅被访问一次。
图的遍历是处理图的算法的基础
常用遍历:
1.深度优先搜索DFS→也是判断是否为连通图的方法之一
采用邻接矩阵时间复杂度为O(|V|**2),邻接表为O(|V|+|E|)
例.
顶点访问序列为:v1,v2,v4,v8,v5,v3,v6,v7
2.广度优先搜索BFS
例.(图同上)
顶点访问序列为:v1,v2,v3,v4,v5,v6,v7,v8
- 对连通图而言,只要从某一顶点出发进行搜索,就可访问图中所有顶点。对非连通图需要从多个顶点出发。
- 可以通过搜索来判断一个无向图是否是连通图
第七章 排序
排序的种类:内部排序与外部排序
待排序记录可用的存储方式:地址连续的顺序表存储,记录的次序由其存储位置决定
1.基本排序法:时间复杂度O(n**2)
插入:直接插入排序→改进:希尔排序
交换:冒泡排序er→改进:快速排序
选择:选择排序→改进:堆排序
2.归并排序
- 堆排序(时间复杂度:O(),不稳定)
step1.写出大顶堆
step2.最后一个元素和根节点交换,重新调整
step3.重复
- 希尔排序(又称缩量增量排序,不稳定)
时间复杂度与增量序列的选取有关,不稳定
- 冒泡排序
- 快速排序(一定递归)
从前往后找大于基准值的,从后往前找找小于基准值的,交换,继续找...
- 归并排序(稳定)
汇总: