数据结构与算法
数据结构与算法
心得:
- 灵活运用取余计算!
- 算法中优先排除特殊情况(
死去的高考数学开始攻击我)
数据结构
数据结构 包括 线性结构 与 非线性结构
线性结构分为顺序存储结构(顺序表)与链式存储结构(链表),区别在于内存分布连续与否
线性结构:数组、队列、链表、栈
非线性结构:二维数组、多维数组、广义表、树结构、图结构
稀疏数组 SparseArray
当一个数组中大部分元素都为0,或者是同一个元素时,可以使用稀疏数组来保存该数组。
处理思路:
1.记录数组一共有几行几列,有多少个不同的值
2.把具有不同值的元素的行、列及值记录在一个小规模的数组中
二维数组转稀疏数组的方法:
1.遍历原始的二维数组,得到有效数据的个数sum
2.根据sum创建稀疏数组int[sum + 1][3] sparseArray
3.将原数组中的有效数据转存入稀疏数组
稀疏数组转二维数组的方法:
1.读取稀疏数组的第一行,以此创建新的二维数组
2.读取稀疏数组后几行的数据,并赋值给二维数组
示例:
1 | public class SparseArray { |
队列 Queue
- 队列是一个有序列表,可以用数组或是列表来实现
- 遵循先进先出的原则(FIFO)
数组实现队列:
因为队列的输出与出入分别从前后端进行处理,所以需要两个变量front及rear分别记录队列前后端的下标,front随数据输出而改变,而rear随数据输入而改变
示例:
1 | class QueueArray { |
存在的问题:因为数组只能使用一次,所以队列满后即便取出前端数据也不能再添加数据,使用过的空间无法重复使用,造成空间的浪费
改进方法:利用数组实现环形队列
数组实现环形队列:
将front, rear修改为分别指向数组前端数据与后端数据的下一位,利用front == rear判断队列是否为空, 利用*(rear + 1) % maxSize == front* 判断队列是否已满,此时队列中有效数据个数为*(rear - front + maxSize) % maxSize*
环形队列实际上牺牲了一个空间,队列大小为maxSize - 1
示例:
1 | class CircleQueueArray{ |
链表 LinkedList
- 链表是有序的列表,由多个节点(*node)*构成
- 每个节点中含data域与next域,next域指向下一个节点
- 链表的每个节点在内存中是不连续存放的
- 链表可带头节点或不带头节点(head),其不存放具体数据,用于指向单链表头
- 双向链表同时还含有pre域,指向上一个节点
示例:
用带head头的单向链表实现水浒英雄排行榜管理
1 | //定义mySingleLinkedList用于管理英雄 |
栈 Stack
- 栈为有序列表
- 栈遵循先入后出(FILO)的原则
- 栈是元素的添加与删除都只能在线性表中的同一端进行的一种特殊线性表,允许插入的一端和删除的一端为变化的一端,称为栈顶(Top),另一端为固定的一端,称为栈底(Bottom)
- 出栈称为pop,入栈称为push
示例:
用栈完成计算机(从字符串中提取数字与符号进行计算)
1 | public class CalculatorWithStack { |
算法
时间频度:一个算法花费的时间与算法中语句的执行次数成正比,一个算法中的语句执行次数称为时间频度或语句频度,记为T(n)。
时间复杂度: 将算法对应的时间频度改为保留系数为1的最高阶次项,记为O(n)。
空间复杂度: 一个算法所耗费的存储空间称为空间复杂度,同样记为O(n)。
递归 Recursion
递归是一种编程技术,其中函数在其定义中直接或间接调用自身。
递归的基本条件包括:子问题与原始问题相同且更简单,以及存在一个终止条件,以防止无限递归。
注:不要在一开始就陷入细节,优先思考母问题与子问题之间的相似性
由于子问题的规模比母问题小,不断递下去,总会有个尽头,即递归的终止条件(base case),直接返回它的答案(归)
回溯算法 Back Tracking
回溯法是一种选优搜索法,又称为试探法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为回溯点。
可以把回溯法看成是递归调用的一种特殊形式。其核心就是 for 循环里面的递归,在递归调用之前做选择,在递归调用之后撤销选择。
排序算法 Sort Algorithm
排序是将一组数据按指定的顺序进行排列的过程
排序的分类:
- 内部排序 使用内存
- 外部排序 使用内存与外存(数据量偏大)
常见内部排序:
- 插入排序 直接插入排序、希尔排序
- 选择排序 简单选择排序、堆排序
- 交换排序 冒泡排序、快速排序
- 归并排序
- 基数排序
冒泡排序 Bubble Sort
冒泡排序通过对 待排序序列从前向后依次比较相邻元素的值,若发现逆序则交换,使值较大的元素逐渐从前移向后部,像水底的气泡般逐渐上浮。
冒泡排序优化:若在一次循环中没有出现交换,则说明已排序完成,此时可提前退出程序
示例:
1 | private static int[] bubbleSorting(int[] array){ |
选择排序 Select Sort
选择排序通过从欲排序的数据中按指定的规则选择出元素,再按规定交换位置后达到排序的目的。
以从小到大排列为例,第一次从第1个至第n个元素中选取最小值,并与第一个元素交换,以此类推,总共交换n-1次。
示例:
1 | private static int[] selectSorting(int[] array){ |
插入排序 Insertion Sort
插入排序通过对欲排序的元素以插入的方式找寻该元素的适当位置,以达到排序的目的。
将n个待排序的元素看成一个有序表和一个无序表,开始时有序表中只包含一个元素,无序表中含n-1个元素,排序过程中每次从无序表中取出第一个元素,依次与有序表元素比较并插入到适当位置,使之成为新的有序表。
示例:
1 | private static int[] insertionSorting(int[] array){ |
希尔排序 Shell Sort
希尔排序是插入排序的一种,它是针对直接插入排序算法的改进。又称缩小增量排序,因 Donald Shell 于 1959 年提出而得名。
它通过比较相距一定间隔的元素来进行,各趟比较所用的距离随着算法的进行而减小,直到只比较相邻元素的最后一趟排序为止。
即为分层嵌套的插入排序。
示例:
1 | private static int[] shellSorting(int[] array){ |
快速排序 Quick Sort
快速排序是对冒泡排序的一种改进;基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列
示例:
1 | private static void quickSorting(int[] array, int left, int right){ |
归并排序 Merge Sort
归并排序(Merge sort)是建立在归并操作上的一种有效、稳定的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
示例:
1 | private static void mergeSorting(int[] arr, int l, int r) { |
查找算法 Search Algorithm
线性查找 Linear Search
略
二分查找 Binary Search
二分查找(Binary Search)是一种在有序数组中查找特定元素的搜索算法。它的工作原理是通过不断地将搜索范围缩小一半来找到目标元素
实际问题
约瑟夫问题(Josephus problem)
约瑟夫环问题起源于一个犹太故事:
罗马人攻占了桥塔帕特,41个人藏在一个山洞中躲过了这场浩劫。这41个人中,包括历史学家Josephus(约瑟夫)和他的一个朋友。剩余的39个人为了表示不向罗马人屈服,决定集体自杀。大家制定了一个自杀方案,所有这41个人围成一个圆圈,由第一个人开始顺时针报数,每报数为3的人就立刻自杀,然后再由下一个人重新开始报数,仍然是每报数为3的人就立刻自杀……,直到所有的人都自杀身亡为止。约瑟夫和他的朋友并不想自杀,于是约瑟夫想到了一个计策,他们两个同样参与到自杀方案中,但是最后却躲过了自杀。
简化问题:
设有N个人围坐一圈,编号为1到N,现从编号为1的人开始报数,数到M的人出列,接着从出列的下一个编号(人)开始重新报数,数到M的人出列,如此下去,直到只剩下最后一个人,并输出其编号。
递推公式法:
$$
f(N,M)=(f(N−1,M)+M)%N
$$
详细推导过程:[约瑟夫环——公式法(递推公式)-CSDN博客]
示例代码:
1 | public static int remove(int n, int m){ |
递归法:
也基于上述公式
示例代码:
1 | public static int remove(int n, int m){ |
链表法:
最纯正的又臭又长
示例代码:
1 | class CircleLinkedList{ |
八皇后问题(Eight queens)
八皇后问题(英文:Eight queens),是由国际象棋棋手马克斯·贝瑟尔于1848年提出的问题,是回溯算法的典型案例。
问题表述为:在8×8格的国际象棋上摆放8个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。
递归回溯:
示例代码:
1 | public class EightQueens { |