07、数据结构与算法---优先级队列(堆)与排序

非基于比较的排序不通过元素之间的比较 来确定顺序,而是利用元素本身的数值特性分布特性进行排序,这里有:计数排序、基数排序、桶排序😆(需要则查)


一、堆的理解与实现

堆的逻辑结构其实就是完全二叉树😊

手动实现:

java 复制代码
package structure;

import java.util.Arrays;

public class MyHeap {

    //代表有效元素的个数,每放一个就+1
    public int useSize = 0;
    public int[] elem;

    public MyHeap(){
        this.elem = new int[9];
    }

    //交换
    public void swap(int i, int j) {
        int tmp = elem[i];
        elem[i] = elem[j];
        elem[j] = tmp;
    }

    //传入元素的方法
    public void inArray(int[] elem) {
        for (int i = 0; i < elem.length; i++) {
            this.elem[useSize] = elem[i];
            useSize++;
        }
    }

    //建立堆
    //parent代表根节点下标,useSize代表结束标志的下标
    public void creatHeap(int useSize) {
        //建堆过程(从最后一个非叶子节点开始)
        for (int parent = (useSize - 1) / 2; parent >= 0; parent--) {
            siftDown(parent, useSize);
        }
    }

    //向下调整(大根堆)
    //curParent:当前根节点
    public void siftDown(int curParent, int useSize) {

        //当前根的孩子
        int child = curParent * 2 + 1;

        while (child < useSize) {
            //确保右孩子存在,且右孩子大于左孩子时,让child指向右孩子
            if (child + 1 < useSize && elem[child] < elem[child + 1]) {
                child++;
            }

            //保证此时child的值是最大的
            if (elem[child] > elem[curParent]) {
                //交换孩子与根
                swap(child, curParent);
                //下面还有则继续向下调整,直到超出范围
                curParent = child;
                child = 2 * curParent + 1;
            } else {
                return;
            }
        }

    }

    //添加元素
    public void offer(int val) {
        //满则扩容
        if (isFull()) {
            elem = Arrays.copyOf(elem, elem.length * 2);
        }
        elem[useSize] = val;

        //向上调整
        siftUp(useSize);
        useSize++;
    }

    public boolean isFull() {
        return useSize == elem.length;
    }

    //向上调整(大根堆)
    public void siftUp(int child) {

        //当前孩子的根
        int curParent = (child - 1) / 2;

        while (curParent >= 0) {
            if (elem[child] > elem[curParent]) {
                //交换
                swap(child, curParent);
                //同理,直到超出范围
                child = curParent;
                curParent = (child - 1) / 2;
            } else {
                return;
            }
        }

    }

    //弹出堆顶元素
    public int poll() {
        if (isEmpty()) {
            return -1;
        }
        int val = elem[0];

        swap(0, useSize-1);
        siftDown(0, useSize-1);
        useSize--;

        return val;
    }

    public boolean isEmpty() {
        return useSize == 0;
    }


    //观察堆顶元素
    public int peek() {
        if (isEmpty()) {
            return -1;
        }
        int val = elem[0];

        return val;
    }

    //堆排序
    public void heapSort(){
        //建立大根堆
        creatHeap(useSize);
        //代替useSize
        int len = useSize;
        //每次交换完就向下调整
        while (len != 0) {
            swap(0, len-1);
            siftDown(0, len-1);
            len--;
        }
    }

    public static void main(String[] args) {
        MyHeap m = new MyHeap();
        int[] arr1 = {1, 2, 3, 4, 5, 6};
        m.inArray(arr1);
        m.creatHeap(m.useSize);

    }
}

二、实现堆的重要方法

1、向下调整(建堆,删除堆顶)

🍉解析:

①假设原本有6个元素

②从第一个非叶子结点开始调整,也就是原来下标为2的位置:

③按同样的方式依次调整(下标1、下标0...),直到下标 0 的根节点调整完毕,调整完成


2、向上调整(插入新元素)

🍉解析:

①假设开始有六个元素,"7" 是新插入的元素

②然后每次比较只和其父节点比较(向下调整是跟左右子节点都比)

③完成


三、比较排序

"插入排序"

🍉解析:

①首先我们假定一个数组:[5, 2, 8, 1, 9]

i 从 1 开始,tmp 暂存当前 i 值,j 一直充当 i 的前一位

②把比 tmp 大的元素依次右移,最后把 tmp 插入到空出位置


java 复制代码
 //插入排序
    public static void insertSort(int[] array){
        for (int i = 0; i < array.length; i++) {
            int tmp = array[i];
            int j = i-1;
            for (; j >= 0; j--) {
                if(array[j] > tmp){
                    array[j+1] = array[j];
                }else {
                    break;
                }
            }
            array[j+1] = tmp;
        }
    }

"希尔排序"

🍉解析:

希尔排序相当于插入排序的优化版

通过逐渐缩小的间隔(gap)将数组分成若干个子序列,分别进行插入排序,让元素能大步跨越到大致位置,最后当 gap=1 时用普通插入排序完成最终排序

🌰代码实现:

java 复制代码
   //希尔排序
    public static void shellSort(int[] array){
        int gap = array.length;
        while (gap>1){
            gap = gap/2;
            shell(array,gap);
        }
    }
    public static void shell(int[]array,int gap){
        for (int i = gap; i < array.length; i++) {
            int tmp = array[i];
            int j = i-gap;

            for (; j >= 0; j-=gap) {
                if(array[j] > tmp){
                    array[j+gap] = array[j];
                }else {
                    break;
                }
            }
            //tmp要放回同组内最后一个被移动的元素空出来的位置
            array[j+gap] = tmp;
        }
    }

"冒泡排序"

🍉解析:

依旧假定一个数组:[5, 2, 8, 1, 9]

像气泡一样每轮将本组最大的元素推到最后边

所以第一轮结果:[2, 5, 1, 8, 9];第二轮结果:[2, 1, 5, 8, 9]

🌰代码实现:

java 复制代码
   //冒泡排序
    public static void bubbleSort(int[] array){
        //表示已经排好了i个元素
        for (int i = 0; i < array.length - 1; i++) {
            boolean flag = false;
            //两两比较
            for (int j = 0; j < array.length-1-i; j++) {
                if(array[j+1] < array[j]){
                    swap(array,j,j+1);
                    flag = true;
                }
            }
            //提前结束整个循环
            if(!flag){
                break;
            }
        }
    }

"选择排序"

🍉解析:

每次都找出最小值,将最小值放到最前面后开始下一轮寻找

🌰代码实现:

java 复制代码
//选择排序
    public static void selectSort(int[] array){
        for (int i = 0; i < array.length; i++) {
            int minIndex = i;
            for (int j = i+1; j < array.length; j++) {
                if(array[j] < array[minIndex]){
                    minIndex = j;
                }
            }
            //将找到的最小值与当前位置 i 交换
            swap(array,i,minIndex);
        }
    }

"快速排序"

写法一:Hoare法

🍉解析:

选取左边第一个元素作为基准,从右向左找比基准小的,从左向右找比基准大的,找到后交换

最终基准落到正确位置,使得左边元素 ≤ 基准 ≤ 右边元素


🌰代码实现:

java 复制代码
 //快速排序
    public static void quickSort(int[] array){
        quick(array,0,array.length-1);
    }
    public static void quick(int[] array,int left,int right){
        if(left >= right){
            return;
        }
        int pivot = partition1(array,left,right);
        //左右递归
        quick(array,left,pivot-1);
        quick(array,pivot+1,right);
    }

    //写法一:Hoare法
    public static int partition1(int[] array,int left,int right){
        //保存基准值,保存基准位置
        int tmp = array[left];
        int start = left;
        while (left < right){
            while (left < right && array[right] >= tmp) {
                right--;
            }
            while (left < right && array[left] <= tmp){
                left++;
            }
            swap(array,left,right);
        }
        swap(array,left,start);
        return left;
    }

写法二:挖坑法

🍉解析:

依旧假定一个数组:[5, 2, 8, 1, 9]

取出它的第一个元素,注意此时多了一个"坑"


从右边开始,r的值大于tmp则r向左移,否则将其放入刚才第一个出现的"坑"


等到上述逻辑都走完,l与r相遇,只需将tmp放入最后这个"坑"即可


🌰代码实现:

java 复制代码
 //写法二:挖坑法
    public static int partition2(int[] array,int left,int right){
        //取出基准值
        int tmp = array[left];

        while (left < right){
            //外层循环保证的是进入循环时left<right
            //这里的这个是因为这个循环在执行的时候right会自己改变,可能会导致越界等
            while (left < right && array[right] >= tmp) {
                right--;
            }
            array[left] = array[right];
            while (left < right && array[left] <= tmp){
                left++;
            }
            array[right] = array[left];
        }
        //代码走到这里l与r是相遇状态
        //返回left跟right都是可以的
        array[left] = tmp;
        return left;
    }

非递归写法

🍉解析:

使用栈来模拟递归的调用过程,通过分治让循环持续进行,每次分区后都将新的子问题边界压入栈中,直到栈为空,整个排序完成

🌰代码实现:

java 复制代码
//非递归快排写法:
    public static void quickSortNonRecursive(int[] array,int left,int right){
        if(left>=right){
            return;
        }

        Stack<Integer> s = new Stack<>();
        s.push(left);
        s.push(right);

        while (!s.isEmpty()){
            //这里是先出右后出左,那后边就要对应好
            int r = s.pop();
            int l = s.pop();

            //分治
            int pivotIndex = partition2(array,l,r);

            // 确保左边至少有俩元素
            // 左边部分
            if(l < pivotIndex-1){
                //后压右,先压左
                s.push(l);
                s.push(pivotIndex-1);
            }

            // 右边部分
            if (r > pivotIndex+1){
                //后压右,先压左
                s.push(pivotIndex+1);
                s.push(r);
            }
        }

    }

"归并排序"

递归写法

🍉解析:

①主要逻辑:先分解,再合并

处理左子树:将左半部分不断拆分为左右子树,递归处理(先左后右)直到有序,左子树处理完毕。

处理右子树:将右半部分同样拆分为左右子树,递归处理(先左后右)直到有序,右子树处理完毕。

合并:将左右两个有序子树合并,递归结束


🌰代码实现:

java 复制代码
//归并排序
    public static void mergeSort(int[] array,int left,int right){
        //递归出口
        if(left >= right){
            return;
        }

        //第一种写法假如left跟right过大会溢出
        //int mid = (left + right)/2;
        int mid = left + (right - left)/2;

        //分解
        mergeSort(array,left,mid);
        mergeSort(array,mid+1,right);

        //合并
        merge(array,left,mid,right);
    }
    //将 [left, mid] 和 [mid+1, right] 两个有序数组合并
    public static void merge(int[] array, int left, int mid, int right) {
        int s1 = left;      // 左半部分起始
        int e1 = mid;       // 左半部分结束
        int s2 = mid + 1;   // 右半部分起始
        int e2 = right;     // 右半部分结束

        //创建临时数组
        int[] tmpArray = new int[right - left + 1];

        //k是临时数组的下标
        int k = 0;
        while (s1 <= e1 && s2 <= e2) {
            if (array[s1] <= array[s2]) {
                tmpArray[k++] = array[s1++];
            } else {
                tmpArray[k++] = array[s2++];
            }
        }

        //走到这里说明:
        // 左半部分(s1..e1)或右半部分(s2..e2)中,至少有一边已经全部被取到 tmpArray 里了
        while (s1 <= e1) {
            tmpArray[k++] = array[s1++];
        }
        while (s2 <= e2) {
            tmpArray[k++] = array[s2++];
        }

        //将临时数组拷贝回原数组
        //注意这里的left+i
        for (int i = 0; i < tmpArray.length; i++) {
            array[left + i] = tmpArray[i];
        }
    }

非递归写法

🍉解析:

①gap为1时,合并成两个两个的,gap为2时合并成四个四个的...

②当gap执行完毕时,gap变成了8,循环结束


🌰代码实现:

java 复制代码
public static void mergeSortNor(int[] array) {
    int gap = 1;  // 初始子数组长度为1
    while (gap < array.length) {  // 当长度小于数组总长度时继续合并
        for (int i = 0; i < array.length; i = i + 2*gap) {
            // 确定三个关键位置
            int left = i;
            int mid = left + gap - 1;
            if(mid >= array.length) {
                mid = array.length-1;
            }
            int right = mid + gap;
            if(right >= array.length) {
                right = array.length-1;
            }
            merge(array, left, mid, right);
        }
        gap *= 2;  // 子数组长度翻倍
    }
}

本章完

相关推荐
用户298698530142 小时前
Java 实现两个 Word 文档的差异比对
java·后端
吃好睡好便好2 小时前
在Matlab中绘制非默认峰值图
开发语言·学习·算法·matlab
小瓦码J码2 小时前
轻量化线程池实战:忙时并发、闲时归零,搞定周期批量任务
java·后端
NagatoYukee2 小时前
Java 商品交易实验(第二版)
java·开发语言
百珏2 小时前
[灰度发布]:灰度流量如何匹配与识别:从特征补全到网关命中引擎
java·后端·架构
Misnearch2 小时前
1345. 跳跃游戏 IV
java·leetcode·bfs
Bechamz2 小时前
大数据开发学习Day34
java·大数据·学习
JoneBB2 小时前
ABAP上传EXCEL模板并将内表内容存到两个sheet中
java·前端·数据库
手揽回忆怎么睡2 小时前
分卷打包命令
java