前言:数据结构,存数据的一种结构;算法: 将数据进行一定的逻辑处理,输出一个理想的期望值的过程;那么怎么来衡量数据结构或者算法的优劣?
评判标准1 根据时间复杂度:
因为程序运行的平台环境和数据量的不同,所以需要一个客观的指标来描述随着数据量的增加,程序在运行时间上的一个变化,使用O 表示;如常见的时间复杂度:
时间复杂度由小到大:
评判标准2 根据空间复杂度:
同样的随着数据量的增加,我们也需要有个客观的指标来对某一个算法运行时所占空间进行好坏的评判;
空间复杂度排序,由小到大:
3 数据的两种结构:
3.1 线性结构:
(1)线性结构特点是数据元素之间存在一对一的线性关系。
(2)线性结构有两种不同的存储结构:顺序存储结构(数组)和链式存储结构(链表)。顺序存储的线性表称为顺序表,顺序表中的存储元素是连续的。
(3)链式存储的线性表称为链表,链表中的存储元素不一定是连续的,元素节点中存放的是数据元素和相邻元素的地址信息。
(4)线性结构常见的有:数组,队列,链表和栈。
3.2 非线性结构:
非线性结构包括:二维数组,多维数组,广义表,树结构,图结构;
4 常见线性结构的时间复杂度:
4.1 数组:
定义:存储相同数据结构,并且开辟的空间是连续的;
插入:在某个位置插入一个数据(注意不是替换);需要将插入位置之后的数据,依次后移一位;
时间复杂度:
删除:删除一个位置数据,需要将该位置之后的数据依次迁移,所以平均耗时和插入相同:
删除末尾是最好的情况,删除数组第一个元素则是最差情况:
查询:
通过下标 查询为O(1) 可以直接找到;如果通过数据内容比较查询则和插入一样,需要依次去比对数据内容,耗时O(N);
为什么数组下标要从0开始:支持指针的数据结构中,编号可以被视作偏移量;
4.2 链表:
链表的开辟空间时非连续,非顺序的存储结构;数据元素的逻辑顺序时通过链表中的指针链接次序实现的;
(1)单向链表:
结构:
每个节点都由数据和指向下一个节点的指针构成;通过头节点可以找到所有数据,尾节点的next 为null;
插入:
新的节点需要插入到某个位置,只需要将它的前置节点指向自己,它自己本身的next指向下一个节点;
时间复杂度 O(1)
删除:
删除某个节点,只需要将它本身移除,并让其前置节点的next 指针指向它的下一个节点;
时间复杂度O(1);·
查询:
通过下标或者内容,获取到某个节点,需要从链表的头部开始一直到尾部进行遍历;所以时间复杂度为O(n);
(2)循环链表:
特殊的单链表:尾节点的next 指针指向头节点:
补充:如果链表中,头节点被删除怎么办?
如果链表中的头节点被删除,那么数据就无法进行遍历,所以头部节点之前在增加一个节点(哨兵节点/dummy节点),该节点没有实际数据,如果头部节点被删除,也可以由它充当头节点;
(3)双向链表:
是在单向链表的基础上,每个节点再增加一个指向前驱节点的指针;
4.3 栈(Stack):
一种先进后出的线性数据结构;改栈中元素空间是否连续,取决于存放的数据本身数据结构是否连续;也就是栈可基于数组实现,可以将栈看成一种特殊的数组,Jdk8 中默认使用数组进行元素的填充;
4.4 队列(Queue):
队列也是一种先进先出线性结构;改队列中元素空间是否连续,取决于存放的数据本身数据结构是否连续;
相比数组,队列对应的操作是数组的子集;也就是说,队列是可基于数组实现的,可以将队列看成一个特殊的数组。
应用:
(1)使用栈来实现队列:
思路:使用两个栈,放入元素时都放入到第一个栈中,获取元素的时候,可以将第一个栈中的每个元素都取出放入到第二个栈里,然后从第二个栈里取出元素记即可;
(2)推荐结果打散:
如:一个视频和图片的集合,返回的集合中,需要满足,最少m个视频之后可以有1张图片;
思路:
使用两个队列,一个视频队列,一个图片队列,将视频和图片的集合 分别放入到图片和视频的队列中;
判断如果图片队列均为空,则可以直接返回原有队列;
判断如果视频队列均为空,则可以直接返回空集合;
如果两个集合都不为空,则循环视频队列,如果发现此时放入集合的视频没有达到m个视频的个数限制,需要直接放入视频到集合中,并增加此时又放入的视频个数变量;如果达到了m个视频的个数限制并且图片集合不为空则放入一张图片,同时将放入的视频个数变量置0,如果满足条件但是图片集合已经为空,则直接放从视频队列中取出视频数据放入到集合中,并增加此时又放入的视频个数变量;
循环完毕后,此时增加一个末尾的判断,如果图片队列还有数据并且,此时放入的视频个数变量达到了m个视频的个数限制则,则需要在放入一张图片;
5 二叉树:
5.1 二叉树定义:
每个节点只能最多有一个左节点,最多只有一个右节点;
满二叉树:除了根节点之外,除了叶子节点之外每个节点多有左右两个节点;
完全二叉树:除了根节点之外,除了叶子节点之外每个节点多有左右两个节点;叶子节点的元素都靠左边排列;
使用数组存放二叉树:
左子节点:2i 右子节点:2i+1;
5.2 二叉查找树(二叉搜索树):
定义:节点的左子节点的值需要小于改节点,右子节点的值需要大于改节点;
-
查找一个元素:时间取决于树的层数;
-
插入一个元素:从根节点依次遍历,找到比某节点大并且改节点右子节点为空进行插入;或者比改节点小并且左子节点为空插入;
-
删除一个节点:
如果叶子节点直接删除,如果不是叶子节点,如果改节点下只有一个左节点或者只有一个右节点,则将左节点或右节点直接更新到删除的节点上;如果改节点既有左节点右有有右节点;需要将改节点的右子树的最小节点更新到删除的节点:
二叉查找树:插入,查询,删除的平均时间复杂度log(n)
5.2. 1二叉查找树的遍历: -
前序遍历:
对于每个节点来说先打印自己节点,然后打印左子节点,最后打印自己;
递归方法:
使用堆数据结果实现:非递归:
-
中序遍历:(遍历出的节点数据是从小到大有序排列的):
对于每个节点来说,先打印左子节点,在打印自己,最后打印右子节点;
递归遍历:
中序遍历:非递归
-
后序遍历:
对于每个节点来时,下打印左子节点,在打印右子节点,最后打印自己;
后序遍历:递归:
后序遍历:非递归:但是左右节点的关系会丢失
后序遍历:非递归:关系不丢失:
思路:依次遍历将左子节点入堆;然后 以左子节点为父节点,在判断改节点是否有左右节点;
然后输出最左的末子节点;继而判断末子节点是否右右子节点;然后遍历右子节点进行依次入堆;
然后输出最左的末子节点;输出最右的末子节点;输出父节点;依次遍历输出;
5.3 平衡二叉树:
当二叉查找树只有左子节点或者只有右子节点的时候,会退化成链表;显然这种情况不能被接受,就需要有一种平衡的二叉树,来解决这种情况;
定义:
平衡二叉搜索树又被称为AVL树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树;平衡二叉树的常用实现方法有红黑树、AVL等;
5.4 红黑树:
定义:
根节点是黑色的;
每个叶子节点是黑车的空节点(叶子节点不存数据)
任何相邻的节点都不能同时为红色,也就是说红色节点是被黑色节点隔开的;
每个节点,从改节点到达其可达叶子节点的所有路径,都包含相同数目的黑色节点;
红黑树是一种特定类型的二叉树,是一种平衡二叉查找树的变体,它的左右子树高差有可能大于 1,所以红黑树不是严格意义上的平衡二叉树(AVL),但 对之进行平衡的代价较低, 其平均统计性能要强于 AVL 。 [2]
由于每一棵红黑树都是一颗二叉排序树,因此,在对红黑树进行查找时,可以采用运用于普通二叉排序树上的查找算法,在查找过程中不需要颜色信息;插入和删除节点的时候通过旋转和改变节点颜色,让其符合红黑树的定义;
6 常用的排序算法:
6.1 冒泡排序:
进行两次循环,依次比较两个位置的数据大小,前一个数据比后一个数据要大,需要进行位置交换:
public static void main(String[] args) {
int[] sort = new int[]{5,3,1,2};
sortInt(sort);
for (int i = 0; i < sort.length; i++) {
System.out.println(sort[i]);
}
}
/**
* 冒泡排序,时间复杂度o(n^2)
* @param sort
*/
private static void sortInt(int[] sort) {
for (int i = 0; i < sort.length; i++){
for (int j = 1; j < (sort.length-i); j++) {
int a = sort[j-1];
int b = sort[j];
if (a>b){
sort[j] = a ;
sort[j-1] = b;
}
}
}
}
6.2 插入排序:
从前一个元素依次向前移动,依次与改插入元素对比,如果发现比插入元素大,则将对比的元素向后移动一位,直到发现比插入元素小则插入到改元素的后一个位置:
public static void main(String[] args) {
int[] sort = new int[] { 5, 3, 1, 2 };
sortInt1(sort);
for (int i = 0; i < sort.length; i++) {
System.out.println(sort[i]);
}
}
/**
* 插入排序
* @param sort
*/
private static void sortInt1(int[] sort) {
int insertNode;
int j;
for (int i = 1; i < sort.length; i++) {
insertNode = sort[i];
j = i - 1;
while (j >= 0 && insertNode < sort[j] ) {
sort[j + 1] = sort[j];
j--;
}
sort[j+1] = insertNode;
}
}
6.3 归并排序:
将一个数组不断的一分为2,然后对每个数组进行排序;然后在对其整体进行一个排序;
public static void main(String[] args) {
int[] sort = new int[] { 5, 3, 1, 2, 6, 9, 3, 7, 8 };
int[] temp = new int[sort.length];
sortInt1(sort, 0, sort.length - 1, temp);
for (int i = 0; i < sort.length; i++) {
System.out.println(sort[i]);
}
}
private static void sortInt1(int[] sort, int start, int end, int[] temp) {
if (start >= end) {
return;
}
int mid = (start + end) / 2;
sortInt1(sort, start, mid, temp);
sortInt1(sort, mid + 1, end, temp);
sortMerge(sort, start, mid, end, temp);
}
private static void sortMerge(int[] sort, int start, int mid, int end,
int[] temp) {
int left = start;
int right = mid + 1;
int index = start;
while (left <= mid && right <= end) {
if (sort[left] > sort[right]) {
temp[index++] = sort[right++];
} else {
temp[index++] = sort[left++];
}
}
while (left <= mid) {
temp[index++] = sort[left++];
}
while (right <= end) {
temp[index++] = sort[right++];
}
for (index = start; index <= end; index++) {
sort[index] = temp[index];
}
}
6.4 选择排序:
进行两遍排序;依次拿到第一次循环的元素,与改元素之后位置元素进行对比,找到最小的元素,然后将最小的元素与第一次循环的元素进行位置比较,如果不在相同位置则进行位置交换:
与冒泡排序区别:冒泡排序只要相邻位置元素不等,就需要交换位置;而选择排序,每一轮只需要最多交换一次位置(空间效率较好);
时间效率验证:时间复杂o(n^2)
public static void main(String[] args) {
int[] sort = new int[] { 5, 3, 1, 2 };
sortInt1(sort);
for (int i = 0; i < sort.length; i++) {
System.out.println(sort[i]);
}
}
private static void sortInt1(int[] sort) {
for (int i = 0; i < sort.length; i++) {
int pos = i;
for (int j = i + 1; j < sort.length; j++) {
if (sort[pos] > sort[j]) {
pos = j;
}
}
if (i != pos) {
int temp = sort[i];
sort[i] = sort[pos];
sort[pos] = temp;
}
}
}
6.5 快速排序:
每次都用改数组的第一个元素值将改数组一分为二;形成左侧数组都小于改元素值,右侧元素都大于该元素值;
public static void main(String[] args) {
int[] sort = new int[] { 5, 3, 1, 2 ,6,9,3,7,8};
sortInt1(sort,0,sort.length-1);
for (int i = 0; i < sort.length; i++) {
System.out.println(sort[i]);
}
}
private static void sortInt1(int[] sort,int start,int end) {
// TODO Auto-generated method stub
if (start>=end){
return;
}
int pivot = sort[start];
int left = start;
int right = end;
while(left <= right){
while(left <= right && sort[left]<pivot){
left++;
}
while (left<=right && sort[right]>pivot){
right--;
}
if (left <= right){
int temp = sort[left];
sort[left] = sort[right];
sort[right] = temp;
left++;
right--;
}
}
sortInt1(sort, start, right);
sortInt1(sort, left, end);
}
几种排序算法的性能比较:
快速排序>归并排序>插入排序>选择排序>冒泡排序;
7 二分查找法 Binary search:
集合必须是有序的,每次在二分的时候可以准确知道二分之后要查找的目标在左边还是右边;