数据结构
什么是数据结构
数据:
- 现实生活中一切可以处理的信息
结构:
-
逻辑结构:事务与事务之间在现实生活中的抽象的一种逻辑关系
- 集合:数据与数据之间除了同属一个集合之外,没有别的关系
- 线性:一对一
- 树状关系:一对多
- 网(图)状关系
-
存储结构
-
顺序存储:在逻辑上相邻的元素,在物理空间上也相邻
-
优点
- 1、查找方便
- 2、存储空间的利用率可以达到1
-
缺点
- 1、插入和删除不方便
- 2、申请空间的时候,必须是一片连续的空间,对空间的要求比较高
- 3、会产生空间碎片
-
-
链式存储:在逻辑上相邻的元素,在物理空间上不一定相邻
-
优点
- 1、插入元素不需要移动大量的空间
- 2、对空间的要求没那么大
-
缺点
- 1、查找不方便
- 2、存储空间的利用率不足1,没有顺序存储的大
-
-
索引存储:依据索引表查找数据大概位置,详细查找数据本身(冲突没有解决号的哈希存储)
-
优点
- 1、查找方便
-
缺点
- 1、有索引表的存在,要浪费空间
- 2、插入元素,删除元素之后,索引表要更新
-
-
哈希存储:根据关键字直接就能定位到记录本身,就能拿到数据
-
优点
- 1、查找方便,插入,删除也方便
-
缺点
- 2、如果哈希函数设计的不合理,查找的效率就会很低
-
-
操作
增、删、改、查、创、销
- 逻辑结构-----确定研究对象,把对象的关系抽象出逻辑结构
- 存储结构-----顺序存储、链式存储、索引存储、哈希存储
- 操作-----研究怎么运算,算法
- 程序
线性结构
顺序表
-
特点
- 1.逻辑上相邻的元素,在物理空间上也相邻===>空间连续
- 2.大小固定
- 3.表满不能存,表空不能取
-
定义
-
创建
-
插入元素
- 头插
- 尾插
-
删除元素
- 头删
- 尾删
-
显示元素
-
销毁,释放空间
-
扩容
- 1、pNew = malloc(sizeof(data_type)size2)
- 2、把数据全部复制一份:pNew = pList->data;
- 3、pTmp暂存0x100 pTmp=pList->data;
- 4、把data指向新的空间
- 5、size=size*2
- 6、free(pTmp)
线性结构的链式存储
-
链表的分类
-
按方向分:
- 单链表
- 双链表
-
按是否循环分
- 循环链表
- 不循环链表
-
按是否带头节点
- 带头节点的链表
- 不带头节点的链表
-
-
链表
-
单链表
-
带头节点的不循环单链表
-
认识链表
- 头节点:数据域无效的结点
- 首节点:第一个存储数据的结点
- 尾节点:指针域为NULL的结点
-
结点定义
-
创建链表
-
插入元素
-
显示链表
-
给链表删除元素
- 头删
- 尾删
- 中间删
-
-
销毁单链表
- 1、按找头删、尾删的原则,把所有的数据节点全部删除(free)
- 2、当只剩下头节点的时候再释放头节点
-
逆置一个单链表
- 对原来的单链表采用头删,对新链表采用头插
-
逆着输出一个单链表
- 栈
-
循环单链表
- 在循环链表下,尾结点的Next指向头结点(一般操作的时候不再指向头节点了,而指向尾结点)
-
-
双链表
-
带头结点的不循环双链表
-
双链表表结点的定义
-
双链表的创建
-
双链表的插入元素
- 头插
- 尾插
- 中间插入
-
双链表的删除元素
- 头删
- 尾删
- 中间删除
-
双链表的显示
-
-
-
受限的线性表
-
栈:受限在操作上
-
只允许数据在一段进行插入和删除操作,允许操作的一段叫做栈顶
-
特点:先进后出(FILO)
-
操作
-
顺序存储
- 同顺序表
-
栈的定义
- 1.存储空间连续
- 2.大小固定
- 3.表满不能存,表空不能取
- 4.只允许在一段(栈顶)进行插入删除操作
-
入栈和出栈
-
打印栈
-
链式存储
- 线性表的链式存储是一样的
- 头是栈顶:头插、头删
- 尾是栈顶:尾插、尾删
- 操作的时候一般选择头作为操作
-
-
什么时候考虑用栈
- 入的顺序和出的顺序发生变化
-
栈的应用
- 1.printf函数的右结合
- 2.函数栈
- 3.中缀表达式 (a+b)/c a+b/c 后缀表达式: ab+c/ abc/+
- 判断一个表达式中的括号是否匹配?
- 判断一个单链表的元素是否关于中心对称?
-
-
队列:受限在了操作上
-
只允许在一段执行插入操作,另一端执行删除操作,允许插入的一段叫做队尾,允许删除操作的一段叫做队头
-
特点:先进先出(FIFO)
-
操作:
-
顺序存储
- 队列的定义
- 队列的创建
- 入队和出队
- 循环队列
-
链式存储:对链表限制住了插入和删除的位置
-
双端队列
-
-
-
串:受限在了存储上
- 只允许存储字符 * ’ a — 1
线性表、链表、顺序表之间的关系
线性表,链表,顺序表之间的区别
- 线性表是一种逻辑结构
- 顺序表和链表是线性表在顺序存储下和链式存储下的体现
链表和顺序表的区别
- 1.链表是链式存储,顺序表是顺序存储
- 2.顺序表会有空间碎片产生,链表没有空间碎片产生
- 3.链表一般多用于插入、删除较多的场景顺序表一般用于查找较多的场景
- 4.顺序表的存储空间利用率比链表的大
链表和顺序表怎么选择?
-
1.从空间来说
- 顺序表对空间的要求比链表大
- 顺序表的利用率比链表大
-
2.从操作来说
- 多用于查找,用顺序表
- 多用于插入、删除,用链表
-
3.从编译环境来说
-
不支持指针类型操作的编译器不能使用链表
- 编译器不支持使用指针类型操作,场景又是多插入、删除操作
- 使用静态链表,本质是二维数组
-
树形结构
什么是树
-
树(Tree)是n(n≥0)个节点的有限集合T,它满足两个条件 :
- 有且仅有一个特定的称为根(Root)的节点;
- 其余的节点可以分为m(m≥0)个互不相交的有限集合T1、T2、……、Tm,其中每一个集合又是一棵树,并称为其根的子树(Subtree)。
树的相关概念:
-
度数:
- 一个节点的子树的个数称为该节点的度数,一棵树的度数是指该树中节点的最大度数。
-
高度(层数、深度):
- 节点的层数等于父节点的层数加一,根节点的层数定义为一。树中节点层数的最大值称为该树的高度或深度。
-
路径:边数:
- 一个节点系列k1,k2, ……,ki,ki+1, ……,kj,并满足ki是ki+1的父节点,就称为一条从k1到kj的路径,路径的长度为j-1,即路径中的边数。路径中前面的节点是后面节点的祖先,后面节点是前面节点的子孙。
-
叶子节点:
- 度数为0的结点
二叉树
-
定义
- 二叉树(Binary Tree)是n(n≥0)个节点的有限集合,它或者是空集(n=0),或者是由一个
根节点以及两棵互不相交的、分别称为左子树和右子树的二叉树组成。二叉树与普通有序树不同,二叉树严格区分
左孩子和右孩子,即使只有一个子节点也要区分左右。
- 二叉树(Binary Tree)是n(n≥0)个节点的有限集合,它或者是空集(n=0),或者是由一个
-
性质
-
1、二叉树第i(i>=1)层上的节点最多为2 i-1个
-
2、深度为k(k>=1)的二叉树最多有2k-1个节点
-
3、在任意一棵二叉树中,树叶的数目比度数为2的节点的数目多一。
- 总节点数为各类结点之和:n=n0+n1+n2
- 总节点数为所有子节点数加一:n=n1+2*n2 +1,故得:n0=n2 +1
-
4、满二叉树
- 深度为k (k≥1)时有2k—1个节点的二叉树。
- 有k层,恰好有2k-1个结点
-
5、完全二叉树
- 只有最下面两层有度数小于2的节点,且最下面一层的叶节点集中在最左边的若干位置上。
- 在满二叉树的基础上,从右往左,从下往上依次去除结点
-
具有n个节点的完全二叉树的深度为
- (log2n)+1或『log2(n+1)。
-
一颗普通的树转成二叉树:
- 依据:左孩子右兄弟的原则
树形结构的顺序存储
- 1、一颗普通的二叉树。顺序存储
- 2、把一颗普通的二叉树补满成满二叉树
- 3、分配满二叉树的结点个空间,按照从左往右、从上往下,依次存入
树形结构的链式存储:
-
树的结点的定义:
-
树的结点的创建:
-
树的结点的插入:
-
树的节点的遍历:
- 先序遍历:
- 中序遍历:
- 后序遍历:
-
深度优先:
- 先序遍历,后序遍历
-
广度优先:
- 层次遍历
- 队列
树的结点的删除:
- 待删除结点没有孩子,直接删除
- 待删除结点有一个孩子,子承父位
- 待删除结点有两个孩子,找右子树中最小的值或者左子树中最大的值来继承
平衡二叉树:
- 每一个结点的左子树的层数和右子树的层数不超过2
- 目的:解决树的降维问题(把树将成链表),提高效率
赫夫曼树(哈夫曼树、最优二叉树)
- 赫夫曼(Huffman)树,又称最优树,是带权路径长度最短的树,有着广泛的应用
- 从树中一个结点到另外一个结点的分支构成一条路径,分支的数目称为路径的长度。树的路径长度是指从树根到每个结点的路径长度之和
- 进一步推广,考虑带权的结点。结点的带权路径长度指的是从树根到该结点的路径长度和结点上权的乘积。树的带权路径长度是指所有叶子节点的带权路径长度之和,记作 WPL 。WPL最小的二叉树就是最优二叉树,又称为赫夫曼树
线索二叉树:
网状结构:
分类:
- 按有无方向可以分为:有向图、无向图
- 按是否带权值:带权图和不带权图
网状结构的顺序存储:
网状结构的链式存储;
算法:
什么是算法
- 算法是有限指令的有序集合。
- 算法是有穷的,程序是无穷的
- 程序 = 算法 + 数据结构
算法的特征:
- 有穷性:算法必须在有限个语句能描述完
- 确定性(无二义性):每一条语句只能有一个解释
- 可行性:能运行的
- 输入:
- 输出:
怎么样评判算法的好坏:
-
效率与低存储量需求(时间复杂度,空间复杂度):
-
时间复杂度:执行这个算法需要花费多少时间。
-
只需要记录量级
- 1.顺序执行的代码,只会影响常数项,可以忽略 O(1)。
- 2.只需要挑选循环中的一个基本操作来分析他的执行步骤的次数与n(问题规模)的关系。
- 3.如果有多层循环嵌套,只需要关注最深层次循环的个数。
-
优化时间复杂度:
- 首先得保证算法的正确性,在这个基础上,思考怎么去减少循环的使用。
-
-
空间复杂度:执行这个代码,需要花费多少空间。
-
空间复杂度的计算:设算法对应问题的规模为n,执行算法所占存储空间的量级为D(n),则D(n)为算法的空间复杂度。
-
优化空间复杂度
-
1.在定义的时候,更少的使用空间;
- 1.字节对齐:
- 2.位域:
-
2.在执行的时候,尽量避免开辟不必要的空间,并且功能结束之后,释放掉不用的空间
-
-
-
-
正确性:算法得无误运行。
-
可读性、可维护性:对人的友好,是从编程规范入手优化。
-
健壮性、鲁棒性:算法在输入有误的信息的时候,代码还能够按照预想的方式执行,不出现BUG,不会退出。
常见的查找算法
-
线性结构
- 顺序查找:遍历
- 折半查找:二分查找 #限制:必须有序、必须是一个顺序表
- 分块查找:块间有序,块内无序
-
树状结构
- 二叉排序树:二叉树的查找
- 二叉平衡树:避免树降维成链表
- 遍历:先序遍历、中序遍历、后续遍历、广度有限、深度优先
-
散列表(哈希表):
-
对给定的k,不经任何比较便能获取所需的记录,其查找的时间复杂度为常数级O©。这就要求在建立记录表的时候,确定记录的key与其存储地址之间的关系f,即使key与记录的存放地址H相对应
-
根据要找的key关键字,直接能找到数据的记录本身。
- key---------------------H:记录
-
构建哈希函数
-
直接地址法:
-
平方取中法:
-
叠加法:
-
质数除余法:
-
-
冲突
-
什么是冲突
- 冲突是指:表中某地址j∈[0,m-1]中己存放有记录,而另一个记录的H(key)值也为j。
-
怎么解决冲突:
-
开放地址法:不太好,因为会将哈希表退化成线性表,还只能使用顺序查找。
-
再次哈希法:也不好,因为麻烦
-
链地址法:
-
-
-
常见的排序算法
-
内部排序:
-
插入:
- 直接插入:重新构建一个链表
- 折半插入:和二分查找类似
- 希尔排序:增量,逐渐减少的,直到增量为1为止
-
交换:
- 冒泡:每一次运行总会将最小的或者最大的放到前面,如果需要交换,一直在交换
- 快速排序*:
-
选择:
- 简单选择:每一次运行总会将最小的或者最大的放到前面,最后只交换一次
- 堆(大根堆,小根堆):根>=左右孩子的,牵扯到树的变化,所以只有特定场景下才使用
-
多路归并:
-
基数排序:
-
-
外部排序:
- 基于多路归并的外部排序: