数据结构笔记二
前言
内容从快排开始,到哈希表和有序表为止,最后整理排序的时间复杂度和空间复杂度,并比较各个排序算法的优劣度
1.快排
快排据老师介绍有不同的版本,这里写的就是最后的版本,老师的代码写的很精妙,要copy的话就要完全按照老师的来,我整了半天决定还是自己写,原理是选择一个随机的数,然后将整个数组分为大于这个数的数在右边,小于这个数的数在左边,中间的是等于这个数的数,然后对左右两边范围的数重复这一个过程,简单来描述就是,每次随机选中一个数将这个数放到整个数组从小到大排序他应该在的位置,然后是下一个随机数,这样不断的循环整个数组也就排好序了。
public static void QuickSort(int[]arr,int L,int R){
if(L<R) {
int ran=arr[L+(int)(Math.random() * (R - L))];
int[] p = partition(arr, L, R,ran);//也就是快排的排序
QuickSort(arr, L, p[0]);
QuickSort(arr, p[1], R);
}
}
public static int[] partition(int[]arr,int L,int R,int ran){
int left=L-1;//<的左边界
int right=R+1;//>的右边界,选中的随机数固定在R处,在循环结束时,在与>右边界交换
//L检查整个数组的指针
while(L<right){
if (arr[L] < ran) {
left++;
if(L!=left)swap(arr,left,L);//防止自己和自己交换
L++;
//简单的写法为 swap(arr,++left,L++);但是不太直观
}
else if(arr[L]>ran){//这里边界移动了包含了一个未检查的数,所以L的位置不能变动
right--;
if(L!=right)swap(arr,right,L);
//这里如上也有简便写法
}
else L++;
}
return new int[]{left,right};
}
2.堆排序
原理就是借助大根堆或小根堆的性质,每次将最小或者最大的数放到数组中应该在 的位置,比如说把最大的数放到最后,最小的数放到最前面。然后将堆的最后一个数放到堆顶再进行一次堆化,具体的原理可以另行百度,这里简单解释
public static void HeapSort(int[]arr){
if(arr==null||arr.length<2) return;
HeapInsert(arr);//先将数组堆排序
int heapsize=arr.length;
for(int i=heapsize-1;i>0;i--){
swap(arr,i,0);
heapsize--;
Heapify(arr,heapsize,0);
}
}
public static void HeapInsert(int[]arr){//将数组变成大根堆
int i=1;
for(int heapsize=2;heapsize<=arr.length;heapsize++){
i=heapsize-1;
while (arr[i]>arr[(i-1)/2]) {//与父节点作比较
swap(arr,i,(i-1)/2);
i=(i-1)/2;
if(i==0)break;
}
}
}
public static void Heapify(int[]arr,int heapsize,int p){//heapsize记录堆的长度,p则指向是哪一个结点需要堆化
int child=-1;//记录左右值最大的孩子
if((p*2+1)<heapsize){
while ((p*2+1)<heapsize){
if((p*2+2)<heapsize){
child=arr[(p*2+1)]<arr[(p*2+2)]?(p*2+2):(p*2+1);
}//取两个孩子中最大的一个
else child=(p*2+1);
if(arr[p]<arr[child]){
swap(arr,p,child);
p=child;
}
else return;
}
}
else return;
}
例题
已知一个几乎有序的数组,几乎有序是指,如果把数组排好顺序的话,每个元素移动的距离可以不超过k,并且k相对于数组来说比较小。请选择一个合适的排序算法针对这个数据进行排序。
这题思路上比较简单使用小根堆,那么前k个数上一定存在着最小值(移动距离不超过k,k位置移动到0位置最多需要距离k),每次弹出一个数又进来一个数,周而复始就完成排序,时间复杂度为O(N)代码如下(偷个懒不想敲了)
在java中PriorityQueue是优先级队列,底层实现就是小根堆,具体的使用建议百度
3.比较器
可以自己重写来定义大小顺序,比如说对于一个学生对象,可以id升序、id降序,年龄升序、年龄降序等等这些都可以通过比较器来实现,在比较器中返回负数则第一个数在前,返回正数则第二个数在前、返回0则顺序随便。下面通过一段代码来展示
public class student {//学生类
int id;
int age;
String name;
}
public static class IdAscend implements Comparator<student>{
@Override
public int compare(student o1, student o2) {
return o1.id-o2.id;
}
}
//创建一个临时演示的数组,并排序打印
student[]a={
new student(4,20,"张三"),
new student(3,21,"李四"),
new student(2,22,"王五"),
new student(1,23,"张大炮"),
};
Arrays.sort(a,new IdAscend());
for (int i=0;i<a.length;i++){
System.out.println(a[i].toString());
}
运行的结果如下
4.桶排序
代码比较复杂又不怎么常用所以这里只简单的放上一个链接
https://blog.youkuaiyun.com/qq_52253798/article/details/122970542
5.排序算法总结
这个总结都来源于b站左程云老师的网课的截图
1,归并排序的额外空间复杂度可以变成0(1),但是非常难,不需要掌握,有兴趣可以搜“归并排序内部缓存法”,但是使用这个方法会失去稳定性
2,“ 原地归并排序”的帖子都是垃圾,会让归并排序的时间复杂度变成0 (N’ 2)
3,快速排序可以做到稳定性问题,但是非常难,不需要掌握,可以搜 “01stable sort"同时空间复杂度会变为O(N)水平
4,所有的改进都不重要,因为目前没有找到时间复杂度0 (N*logN),额外空间复杂度0(1),又稳定的排序。
5,有一道题目,是奇数放在数组左边,偶数放在数组右边,还要求原始的相对次序不变,碰到这个问题,可以怼面试官。
可以使用综合排序,即在一个排序的问题上采用两种或多种排序的思想,因为时间复杂度的瓶颈出现在大数据样本中,在小数据量中时间复杂度的级别反而没有时间复杂度的常数时间影响大。所以可以将两种或者几种排序拼接起来,发挥各自的优势。
哈希表和有序表
文字描述同样来源于老师的课件
哈希表的简单介绍
1)哈希表在使用层面上可以理解为一种集合结构
2)如果只有key,没有伴随数据value,可以使用HashSet结构(C++中叫UnOrderedSet)
3)如果既有key,又有伴随数据value,可以使用HashMap结构(C++中叫UnOrderedMap)
4)有无伴随数据,是HashMap和HashSet唯一的区别,底层的实际结构是一回事
5)使用哈希表增(put)、删(remove)、改(put)和查(get)的操作,可以认为时间复杂度为0(1),但是常数时间比较大
6)放入哈希表的东西,如果是基础类型,内部按值传递,内存占用就是这个东西的大小
7)放入哈希表的东西,如果不是基础类型,内部按引用传递,内存占用是这个东西内存地址的大小.
有序表的简单介绍
1)有序表在使用层面上可以理解为一种集合结构
2)如果只有key,没有伴随数据va lue,可以使用TreeSet结构(C++中叫OrderedSet)
3)如果既有key,又有伴随数据va lue,可以使用Tr eeMap结构(C++中叫OrderedMap)
4)有无伴随数据,是TreeSet和TreeMap唯 一的区别,底层的实际结构是一回事
5)有序表和哈希表的区别是,有序表把key按照顺序组织起来,而哈希表完全不组织
5)红黑树、AVL树、size-balance-tree和跳表等都属于有序表结构,只是底层具体实现不同
6)放入有序表的东西,如果是基础类型,内部按值传递,内存占用就是这个东西的大小
7)放入有序表的东西,如果不是基础类型,必须提供比较器,内部按引用传递,内存占
用是这个东西内存地址的大小
8)不管是什么底层具体实现,只要是有序表,都有以下固定的基本功能和固定的时间复
杂度