数据结构和算法-十大排序、搜索、数据结构

框架

一、数据结构:线性结构和非线性结构
线性结构:
1)数据元素之间存在一对一的线性关系
2)线性结构有两种不同的存储结构,即顺序存储结构和链式存储结构。顺序存储的线性表称为顺序表,顺序表中的存储元素是连续的
3)链式存储的线性表称为链表,链表中的存储元素不一定是连续的,元素节点中存放数据元素以及相邻元素的地址信息。
4)线性结构常见的有:数组、队列、链表和栈。
非线性结构:
非线性结构包括:二维数组,多维数组,广义表,树结构,图结构
二、稀疏数组和队列
1.稀疏数组
1.1基本介绍
当一个数组中大部分元素为0,或者为同一个值的数组时,可以使用稀疏数组来保存该数组。
稀疏数组的处理方法是:

  1. 记录数组一共有几行几列,有多少个不同的值
  2. 把具有不同值的元素的行列及值记录在一个小规模的数组中,从而缩小程序的规模
    2.队列
    2.1队列介绍
     队列是一个有序列表,可以用数组或是链表来实现。
     遵循先入先出的原则。即:先存入队列的数据,要先取出。后存入的要后取出
     示意图:(使用数组模拟队列示意图)
    2.2 数组模拟队列—》数组使用一次就不能再使用,添加元素已满,无法添加
     队列本身是有序列表,若使用数组的结构来存储队列的数据,则队列数组的声明如下图, 其中 maxSize 是该队列的最大容量。
     因为队列的输出、输入是分别从前后端来处理,因此需要两个变量 front及 rear分别记录队列前后端的下标,front指向队列头的前一个位置上数据(初始化值-1),front会随着数据输出而改变,而rear则是随着数据输入而改变,rear是指向队列的最后一个数据,如图所示:

2.3 数组模拟环形队列—解决数组无法使用问题,算法取模
对前面的数组模拟队列的优化,充分利用数组. 因此将数组看做是一个环形的。(通过取模的方式来实现即可)。
分析说明:

  1. 尾索引的下一个为头索引时表示队列满,即将队
    列容量空出一个作为约定,这个在做判断队列满的
    时候需要注意 (rear + 1) % maxSize == front 满]
  2. rear == front [空]
  3. front指向队列的第一个元素,初始值为0
  4. rear指向队列的最后一个元素的后一个位置,希望空出一个空间
    rear初始值是0
    5)rear和front都是循环的值,进行添加或取出元素时都是取模体现循环
    环形数组模拟的队列的输出元素的个数
    (cq.rear + cq.maxSize – cq.front) % cq.maxSize

三、链表

四、栈

  1. 栈的英文为(stack)
  2. 栈是一个先入后出(FILO-First In Last Out)的有序列表。
  3. 栈(stack)是限制线性表中元素的插入和删除只能在线性表的同一端进行的一种特殊线性表。允许插入和删除的一端,为变化的一端,称为栈顶(Top),另一端为固定的一端,称为栈底(Bottom)。
  4. 根据栈的定义可知,最先放入栈中元素在栈底,最后放入的元素在栈顶,而删除元素刚好相反,最后放入的元素最先删除,最先放入的元素最后删除
  5. 出栈(pop)和入栈(push)的概念(如图所示)

栈的应用场景:

  1. 子程序的调用:在跳往子程序前,会先将下个指令的地址存到堆栈中,直到子程序执行完后再将地址取出,以回到原来的程序中。
  2. 处理递归调用:和子程序的调用类似,只是除了储存下一个指令的地址外,也将参数、区域变量等数据存入堆栈中。
  3. 表达式的转换[中缀表达式转后缀表达式]与求值(实际解决)。
  4. 二叉树的遍历。
  5. 图形的深度优先(depth一first)搜索法。

五、前缀、后缀、中缀
1.前缀
从右至左扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(栈顶元素 和 次顶元素),并将结果入栈;重复上述过程直到表达式最左端,最后运算得出的值即为表达式的结果

首先了解一下前缀表达式

前缀表达式就是不含括号的算术表达式,而且它是将运算符写在前面,
操作数写在后面的表达式,也称为“波兰式”。例如,- 1 + 2 3,它等价于1-(2+3);

前缀表达式如何求值

对于一个前缀表达式的求值而言,首先要从右至左扫描表达式,从右边第一个字符开始判断,如果当前字符是数字则一直到数字串的末尾再记录下来,如果是运算符,则将右边离得最近的两个“数字串”作相应的运算,以此作为一个新的“数字串”并记录下来。一直扫描到表达式的最左端时,最后运算的值也就是表达式的值。
例如,前缀表达式“- 1 + 2 3“的求值,扫描到3时,记录下这个数字串,扫描到2时,记录下这个数字串,当扫描到+时,将+右移做相邻两数字的运算符,记为2+3,结果为5,记录下这个新数字串,并继续向左扫描,扫描到1时,记录下这个数字串,扫描到-时,将-右移做相邻两数字串的运算符,记为1-5,结果为-4,
所以表达式的值为-4。

前缀表达式有什么用处

前缀表达式是一种十分有用的表达式,它将中缀表达式转换为可以依靠简单的操作就能得到运算结果的表达式。例如,(a+b)*(c+d)转换为 *,+,a,b,+,c,d。它的优势在于只用两种简单的操作,入栈和出栈就可以解决任何中缀表达式的运算。其运算方式为:如果当前字符(或字符串)为数字或变量,则压入栈内;如果是运算符,则将栈顶两个元素弹出栈外并作相应运算,再将结果压入栈内。当前缀表达式扫描结束时,栈里的就是中缀表达式运算的最终结果。
2.后缀表达式—逆波兰计算器
后缀表达式的计算机求值;
从左至右扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(次顶元素 和 栈顶元素),并将结果入栈;重复上述过程直到表达式最右端,最后运算得出的值即为表达式的结果。

例如: (3+4)×5-6 对应的后缀表达式就是 3 4 + 5 × 6 - , 针对后缀表达式求值步骤如下:

  1. 从左至右扫描,将3和4压入堆栈;
  2. 遇到+运算符,因此弹出4和3(4为栈顶元素,3为次顶元素),计算出3+4的值,得7,再将7入栈;
  3. 将5入栈;
  4. 接下来是×运算符,因此弹出5和7,计算出7×5=35,将35入栈;
  5. 将6入栈;
    最后是-运算符,计算出35-6的值,即29,由此得出最终结果
    3.中缀转后缀表达式实现----算法
    具体步骤如下:
  6. 初始化两个栈:运算符栈s1和储存中间结果的栈s2;
  7. 从左至右扫描中缀表达式;
  8. 遇到操作数时,将其压s2;
  9. 遇到运算符时,比较其与s1栈顶运算符的优先级:
    (1) 如果s1为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈;
    (2) 1.若优先级比栈顶运算符的高,也将运算符压入s1;
    2.若优先级不比栈顶运算符的高,将s1栈顶的运算符弹出并压入到s2中,再次转到(4-1)与s1中新的栈顶运算符相比较;
  10. 遇到括号时:
    (1) 如果是左括号“(”,则直接压入s1
    (2) 如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃
  11. 重复步骤2至5,直到表达式的最右边
  12. 将s1中剩余的运算符依次弹出并压入s2
  13. 依次弹出s2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式。
    六、递归
  1. 递归用于解决什么样的问题
  1. 各种数学问题如: 8皇后问题 , 汉诺塔, 阶乘问题, 迷宫问题, 球和篮子的问题(google编程大赛)
  2. 各种算法中也会使用到递归,比如快排,归并排序,二分查找,分治算法等.
  3. 将用栈解决的问题–>第归代码比较简洁
    2.递归需要遵守的重要规则
  4. 执行一个方法时,就创建一个新的受保护的独立空间(栈空间),会从栈顶依次执行方法到栈底
  5. 方法的局部变量是独立的,不会相互影响, 比如n变量
  6. 如果方法中使用的是引用类型变量(比如数组八皇后),就会共享该引用类型的数据.
  7. 递归必须向退出递归的条件逼近,否则就是无限递归,出现StackOverflowError,死龟了:)
  8. 当一个方法执行完毕,或者遇到return,就会返回(回溯到之前运行的方法中),遵守谁调用,就将结果返回给谁,同时当方法执行完毕或者返回时,该方法也就执行完毕。

七、排序
1.冒泡排序(bubble sorting)–将数往后放,冒泡最后的数不需要再比较
冒泡排序(Bubble Sorting)的基本思想是:通过对待排序序列从前向后(从下标较小的元素开始),依次比较相邻元素的值,若发现逆序则交换,使值较大
的元素逐渐从前移向后部,就象水底下的气泡一样逐渐向上冒。
因为排序的过程中,各元素不断接近自己的位置,如果一趟比较下来没有进行过交换,就说明序列有序,因此要在排序过程中设置一个标志flag判断元素是否进行过交换。从而减少不必要的比较。(这里说的优化,可以在冒泡排序写好后,在进行)
2.选择排序—选出满足条件的往前放
选择排序(select sorting)也是一种简单的排序方法。它的基本思想是:第一次从arr[0]arr[n-1]中选取最小值,与arr[0]交换,第二次从arr[1]arr[n-1]中选取最小值,与arr[1]交换,第三次从arr[2]arr[n-1]中选取最小值,与arr[2]交换,…,第i次从arr[i-1]arr[n-1]中选取最小值,与arr[i-1]交换,…, 第n-1次从arr[n-2]~arr[n-1]中选取最小值,与arr[n-2]交换,总共通过n-1次,得到一个按排序码从小到大排列的有序序列。

3.插入排序

插入排序(Insertion Sorting)的基本思想是:
把n个待排序的元素看成为一个有序表和一个无序表,开始时有序表中只包含一个元素,无序表中包含有n-1个元素,排序过程中每次从无序表中取出第一个元素,把它的排序码依次与有序表元素的排序码进行比较,将它插入到有序表中的适当位置,使之成为新的有序表。
插入排序思路图:

4.希尔排序
希尔排序法介绍
希尔排序是希尔(Donald Shell)于1959年提出的一种排序算法。希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序。
希尔排序法基本思想
希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止

5.快速排序
快速排序法示意图:
快速排序(Quicksort)是对冒泡排序的一种改进。基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列

6.归并排序
归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。

归并排序思想示意图2-合并相邻有序子序列:
再来看看治阶段,我们需要将两个已经有序的子序列合并成一个有序序列,比如上图中的最后一次合并,要将[4,5,7,8]和[1,2,3,6]两个已经有序的子序列,合并为最终序列[1,2,3,4,5,6,7,8],来看下实现步骤:

7.基数排序

  1. 基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,顾名思义,它是通过键值的各个位的值,将要排序的元素分配至某些“桶”中,达到排序的作用。
  2. 基数排序法是属于稳定性的排序,基数排序法的是效率高的稳定性排序法
  3. 基数排序(Radix Sort)是桶排序的扩展
  4. 基数排序基本思想
    将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。

8.常用排序算法总结和对比

相关术语解释:

  1. 稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面;
  2. 不稳定:如果a原本在b的前面,而a=b,排序之后a可能会出现在b的后面;
  3. 内排序:所有排序操作都在内存中完成;
  4. 外排序:由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行;
  5. 时间复杂度: 一个算法执行所耗费的时间。
  6. 空间复杂度:运行完一个程序所需内存的大小。
  7. n: 数据规模
  8. k: “桶”的个数
  9. In-place: 不占用额外内存
  10. Out-place: 占用额外内存

八、查找算法
在java中,我们常用的查找有四种:要求数组是有序的
1) 顺序(线性)查找:按照数组元素列表一个一个查找
2) 二分查找/折半查找
3) 插值查找
4) 斐波那契查找
1.二分查找
数组必须有序,进行查找类似于归并排序左递归右递归。Mid = (left+right)/2
2. 插值查找算法—自适应查找,在mid中加了寻找值的大小
插值查找原理介绍:

  1. 插值查找算法类似于二分查找,不同的是插值查找每次从自适应mid处开始查找。
  2. 将折半查找中的求mid 索引的公式 , low 表示左边索引left, high表示右边索引right.
    key 就是前面我们讲的 findVal
  3. int mid = low + (high - low) * (key - arr[low]) / (arr[high] - arr[low]) ;/插值索引/
    对应前面的代码公式:
    int mid = left + (right – left) * (findVal – arr[left]) / (arr[right] – arr[left])
  4. 举例说明插值查找算法 1-100 的数组
    插值查找注意事项:
  5. 对于数据量较大,关键字分布比较均匀的查找表来说,采用插值查找, 速度较快.
  6. 关键字分布不均匀的情况下,该方法不一定比折半查找要好
  1. 斐波那契(黄金分割法)查找基本介绍:
  1. 黄金分割点是指把一条线段分割为两部分,使其中一部分与全长之比等于另一部分与这部分之比。取其前三位数字的近似值是0.618。由于按此比例设计的造型十分美丽,因此称为黄金分割,也称为中外比。这是一个神奇的数字,会带来意向不大的效果。
  2. 斐波那契数列 {1, 1, 2, 3, 5, 8, 13, 21, 34, 55 } 发现斐波那契数列的两个相邻数 的比例,无限接近 黄金分割值0.618

斐波那契(黄金分割法)原理:
斐波那契查找原理与前两种相似,仅仅改变了中间结点(mid)的位置,mid不
再是中间或插值得到,而是位于黄金分割点附近,即mid=low+F(k-1)-1
(F代表斐波那契数列),如下图所示
对F(k-1)-1的理解:

  1. 由斐波那契数列 F[k]=F[k-1]+F[k-2] 的性质,可以得到 (F[k]-1)=(F[k-1]-1)+(F[k-2]-1)+1 。该式说明:只要顺序表的长度为F[k]-1,则可以将该表分成长度为F[k-1]-1和F[k-2]-1的两段,即如上图所示。从而中间位置为mid=low+F(k-1)-1
  2. 类似的,每一子段也可以用相同的方式分割
  3. 但顺序表长度n不一定刚好等于F[k]-1,所以需要将原来的顺序表长度n增加至F[k]-1。这里的k值只要能使得F[k]-1恰好大于或等于n即可,由以下代码得到,顺序表长度增加后,新增的位置(从n+1到F[k]-1位置),都赋为n位置的值即可。

九、二叉树
1.为什么需要树这种数据结构

  1. 数组存储方式的分析
    优点:通过下标方式访问元素,速度快。对于有序数组,还可使用二分查找提高检索速度。
    缺点:如果要检索具体某个值,或者插入值(按一定顺序)会整体移动,效率较低 [示意图]
  2. 链式存储方式的分析
    优点:在一定程度上对数组存储方式有优化(比如:插入一个数值节点,只需要将插入节点,链接到链表中即可, 删除效率也很好)。
    缺点:在进行检索时,效率仍然较低,比如(检索某个值,需要从头节点开始遍历) 【示意图】
  3. 树存储方式的分析
    能提高数据存储,读取的效率, 比如利用 二叉排序树(Binary Sort Tree),既可以保证数据的检索速度,同时也可以保证数据的插入,删除,修改的速度。案例: [7, 3, 10, 1, 5, 9, 12]
    2.树示意图
    树的常用术语(结合示意图理解):
  4. 节点
  5. 根节点
  6. 父节点
  7. 子节点
  8. 叶子节点 (没有子节点的节点)
  9. 节点的权(节点值)
  10. 路径(从root节点找到该节点的路线)
  11. 子树
  12. 树的高度(最大层数)
  13. 森林 :多颗子树构成森林
  1. 二叉树的概念
  1. 树有很多种,每个节点最多只能有两个子节点的一种形式称为二叉树。
  2. 二叉树的子节点分为左节点和右节点。
  3. 如果该二叉树的所有叶子节点都在最后一层,并且结点总数= 2^n -1 , n 为层数,则我们称为满二叉树。
  4. 如果该二叉树的所有叶子节点都在最后一层或者倒数第二层,而且最后一层的叶子节点在左边连续,倒数第二层的叶子节点在右边连续,我们称为完全二叉树。

4.二叉树遍历的说明
使用前序,中序和后序对下面的二叉树进行遍历

前序遍历: 先输出父节点,再遍历左子树和右子树
中序遍历: 先遍历左子树,再输出父节点,再遍历右子树
后序遍历: 先遍历左子树,再遍历右子树,最后输出父节点
小结: 看输出父节点的顺序,就确定是前序,中序还是后序
5.二叉树查找节点

6.二叉树-删除节点
要求:删除节点要提前一个节点检测(其左右节点)

  1. 如果删除的节点是叶子节点,则删除该节点
  2. 如果删除的节点是非叶子节点,则删除该子树.

7.顺序化存储二叉树

8.线索化二叉树
线索二叉树基本介绍

  1. n个结点的二叉链表中含有n+1 【公式 2n-(n-1)=n+1】 个空指针域。利用二叉链表中的空指针域,存放指向该结点在某种遍历次序下的前驱和后继结点的指针(这种附加的指针称为"线索")
  2. 这种加上了线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树(Threaded BinaryTree)。根据线索性质的不同,线索二叉树可分为前序线索二叉树、中序线索二叉树和后序线索二叉树三种
  3. 一个结点的前一个结点,称为前驱结点—负责找到大左右子树的左叶子节点(中序后序),这是开始遍历的节点
  4. 一个结点的后一个结点,称为后继结点—负责遍历
    线索二叉树应用案例
    思路分析: 中序遍历的结果:{8, 3, 10, 1, 14, 6}
    说明: 当线索化二叉树后,Node 节点的 属性 left 和 right ,有如下情况:
  5. left 指向的是左子树,也可能是指向的前驱节点. 比如 ① 节点 left 指向的左子树, 而 ⑩ 节点的 left 指向的就是前驱节点.
  6. right指向的是右子树,也可能是指向后继节点,比如 ① 节点right 指向的是右子树,而⑩ 节点的right 指向的是后继节点.

遍历线索化二叉树
说明:对前面的中序线索化的二叉树, 进行遍历
分析:因为线索化后,各个结点指向有变化,因此原来的遍历方式不能使用,这时需要使用新的方式遍历线索化二叉树,各个节点可以通过线型方式遍历,因此无需使用递归方式,这样也提高了遍历的效率。 遍历的次序应当和中序遍历保持一致。

通过方法递归,一步步回溯时,遍历输出,删除,线索化—不懂的时候,画图分析梳理好回溯(注意回溯方法的传入的参数note)顺序,调用递归方法的节点note.
十、树结构实际应用
1.堆排序

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值