总结数据结构

  • 数据结构分类:
  1. 逻辑结构:集合结构、线性结构、树形结构、图形结构
  2. 物理结构:顺序存储结构、链接存储结构
  • 算法:
  1. 算法是解决特定问题求解步骤的描述,在计算机中表现为指令的有限序列,并且每条指令表示一个或多个操作
  2. 算法具有五个基本特性:输入、输出、有穷性、确定性和可以行性
  3. 循环的复杂度:等于循环体的复杂度乘以该循环运行的次数
  • 推导大O阶方法:
  1. 用常数1取代运行时间中的所有加法常数
  2. 在修改后的运行函数中,只保留最高阶项
  3. 如果最高阶项存在且不是1,则去除与这个项相乘的常数
  • 时间复杂度所耗时间大小排列:

时间复杂度:指运行时间的需求

空间复杂度:指空间需求

线性表(数组)

  • 线性表(List):零个或多个数据元素的有限序列
  1. 线性表的顺序存储结构,指的是用一段地址连续的存储单元依次存储线性表的数据元素。描述顺序存储结构需要三个属性:
  2. 存储空间的起始位置:数组data,它的存储位置就是存储空间的存储位置。
  3. 线性表的最大存储容易:数组长度MaxSize。
  4. 线性表的当前长度:length。
  5. 线性表的顺序存储结构,在存、读数据时,时间复杂度是O(1),而插入或删除时,时间复杂度都是O(n)。说明适合元素个数不太变化,而更多是存储数据的应用。

单链表

  1. 插入和删除算法:由两部分组成,第一部分是遍历查找第i个元素;第二部分是插入和删除元素。
  2. 在插入和删除操作上,与线性表的顺序存储结构是没有太大优势,但是如果插入多个,比如10个时对顺序存储意味着,第一次插入要移动(n-i)个元素,每次都是O(n);而链表只需要在第一次找到第i个位置指针,此时为O(n),接下来移动指针就是O(1)。所以对于插入和删除数据越频繁的操作,单链表的效率优势就越明显。
  • 静态链表:用数组描述的链表叫做静态链表,数组元素由两个数据域组成,data和next指针,存放该元素后继在数组的下标。

栈和队列

  • 栈(stack)是限定公在表尾进行插入和删除操作的线性表。
  1. 我们把允许插入和删除的一端称为栈顶(top),另一端称为栈底(bottom),不含任何数据元素的栈称为空栈。(先进后出)

  • 递归定义:在高级语言中,调用自己和其它函数并没有本质的不同。我们把一个直接调用自己或通过一系列的调用语句间接地调用自己的函数,称做递归函数。有以下用途:
  • 递归是用了栈原理实现:在前行阶段,对于每一层递归,函数的局部变量、参数值以及返回地址都被压入栈中。在退回阶段,位于栈顶的局部变量、参数和返回地址被弹出,用于返回调用层次中执行代码的其余部分,也就是恢复了调用的状态。
  • 乘除加减(四则运算)也是运用栈原理:有左括号就进栈,而后面有右括号就出栈,期间让数字运算。
  • 后缀(逆波兰):从左到右遍历表达式的每个数字和符号,遇到数字就进栈,遇到符号就将处于栈顶的两个数字出栈,进行运算,运算结果进栈,一直到获得结果。
  • 结果:要想让计算机具有处理我们通常的标准(中缀)表达式的能力,最重要的就是两步:
  • 将中缀表达式转化为后缀表达式(栈用来进出运算的符号)
  • 将后缀表达式进行运算得出结果(栈用来进出运算的数字)
  • 迭代和递归的区别是:迭代使用的是循环结构,递归使用的是选择结构。递归能使程序结构清晰、简洁、容易理解。但是大量递归调用会建立函数的副本,会耗费大量的时间和内存。迭代则不需要反复调用函数和占用额外的内存。

 队列

  • 队列(queue)是只允许在一端进行插入操作、而在另一端进行删除操作的线性表。
  • 循环队列和链队列比较,基本操作都是常数时间,即O(1),不过循环队列是事先申请好空间,使用期间不释放;而对于链队列,每次申请和释放结点也会存在一些时间开销,如果入队出队频繁,则还是循环队列好点。对于空间上来说,循环队列必须有一个固定长度,所以有了存储元素个数和空间浪费的问题;而链队列不存在这个问题,尽管需要一个指针域会产生一些空间的开销,但可以接受。所以在空间上,链队列更加灵活。

字符串

  • 串(string):是由零个或多个字符组成的有限序列,又名叫字符串。
  • 线性表更关注的是单个元素的操作,比如查找一个元素,插入或删除一个元素,但串中更多的是查找子串位置、得到指定位置子串、替换子串等操作。

  • 字符串的比较:KMP模式匹配算法

 https://www.cnblogs.com/zhangtianq/p/5839909.html

  树(Tree):用多个单链表来表示结构

  • 是n(n>=0)个结点的有限集。n=0时称为空树。在任意一棵非空树中:有且仅有一个特定的称为根(Root)的结点;当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1、T2、....、Tn,其中每一个集合本身又是一棵树,并且称为根的子树(SubTree)。

二叉树:

  • 五种基本形态:空二叉树;只有一个根结点;根结点只有左子树;根结点只有右子树;根结点既有左子树又有右子树; 线性表可以理解成树的一种特殊形态,斜树,即所有子树都是左节点或右节点。
  • 完全二叉树:用顺序存储,不是完全二叉树,因为有些空结点,会造成数组中有空间没用到。
  • 二叉树:存储结构用二叉链表,一个数据域和两个指针域。
  • DLR--前序遍历(根在前,从左往右,一棵树的根永远在左子树前面,左子树又永远在右子树前面 )
  • LDR--中序遍历(根在中,从左往右,一棵树的左子树永远在根前面,根永远在右子树前面)
  • LRD--后序遍历(根在后,从左往右,一棵树的左子树永远在右子树前面,右子树永远在根前面)
  • 已知前中和中后可以确定唯一二叉树,已知前后不能确定唯一二叉树。

 

  • 线索二叉树:存储结构双向链表,空的左子节点指向前驱节点,空的右子节点指向后继节点。另外加两个bool确定是不是子节点。
    如果二叉树需要经常遍历或查找结点时,需要遍历序列中的前驱和后继,用线索二叉链表的存储结构就是不错的选择。

    赫夫曼树:最基本的压缩编码方法-赫夫曼编码。一般地,设需要编码的字符集为{d1,d2,...dn},各个字符在电文中出现的次数或频率集合为{w1,w2,...,wn},以d1,d2,...,dn作为叶子结点,以w1,w2,...,wn作为相应叶子结点的权值来构造一棵赫夫曼树。规定赫夫曼树的左分支代表0,右分支代表1,则从根结点到叶子结点所经过的路径分支组成的0和1的序列便为该结点对应字符编码,这就是赫夫曼编码。

查找(Searching)

  • 就是根据给定的某个值,在查找表中确定一个其关键字等于给定值的数据元素(或记录)。
  • 从逻辑上说,查找基于数据结构是集合,集合中的记录之间没有本质关系。在存储时可以将查找集合组织成表、树等结构。
  • 静态查找表,可以用线性结构来组织数据,可以使用顺序查找算法,如果对主关键字排序,则可以应用折半查找等技术进行高效查找。
  • 动态查找,可以考虑二叉排序树的查找技术。还可以用散列表结构来查找。
  • 静态查找:从第一个或最后一个开始,遍历查找每一个元素。用for会每次判断是否越界,可以设置一个最小的值(哨兵)然后用while循环判断值是否相等。

  • 折半查找(Binary Search):前提是线性表记录的是关键码有序,线性表必须采用顺序存储。思想:在有序表中,取中间记录作为比较对象,若相等则查找成功;若定值小于中间记录的关键定,则在左半区继续查,若大于则在右半区继续查。
  1. 缺点:对频繁插入和删除的表来说,维护有序的排序会带来不小的工作量,不建议使用。
  • 插值查找:根据查找的关键字key与查找表中最大最小记录的关键字比较后的查找方法,计算定值不是中间,而且按插值比较出来。
  1. 缺点:分布不均匀的数据,不适合用插值查找。
  • 斐波那契查找:按黄金分割比例。
  • 折半查找进行加法与除尘运算(mid=(low+hight)/2),插值查找进行复杂四则运算(mid=low+(high-low)*(key-a[low])/(a[high]-a[low])),而斐波那契查找进行加减运算(mid=low+F[k-1]-1),在海量数据的查找过程中,这种细微的差别可能会影响最终的查找效率。

如果查找的数据集是有序线性表,并且是顺序存储,查找可以用折半、插值、斐波那契查找算法实现,可惜因为有序,在插入和删除操作上,就需要耗费大量的时间。

  • 二叉排序树:按中序排序,查找和插入容易,删除难。
  • 平衡二叉树:比较理想的一种动态查找表算法,查找和插入和删除时间复杂度为O(logN)。适合集合本身没有顺序,频繁查找的同时也需要经常插入和删除的操作。
  • B树:为了内存和硬盘数据交互准备的。

哈希表(散列表)

  • 顺序表查找是a[i]与key的值是“==”,直到相等才算是查找成功,返回i。有序表查找时,可以复用a[i]与key的"<"或">"来折半查找,直到找到下标i。最终目的都是为了找到i,其实也就是相对的下标,再通过顺序存储的位置计算方法,Loc(ai)=Loc(a1)+(i-1)*c,也就是通过第一个元素内存存储位置加上i-1个单元位置,得到最后的内存地址。
  • 哈希表定义:散列技术是记录在存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字key对应一个存储位置f(key)。我们把对应关系f称为散列函数,又称为哈希(Hash)函数。按这个思想,采用散列技术将记录存储在一块连续的存储空间中,这块连续存储空间称为散列表或哈希表(Hash table)。
  • 散列技术既是一种存储方法,也是一种查找方法。
  • 散列技术最适合的求解问题是查找与给定值相等的记录。
  • 哈希函数构造原则:计算简单;散列地址分布均匀
  1. 计算散列地址所需的时间
  2. 关键字的长度
  3. 散列表的大小
  4. 关键字的分布情况
  5. 记录查找的频率
  • 直接定址法:f(key)=a*key+b(a,b为常数)
  • 数字分析法:如取手机号码后四位作为关键字,因为前三位对应品牌,中间四位对应归属地,重复机率大。
  • 平方取中法:将关键字平方后取中间的几位作地址。适合不知道关键字分布,位数不大的情况。
  • 折叠法:将关键字从左到右分割成位数相等的几部分,求和后到后几们作散列地址。适合关键字位数较多的情况。
  • 随机数法:取关键字的随机函数为散列地址。f(key)=random(key)。适合关键字长度不等时。
  • 除留余数法:最常用的构造函数方法。公式为f(key)=key mod p(p<=m)(散列表长为m,通常p为小于或等于表长的最小质数或不包含小于20质因子的合数)
  • 处理散列冲突:
  1. 开放定址法:就是一旦发生冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入。公式:fi(key)=(f(key)+di)MOD m(di=1,2,3,...,m-1)。也叫线性探测法。
  2. 随机探测法:即线性探测法的di不是线性加1,而是用随机函数。公式:fi(key)=(f(key)+di)MOD m(di=1平方,-1平方,2平方,-2平方,...,q平方,q<=m/2)fi(key)=(f(key)+di) MOD m(di是一个随机数列)
  3. 再散列函数法:fi(key) = RHi(key)(i=1,2,...,k),这里的RHi就是不同的散列函数,可以把前面说的除留余数、折叠、平方取中全用上,当散列地址冲突时,就换一个散列函数计算。缺点:相应增加了计算时间。
  4. 链地址法:将所有关键字为同义词的记录存储在一个单链表,有冲突就增加结点。如果冲突多,查找时需要遍历单链表的性能损耗。
  5. 公共溢出区法:分为基本表和溢出表,如果基本表找不到就去溢出表找。冲突数据很少的情况下,性能还是非常高的。

  • 哈希表查找性能分析:在所有的查找中效率是最高的,因为时间复杂度是O(1),不过是在没冲突的情况下。所以平均查找复杂度取决于下面几种:
  1. 散列函数是否均匀
  2. 处理冲突的方法,像链地址不会产生堆积,因而具有更佳的平均查找性能。
  3. 散列表的装填因子,装填因子a=填入表的记录个数/散列表长度。越大就越容易冲突,通常将散列表空间设置比查找集合大,虽然浪费了一定的空间,但换来的是查找效率的大提升。

排序

  • 内排序和外排序:内排是排序过程中,待排序的所有记录全部被放置在内存中。外排序是由于排序记录个数太多,不能同时放置在内存,需要在内外存之间多次交换数据才能进行。
  1. 内排序:插入排序、交换排序、选择排序、归并排序。
  2. 简单算法:冒泡排序、简单选择、直接插入
  3. 改进算法:希尔排序、堆排序、归并排序、快速排序
  • 冒泡排序(Bubble Sort):两两比较相郊记录的关键字,如果反序则交换,直到没有反序的记录为止。
  • 选择排序(Simple Selection Sort):通过n-i次关键字间的比较,从n-i+1个记录中选出关键字最小的记录,并和第i(1<=i<=n)个记录交换。(选择一个最小的来交换)
  • 直接插入(Straight Insertion Sort):将一个记录插入到已经排好序的有序中,从而得到一个新的、记录数增1的有序表。
  • 希尔排序(Shell Sort):将相距某个"增量"的记录组成一个子序列,这样保证子序列内分别直接插入排序后得到的结果是基本有序而不是局部有序。
  • 堆排序(Heap Sort):完全二叉树,每个结点的值大于或等于左右孩子称大顶堆,小于或等于左右孩子称小顶堆。
  • 归并排序(Merging Sort):像一个倒立的树。假设初始序列含有n个记录,则可以看成是n个有序的子序列,每个子序列的长度为1,然后两两归并,得到[n/2]([x]表示不小于x的最小整数)个长度为2或1的有序子序列;再两两归并。有比较if(SR[i]<SR[j])所以是需要两两比较,不存在跳跃,所以是稳定的排序,是一种比较占用内存(需要复制子序列,然后进行合并),但却效率高且稳定的算法。
  • 快速排序(Quick Sort):通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续里德排序,以达到整个序列有序的目的。
  1. 希尔相当直接插入,同属于插入类;堆排序相当选择排序,同属于选择类;快速排序相当于最慢的冒泡排序,属于交换类。
  2. 如果数组非常小,快速排序反而不如直接插入排序来得更好(直接插入是简单排序中性能最好的)。因为快速排序用到了递归操作。
  3. 快速排序>>希尔排序>直接插入>选择排序>冒泡排序

猜你喜欢

转载自www.cnblogs.com/wwhhgg/p/12566268.html