算法很美笔记(Java)——查找与排序(下-堆,计数,桶,基数)

通用方法

public static void print(int[] A) {
        for (int i = 0; i < A.length; i++) {
            System.out.print(A[i] + "   ");
        }
        System.out.println(" ");
    }
//交换数组的两个元素
public static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
public static int[] getMaxAndMin(int[] A) {
        int max = A[0]; // 使用数组的第一个元素初始化max
        int min = A[0];
        for (int i = 1; i < A.length; i++) { // 从数组的第二个元素开始遍历
            if (A[i] > max) {
                max = A[i];
            }
            if (A[i] < min) {
                min = A[i];
            }
        }
        return new int[]{max, min};
    }
//得到data特定位数上的数字
    public static int getDigitData(int data, int d) {
        while (d - 1 > 0) {
            data /= 10;
            d--;
        }
        return data % 10;
    }

顺序存储

给一个节点,下标为i

他的子节点下标是(2i + 1)和(2i + 2)

他的父节点下标是(i - 1) / 2

先序、中序、后序遍历

    public static void preOrder(int[] arr,int index) {
//        终止条件
        if (index >= arr.length) {
            return;
        }
//        根节点
        System.out.println(arr[index]);
//        左右子节点
        preOrder(arr,(2 * index + 1));
        preOrder(arr,(2 * index + 2));
//        中序遍历就是把sout放preOrder中间
//        后序遍历就是把sout放preOrder后面
    }

堆排序(不常用)

时间复杂度O(nlgn),含的常数大                                                    

没有快排和分治快   

堆排序详细图解(通俗易懂)-CSDN博客

小顶堆

排完是降序数组

1堆化

从最后一个非叶子节点开始,向上构建最小堆  即:从i = A.length/2 - 1一直到0,对每一个元素进行检验排序。

显然,他是倒着检验排序的(先处理下面的枝叶,再处理上面的,最后处理根,一组一组处理,一组里含一个根和他的叶子节点

如果A[i]比他的两个孩子都小,就不用调整

否则,找到两个孩子中较小的那个,与A[i]交换

i变为发生交换的那个孩子位置,再进行检验交换(递归),没发生位置变化的不用检验

2按序输出元素

顶端数是最小数,末位数一般是最大数,但不绝对。

将顶端数与末位数交换,此时,末位数为最小数,剩余待排序数组个数为n-1

将n-1个数再构造成小根堆

再重复上述步骤(交换,构造……)

public static void MinHeap(int[] A) {
        int n = A.length;
        for (int i = n/2 - 1; i >= 0; i--) {
            MinHeapFixDown(A,i,n);
        }
    }
//递归处理i位置的元素及其所有叶子节点
//每次递归处理一个根和紧挨着的叶子节点(最多两个)
    public static void MinHeapFixDown(int[] A, int i,int n) {
//        找到i的左右孩子
        int left = 2 * i + 1;
        int right = 2 * i + 2;
//        递归结束条件:i是叶子节点
//        想要i是叶子节点,就要i没有左右孩子
//        左孩子比右孩子小,如果左孩子没有了,肯定没有右孩子
//        写等于号的原因是
//        如果当前left是堆的最后一个元素,没有叶子节点,在数组里是最后一个索引,那么他也不用检验并处理
        if (left >= n) {
//            左孩子越界
            return;
        }
//        如果左孩子没越界
//        就只剩下两种可能:只有左孩子或者左右孩子都有
//        较小孩子初始化left
        int min = left;
//        如果右孩子越界
//        较小的孩子一定是左孩子,不用写
        if (right < n) {
//          如果左右孩子都有,min赋值较小的一方
//          因为已经默认min = left;,所以只需考虑right是较小的一种情况
            if (A[right] < A[left]) {
                min = right;
            }
        }
//        如果A[i]小于两个孩子,就不用调整
        if (A[i] <= A[min]) {
            return;
        }
//        否则,与较小的孩子交换
        int temp = A[i];
        A[i] = A[min];
        A[min] = temp;
//        这个函数检验的是i的位置
//        所以想要递归检验被更改了位置的那个孩子位置
//        就把i设为min去检验
        MinHeapFixDown(A,min,n);
    }
    public static void HeapSort(int[] A) {
//        先堆化
        MinHeap(A);
//        按序输出元素
        for (int i = A.length - 1; i > 0; i--) {
            swap(A,0,i);
//            缩小堆的范围(0 ~ i - 1),对对顶元素进行向下调整
            MinHeapFixDown(A,0,i);
        }
    }

第二种易读代码

// 构建最小堆
public static void NewMinHeap(int[] A) {
    int n = A.length;
    for (int i = n / 2 - 1; i >= 0; i--) {
        NewMinHeapFixDown(A, i, n);
    }
}

    // 递归地调整以i为根的子树,使其成为最小堆
    public static void NewMinHeapFixDown(int[] A, int i, int n) {
        int left = 2 * i + 1;
        int right = 2 * i + 2;
        int min = i;

        if (left < n && A[left] < A[min]) {
            min = left;
        }

        if (right < n && A[right] < A[min]) {
            min = right;
        }

        if (min != i) {
            swap(A, i, min);
            NewMinHeapFixDown(A, min, n);
        }
    }

    // 堆排序算法
    public static void NewMinHeapSort(int[] A) {
        // 先构建最小堆
        NewMinHeap(A);

        // 逐个将堆顶元素(最小值)与堆尾元素交换,并重新调整剩余堆
        for (int i = A.length - 1; i > 0; i--) {
            swap(A, 0, i); // 将堆顶元素(最小值)与堆尾元素交换
            NewMinHeapFixDown(A, 0, i); // 重新调整剩余元素为最小堆
        }
    }

大顶堆

排完是升序数组

先堆化,再将顶端数与末位数交换,再堆化,再交换……每次缩小堆化范围

堆化:

从最后一个子树开始(i = A.length/2 - 1)从后往前(从上往下)检验调整,调整成大根堆

代码方面与小顶堆比,只更改了if判断的“<”变“>”

// 构建最大堆  
public static void MaxHeap(int[] A) {
    int n = A.length;
    for (int i = n / 2 - 1; i >= 0; i--) {
        MaxHeapFixDown(A, i, n);
    }
}

    // 递归地调整以i为根的子树,使其成为最大堆  
    public static void MaxHeapFixDown(int[] A, int i, int n) {
        int left = 2 * i + 1;
        int right = 2 * i + 2;
        int max = i;

        if (left < n && A[left] > A[max]) {
            max = left;
        }

        if (right < n && A[right] > A[max]) {
            max = right;
        }

        if (max != i) {
            swap(A, i, max);
            MaxHeapFixDown(A, max, n);
        }
    }

    // 堆排序算法  
    public static void MaxHeapSort(int[] A) {
        // 先构建最大堆  
        MaxHeap(A);

        // 逐个将堆顶元素(最大值)与堆尾元素交换,并重新调整剩余堆  
        for (int i = A.length - 1; i > 0; i--) {
            swap(A, 0, i); // 将堆顶元素(最大值)与堆尾元素交换  
            MaxHeapFixDown(A, 0, i); // 重新调整剩余元素为最大堆  
        }
    }

计数排序

时间复杂度O(n)

空间复杂度O(n)

优点:快,比快排和分治快

缺点:如果数据范围大,数据稀疏,会导致辅助空间大且稀疏,造成空间浪费

重复元素解决:

在helper中对应位置计数,有几个重复元素就累计几。

更新原数组的时候,每个位置要抽光才能结束抽下一个,所有用while

负数解决:

元素本身不再对应下标,而是元素本身经过移位后对应下标

public static void countSort(int[] A) {
        int max = getMaxAndMin(A)[0];
        int min = getMaxAndMin(A)[1];
//    如果没有负数,就正常记,如果有,就移位
        if (min > 0) {
            min = 0;
        }
//        因为我们需要索引getMax(A),初始化getMax(A) + 1最大索引才是getMax(A)
//        元素移位,对应辅助空间的大小也要移位扩张
        int[] helper = new int[max + 1 - min];
//    计数
        for (int elem : A) {
//        用+= 1是因为每有一个下标为elem的数,计数都+1
            helper[elem - min] += 1;
        }
//    更新原数组
        int index = 0;
        for (int i = 0; i < helper.length; i++) {
//        把i位置所有的数都更新到A中
            while (helper[i] != 0) {
                A[index++] = i + min;
                helper[i]--;
            }
        }
    }

桶排序

时间复杂度O(n) ~ O(nlgn)

如果元素分布均匀(即:每个元素一个桶),且桶的数量=元素数量。时间复杂度O(n)

如果分布很不均匀,则时间复杂度O(nlgn)

网站是一个算法可视化的网站Data Structure Visualization (usfca.edu)

1每个桶的容量是事先不知道的,所以不能用静态容器(数组),用动态容器(链表)

2将n个元素分配到各个桶中:取出一个元素的值,通过一定式子运算,得到对应的桶下标,放到这个桶里,如此反复

3放元素的时候,要和桶内原有元素比较,按次序插入

(和计数排序不同的是。计数排序记录的是数量,桶排序记录的是真正的元素)

4全部放完后,把各个桶内的元素,按桶的次序以及桶内的顺序放回原数组

基数排序

数据分布不均匀时间复杂度会变高

待排数有0和负数时,

如果只有两位数,就排两次,有三位数,就排三次,有n位数,就排n次
每次排序都用相同的那一些桶,中途别换

算法用数据结构中的表(ArrayList)实现

 //    桶编号0~9
    static ArrayList[] bucket = new ArrayList[10];

    //    初始化桶,
    static {
        for (int i = 0; i < bucket.length; i++) {
//        每个桶的内容用一个ArrayList存
            bucket[i] = new ArrayList<>();
        }
    }

    //处理负数和0
    public static int dealNums(int[] arr) {
        int oldMin = getMaxAndMin(arr)[1];
//        如果最小值是负数就有负数,进行处理
//        如果最小值是0,就说明存在0且没负数
//        将所有数向右移一位,使算法能使用
//        没负数没0,就将min置为0,相当于没处理
        if (oldMin == 0) {
            oldMin = 1;
        } else if (oldMin > 0) {
            oldMin = 0;
        }
        oldMin--;
        for (int i = 0; i < arr.length; i++) {
            arr[i] -= oldMin;
        }
//        后面恢复移位时要用这个
        return oldMin;
    }

    public static void sort(int[] arr) {
        int oldMin = dealNums(arr);
//        入桶依据的位:d
//        初始化为1:首先让个位入桶
        int d = 1;
//        找出最大值,用于确定最多有几位数字,也就是循环多少遍
        int max = getMaxAndMin(arr)[0];
//        找出最大值的位数
        int dNum = 1;
        while (max / 10 != 0) {
            dNum++;
//            更新max
            max /= 10;
        }
//        开始循环按位排序
        while (d <= dNum) {
//            函数重载
            sort(arr, d++);
        }
//        恢复数组(将处理前的移位再移回去)
        for (int i = 0; i < arr.length; i++) {
            arr[i] += oldMin;
        }
    }

    //    将数组按位分配和收集
    public static void sort(int[] arr, int d) {
//  一个个入桶
        for (int i = 0; i < arr.length; i++) {
            putInBucket(arr[i], getDigitData(arr[i], d));
        }
//按顺序抽出(收集)
//        初始化原数组的索引
        int index = 0;
//        循环每个桶
        for (int i = 0; i < bucket.length; i++) {
//            取出桶里的元素
            for (Object elem : bucket[i]) {
                arr[index++] = (int) elem;
            }
        }
//        清空桶,释放空间
        for (ArrayList b : bucket) {
            b.clear();
        }
    }

    //    入桶
    public static void putInBucket(int data, int digitData) {
        switch (digitData) {
            case 0:
                bucket[0].add(data);
                break;
            case 1:
                bucket[1].add(data);
                break;
            case 2:
                bucket[2].add(data);
                break;
            case 3:
                bucket[3].add(data);
                break;
            case 4:
                bucket[4].add(data);
                break;
            case 5:
                bucket[5].add(data);
                break;
            case 6:
                bucket[6].add(data);
                break;
            case 7:
                bucket[7].add(data);
                break;
            case 8:
                bucket[8].add(data);
                break;
            case 9:
                bucket[9].add(data);
                break;
        }
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值