1. 桶排序
1.1 实现1-设定每个桶的数值范围(桶大小)
输入参数:要排序的arr和一个桶的数据范围bucketSize。
- bucketSize:我们可以通过观察array数据,设定合理的bucketSize。
- bucketCount:桶的数量bucketCount由bucketSize确定:floor((max-min)/bucketSize) + 1
- bucketIndex:放入桶的序号 = (int) Math.floor((arr[i] - minValue) / bucketSize)
- buckets/bucketList:桶集合buckets是一个可以存放数据的集合,这里使用二维数组int[ ][ ]。因此往桶里放数要使用“数组扩容”函数。
桶排序适合数据分布均匀的情况,每个桶中的元素数量差不多。
若数据分布不均,要么就有很多空桶,比如[ 8,2,9,10,1,23,53,22,12,9000 ],bucketSize设5,那么空桶就很多;要么就会桶中数量不均,有的桶很多数,这样就没有起到“桶”的作用。
重要代码:
桶大小是输入参数。
桶列表:int[][] buckets = new int[bucketCount][0];
桶数量:int bucketCount = (int) Math.floor((maxValue - minValue) / bucketSize) + 1;
桶序号:int index = (int) Math.floor((arr[i] - minValue) / bucketSize);
数入桶:buckets[index] = arrAppend(buckets[index], arr[i]);
完整代码:
//Java 代码实现
public class BucketSort implements IArraySort {
private static final InsertSort insertSort = new InsertSort();
@Override
public int[] sort(int[] sourceArray) throws Exception {
// 对 arr 进行拷贝,不改变参数内容
int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
return bucketSort(arr, 5);
}
private int[] bucketSort(int[] arr, int bucketSize) throws Exception {
if (arr.length == 0) {
return arr;
}
int minValue = arr[0];
int maxValue = arr[0];
for (int value : arr) {
if (value < minValue) {
minValue = value;
} else if (value > maxValue) {
maxValue = value;
}
}
int bucketCount = (int) Math.floor((maxValue - minValue) / bucketSize) + 1; //桶的数量=floor((max-min)/bucketSize) + 1
int[][] buckets = new int[bucketCount][0]; //用二元数组表示多个桶,第[0]维度表示桶index,第[1]维度表示桶中元素index
// 利用映射函数将数据分配到各个桶中
for (int i = 0; i < arr.length; i++) {
int index = (int) Math.floor((arr[i] - minValue) / bucketSize); //bucketIndex
buckets[index] = arrAppend(buckets[index], arr[i]); //通过复制原数组arr[n]到新建数组arr[n+1]实现自动扩容,O(n)
}
int arrIndex = 0;
for (int[] bucket : buckets) {
if (bucket.length <= 0) {
continue;
}
// 对每个桶进行排序,这里使用了插入排序
bucket = insertSort.sort(bucket);
for (int value : bucket) {
arr[arrIndex++] = value; //将排好序的所有桶中的元素依次赋值到原数组上
}
}
return arr; //返回原数组
}
/**
* 自动扩容,并保存数据
*
* @param arr
* @param value
*/
private int[] arrAppend(int[] arr, int value) {
arr = Arrays.copyOf(arr, arr.length + 1);
arr[arr.length - 1] = value;
return arr;
}
}
实现2:设定桶的数量(=数组元数个数)
- 输入参数:待排序数组arr
- bucketCount:桶的数量为数组元素的数量,即arr.length. 因为理想情况下每个数据装一个桶
- bucketSize:桶的size由桶的数量确定:bucketSize = (max-min)/(bucketCount-1)
- bucketIndex:放入第几号桶 = (int) (arr[i] /bucketSize) - 1
- buckets/bucketList:桶集合buckets是一个可以存放数据的集合,这里使用
ArrayList<ArrayList<Integer>>。往arraylist里放数使用bucketList.get(num).add(arr[i]).get()函数得到ArrayList<<>>里的ArrayList<>,add()函数往ArrayList<>里添加一个元素。
重要代码:
桶数量是arr.length。
桶列表:
//构成一个arr.length个桶的集合
ArrayList<ArrayList<Integer>> bucketList = new ArrayList<>();
for(int i = 0; i < length; i++){
bucketList.add(new ArrayList<>());
}
桶大小:float section = (float) diff / (float) (length - 1);
桶序号:
int num = (int) (arr[i]/ section) - 1; //bukcetIndex
if(num < 0){
num = 0;
}
数入桶:bucketList.get(num).add(arr[i]);
完整代码:
public static void sort(int[] arr){
//最大最小值
int max = arr[0];
int min = arr[0];
int length = arr.length;
for(int i=1; i<length; i++) {
if(arr[i] > max) {
max = arr[i];
} else if(arr[i] < min) {
min = arr[i];
}
}
//最大值和最小值的差
int diff = max - min;
//桶列表
ArrayList<ArrayList<Integer>> bucketList = new ArrayList<>();
for(int i = 0; i < length; i++){
bucketList.add(new ArrayList<>());
}
//每个桶的存数区间
float section = (float) diff / (float) (length - 1); //bucketSize = (max-min)/(bucketCount-1)
//数据入桶
for(int i = 0; i < length; i++){
//当前数除以区间得出存放桶的位置 减1后得出桶的下标
//float转int即(int)为取整数部分,截去小数部分。要向上或向下取整用(int)Math.ceil(),(int)Math.floor(),要四舍五入取整为(int)Math.round().
int num = (int) (arr[i]/ section) - 1; //bukcetIndex
if(num < 0){
num = 0;
}
bucketList.get(num).add(arr[i]); //向index=num的桶里添加元素arr[i]
}
//桶内排序
for(int i = 0; i < bucketList.size(); i++){
//jdk的排序速度当然信得过
Collections.sort(bucketList.get(i));
}
//写入原数组
int index = 0;
for(ArrayList<Integer> arrayList : bucketList){
for(int value : arrayList){
arr[index] = value;
index++;
}
}
}
Note: int() vs floor() in python. java中(int) vs (int)Math.floor() 也是一样。the floor() is always going to be lower or equal to the original. int() is going to be closer to zero or equal。
对于正数来说,二者相同,对于负数来说,Rounding down on negative numbers means that they move away from 0, truncating moves them closer to 0.
floor() rounds down. int() truncates. The difference is clear when you use negative numbers:
>>> import math
>>> math.floor(-3.5)
-4
>>> int(-3.5)
-3
Rounding down on negative numbers means that they move away from 0, truncating moves them closer to 0.
Putting it differently, the floor() is always going to be lower or equal to the original. int() is going to be closer to zero or equal.
2. 堆排序
大顶堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]
小顶堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]
2.0 堆的元素索引计算方式
22 // 返回完全二叉树的数组表示中,一个索引所表示的元素的父亲节点的索引
23 private int parent(int index){
24 return (index - 1) / 2;
25 }
26
27 // 返回完全二叉树的数组表示中,一个索引所表示的元素的左孩子节点的索引
28 private int leftChild(int index){
29 return index * 2 + 1;
30 }
31
32 // 返回完全二叉树的数组表示中,一个索引所表示的元素的右孩子节点的索引
33 private int rightChild(int index){
34 return index * 2 + 2;
35 }
2.1 c++实现
堆是利用完全二叉树结构的数组。
分为大顶堆构建(调整)函数,swap函数(交换堆顶最大值和最后一个元素)和main函数(每次调用两个函数,都将堆数组长度这个输入参数减1。得到排好序的大顶堆数组后,打印)。
完整代码:
/* Function: 构建大顶堆 */
void BuildMaxHeap(int *heap, int len)
{
int i;
int temp;
for (i = len/2-1; i >= 0; i--)
{
if ((2*i+1) < len && heap[i] < heap[2*i+1]) /* 根节点小于左子树 */
{
temp = heap[i];
heap[i] = heap[2*i+1];
heap[2*i+1] = temp;
/* 检查交换后的左子树是否满足大顶堆性质 如果不满足 则重新调整子树结构 */
if ((2*(2*i+1)+1 < len && heap[2*i+1] < heap[2*(2*i+1)+1]) || (2*(2*i+1)+2 < len && heap[2*i+1] < heap[2*(2*i+1)+2]))
{
BuildMaxHeap(heap, len);
}
}
if ((2*i+2) < len && heap[i] < heap[2*i+2]) /* 根节点小于右子树 */
{
temp = heap[i];
heap[i] = heap[2*i+2];
heap[2*i+2] = temp;
/* 检查交换后的右子树是否满足大顶堆性质 如果不满足 则重新调整子树结构 */
if ((2*(2*i+2)+1 < len && heap[2*i+2] < heap[2*(2*i+2)+1]) || (2*(2*i+2)+2 < len && heap[2*i+2] < heap[2*(2*i+2)+2]))
{
BuildMaxHeap(heap, len);
}
}
}
}
/* Function: 交换交换根节点和数组末尾元素的值*/
void Swap(int *heap, int len)
{
int temp;
temp = heap[0];
heap[0] = heap[len-1];
heap[len-1] = temp;
}
int main()
{
int a[6] = {7, 3, 8, 5, 1, 2};
int len = 6; /* 数组长度 */
int i;
for (i = len; i > 0; i--)
{
BuildMaxHeap(a, i);
Swap(a, i);
}
for (i = 0; i < len; i++)
{
printf("%d ", a[i]);
}
return 0;
}
2.2 java实现
2.2.1 升序写法(构建大顶堆)
-
构建最大堆的函数buildMaxHeap()会利用调整堆函数heapify(),有两种实现方式:
1)用下沉函数调整(基准是parent)。从最后一个非叶子节点开始,遍历每一个非叶子节点 (i = (int)arr.length/2 - 1)。将parent和left and right child比较,若交换,再进入letf subtree or right subtree,直到下沉到正确的位置。heapify()可以使用递归或迭代实现。迭代实现中,需要维护更新current,left,right。
2)用上浮函数调整(基准是child)。遍历从第一个节点到最后一个节点作为child (i = 0 ~ arr.length - 1),和自己的parent(parentIndex = (childIndex - 1) /2) 比较,若交换,则再与parent的parent比较,直到调整到正确的位置。调整可以使用递归或迭代实现。迭代实现中,需要维护更新parent,child。 -
调整堆函数:当前元素是parent,和left、right child比较,下沉当前元素函数(即保证堆性质的函数)heapify(arr, 0, heapSize),有两种实现方式:
1)递归:这个返回值为void的函数里没有写停止条件和return;,是因为在返回类型为 void 的函数中,return 返回语句不是必需的,隐式的 return 发生在函数的最后一个语句完成时。一般情况下,返回类型是 void 的函数使用 return 语句是为了引起函数的强制结束。
2)迭代:为了确保交换后的子节点仍然大于其子子节点,将index换为bigIndex,继续与其子节点进行比较(即交换后,去交换了的子节点为root的树继续看root能不能继续“下沉”。 -
交换函数swap()用于交换堆顶元素和堆数组最后一个元素。
完整代码:
class HeapSort {
public static void heapSort(int[] arr){
// 首先将arr变成大顶堆
int size = arr.length;
bigHeap(arr, size);
while (size > 1){
//将堆顶与最小叶子节点进行交换
swap(arr,0,size-1);
// 将此时最小叶子节点移除(前堆顶)
size--;
// 将剩余数组重新变成大堆顶
heapRestRec(arr,0,size);
}
}
// 将一个数组变成大堆顶形式
public static void bigHeap(int[] arr, int len){
for (int i = 0; i < arr.length; i++) {//循环数组中的每一个节点
heapifyUpRec(arr, i, len);
}
}
//上浮调整函数:迭代形式
public static void heapifyUp(int[] arr, int index, int len) {
int currentIndex = index;
int fatherIndex = (index - 1) / 2;
while (arr[fatherIndex] < arr[currentIndex]) {//和父节点比较,如果父节点小,那么交换父节点和子节点
swap(arr, fatherIndex, currentIndex);
// 将现节点往上移,看上面是否满足情况
currentIndex = fatherIndex;
fatherIndex = (currentIndex - 1) / 2;
}
}
//上浮调整函数:递归形式
public static void heapifyUpRec(int[] arr, int index, int len) {
int currentIndex = index;
int fatherIndex = (index - 1) / 2;
while (arr[fatherIndex] < arr[currentIndex]) {//和父节点比较,如果父节点小,那么交换父节点和子节点
swap(arr, fatherIndex, currentIndex);
// 将现节点往上移,看上面是否满足情况
heapifyUpRec(arr, fatherIndex, len);
}
}
//下沉调整函数,将索引为index的元素下沉到指定位置,使堆重新变成大堆顶形式
// 迭代写法
public static void heapRest(int[] arr, int index, int size){
int left = index*2 +1;
int right = index*2 +2;
int bigIndex;
// 直到所有节点都大于子节点退出
while (left < size){
// 比较左右叶子节点大小,记录为bigindex
if (right <size && arr[right] > arr[left] ){
bigIndex = right;
}else {
bigIndex = left;
}
// 如果子节点的最大值大于父节点,则进行交换
if (arr[bigIndex] > arr[index]){
swap(arr,bigIndex,index);
// 为了确保交换后的子节点仍然大于其子子节点,将index换为bigIndex,继续与其子节点进行比较
index = bigIndex;
left = index*2 +1;
right = index*2 +2;
}else {
// 如果父节点大于子节点,则跳出循环
break;
}
}
}
// 将其重新变成大堆顶形式
// 递归写法
public static void heapRestRec(int[] arr, int index, int size){
int left = index*2 +1;
int right = index*2 +2;
int bigIndex;
// 确定递归的出口,即右节点超出数组引索范围
if (left >= size){
return;
}
if (right<size && arr[right]> arr[left]){
bigIndex = right;
}else {
bigIndex = left;
}
if (arr[bigIndex] > arr[index]){
swap(arr,bigIndex,index);
// 将bigindex作为父节点,继续下探
heapRestRec(arr,bigIndex,size);
}
}
public static void swap(int[] arr, int ind1, int ind2){
int temp = arr[ind1];
arr[ind1] = arr[ind2];
arr[ind2] = temp;
}
public static void main(String[] args) {
int [] arr = {88,99,34,5,6,8};
heapSort(arr);
for (int e: arr
) {
System.out.println(e);
}
}
}
另一种写法,构建最大堆的函数和下沉堆顶元素函数都用下沉函数heapify搞定。构建最大堆的函数对非叶子节点倒序使用heapify(arr, i, arr.length), 调整函数为heapify(arr, 0, size)。
//Java 代码实现
public class HeapSort implements IArraySort {
@Override
public int[] sort(int[] sourceArray) throws Exception {
// 对 arr 进行拷贝,不改变参数内容
int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
int len = arr.length;
//构建最大堆函数
buildMaxHeap(arr, len);
//排序,每次都将length--的堆数组重新调整为最大堆。
for (int i = len - 1; i > 0; i--) {
swap(arr, 0, i);
len--;
heapify(arr, 0, len); //下沉顶端元素。shift_down(0),将最顶端的元素下沉到相应位置
}
return arr;
}
private void buildMaxHeap(int[] arr, int len) {
for (int i = (int) Math.floor(len / 2) - 1; i >= 0; i--) {//从最后一个非叶子节点开始调整,调整每一个非叶子节点
heapify(arr, i, len);
}
}
//调整函数的递归写法,i是父节点索引,len是当前堆数组的长度
private void heapify(int[] arr, int i, int len) {
int left = 2 * i + 1;
int right = 2 * i + 2;
int largest = i;
//父节点应该与左子交换
if (left < len && arr[left] > arr[largest]) {
largest = left;
}
//父节点应该与右子交换
if (right < len && arr[right] > arr[largest]) {
largest = right;
}
if (largest != i) {//如果父节点与左子或右子交换,那么被交换的子得到较小的那个数后,应该继续检查是否还能向下沉,所以递归式的调用heapify(arr, largest, len)。
swap(arr, i, largest);
heapify(arr, largest, len);
}
}
private void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
调整函数的迭代写法:
// 迭代写法,index是父节点索引
public static void heapRest(int[] arr, int index, int size){
int left = index*2 +1;
int right = index*2 +2;
int bigIndex; //即largest
// 直到所有节点都大于子节点退出
while (left < size){ //size即len
// 比较左右叶子节点大小,记录为bigindex
if (right <size && arr[right] > arr[left] ){
bigIndex = right;
}else {
bigIndex = left;
}
// 如果子节点的最大值大于父节点,则进行交换
if (arr[bigIndex] > arr[index]){
swap(arr,bigIndex,index);
// 为了确保交换后的子节点仍然大于其子子节点,将index换为bigIndex,继续与其子节点进行比较(即交换后,去交换了的子节点为root的树继续看root能不能继续“下沉”)
index = bigIndex;
left = index*2 +1;
right = index*2 +2;
}else {
// 如果父节点大于子节点,则跳出循环
break;
}
}
}
构建最大堆函数的另一种写法:从第一个堆节点开始调整,如果他的父节点他,那么上移现节点。更新currentIndex和他的parentIndex,看能否继续上移。
// 将一个数组变成大堆顶形式
public static void bigHeap(int[] arr, int len){
for (int i = 0; i < arr.length; i++) {//循环数组中的每一个节点
heapifyUp(arr, i, len);
}
}
private static void heapifyUp(int[] arr, int index, int len){
int currentIndex = index;
int fatherIndex = (index-1)/2;
while (arr[fatherIndex]<arr[currentIndex]){//和父节点比较,如果父节点小,那么交换父节点和子节点
swap(arr,fatherIndex,currentIndex);
// 将现节点往上移,看上面是否满足情况
currentIndex = fatherIndex;
fatherIndex = (currentIndex-1)/2;
}
2.2.2 降序写法(构建小顶堆)
public static void heapSortDescent(int[] arr){
// 现将其转换为小顶堆
smallHeap(arr);
int size = arr.length;
while (size > 1){
swap(arr,0,size-1);
size--;
smallHeapRes(arr,0,size);
}
}
// 将数组转换为小顶堆
public static void smallHeap(int[] arr){
for (int i = 0; i < arr.length; i++) {
int cur = i;
int father = (i-1)/2;
while (arr[cur] < arr[father]){
swap(arr,cur,father);
cur = father;
}
}
}
// 将剩余数组转换为小顶堆
private static void smallHeapRes(int[] arr, int index, int size) {
int left = index*2+1;
int right = index*2 +2;
int smallInd;
if (left >= size){
return;
}
if (right < size && arr[right]<arr[left]){
smallInd = right;
}else {
smallInd = left;
}
if (arr[index] > arr[smallInd]){
swap(arr,index,smallInd);
smallHeapRes(arr,smallInd,size);
}
}
public static void main(String[] args) {
int [] arr = {1,2,34,5,6,8};
heapSortDescent(arr);
for (int e: arr
) {
System.out.println(e);
}
}
}
reference:
https://blog.youkuaiyun.com/unspoken0714/article/details/107921229
https://mp.weixin.qq.com/s/vn3KiV-ez79FmbZ36SX9lg
https://www.cnblogs.com/lanhaicode/p/10546257.html
https://mp.weixin.qq.com/s?__biz=MzUyNjQxNjYyMg==&mid=2247484290&idx=1&sn=971ae3365a8a9d284e427e623d825bba&scene=21#wechat_redirect
https://mp.weixin.qq.com/s/Qf416rfT4pwURpW3aDHuCg
3457

被折叠的 条评论
为什么被折叠?



