持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第13天,点击查看活动详情
下面是我整理的跟着b站左程云的数据结构与算法学习笔记,欢迎大家一起学习。
认识堆
堆结构是用数组实现的完全二叉树结构
优先级队列结构,就是堆结构
从左往右依次遍满为完全二叉树,左边没有则不为完全二叉树
二叉树 确定好数组长度size
左:2i+1
右:2i+2
父:(i-1)/2
大根堆:完全二叉树中,每一颗子树的最大值是头节点的值
小根堆:完全二叉树中,每一颗子树的最小值是头节点的值
将输入的值转化为完全二叉树 设heapsize为该数组的长度,当数组长度>=3时,与在(i-1)/2上的父的数作比较,若大于父则交换,并逐步往上做比较,直到父的位置为0,只需要不断往上比较,这个过程叫做heapinsert,就可以确保大根堆
代码实现(向上比较)
public static void heapInsert(int[] arr,int index){
while (arr[index]>arr[(index-1)/2]){
swap(arr,index,(index-1)/2);
index=(index-1)/2;
}
}
//交换数
public static void swap(int[] arr,int i,int j){
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
思考:如何取最大值返回并删除
解题思路:
将数组的最后一个数放在0位置上,并将heapsize--(移除掉最后一个);此时5位置已经从堆中去除,将两个孩子中的最大值与父作比较,如果父节点小于孩子最大值,父节点与最大值的孩子交换,每次交换后已交换后的孩子为父节点,重复以上操作直到父节点上没有孩子或者父节点大于孩子中的最大值
代码实现:(向下比较)
public static void heapify(int[] arr,int index,int heapSize){
int left=index*2+1;//左孩子的下标
while (left<heapSize){
int largest=left+1<heapSize&&arr[left+1]>arr[left]?left+1:left;
if (largest==index){
break;
}
swap(arr, largest,index);
index=largest;
left=index*2+1;
}
}
进阶,突然数组中的某一个数发生变化
若知道变大,则向上 使用heapInsert()方法 若知道变小,则向下 使用heapify()方法 若不知道则heapInsert()和heapify()一起使用 完全二叉树的高度 logN级别, 新增或删去数字的代价是logN级别 通过不断移除最大数可以得到排序好的数组
堆排序的主要代码
public static void main(String[] args) {
int[] arrs={4, 6, 2, 10};
heapSort(arrs);
for (int arr:arrs) {
System.out.println(arr);
}
}
public static void heapSort(int[] arr){
if (arr==null||arr.length<2){
return;
}
for (int i = 0; i < arr.length ; i++) { //O(N)
heapInsert(arr, i);
}
int heapSize= arr.length;
swap(arr, 0, --heapSize);
while (heapSize>0){ //O(N)
heapify(arr, 0, heapSize); //O(logN)
swap(arr, 0, --heapSize);
}
}
算法复杂度 O(NlogN) 额外空间复杂度 O(1)
在不断heapInsert()过程中,可以简化为将所有数字先构建成一个二叉树网络,从下面一级一级往上并不断向下heapify()就可以更快排好序,即不断向上扩展树,向下完善树
复杂度 T(N)=1N/2 +2N/4+3N/8+4N/16+.... 1 2T(N)=2 N/2 +4*N/4+6N/8+8N/16+.... 2 2-1式得 T(N)=N+N/2+N/4+N/8+... -> O(N) 相当于将代码
for (int i = 0; i < arr.length ; i++) { //O(N)
heapInsert(arr, i);
}
//转化为
for (int i = arr.length-1; i >=0 ; i--) {
heapify(arr, i, arr.length);
}
复杂度不会影响,但是第一步会快许多
堆排序扩展题目
已知一个几乎有序的数组,几乎有序指把数组排好顺序的话,每个元素移动的距离可以不超过k,并且k相对于数组来说比较小。 将小根堆的最小值依次弹出,数组有序 使用现成的堆结构
public static void main(String[] args) {
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();
priorityQueue.add(7);
priorityQueue.add(4);
priorityQueue.add(9);
priorityQueue.add(2);
priorityQueue.add(8);
priorityQueue.add(3);
while (!priorityQueue.isEmpty()){
System.out.println(priorityQueue.poll());
}
}
耗尽扩容时成倍扩容 扩容时会将数据复制到扩容后的数组中,单次扩容的代价 O(N) 假设扩容N次时,单词扩容的平均代价为 O(N*logN)/N=O(logN) 不容易轻易改变堆结构,手写堆才能在需要改变堆结构时高效改变堆结构,系统的改变结构不够高效
实现堆结构在某位置上的有序
代码实现
public void sortedArrDistanceLessK(int[] arr,int k){
PriorityQueue<Integer> heap = new PriorityQueue<>();
int index=0;
for (; index <Math.min(arr.length, k) ; index++) {
heap.add(arr[index]);
}
int i=0;
for(;index<arr.length;i++,index++){
heap.add(arr[index]);
arr[i]=heap.poll();
}
while (!heap.isEmpty()){
arr[i++]=heap.poll();
}
}
比较器的使用
- 比较器的实质是重载比较运算符
- 比较器可以很好的应用在特殊标准的排序上
- 比较器可以更好的应用在根据特殊标准排序的结构上 Java对所有比较器的规则 先实现Comparator接口,并重写compare方法(返回int) 结果分类
- 返回负数的,第一个参数排前
- 返回正数的,第一个参数排后
- 返回0时,都可以 不使用比较器
public static void main(String[] args) {
Integer[] arr={5,6,4,1,3,9,0};
//Arrays.sort会根据内存数据大小排序
Arrays.sort(arr);
for (int ar:arr) {
System.out.println(ar);
}
}
得到从小到大的排序 加上自己实现的比较器就可以实现由大到小的排序
public static class MyComp implements Comparator<Integer> {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
}
public static void main(String[] args) {
Integer[] arr={5,6,4,1,3,9,0};
//Arrays.sort会根据内存数据大小排序
Arrays.sort(arr,new MyComp());
for (int ar:arr) {
System.out.println(ar);
}
}
加上比较器的代码实现
public String name;
public int id;
public int age;
public Student(String name, int id, int age) {
this.name = name;
this.id = id;
this.age = age;
}
}
public static class IdAscendingComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return o1.id - o2.id;
}
}
public static void printStudents(Student[] students) {
for (Student student : students) {
System.out.println("Name : " + student.name + ", Id : " + student.id + ", Age : " + student.age);
}
}
public static void main(String[] args) {
Student student1 = new Student("A", 2, 23);
Student student2 = new Student("B", 3, 21);
Student student3 = new Student("C", 1, 22);
Student[] students = new Student[] { student1, student2, student3 };
Arrays.sort(students, new IdAscendingComparator());
printStudents(students);
}
就可以实现按照比较器排序的方法 在实现大根堆与小根堆中,也可以参考比较器的使用 使用比较器Comparator
- 返回负数的,第一个参数放下面
- 返回正数的,第一个参数放上面
- 返回0时,都可以 按照这种思路,可以自己定义比较器进行复杂数据的排序 如排序自定义的类时先按班级从小到大,再按学号从大到小的划分。
不基于比较的排序 根据数据状况排出特殊的数据出来
基数排序
如将不同的数先根据个位数不同存进相应数字的桶中,即根据个位数排好序,再往高位数类推 桶中出来的数按照先进先出的原则,也可以实现从小到大
解题思路 根据数据最小位置上的数确定<=其本身的数
如 有 2 4 6 11
0 1 2 3
即在个位上小于等于0的有两个,小于等于1的有4个,当从右往左遍历数组(从后面先)遇到个位为2 放数组的5位置上,下一个个位为2的数自动放到数组的4位置上