数据 |
||
数据对象 |
||
数据元素 |
数据元素 |
数据元素 |
数据项1 |
数据项1 |
数据项1 |
数据项2 |
数据项2 |
数据项2 |
数据项3 |
数据项3 |
数据项3 |
... |
... |
... |
逻辑结构:
同属一个集合,无其他关系
线性(一对一)
树形(一对多)
图形(多对多)
物理结构
顺序存储结构:一段地址连续的存储单元
链式存储结构:把数据元素存储到任意的存储单元,其地址可以连续也可以不连续,数据域,指针域
...
一些基础知识
算法特性:有穷、确定、可行、输入(0或多个)、输出(至少一个)
算法设计的要求:正确、可读、健壮、时间效率高和存储量低(空间可以拿来换取时间)
渐进增长:给定两个函数f(n)和g(n),如果存在一个整数N,使得对所有的n>N,f(n)总比g(n)大,那么,f(n)的渐进增长快于g(n)
推导大O阶:
用常数1取代运行时间中的所有加法常数
修改后的运行次数函数里,只保留最高阶层
如果最高阶项存在且不是1,去除与这个项相乘的常数
大O阶时间复杂度
O(1)<O(log n)<O(n)<O(n*log n)<O(n²)<O(n的三次方)<O(2的n次方)<O(n!)<O(n的n次方)
线性表和单链表
线性表和单链表
版权声明:本文为CSDN博主「pengyingt」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/pengyingt/article/details/81160774
静态链表(用数组来代替指针)
让数组的元素都由两个数据域组成,data和cur。data用来存放数据元素,cur相当于单链表中的next指针(一个数据项)
优点:在插入和删除操作时,只需要修改游标,不需要移动元素,从而改进了在顺序存储结构中插入与删除需移动大量元素的缺点
缺点:
没有解决连续存储分配带来的表长难以确定的问题
失去了顺序存储结构随机存取的特性(头节点的cur为第一个空闲下标)
循环链表(从当中一个结点出发能访问到全部结点)
将单链表中终端结点的指针端从空指针改为指向头结点,使得整个链表形成一个环
双向链表(在循环链表基础上还可以随时反向遍历)
在单链表的每个结点中,再设置一个指向其前驱结点的指针
栈与队列
栈:限定仅在表尾进行插入和删除操作的线性表(后进先出)
如果栈的使用过程中元素的变化在可控范围内,则用顺序栈,反之,如果元素变化不可预料则用链栈
顺序栈的技巧:两栈共享空间
两栈共享空间(当两个栈的空间需求有相反关系时,也就是一个栈增长另一个栈缩短时,且两栈内元素具有相同数据类型)
栈1为空→top 1=-1;
栈2为空→top 2=n;
栈满:top 1+1==top2;
栈的应用:中缀表达式与后缀表达式互转
后缀表达式计算:从左到右遍历表达式的每个数字和符号,遇到是数字就进栈,遇到是符号就将处于栈顶的两个数字出栈进行运算,运算结果进栈,直到最终获得结果
中缀转后缀规则:从左到右遍历中缀表达式中的每个数字和符号,若是数字就输出,若是符号则判断其与栈顶符号的优先级,是右括号或优先级低于栈顶符号则栈顶元素依次出栈并输出,并将当前符号进栈,直到最终输出后缀表达式为止
队列:只允许在一端(队尾)进行插入操作,而在另一端(队头)进行删除操作的线性表
在可以确定队列长度最大值的情况下用循环队列。反之,在无法预估队列长度时用链队列
顺序队列的技巧:循环队列
循环队列:队列的一种头尾相接的顺序存储结构(front为队头,rear为队尾元素的下一个,flag为标志变量(可有可无,无则还剩一个元素空间为队满))
无flag时
队满条件:(rear+1)➗QueueSize==front
队列长度公式:(rear-front+QueueSize)➗QueueSize
串
字符串:由零个或多个字符组成的有限序列
空串:零个字符的串(空格也没有)
空格串:只包含空格的串(有长度,可以不止一个空格)
串的顺序存储
串的存储空间可以在程序执行过程中动态分配,比如在计算机中存在一个自由存储区,叫堆,这个堆可以由C语言的动态分配函数malloc()和free()来管理(C++中new()和delete())
扩展:动态分配——百度百科
C/C++定义了4个内存区间:代码区,全局变量与静态变量区,局部变量区即栈区,动态存储区,即堆(heap)区或自由存储区(free store)。
堆的概念:
通常定义变量(或对象),编译器在编译时都可以根据该变量(或对象)的类型知道所需内存空间的大小,从而系统在适当的时候为他们分配确定的存储空间。这种内存分配称为静态存储分配;有些操作对象只在程序运行时才能确定,这样编译时就无法为他们预定存储空间,只能在程序运行时,系统根据运行时的要求进行内存分配,这种方法称为动态存储分配。所有动态存储分配都在堆区中进行。
当程序运行到需要一个动态分配的变量或对象时,必须向系统申请取得堆中的一块所需大小的存贮空间,用于存贮该变量或对象。当不再使用该变量或对象时,也就是它的生命结束时,要显式释放它所占用的存贮空间,这样系统就能对该堆空间进行再次分配,做到重复使用有限的资源。 [1]
https://zhuanlan.zhihu.com/p/475221695
串的链式存储结构(一个节点可以存放多个字符)
串的链式存储结构除了在链接串与串操作时有一定方便之外,总的来说不如顺序存储灵活,性能也不如顺序存储结构好
串的应用:子串的定位操作
朴素的模式匹配算法(一位一位对)→KMP模式匹配算法(比较前后缀)→改进KMP(比较next[j])
树
树:n个(n大等于0)结点的有限集,n=0时成为空树,在任意一颗非空树中有且仅有一个根节点,子树的个数没有限制,但它们一定是互不相交的
终端结点(叶子结点):结点拥有的子树数(度)为0
树的度:树内各结点度的最大值
树的深度:从上往下,根为第一层,结点的最大层次
树的高度:从下往上数
树的存储结构表示
双亲表示法(parent双亲, firstchild长子, rightsib右边同级)
在每个结点中,附设一个指示器指示其双亲结点到链表中的位置(根节点的双亲为空记为-1,双亲为根结点记为0)
孩子表示法:线性表顺序存储结点名和firstchild指针,以单链表形式指向下一个child直至为空(需要的话还可以加个parent下标)
孩子兄弟表示法(长子和右边是谁 二叉树)(找双亲麻烦)
二叉树
二叉树:
made 打字好累下次再打
红黑树
https://baike.baidu.com/item/%E7%BA%A2%E9%BB%91%E6%A0%91/2413209?fr=aladdin
图形
查找
查找表:由同一类型的数据元素(或记录)构成的集合
关键字:数据元素中某个数据项的值
主关键字:该关键字可以唯一地标识一个记录
次关键字:可以识别多个数据元素(或记录)的关键词
查找:根据给定的某个值,在查找表中确定一个其关键字等于给定值的数据元素(或记录)
静态查找表:只做查找操作的查找表
查询某个“特定的”数据元素是否在查找表中
检索某个“特定的”数据元素和各种属性
顺序表查找(设置一个哨兵)
有序表查找(折半查找、插值查找、斐波那契数列查找)
线性索引查找(稠密索引 每个记录对应一个索引项、分块索引 块内无序块间有序、倒排索引 记录号表存储具有相同次关键字的所有记录的记录号 由属性值来确定记录的位置))
动态查找表:在查找过程中同时插入查找表中不存在的数据元素,或者从查找表中删除已经存在的某个数据元素
查找时插入数据元素
查找时删除数据元素
二叉排序树 中序遍历则从小到大
平衡二叉树(AVL树)每个结点的左子树和右子树的高度(从叶子节点往上数)差至多等于1
多路查找树: 每一个结点的孩子数可以多于两个,且每一个结点处可以存储多个元素(是查找树,具有特定的排序关系)
2-3树 每一个结点都具有两个孩子(2结点 包含一个元素和两个孩子 或没有孩子)或三个孩子(3结点 包含一小一大两个元素和三个孩子 或没有孩子 3结点的孩子左子树小于较小元素,右子树大于较大元素,中间子树包含介于两元素之间的元素)
2-3-4树 (2结点 3结点 4结点 包含小中大三个元素和四个孩子 或没有孩子 4结点的孩子也许像3结点孩子那样排序)
B树(针对内存与外存之间的存取而专门设计 内外存的查找性能更多取决于读取的次数) 一种平衡的多路查找树(结点最大的孩子数目为B树的阶) 有一个记录当前结点元素个数的数据域
B+树 所有叶子结点包含全部关键字的信息和指向含这些关键字记录的指针,叶子结点本身依关键字的大小自小而大顺序链接
哈希表 (查找与给定值相等的记录)
优点:没有比较。查找性能高
缺点:同样的关键字例如男女不行 范围查找不行 冲突(即key1不等于key2,但f(key1)=f(key2)) 记录之间没有任何关联
散列技术:在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每一个关键字对应一个存储位置f(key),查找时,根据这个确定的对应关系找到给定值key的映射f(key),若查找集合中存在这个记录,则必定在f(key)的位置上
散列函数(哈希函数):对应关系f
哈希表(散列表):采用散列技术将记录存储在的一块连续的存储空间
散列地址:关键字对应的记录存储位置
散列表查找步骤:
在存储时,通过散列函数计算记录的散列地址,并按此散列地址存储该记录(存储)
当查找记录时,通过同样的散列函数计算记录的散列地址,按此散列地址访问该纪录(查找)
散列函数的构造方法
直接定址法(计算简单、散列地址分布均匀)(需事先知道关键字的分布情况,适合查找表较小且连续)
取关键字的某个线性函数值为散列地址
例子:年份
f(key)=a*key+b (a、b为常数)
数字分析法(适合处理关键字位数比较大的情况,需要事先知道关键字的分布且关键字的若干位分布较均匀)
关键字是位数较多的数字
平方取中法(适合不知道关键字的分布,位数又不是很大)
平方后取中间几位数字
折叠法(事先不知道关键字的分布且关键字位数较多)
将关键字从左到右分割成位数相等的几部分(最后一部分位数不够可以短些)然后将这几部分叠加求和,并按散列表表长,抽取后几位
除留余数法(最为常用)
f(key)=key mod p (p小等于m)
mod是取模(求余数)的意思,且不仅可以对关键字直接取模,也可以在折叠、平方取中后再取模
若散列表表长为m,通常p为小于或等于表长(最好接近m)的最小质数或不包含小于20质因子的合数
随机数法(当关键字的长度不等时)
选择一个随机数,取关键字的随机函数值为它的散列地址
散列表解决冲突的办法
开放定址法
一旦发生了冲突就去寻找下一个空的散列地址(线性探测法 一直线性往后、二次探测法 增加负数的平方运算、随机探测法 位移量采用随机函数计算伪随机数得到)
会产生堆积
再散列函数法
事先准备多个散列函数,散列地址冲突时就换一个散列函数计算,相应增加了计算时间
链地址法
后面加单链表
公共溢出区法
不对就去公共溢出区找
排序
排序:假设含有n个记录的序列{r1,r2,......,rn},其相应的关键字分别为{k1,k2,......,kn},需确定1,2,......,n的一种排列p1,p2,......,pn,使其相应的关键字满足kp1小等于kp2小等于......小等于kpn(非递减或非递增)关系,即使得序列成为一个按关键字有序的序列{rp1,rp2,......,rpn},这样的操作称为排序
排序的稳定性:假设ki=kj(1小等于i小等于n,1小等于j小等于n,i≠j),且在排序前的序列中ri领先于rj(即i<j)。如果排序后ri仍领先于rj,则称所用的排序方法是稳定的;反之,若可能使得排序后的序列中rj领先于ri,则称所用的排序方法是不稳定的
内排序:在排序整个过程中,待排序的所有记录全部被放置在内存中
外排序:由于排序的记录个数太多,不能同时放置在内存,整个排序过程需要在内外存之间多次交换数据才能进行
内排序的性能
时间性能:比较和移动次数要尽量少
辅助空间:辅助储存空间是除了存放待排序所占用的存储空间之外,执行算法所需要的其他存储空间
算法的复杂度:算法本身的复杂度
冒泡排序O(n²)
两两比较相邻记录的关键字,如果反序则交换,直到没有反序的记录为止(两两比较,较小的往前换,如果都没有交换则确定下来)
简单选择排序O(n²)
通过n-i次关键字间的比较,从n-i+1个记录中选出关键字最小的记录,并和第i(1小等于i小等于n)个记录交换之(一个一个比较从最小的排起)
直接插入排序O(n²)(性能略好于冒泡和简单选择排序)
将一个记录插入到已经排好序的有序表中,从而得到一个新的、记录数增1的有序表(将第一个记录作为有序表,然后比较剩余记录的位置放在左边还是右边还是中间)
希尔排序O(n的1.5次方)
分割成几组,然后将相距某个增量的记录组成一个子序列,这样就能保证在子序列内分别进行直接插入排序后得到的结果是基本有序而不是局部有序
堆排序O(n*log n)
大顶堆:每个结点的值都大于或等于其左右孩子结点的值
小顶堆:每个结点的值都小于或等于其左右孩子结点的值
将待排序的序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将他移走(将其与堆数组末尾元素交换,此时末尾元素就是最大值),然后将剩余的n-1个序列重新构造成一个堆,这样就会得到n个元素中的次小值,如此反复执行得到一个有序序列
归并排序O(nlogn)
假设初始序列含有n个记录,则可以看成是n个有序的子序列,每个子序列的长度为1,然后两两归并,得到n/2(不小于其的最小整数)个长度为2或1的有序子序列,再两两归并,如此重复直至得到一个长度为n的有序序列为止(2路归并排序)
使用归并排序时尽量使用非递归方法(节省了空间,时间性能上也有一定提升)
快速排序O(nlogn)
通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序的目的
枢轴:该关键字的左边值都比他小,右边的值都比他大