跳至主要內容

SortAlgorithm

博识笔记大约 8 分钟notealgorithm

Sort Algorithm: CountSort、BucketSort、RadixSort、HeapSort

SortAlgorithm

七、CountSort

给定20个随机数,其范围为0~10,得到最大数max,则创建数组大小为11的一维数组,初始时数组中的元素均为0,遍历原数组,若第一个数为9,则数组下标为9的元素加1:依次遍历并修改数组。最后输出数组元素的下标值,元素的值是几,就输出几次

public static int[] countSort(int[] arr) {
  //1.得到数列的最大值
  	int max = arr[0];
  	for(int i = 1; i < arr.length; i++) {
      if(arr[i] > max) {
        max = arr[i];
      }
  	}
  	//2.根据数列最大值确定统计数组的长度
  	int[] countArray = new int[max + 1];
  	//3.遍历数列,填充统计数组
  	for(int i = 0; i < arr.length; i++) {
    	countArray[arr[i]]++;
  	}
  	//4.遍历统计数组,输出结果
  	int index = 0;
  	int[] sortedArray = new int[arr.length];
  	for(int i = 0; i < countArray.length; i++) {
    	for(int j = 0; j < countArray[i]; j++) {
      		sortedArray[index++] = i;
    	}
 	}
  	return sortedArray;
}

以上是以数列的最大值决定统计数组的长度,此方式可能会浪费大量的内存。(输入数列的最大值+1)

改进: 以数列最大值和最小值的差+1 作为统计数组的长度;同时数列的最小值作为一个偏移量,用于统计数组的对号入座。

例如: 95 94 91 98 99 90 93 91 92 99

统计数组长度: 99 -90 +1 = 10, 偏移量为90,对于95,对应的统计数组下标是95-90 = 5

稳定排序

public static int[] countSort(int[] arr) {
  //1.得到数列的最大值和最小值,并算出差值d
  	int max = arr[0];
  	int min = arr[0];
  	for(int i = 1; i < arr.length; i++) {
      if(arr[i] > max) {
        max = arr[i];
      }
      if(arr[i] < min) {
        min = arr[i];
      }
  	}
  	int d = max - min;
  	//2.创建统计数组并统计对应元素的个数
  	int[] countArray = new int[d + 1];
  	for(int i = 0; i < arr.length; i++) {
    	countArray[arr[i] - min]++;
  	}
  	//3.统计数组做变形,后面的元素等于前面的元素之和
  	/*
  	* 为了保证排序的稳定性,将统计数组从第二个元素开始,每一个元素都加上
  	* 前面所有元素之和。 这样相加,是让统计数组存储的元素值等于相应整数的最终排序位置。
  	*/
  	int sum = 0;
  	for(int i = 0; i < countArrya.length; i++) {
    	sum += countArray[i];
      	countArraay[i] = sum;
  	}
  	//4.倒序遍历统计数组,从统计数组找到正确位置
  	int[] sortedArray = new int[arr.length];
  	for(int i = arr.length - 1; i >= 0; i--) {
      		sortedArray[countArray[array[i] - min] - 1] = arr[i];
    		countArray[arr[i] - min]--;
 	}
  	return sortedArray;
}

八、BucketSort

每一个桶代表一个区间范围,里面可以承载一个或多个元素。

九、Radixsort

基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或(binsort)

它是通过键值的各个位的值,将要排序的元素分配至某些“桶”中,达到排序的作用

基数排序是属于稳定性的排序,效率高

基数排序是桶排序的扩展,速度很快

基数排序是经典的空间换时间的方式,占用内存很大,当对海量数据排序时,容易造成 OutOfMemoryError

基数排序是1887年赫尔曼·何乐礼发明的。

实现:将整数按位数切割成不同的数字,然后按每个位数分别比较(按位数比较的排序方法)

思路:

将所有待比较数值(自然数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。

这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。

时间复杂度:O(k(n+m))

空间复杂度:O(n+m)

 // 基数排序(该代码不能对负数进行排序)
    public static void radixSort(int[] arr) {
        // 得到数组中最大的数的位数
        int max = arr[0];
        for (int i = 1; i < arr.length; i++) {
            if (max < arr[i]) {
                max = arr[i];
            }
        }
        // 得到最大的数是几位数
        int maxLength = (max + "").length();

        // 定义一个二维数组,表示10个桶,每个桶就是一个一维数组
        // 每个桶中存放对应的数据,大小为 arr.length
        int[][] bucket = new int[10][arr.length];
        // 记录每个桶中实际存放数据的个数,用一维数组来记录
        int[] bucketElementCounts = new int[10];

        // 需要进行maxLength 次的排序,每次排序根据对应的位数决定如何存放数据

        for (int i = 0, n = 1; i < maxLength; i++, n *= 10) {

            for (int j = 0; j < arr.length; j++) {
                //得到每个元素的个位(十位、百位)的值
//                int digitOfElement = arr[j] / n % 10;
                int digitOfElement = arr[j] / n % 10;
                //放入到对应的桶中,bucketElementCounts[digitOfElement]表示第几个桶
                bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
                bucketElementCounts[digitOfElement]++;
            }
            // 按照桶的顺序,依次将数据放入原数组
            int index = 0;
            for (int k = 0; k < bucketElementCounts.length; k++) {
                //判断通知是否有数据
                if (bucketElementCounts[k] != 0) {
                    for (int l = 0; l < bucketElementCounts[k]; l++) {
                        arr[index++] = bucket[k][l];
                    }
                }
                // 在一轮排序结束后,需将bucketElementCounts[k] = 0
                bucketElementCounts[k] = 0;
            }
           System.out.println(Arrays.toString(arr));
        }

    }


十、HeapSort

1)堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最好、最坏、平均

时间复杂度均为O(nlogn),是不稳定排序

2)堆是具有以下性质的完全二叉树:每个节点的值都大于或等于其左右孩子节点的值,称为大顶堆;

(对节点的左孩子的值和右孩子的值的大小关系没有要求)

​ 每个节点的值都小于或等于其左右孩子节点的值,称为小顶堆

3)大顶堆:

大顶堆特点: arr[i] >= arr[2i + 1] && arr[i] >= arr[2i + 2] //i对应第几个节点,i从0开始编号

4)小顶堆:

小顶堆特点:arr[i] <= arr[2i + 1] &&arr[i] <= arr[2i + 2] //i对应第几个节点,i从0开始编号

5)一般升序采用大顶堆,降序采用小顶堆

堆排序的基本思想:

1)将待排序序列构造成一个大顶堆

2)此时,整个序列的最大值就是堆顶的根节点

3)将其与末尾元素进行交换,此时末尾即为最大值

4)将剩余的n-1个数再构造成大顶堆,再将顶端数与n-1位置的数交换,如此反复执行,便能得到有序数组

在构建大顶堆的过程中,元素的个数逐渐减少,最后就得到一个有序序列。

i索引

1.父结点索引:(i-1)/2(这里计算机中的除以2,省略掉小数)

2.左孩子索引:2*i+1

3.右孩子索引:2*i+2

最后一个非叶子节点的索引 i= arr.length/2-1

分析: 实现堆排序,主要完成两个步骤:1)将arr数组(二叉树)先进行调整成大顶堆 ​ 2)取出堆顶与最后一个节点交换,将arr.length-1个数据继续调整为大顶堆 因为初始数组是普通的二叉树,将其调整为大顶堆只能是从下往上最后一个非子结点开始,逐步调整为子大顶堆。 最后一个非叶子节点的索引为arr.length / 2 - 1(即非叶子节点的个数),从下往上进行调整,直到索引为0 public static void adjustHeap(int[] arr, int i, int length)方法将一个二叉树调整为大顶堆,其中的i表示的是将以i索引 位置为堆顶进行的大顶堆转换,因此从最后一个非叶子节点开始,每调用一次该方法就得到一个大顶堆,直到索引为0整个 二叉树调整为了大顶堆。【此时进行堆顶元素的交换时,排好的二叉树只调用一次该方法即可达到大顶堆的调整】 然后将堆顶元素与arr数组最后一个位置进行交换,剩余arr.length继续进行大顶堆调整,反复进行,直到全部序列排好。

package tree;

import java.util.Arrays;

public class HeapSort {
    public static void main(String[] args) {
        // 升序排列
        int[] arr = {4, 6, 8, 5, 9};
        heapSort(arr);
    }
    //堆排序
    public static void heapSort(int[] arr) {
        int temp = 0;
        //将无序序列构建成一个堆,根据升序降序选择大顶堆或小顶堆
        for (int i = arr.length / 2 -1; i >= 0 ; i--) {
            adjustHeap(arr, i, arr.length);
        }
        // 将堆顶元素与末尾元素交换,将最大元素沉到数组末端
        // 重新调整结构,使其满足堆定义,然后继续交换堆顶元素和当前末端元素,直到整个序列有序
        for (int j = arr.length - 1; j > 0; j--) {
            //交换
            temp = arr[j];
            arr[j] = arr[0];
            arr[0] = temp;
            adjustHeap(arr, 0 ,j);
        }
        System.out.println(Arrays.toString(arr));

    }
    //将一个数组(二叉树)调整成一个大顶堆

    /**
     *
     * @param arr 待调整的数组
     * @param i   表示非叶子节点在数组中的索引
     * @param length 对多少个元素继续调整 , length在逐渐减少
     */
    public static void adjustHeap(int[] arr, int i, int length) {
        int temp = arr[i]; //将当前元素保存在临时遍历
        // k = i * 2 + 1  k表示i节点的左子节点
          //以i为索引位置向下进行大顶堆的调整
        for (int k = i * 2 + 1; k < length; k = k * 2 + 1) {
            if (k + 1 < length && arr[k] < arr[k + 1]) { // 若左子节点的值小于右子节点的值
                k++; // k 指向右子节点
            }
            if (arr[k] > temp) { //如果子节点大于父节点
                arr[i] = arr[k]; // 把较大的值赋给当前节点
                i = k;  // i指向k 继续循环比较
            } else {
                break;
            }
        }
        arr[i] = temp;
    }
}