写在前面
你们好,我是小庄。很高兴能和你们一起学习c语言。如果您对编程感兴趣的话可关注我的动态.
写博文是一种习惯,在这过程中能够梳理知识和巩固知识点。
一、绪论
1、什么是数据、数据元素、数据项、数据对象、数据结构?
(1)、数据
客观事物的符号表示,指所有能输入到计算机并被计算机程序处理的符号的总称。如:数字、字符、图形、声音、动画等特殊编码定义后的数据。
(2)、数据元素
数据的基本单位,在计算机中通常作为一个整体进行考虑和处理,数据元素用于完整的描述一个对象。如:一个学生记录,树中棋盘的一个格局状态,图中的一个顶点等。
(3)、数据项
组成数据元素的、有独立含义的、不可分割的最小单位。如:学生信息表中的学号、姓名、性别都是数据项。
(4)、数据对象
性质相同的数据元素的集合,是数据的一个子集。如整数数据对象{1,2,3,4···},学生基本信息表也可以是一个对象。
(5)、数据结构
相互之间存在一种或多种特定关系的数据元素的集合。数据结构是带“结构”的数据元素的集合,“结构”是数据元素之间的关系。
2、数据结构的存储
(1)、物理结构
a、顺序存储 b、链式存储 c、索引存储、d、散列存储
(2)、逻辑结构
a、集合结构 b、线性结构 c、树形结构 d、图形结构
3、抽象数据类型
由用户定义的,表示应用问题的数据模型,以及定义在这个模型上的一组操作的总称,包括:数据对象,数据对象上关系上的集合,对数据对象的基本操作的集合。
4、算法和算法分析
(1)、含义
算法是对特定问题求解步骤的一种描述,它是指令的有限序列,其中每一条指令表示一个或多个操作。
(2)、算法的五个特征
a、有穷性 b、确定性 c、可行性(有效性) d、零个或多个输入 e、一个或多个输出
(3)、算法设计要求
a、正确性 b、可读性 c、健壮性 d、效率与低存储量需求
(4)、算法实现的三要素
a、数据 b、运算 c、控制
二、表
1、表的概念
表是由n个同一类型的元素a(1),a(2),a(3)···,a(n)组成的有限序列
2、表的逻辑特征
(1)、非空的表有且仅有一个开始元素,该元素没有前驱,而有一个后继;
(2)、有且仅有一个结束元素,结束元素没有后继,而有一个前驱;
(3)、其余的元素都有一个前驱和一个后继。
(4)、表是一种线性结构
3、存储结构
(1)、顺序存储结构 -> 数组实现表
(2)、链式存储结构 -> 链表实现表
4、实现表
(1)、用数组实现表
优点:
a、随机访问性强
b、查找速度快
缺点:
a、插入和删除效率低
b、可能浪费内存
c、内存空间要求高,必须有足够的连续内存空间
d、数组大小固定,不能动态拓展
(2)、链表实现表
优点:
a、插入删除速度快
b、内存利用率高,不会浪费内存
c、大小没有固定,拓展很灵活
缺点:
a、不能随机查找,必须从第一个开始遍历,查找效率低
5、循环链表特点
最后元素的指针指向表首,实现一个环。判断一次循环使用模(%)运算
6、双链表特点
增加一个前驱空间,使用左右指针的方式表示前后驱。
三、栈
1、栈的特点
先进后出,类似放碟子和取碟子的一个过程
2、数组实现栈
入栈:先判断栈是否满了,否则出现上溢;
出栈:先判断栈是否为空,否则出现下溢。
数据元素:top(栈顶);maxtop(栈空间);StackItem *data(存储栈元素的数组)。//typedef int StackItem;StackItem表示int类型
3、链表实现栈
数据元素:StackItem element(栈元素);typedef struct snode *slink; slink next;(下一个指针)
push(入栈)、pop(出栈),入栈和出栈都在链尾操作
四、队列
1、队列的特点
先进先出,类似正常的排队,先排先出,删除操作只能在链首,插入操作只能在链尾
2、链表实现队列
类似单链表的操作,只是比较特殊,删除操作只能在链首,插入操作只能在链尾
3、循环数组实现队列
实现思路:在删除操作不需要移动后面的元素,空出一个空间用于判断是否到最后一个元素
五、排序与选择算法
1、简单排序
(1)、冒泡排序
时间复杂度为O(n2)
思路:使用一个临时变量,前后两两比较,进行排序
(2)、插入排序
时间复杂度为O(n2)
思路:使用一个新的空数组,逐个遍历一个无序数组,然后逐个比较插入到新的空数组。
(3)、选择排序
时间复杂度为O(n2)
思路:假设第一个元素下标为最小,遍历所有元素,如果比这个元素小则交换值,下标进行加一,继续比较,直到最后一个元素下标不进行比较。
2、快速排序
时间复杂度为O(nlogn)
思路:选择一个基准元素,选择一个左区间和右区间。把大的放左边,小的放右边(或者相反),使用递归的方式进行排序。关键是确定基准点
(2)、非递归快速排序
使用栈的方式进行排序,最坏情况只耗费logn栈空间,典型的用空间换时间。
3、合并排序
时间复杂度为O(nlogn)
思路:先分成若干个两个单位排序,然后不断左右合并排序,最终排序完成
4、计数排序
前提:必须知道待排序序列中所有元素的范围,min~max,这样才能构造一个数组
时间复杂度为O(n)
思路:设置一个数组存放排序的结果,设置一个辅助数组用于对输入的元素进行计数,然后根据下标输出对应计数的个数(不为0的个数)
5、桶排序
前提:必须知道待排序序列中所有元素的范围,min~max,这样才能构造一个数组
时间复杂度为O(n)
桶排序是在计数排序的基础上衍生的
思路:把一个长度分为多个桶,在每个桶里面进行计数排序,最后按着桶的顺序进行拼接
6、基数排序
前提:必须知道待排序序列中所有元素的范围,min~max
时间复杂度为O(n)
思路:分为十个桶,从个位,十位,百位···一步步排序,排序的次数根据最大值的位数。
六、树
1、树的定义
零个节点为空树
第一个节点为根节点
最后一个节点为叶节点
子树不相交
度:儿子结点个数
树的度:最大结点个数
树的高度:根结点到叶结点的最大值,树的层数
树的深度:根结点到叶结点的最大值,树的层数
结点n的高度:n结点到叶子结点所有路径上包含结点个数的最大值。
2、二叉树
(1)、二叉树
树的度不超过2,空树也是二叉树
(2)、满二叉树
每一层的结点数都达到最大值,比如树有n层,则第n层的叶子结点数为2的n-1次方
(3)、完全二叉树
满二叉树也是完全二叉树,叶结点只能出现在最下层和次下层,并且最下面一层的结点都集中在该层最左边的若干位置。也就是说,完全二叉树出现的位置要和满二叉树一致,不能有右叶子结点而没有左叶子结点
(4)、二叉树的运算
a、深度为n的树,根的高度为1时(默认为0),最多有 (2*n)-1 个结点
b、第n层的树(假设从1层开始数),最多有2*(n-1)个结点
c、空指针域的个数=2n-(n-1)=n+1
(5)、二叉树的五种形态
- 空树
- 只有根结点的树
- 根结点+左结点的二叉树
- 根结点+右结点的二叉树
- 根结点+左右结点的二叉树
3、树的遍历
思路:根据根结点的位置不同分为前序、中序、后序遍历
(1)、前序遍历
从根结点开始,遍历左子树左结点结束后遍历右结点,以此类推
(2)、中序遍历
从最左叶结点开始遍历,然后到父结点,再到右结点,左子树结束后,遍历根结点,然后到右子树的左结点,以此类推。
(3)、后序遍历
从最左叶结点开始遍历,如果右结点不是叶子结点,则先遍历完叶子结点后遍历父结点,遍历完左右子树后遍历根结点
4、树的表示法
(1)、父结点数组表示法
(2)、儿子链表表示法
(3)、左儿子右兄弟表示法
5、线索二叉树
思路:增加左右线索标志,如果左右子树有值时,则线索标志表示0,如果左右子树没有值时,则线索标志表示1,左线索为一个指针指向前驱结点,右线索为一个指针指向后继结点。
6、二叉搜索树(排序树)
(1)、特点:
a、子树的左结点比右结点小
b、中序排序是有序的
c、在构造二叉树时,每次插入的新结点都是新的叶子结点
d、拥有类似折半查找的特性,又采用了链表作为存储结构,因此是动态查找表的一种适宜表示
(2)、二叉排序树的删除
假设删除的结点为P结点
a、若p结点没有左子树(含叶子结点的情况),则用p结点的右孩子替代它。
b、若p结点没有右子树(含叶子结点的情况),则用p结点的左孩子替代它。
c、若p结点既有左子树又有右子树,则用左子树中最大的结点替代它。
七、散列表(哈希表)
1、数组实现
思路:关键码值映射到表中一个位置来访问记录,设置一个数组,将数字对数组的长度进行模取余进行存放,如果该下标已有数值,则下标进行增加,直到存满为止。
特点:寻址容易,插入和删除困难
2、链表实现
思想:同样设置一个数组,将数字对数组的长度进行模取余进行存放,只是存储的方式是链表。如果下标有数值,则在该数值链上上一个数值。
特点:寻址困难,插入和删除容易。
3、散列技术
(1)、除余法(最常用)
(2)、数乘法
(3)、平方取中法
(4)、基数转换法
(5)、随机数法
八、优先队列
1、什么是优先队列
不遵循先进先出的原则,根据优先级进行出列
使用线性数据结构时间复杂度会高,一般使用堆或者树来实现
2、优先队列树和堆
堆的定义:当一颗二叉树的每个结点都大于等于它的两个子结点时,它被成为堆有序。
大根堆:根结点最大,从上向下值不断减少
小根堆:根结点最小,从上向下值不断增加
调整二叉树为大小根堆:
a、(从上向下调整)让当前结点与它的左右孩子进行比较,哪个比较小就和它交换,更新询问节点的下标为被交换的孩子节点下标,否则退出。
b、(从下向上调整)让当前结点和它的父亲节点比较,若比父亲节点小就交换,然后将当前询问的节点下标更新为原父亲节点下标,否则退出。
3、哈夫曼树
(1)、最优二叉树:
a、权值越大的叶子离根越近
b、具有相同带权结点的哈夫曼树不唯一
c、包含n个叶子结点的哈夫曼树中共有2n-1个结点
d、包含n棵树的森林要经过n-1次合并才能形成哈夫曼树,共产生n-1个新结点。
e、满二叉树不一定是哈夫曼树
(2)、如何构造一个哈夫曼树?
a、在 n 个权值中选出两个最小的权值,对应的两个结点组成一个新的二叉树,且新二叉树的根结点的权值为左右孩子权值的和;
b、在原有的 n 个权值中删除那两个最小的权值,同时将新的权值加入到 n–2 个权值的行列中,以此类推;
c、重复 a 和 b (使用递归),直到所有的结点构建成了一棵二叉树为止,这棵树就是哈夫曼树。
(3)、结构元素:weight(结点权重);parent,left,right(父结点、左孩子,右孩子在数组中的位置下标)
(4)、哈夫曼编码:
在建立哈夫曼树后,在左孩子结点设置标为0,右孩子结点设置标为1。用哈夫曼树设计总长最短的二进制前缀编码。
九、并查集
1、什么是并查集
并查集是一种树形结构,又叫“不相交集合”,保持了一组不相交的动态集合,每个集合通过一个代表来识别,代表即集合中的某个成员,通常选择根做这个代表。
2、并查集的实现
主要分为查询和合并两个步骤
查询的核心代码如下
//使用递归的方式进行查询,一层一层访问父节点,直至根节点(根节点的标志就是父节点是本身)。要判断两个元素是否属于同一个集合,只需要看它们的根节点是否相同即可。
int find(int x){
if(fa[x]==x){
return x;
}
else return find(fa[x]);
}
合并的核心代码如下
//先找到两个集合的代表元素,然后将前者的父节点设为后者即可。
inline void merge(int i,int j){
fa[find(i)]=find(j);
}
十、图
1、图的重要内容
(1)、有向图
<1,2>符号的集合表示1指向2,1为起点,2为终点
(2)、无向图
(1,2)符号的集合表示边为1和2组成的边
(3)、完全图
顶点为n,边数为e的图,
a、当e=n(n-1)/2的无向图为完全无向图
b、当e=n(n-1)的有向图为完全有向图
(4)、顶点的度
关联该顶点的边的数目,有向图中,顶点的度为入度和出度的和
顶点数n、边数e和度数之间的关系有
边数=各顶点度数和的一半
(5)、连通图
特点:两个不同顶点之间都是连通的
2、邻接矩阵实现图
思路:设置二维数组,设置默认值为0,如果对应的值有边,则变为1
特点:适合存储稠密图
元素:WItem Noedge(无边标记)、int n(顶点数)、int e(边数)、WItem **a(邻接矩阵)
3、邻接表实现图
思路:以每个顶点作为一个单链表,把边进行链接,附带可权值
特点:适合存储稀疏图
元素:int v(边的另一个顶点)、typedef struct Inode *glink; glink next(邻接表的指针),如果有权值则进行增加
4、图的遍历
(1)、广度优先搜索
a、访问顶点v
b、访问顶点v的所有未被访问过的邻接点,假设访问次序是vi1,vi2,…,vit 。
c、按 vi1,vi2,…,vit 的次序,访问每个顶点的所 有未被访问过的邻接点,直到图中所有和初始点 v有路径相通的顶点都被访问过为止。
顺序一致,用队列实现
(2)、深度优先搜索
a、访问顶点v
b、选择一个与顶点v相邻且没被访问过的顶点w, 从w出发深度优先遍历。
c、直到图中与v相邻的所有顶点都被访问过为止。
5、单源最短路径
最短路径问题:如果从图中某一顶点(称为源点)到达另 一顶点(称为终点)的路径可能不止一条,如何找到一条 路径使得沿此路径上各边上的权值总和达到最小。
思路:先求出最短的一条路径,然后将顶点添加到新的集合中,原集合删除该顶点,两条路径选最小者,然后继续寻找最短的路径,以此类推
时间复杂度为O(n的3次方)
6、拓扑排序
拓扑排序是有序排序
步骤:
(1)、从AOV网中选择一个没有前驱(即入度为0)的 顶点并且输出它。
(2)、从AOV网中删去该顶点,并且删去从该顶点发出 的全部有向边。
(3)、重复上述两步,直到剩余的网中不再存在没有前 驱的顶点为止。
特点:使用链表的方式存储
7、最小支撑树
通过深度优先遍历产生的生成树为深度优先生成树
通过广度优先遍历产生的生成树为广度优先生成树
(1)、Prim算法
适合于稠密图
(2)、Krustal算法