3.堆排序和比较器

时间:2023-01-30 22:02:57

1. 堆

堆结构就是用数组实现的完全二叉树结构,对于结点i,左孩子2*i+1、右孩子2*i+2、父节点(i-1)/ 2

  1. 完全二叉树中如果每棵子树的最大值都在顶部就是大根堆
    3.堆排序和比较器
  2. 完全二叉树中如果每棵子树的最小值都在顶部就是小根堆
  3. 堆结构的heapInsert与heapify操作
  4. 堆结构的增大和减少
  5. 优先级队列结构,就是堆结构

1.1 堆的构建(大顶堆)

方法:
先给出一个序列:[4,6,5,8,9]
图解流程:
3.堆排序和比较器3.堆排序和比较器

这个过程我们也叫做堆结构的heapInsert(一个新结点加入到堆中依次向上比对的过程)看具体的代码:

for(int i = 0; i < arr.length; i++) {
	heapInsert(arr, i);
}

public static void heapInsert(int[] arr, int index) {
	//当前子节点和父节点值比较,满足条件进行交换
	while(arr[index] > arr[(index - 1) / 2]) {
		//这里swap就是一个交换函数
		swap(arr, index, (index - 1) / 2);
		index = (index - 1) / 2;
	}
}

堆结构的heapify的过程:
当我们形成一个大顶堆时,当堆中一个数据突然发生变化(变小)时,会有可能使得大顶堆不存在,得做下沉操作,heapify的过程就是使得该完全二叉树重新变成大顶堆。
3.堆排序和比较器
就是将当前的结点与其左右子节点进行比较,大的进行交换

public static void heapify(int[] arr, int index, int size) {
        //左子节点
        int left = index * 2 + 1;
        while (left < size) {
            //两个孩子中,谁的值大,把下标给largest
            int largest = left + 1 < size && arr[left + 1] > arr[left] ? left + 1 : left;
            //父和孩子之间,谁的值大,把小标给largest
            largest = arr[largest] > arr[index] ? largest : index;
            if (largest == index) {
                break;
            }
            //将两个数进行交换
            swap(arr, largest, index);
            index = largest;
            left = index * 2 + 1;
        }
    }

1.2 堆排序

第一步:这就是堆排序的主要代码,先将无序的序列通过heapInsert的过程变成大顶堆;
第二步:把最后一个数与堆顶的数进行交换,size-1;
第三步:通过heapify的过程重新构建大根堆;
接下来的过程就是最后一个数与堆顶的数进行比较,size-1,再次重新构成大根堆,以此类推最终数组的顺序就是从小 到大的一个排序。
所有代码如下:

public class HeapSortV2 {
    public static void main(String[] args) {
        int[] arr = {10,9,40,30,28,-5,29};
        System.out.println("排序前:" + Arrays.toString(arr));
        heapSort(arr);
        System.out.println("排序后:" + Arrays.toString(arr));


    }

    public static void heapSort(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }
        //给定一个数组,我们先要实现的就是根据数组形成大根堆,我们用heapInsert
        for (int i = 0; i < arr.length; i++) {
            heapInsert(arr, i);
        }

        //我们是如何根据大根堆来完成排序的呢,我们分析大根堆的根节点是当前数组中的最大的值
        //我们将根节点的数字与叶子节点的最后一个值进行交换,并将size-1
        int size = arr.length;
        swap(arr, 0, --size);
        while (size > 0) {
            heapify(arr, 0, size);
            swap(arr, 0, --size);
        }
    }

    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 temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    public static void heapify(int[] arr, int index, int size) {
        //左子节点
        int left = index * 2 + 1;
        while (left < size) {
            //两个孩子中,谁的值大,把下标给largest
            int largest = left + 1 < size && arr[left + 1] > arr[left] ? left + 1 : left;
            //父和孩子之间,谁的值大,把小标给largest
            largest = arr[largest] > arr[index] ? largest : index;
            if (largest == index) {
                break;
            }
            //将两个数进行交换
            swap(arr, largest, index);
            index = largest;
            left = index * 2 + 1;
        }
    }
}

运行结果:
3.堆排序和比较器

1.3 题目练习

题目描述:
已知一个几乎有序的数组,几乎有序是指,如果把数组排好顺序的话,每个元素移动的距离可以不超过k,并且k相对于数组来说比较小。请选择一个合适的排序算法针对这个数据进行排序。
题解
由于数组是几乎有序的,假如在第k+1个数之后存在最小值,那么元素移动到第一个位置需要移动超过k次,不符合,所以最小值必在前k+1个数里。

可以选择k+1大小的小根堆进行排序,先扫描数组的前k+1个数构建小根堆。然后取出堆顶元素放在数组的第1个位置,并把数组的第k+2个数插入小根堆。再取出堆顶元素放在数组的第2个位置,并把数组的第k+3个数插入小根堆……依次类推,扫描完数组后,把小根堆依次出堆即可。

代码:

public class SortArrayDistanceLessK {

    public void sortedArrDistanceLessK(int[] arr, int k) {
        //优先队列默认小根堆
        PriorityQueue<Integer> heap = new PriorityQueue<>();
        int index = 0;
        for (int i = 0; i < arr.length; i++) {
            heap.add(arr[i]); // 入堆
            if (i > k) { //当扫描到完前k+1个数后,堆顶元素出堆,并放入数组的对应位置
                arr[index++] = heap.poll();  
            }
        }
        //扫描完数组,依次出堆即可把剩余元素排完
        while (!heap.isEmpty()) {
            arr[index++] = heap.poll();
        }
    }
}

2. 比较器

比较器的使用
1)比较器的实质就是重载比较运算符
2)比较器可以很好的应用在特殊标准的排序上
3)比较器可以很好的应用在根据特殊标准排序的结构上

代码:

import java.util.Arrays;
import java.util.Comparator;

public class Code03_Comparator {

    public static class Student {
        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;
        }

        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", id=" + id +
                    ", age=" + age +
                    '}';
        }
    }

    public static class IdAAscendingComparator implements Comparator<Student> {

        // 返回负数的时候,第一个参数排在前面
        // 返回正数的时候,第二个参数排在前面
        // 返回0的时候, 谁在前面无所谓
        @Override
        public int compare(Student o1, Student o2) {
            return o1.id - o2.id;
//            return 0;
        }
    }

    public static class IdDescendingComparator implements Comparator<Student> {

        @Override
        public int compare(Student o1, Student o2) {
            return o2.id - o1.id;
        }
    }

    public static class AgeAscendingComparator implements Comparator<Student> {

        @Override
        public int compare(Student o1, Student o2) {
            return o1.age - o2.age;
        }
    }

    public static class AgeDescendingComparator implements Comparator<Student> {

        @Override
        public int compare(Student o1, Student o2) {
            return o2.age - o1.age;
        }
    }

    public static void main(String[] args) {
        int[] arr = {5,4,3,2,7,9,1,0};
        Arrays.sort(arr);
        System.out.println(Arrays.toString(arr));

        System.out.println("=======================");
        Student studnet1 = new Student("A", 2, 20);
        Student studnet2 = new Student("B", 3, 21);
        Student studnet3 = new Student("C", 1, 22);

        Student[] students = {studnet1, studnet2, studnet3};
        System.out.println("第一条打印");

        System.out.println("==========id升序排序==========");
        Arrays.sort(students, new IdAAscendingComparator());
        System.out.println(Arrays.toString(students));


        System.out.println("==========id降序排序==========");
        Arrays.sort(students, new IdDescendingComparator());
        System.out.println(Arrays.toString(students));

        System.out.println("==========Age升序排序==========");
        Arrays.sort(students, new AgeAscendingComparator());
        System.out.println(Arrays.toString(students));

        System.out.println("==========Age降序排序==========");
        Arrays.sort(students, new AgeDescendingComparator());
        System.out.println(Arrays.toString(students));

    }
}

运行结果:
3.堆排序和比较器

import java.util.Comparator;
import java.util.PriorityQueue;

public class HeapTest {
    public static void main(String[] args) {
        PriorityQueue<Integer> heap = new PriorityQueue<>(new ACom());
        heap.add(6);
        heap.add(9);
        heap.add(3);
        heap.add(2);
        heap.add(10);
        while (!heap.isEmpty()) {
            System.out.println(heap.poll());
        }
    }

    public static class ACom implements Comparator<Integer> {

        // 如果返回负数,认为第一个参数应该放在上面
        // 如果返回正数,认为第二个参数放在上面
        // 如果返回0, 认为谁放前面都行
        @Override
        public int compare(Integer o1, Integer o2) {
            return o1 - o2;
        }

    }
}

运行结果:
3.堆排序和比较器