Java十大排序算法

news2024/12/22 20:51:54

排序算法

对一序列对象根据某个关键字进行排序

(1)稳定性

在排序中对于相等的两个元素a、b。如果排序前a在b的前边,排序之后a也总是在b的前边。位置不会因为排序而改变称之为稳定。反之,如果排序后a、b的位置可能会发生改变,那么就称之为不稳定

(2)时间复杂度

一个算法执行所耗费的时间
时间复杂度本意是预估算法的执行时间,但实际上一个程序在计算机上执行的速度是非常快的,时间几乎可以忽略不计了,也就是失去了意义,所以这里意思是算法中执行频度最高的代码的执行的次数。反应当n发生变化时,执行次数的改变呈现一种什么样的规律

(3)空间复杂度

运行完一个程序所需内存的大小
指算法在计算机内执行时所需存储空间的度量,它也是数据规模n的函数

(4)排序

内排序:所有排序操作都在内存中完成
外排序:由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行


常见排序算法可以分为两大类

(1)非线性时间(比较类排序)

通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此称为非线性时间比较类排序
快速排序、归并排序、堆排序、冒泡排序等属于比较排序
在排序的最终结果里,元素之间的次序依赖于它们之间的比较。每个数都必须和其他数进行比较,才能确定自己的位置

比较排序优势:适用于各种规模的数据,也不在乎数据的分布,都能进行排序。可以说,比较排序适用于一切需要排序的情况

(2)线性时间(非比较类排序)

不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此称为线性时间非比较类排序

计数排序、基数排序、桶排序则属于非比较排序 。非比较排序是通过确定每个元素之前,应该有多少个元素来排序。针对数组arr,计算arr[i]之前有多少个元素,则唯一确定了arr[i]在排序后数组中的位置

非比较排序只要确定每个元素之前的已有的元素个数即可,所有一次遍历即可解 = 据规模和数据分布有一定的要求


冒泡排序(Bubble Sort)

一种简单的排序算法。重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端

即对n个数进行排序,每次都是由前一个数跟后一个数比较,每循环一轮, 就可以将最大的数移到数组的最后, 总共循环n-1轮,完成对数组排序

(1)比较相邻的元素。如果第一个比第二个大,就交换它们两个
(2)对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数
(3)针对所有的元素重复以上的步骤,除了最后一个
(4)重复步骤1~3,直到排序完成

public static int[] bubbleSort(int[] array) {
	if (array == null){
		return;
	}
	int len = array.length;
	// i控制循环次数,长度为len的数组只需要循环len-1次,i的起始值为0所以i<len-1
	for (int i = 0; i < len - 1; i++) {
		// j控制比较次数,第i次循环内需要比较len-i次
		// 但是由于是由arr[j]跟arr[j+1]进行比较,所以为了保证arr[j+1]不越界,j<len-i-1
		for (int j = 0; j < len - i - 1; j++) {
			// 如果前一个数比后一个数大,则交换位置将大的数往后放
			if (array[j] > array[j + 1]) {
				int temp = array[j + 1];
				array[j + 1] = array[j];
				array[j] = temp;
			}
		}
	}
	return array;
}

在这里插入图片描述


选择排序(Selection Sort)

表现最稳定的排序算法之一 ,简单直观。因为无论什么数据进去都是O(n2)的时间复杂度 ,所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间。理论上讲,选择排序可能也是平时排序一般人想到的最多的排序方法

选择排序可以说是冒泡排序的改良版,不再是前一个数跟后一个数相比较, 而是在每一次循环内都由一个数去跟 所有的数都比较一次,每次比较都选取相对较小的那个数来进行下一次的比较,并不断更新较小数的下标 这样在一次循环结束时就能得到最小数的下标,再通过一次交换将最小的数放在最前面,通过n-1次循环之后完成排序。 这样相对于冒泡排序来说,比较的次数并没有改变,但是数据交换的次数大大减少

工作原理
首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置
再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕

n个记录的直接选择排序可经过n-1趟直接选择排序得到有序结果
(1)初始状态:无序区为R[1…n],有序区为空
(2)第i趟排序(i=1、2、3、…、n-1)开始时,当前有序区和无序区分别为R[1…i-1]和R(i…n)。该趟排序从当前无序区中-选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1…i]和R[i+1…n)分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区
(3)n-1趟结束,数组有序化了

public static int[] selectSort(int[] array) {
	if (array == null){
		return;
	}
	int len = array.length;
	// i控制循环次数,长度为len的数组只需要循环len-1次,i的起始值为0所以i<len-1
	for (int i = 0; i < len - 1; i++) {
		// minIndex 用来保存每次比较后较小数的下标
		int minIndex = i;
		// j控制比较次数,因为每次循环结束之后最小的数都已经放在了最前面,所以下一次循环的时候就可以跳过这个数,所以j的初始值为i+1而不需要每次循环都从0开始,并且j<len即可
		for (int j = i + 1; j < len; j++) {
			// 每比较一次都需要将较小数的下标记录下来
			if (array[minIndex] > arr[j]) {
				minIndex = j;
			}
		}
		// 当完成一次循环时,就需要将本次循环选取的最小数移动到本次循环开始的位置
		if (minIndex != i) {
			int temp = array[i];
			array[i] = array[minIndex];
			array[minIndex] = temp;
		}
		//如果完成一次循环,最小数是本身则不作操作,继续进行下一循环
		// 打印每次循环结束之后数组的排序状态(方便理解)
		System.out.println("第" + (i + 1) + "次循环之后效果:" + Arrays.toString(array));
	}
	return array;
}

在这里插入图片描述


插入排序(Insertion Sort)

简单直观的排序算法(见缝插针
工作原理:通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间

一般来说,插入排序都采用in-place在数组上实现
(1)从第一个元素开始,该元素可以认为已经被排序
(2)取出下一个元素,在已经排序的元素序列中从后向前扫描
(3)如果最后一个已排序元素大于新元素,将该元素移到下一位置
(4)重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
(5)将新元素插入到该位置后
(6)重复步骤2~5

public static int[] insertionSort(int[] array) {
	if (array == null){
		return;
	}
	int len = array.length;
	// i控制循环次数,因为已经默认第一个数的位置是正确的,所以i的起始值为1,i<len,循环len-1次
	for (int i = 1; i < len; i++) {
		int j = i;// 变量j用来记录即将要排序的数的位置即目标数的原位置
		int target = array[j];// target用来记录即将要排序的那个数的值即目标值
		// while循环用来为目标值在已经排好序的数中找到合适的位置,因为是从后向前比较,并且是与j-1位置的数比较,所以j>0
		while (j > 0 && target < array[j - 1]) {
			// 当目标数的值比它当前位置的前一个数的值小时,将前一个数的位置向后移一位。并且j--使得目标数继续与下一个元素比较
			array[j] = array[j - 1];
		j--;
		}
		// 更目标数的位置
		array[j] = target;
		// 打印每次循环结束之后数组的排序状态(方便理解)
		System.out.println("第"+(i)+"次循环之后效果:"+Arrays.toString(array));
	}
	return array;
}

在这里插入图片描述


希尔排序(Shell Sort)

希尔排序也称为“缩小增量排序”,原理是先将需要排的数组分成多个子序列,这样每个子序列的元素个数就很少,再分别对每个对子序列进行插入排序。在该数组基本有序后 再进行一次直接插入排序就能完成对整个数组的排序。所以,要采用跳跃分割的策略。这里引入“增量”的概念,将相距某个增量的记录两两组合成一个子序列,然后对每个子序列进行直接插入排序, 这样得到的结果才会使基本有序(即小的在前边,大的在后边,不大不小的在中间)。希尔排序就是 直接插入排序的升级版

先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,具体算法描述:
(1)选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1
(2)按增量序列个数k,对序列进行k 趟排序
(3)每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度

public static int[] shellSort(int[] array) {
	if (array == null){
		return null;
	}
	
	int len = array.length; // 数组的长度
	int k = len / 2; // 初始的增量为数组长度的一半
	// while循环控制按增量的值来划不同分子序列,每完成一次增量就减少为原来的一半
	// 增量的最小值为1,即最后一次对整个数组做直接插入排序
	while (k > 0) {
		// 里边其实就是升级版的直接插入排序了,是对每一个子序列进行直接插入排序,
		// 所以直接将直接插入排序中的‘1’变为‘k’就可以了
		for (int i = k; i < len; i++) {
			int j = i;
			int target = array[i];
			while (j >= k && target < array[j - k]) {
				array[j] = array[j - k];
				j -= k;
			}
			array[j] = target;
		}
		// 不同增量排序后的结果
		System.out.println("增量为" + k + "排序之后:" + Arrays.toString(array));
		k /= 2;
	}
	return array;
}

在这里插入图片描述


归并排序(Merge Sort)

从上到下递归拆分,然后从下到上逐步合并

(1)递归拆分

先把待排序数组分为左右两个子序列,再分别将左右两个子序列拆分为四个子子序列,以此类推直到最小的子序列元素的个数为两个或者一个为止。

(2)逐步合并

将最底层的最左边的一个子序列排序,然后将从左到右第二个子序列进行排序,再将这两个排好序的子序列合并并排序,然后将最底层从左到右第三个子序列进行排序… 合并完成之后记忆完成了对数组的排序操作
一定要注意是从下到上层级合并,可以理解为递归层级返回

1.把长度为n的输入序列分成两个长度为n/2的子序列
2.对这两个子序列分别采用归并排序
3.将两个排序好的子序列合并成一个最终的排序序列

// 递归拆分
public static int[] MergeSort(int[] array) {
	if (array.length < 2){
		return array;
	}
	int mid = array.length / 2;
	int[] left = Arrays.copyOfRange(array, 0, mid);
	int[] right = Arrays.copyOfRange(array, mid, array.length);
	return merge(MergeSort(left), MergeSort(right)); //递归
}

// 逐步合并;先排序,再合并
@param left
@param right
public static int[] merge(int[] left, int[] right) {
	int[] result = new int[left.length + right.length];
	// index:临时数组下标
	// l:左数组下标
	// r:右数组下标
	for (int index = 0, l = 0, r = 0; index < result.length; index++) {
		if (l >= left.length){
			result[index] = right[r++];
		} else if  (r >= right.length){
			result[index] = left[l++];
		} else if (left[l] > right[r]){
			result[index] = right[r++];
		} else {
			result[index] = left[l++];
		}
	}
	return result;
}

在这里插入图片描述


快速排序(Quick Sort)

快速排序也采用了分治的策略,这里引入了“基准数”的概念
快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)

1.数列中挑出一个元素为基准数(pivot )(一般将待排序的数组的第一个数作为基准数)
2.重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作
3.重复1、2步骤。**递归(recursive)**把小于基准值元素的子数列和大于基准值元素的子数列排序
通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序


/**
  * 分区过程
  * @param arr   待分区数组
  * @param left  待分区数组最小下标
  * @param right 待分区数组最大下标
  */
public static void quickSort(int[] arr, int left, int right) {
	if (left < right) {
		int temp = qSort(arr, left, right);
		quickSort(arr, left, temp - 1);
		quickSort(arr, temp + 1, right);
	}
}

/**
  * 排序过程
  * @param arr   待排序数组
  * @param left  待排序数组最小下标
  * @param right 待排序数组最大下标
  * @return 排好序之后基准数的位置下标,方便下次的分区
  * /
public static int qSort(int[] arr, int left, int right) {
	int temp = arr[left];// 定义基准数,默认为数组的第一个元素
	
	// 循环执行的条件
	while (left < right) {
		// 因为默认的基准数是在最左边,所以首先从右边开始比较进入while循环的判断条件
		// 如果当前arr[right]比基准数大,则直接将右指针左移一位,当然还要保证left<right
		while (left < right && arr[right] > temp) {
			right--;
		}
		
		// 跳出循环说明当前的arr[right]比基准数要小,那么直接将当前数移动到基准数所在的位置,并且左指针向右移一位(left++)
		// 这时当前数(arr[right])所在的位置空出,需要从左边找一个比基准数大的数来填充。
		if (left < right) {
			arr[left++] = arr[right];
		}
		
		// 下面的步骤是为了在左边找到比基准数大的数填充到right的位置。
		// 因为现在需要填充的位置在右边,所以左边的指针移动,如果arr[left]小于或者等于基准数,则直接将左指针右移一位
		while (left < right && arr[left] <= temp) {
			left++;
		}
		// 跳出上一个循环说明当前的arr[left]的值大于基准数,需要将该值填充到右边空出的位置,然后当前位置空出。
		if (left < right) {
			arr[right--] = arr[left];
		}
	}
	
	// 当循环结束说明左指针和右指针已经相遇。并且相遇的位置是一个空出的位置,
	// 这时候将基准数填入该位置,并返回该位置的下标,为分区做准备。
	arr[left] = temp;
	return left;
}

public static void main(String[] args) {
	// TODO Auto-generated method stub
	int[] arr = { 72, 6, 57, 88, 60, 42, 83, 73, 48, 85 };
	quickSort(arr, 0, 9);
	System.out.println(Arrays.toString(arr));
}
/**
  * 快速排序方法
  * /
public static int[] QuickSort(int[] array, int start, int end) {
	if (array.length < 1 || start < 0 || end >= array.length || start > end){
		return null;
	}
	int smallIndex = partition(array, start, end);
	if (smallIndex > start){
		QuickSort(array, start, smallIndex - 1);
	}
	if (smallIndex < end){
		QuickSort(array, smallIndex + 1, end);
	}
	return array;
}

/**
  * partition分区,
  * @param arr   待排序数组
  * @param start  待排序数组最小下标
  * @param end 待排序数组最大下标
  * @return 排好序之后基准数的位置下标,方便下次的分区
  * /
public static int partition(int[] array, int start, int end) {
	int pivot = (int) (start + Math.random() * (end - start + 1));
	int smallIndex = start - 1;
	swap(array, pivot, end);
	for (int i = start; i <= end; i++)
		if (array[i] <= array[end]) {
			smallIndex++;
			if (i > smallIndex){
				swap(array, i, smallIndex);
			}
		}
	}
	return smallIndex;
}

// 交换数组内两个元素
public static void swap(int[] array, int i, int j) {
	int temp = array[i];
	array[i] = array[j];
	array[j] = temp;
}

在这里插入图片描述


堆排序(Heap Sort)

堆排序(Heapsort) 是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点
堆是一种特殊的完全二叉树,分为大顶堆和小顶堆

大顶堆:每个结点的值都大于它的左右子结点的值,升序排序用大顶堆
小顶堆:每个结点的值都小于它的左右子结点的值,降序排序用小顶堆

所以,需要先将待排序数组构造成大顶堆的格式,这时候该堆的顶结点就是最大的数,将其与堆的最后一个结点的元素交换。再将剩余的树重新调整成堆,再次首节点与尾结点交换,重复执行直到只剩下最后一个结点完成排序

算法描述
步骤1:
将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的无序区;

步骤2:
将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n];

步骤3:
由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int[] arr = { 72, 6, 57, 88, 60, 42, 83, 73, 48, 85 };
		heapSort(arr);
		System.out.println(Arrays.toString(arr));
	}

	public static void heapSort(int[] arr) {
		if (arr == null) {
			return;
		}
		int len = arr.length;
		// 初始化大顶堆(从最后一个非叶节点开始,从左到右,由下到上)
		for (int i = len / 2 - 1; i >= 0; i--) {
			adjustHeap(arr, i, len);
		}
		// 将顶节点和最后一个节点互换位置,再将剩下的堆进行调整
		for (int j = len - 1; j > 0; j--) {
			swap(arr, 0, j);
			adjustHeap(arr, 0, j);
		}
	}

	/**
	 * 整理树让其变成堆
	 * 
	 * @param arr 待整理的数组
	 * @param i   开始的结点
	 * @param j   数组的长度
	 */
	public static void adjustHeap(int[] arr, int i, int j) {
		int temp = arr[i];// 定义一个变量保存开始的结点
		// k就是该结点的左子结点下标
		for (int k = 2 * i + 1; k < j; k = 2 * k + 1) {
			// 比较左右两个子结点的大小,k始终记录两者中较大值的下标
			if (k + 1 < j && arr[k] < arr[k + 1]) {
				k++;
			}
			// 经子结点中的较大值和当前的结点比较,比较结果的较大值放在当前结点位置
			if (arr[k] > temp) {
				arr[i] = arr[k];
				i = k;
			} else {// 说明已经是大顶堆
				break;
			}
		}
		arr[i] = temp;
	}

	/**
	 * 交换数据
	 * 
	 * @param arr
	 * @param num1
	 * @param num2
	 */
	public static void swap(int[] arr, int num1, int num2) {
		int temp = arr[num1];
		arr[num1] = arr[num2];
		arr[num2] = temp;
	}
	// 声明全局变量,用于记录数组array的长度;
	static int len;

	/**
	 * 堆排序算法
	 *
	 * @param array
	 * @return
	 */
	public static int[] HeapSort(int[] array) {
		len = array.length;
		if (len < 1)
			return array;
		// 1.构建一个最大堆
		buildMaxHeap(array);
		// 2.循环将堆首位(最大值)与末位交换,然后在重新调整最大堆
		while (len > 0) {
			swap(array, 0, len - 1);
			len--;
			adjustHeap(array, 0);
		}
		return array;
	}

	/**
	 * 建立最大堆
	 *
	 * @param array
	 */
	public static void buildMaxHeap(int[] array) {
		// 从最后一个非叶子节点开始向上构造最大堆
		// for循环这样写会更好一点:i的左子树和右子树分别2i+1和2(i+1)
		for (int i = (len / 2 - 1); i >= 0; i--) {
			adjustHeap(array, i);
		}
	}

	/**
	 * 调整使之成为最大堆
	 *
	 * @param array
	 * @param i
	 */
	public static void adjustHeap(int[] array, int i) {
		int maxIndex = i;
		// 如果有左子树,且左子树大于父节点,则将最大指针指向左子树
		if (i * 2 < len && array[i * 2] > array[maxIndex])
			maxIndex = i * 2; // 感谢网友矫正,之前是i*2+1
		// 如果有右子树,且右子树大于父节点,则将最大指针指向右子树
		if (i * 2 + 1 < len && array[i * 2 + 1] > array[maxIndex])
			maxIndex = i * 2 + 1; // 感谢网友矫正,之前是i*2+2
		// 如果父节点不是最大值,则将父节点与最大值交换,并且递归调整与父节点交换的位置。
		if (maxIndex != i) {
			swap(array, maxIndex, i);
			adjustHeap(array, maxIndex);
		}
	}

在这里插入图片描述


计数排序(Counting Sort)

计数排序 的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。 作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。

计数排序(Counting sort) 是一种稳定的排序算法。计数排序使用一个额外的数组C,其中第i个元素是待排序数组A中值等于i的元素的个数。然后根据数组C来将A中的元素排到正确的位置。它只能对整数进行排序。

算法描述
步骤1:找出待排序的数组中最大和最小的元素;
步骤2:统计数组中每个值为i的元素出现的次数,存入数组C的第i项;
步骤3:对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加);
步骤4:反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1

计数排序采用了一种全新的思路,不再是通过比较来排序,而是将待排序数组中的最大值+1作为一个临时数组的长度,然后用临时数组记录待排序数组中每个元素出现的次数。最后再遍历临时数组,因为是升序,所以从前到后遍历,将临时数组中值>0的数的下标循环取出,依次放入待排序数组中,即可完成排序。计数排序的效率很高,但是实在牺牲内存的前提下,并且有着限制,那就是待排序数组的值必须 限制在一个确定的范围

算法分析
当输入的元素是n 个0到k之间的整数时,它的运行时间是 O(n + k)。计数排序不是比较排序,排序的速度快于任何比较排序算法。由于用来计数的数组C的长度取决于待排序数组中数据的范围(等于待排序数组的最大值与最小值的差加上1),这使得计数排序对于数据范围很大的数组,需要大量时间和内存

public static void main(String[] args) {
	// TODO Auto-generated method stub
	int[] arr = { 72, 6, 57, 88, 60, 42, 83, 73, 48, 85 };
	countSort(arr);
	System.out.println(Arrays.toString(arr));
}

public static void countSort(int[] arr) {
	if (arr == null)
		return;
	int len = arr.length;
	// 保存待排序数组中的最大值,目的是确定临时数组的长度(必须)
	int maxNum = arr[0];
	// 保存待排序数组中的最小值,目的是确定最终遍历临时数组时下标的初始值(非必需,只是这样可以加快速度,减少循环次数)
	int minNum = arr[0];
	// for循环就是为了找到待排序数组的最大值和最小值
	for (int i = 1; i < len; i++) {
		maxNum = maxNum > arr[i] ? maxNum : arr[i];
		minNum = minNum < arr[i] ? minNum : arr[i];
	}
	// 创建一个临时数组
	int[] temp = new int[maxNum + 1];
	// for循环是为了记录待排序数组中每个元素出现的次数,并将该次数保存到临时数组中
	for (int i = 0; i < len; i++) {
		temp[arr[i]]++;
	}
	// k=0用来记录待排序数组的下标
	int k = 0;
	// 遍历临时数组,重新为待排序数组赋值。
	for (int i = minNum; i < temp.length; i++) {
		while (temp[i] > 0) {
			arr[k++] = i;
			temp[i]--;
		}
	}
}
	/**
	 * 计数排序
	 *
	 * @param array
	 * @return
	 */
	public static int[] CountingSort(int[] array) {
		if (array.length == 0)
			return array;
		int bias, min = array[0], max = array[0];
		for (int i = 1; i < array.length; i++) {
			if (array[i] > max)
				max = array[i];
			if (array[i] < min)
				min = array[i];
		}
		bias = 0 - min;
		int[] bucket = new int[max - min + 1];
		Arrays.fill(bucket, 0);
		for (int i = 0; i < array.length; i++) {
			bucket[array[i] + bias]++;
		}
		int index = 0, i = 0;
		while (index < array.length) {
			if (bucket[i] != 0) {
				array[index] = i - bias;
				bucket[i]--;
				index++;
			} else
				i++;
		}
		return array;
	}

桶排序(Bucket Sort)

桶排序 是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。

桶排序 (Bucket sort)的工作的原理:
假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排

算法描述
步骤1:人为设置一个BucketSize,作为每个桶所能放置多少个不同数值(例如当BucketSize==5时,该桶可以存放{1,2,3,4,5}这几种数字,但是容量不限,即可以存放100个3);
步骤2:遍历输入数据,并且把数据一个一个放到对应的桶里去;
步骤3:对每个不是空的桶进行排序,可以使用其它排序方法,也可以递归使用桶排序;
步骤4:从不是空的桶里把排好序的数据拼接起来。

注意:如果递归使用桶排序为各个桶排序,则当桶数量为1时要手动减小BucketSize增加下一循环桶的数量,否则会陷入死循环,导致内存溢出。

桶排序其实就是计数排序的强化版,需要利用一个映射函数首先定义有限个数个桶,然后将待排序数组内的元素按照函数映射的关系分别放入不同的桶里边,现在不同的桶里边的数据已经做了区分,比如A桶里的数要么全部大于B桶,要么全部小于B桶里的数。但是A,B桶各自里边的数还是乱序的。所以要借助其他排序方式(快速,插入,归并)分别对每一个元素个数大于一的桶里边的数据进行排序。最后再将桶里边的元素按照顺序依次放入待排序数组中即可。

在这里插入图片描述

	/**
	 * 桶排序
	 *
	 * @param array
	 * @param bucketSize
	 * @return
	 */
	public static ArrayList<Integer> BucketSort(ArrayList<Integer> array, int bucketSize) {
		if (array == null || array.size() < 2)
			return array;
		int max = array.get(0), min = array.get(0);
		// 找到最大值最小值
		for (int i = 0; i < array.size(); i++) {
			if (array.get(i) > max)
				max = array.get(i);
			if (array.get(i) < min)
				min = array.get(i);
		}
		int bucketCount = (max - min) / bucketSize + 1;
		ArrayList<ArrayList<Integer>> bucketArr = new ArrayList<>(bucketCount);
		ArrayList<Integer> resultArr = new ArrayList<>();
		for (int i = 0; i < bucketCount; i++) {
			bucketArr.add(new ArrayList<Integer>());
		}
		for (int i = 0; i < array.size(); i++) {
			bucketArr.get((array.get(i) - min) / bucketSize).add(array.get(i));
		}
		for (int i = 0; i < bucketCount; i++) {
			if (bucketSize == 1) { // 如果带排序数组中有重复数字时
				for (int j = 0; j < bucketArr.get(i).size(); j++)
					resultArr.add(bucketArr.get(i).get(j));
			} else {
				if (bucketCount == 1)
					bucketSize--;
				ArrayList<Integer> temp = BucketSort(bucketArr.get(i), bucketSize);
				for (int j = 0; j < temp.size(); j++)
					resultArr.add(temp.get(j));
			}
		}
		return resultArr;
	}
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int[] arr = { 72, 6, 57, 88, 60, 42, 83, 73, 48, 85 };
		bucketSort(arr);
		System.out.println(Arrays.toString(arr));
	}

	public static void bucketSort(int[] arr) {
		if (arr == null)
			return;
		int len = arr.length;
		// 定义桶的个数,这里k的值要视情况而定,这里我们假设待排序数组里的数都是[0,100)之间的。
		int k = 10;
		// 用嵌套集合来模拟桶,外层集合表示桶,内层集合表示桶里边装的元素。
		List<List<Integer>> bucket = new ArrayList<>();
		// for循环初始化外层集合即初始化桶
		for (int i = 0; i < k; i++) {
			bucket.add(new ArrayList<>());
		}
		// 循环是为了将待排序数组中的元素通过映射函数分别放入不同的桶里边
		for (int i = 0; i < len; i++) {
			bucket.get(mapping(arr[i])).add(arr[i]);
		}
		// 这个循环是为了将所有的元素个数大于1的桶里边的数据进行排序。
		for (int i = 0; i < k; i++) {
			if (bucket.size() > 1) {
				// 因为这里是用集合来模拟的桶所以用java写好的对集合排序的方法。
				// 其实应该自己写一个方法来排序的。
				Collections.sort(bucket.get(i));
			}

		}
		// 将排好序的数重新放入待排序数组中
		int m = 0;
		for (List<Integer> list : bucket) {
			if (list.size() > 0) {
				for (Integer a : list) {
					arr[m++] = a;
				}
			}
		}
	}

	/**
	 * 映射函数
	 * 
	 * @param num
	 * @return
	 */
	public static int mapping(int num) {
		return num / 10;
	}

基数排序(Radix Sort)

基数排序也是非比较的排序算法,对每一位进行排序,从最低位开始排序,复杂度为O(kn),为数
组长度,k为数组中的数的最大的位数;

基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。基数排序基于分别排序,分别收集,所以是稳定的。

算法描述
步骤1:取得数组中的最大数,并取得位数;
步骤2:arr为原始数组,从最低位开始取每个位组成radix数组;
步骤3:对radix进行计数排序(利用计数排序适用于小范围数的特点)

就是将待排序数据拆分成多个关键字进行排序,也就是说,基数排序的实质是多关键字排序。多关键字排序的思路是将待排数据里德排序关键字拆分成多个排序关键字; 第1个排序关键字,第2个排序关键字,第3个排序关键字…然后,根据子关键字对待排序数据进行排序。

基数排序有两种方法:
①MSD 从高位开始进行排序
②LSD 从低位开始进行排序

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int[] arr = { 720, 6, 57, 88, 60, 42, 83, 73, 48, 85 };
		redixSort(arr, 10, 3);
		System.out.println(Arrays.toString(arr));
	}

	public static void redixSort(int[] arr, int radix, int d) {
		// 缓存数组
		int[] tmp = new int[arr.length];
		// buckets用于记录待排序元素的信息
		// buckets数组定义了max-min个桶
		int[] buckets = new int[radix];

		for (int i = 0, rate = 1; i < d; i++) {

			// 重置count数组,开始统计下一个关键字
			Arrays.fill(buckets, 0);
			// 将data中的元素完全复制到tmp数组中
			System.arraycopy(arr, 0, tmp, 0, arr.length);

			// 计算每个待排序数据的子关键字
			for (int j = 0; j < arr.length; j++) {
				int subKey = (tmp[j] / rate) % radix;
				buckets[subKey]++;
			}

			for (int j = 1; j < radix; j++) {
				buckets[j] = buckets[j] + buckets[j - 1];
			}

			// 按子关键字对指定的数据进行排序
			for (int m = arr.length - 1; m >= 0; m--) {
				int subKey = (tmp[m] / rate) % radix;
				arr[--buckets[subKey]] = tmp[m];
			}
			rate *= radix;
		}
	}
	/**
	 * 基数排序
	 * 
	 * @param array
	 * @return
	 */
	public static int[] RadixSort(int[] array) {
		if (array == null || array.length < 2)
			return array;
		// 1.先算出最大数的位数;
		int max = array[0];
		for (int i = 1; i < array.length; i++) {
			max = Math.max(max, array[i]);
		}
		int maxDigit = 0;
		while (max != 0) {
			max /= 10;
			maxDigit++;
		}
		int mod = 10, div = 1;
		ArrayList<ArrayList<Integer>> bucketList = new ArrayList<ArrayList<Integer>>();
		for (int i = 0; i < 10; i++)
			bucketList.add(new ArrayList<Integer>());
		for (int i = 0; i < maxDigit; i++, mod *= 10, div *= 10) {
			for (int j = 0; j < array.length; j++) {
				int num = (array[j] % mod) / div;
				bucketList.get(num).add(array[j]);
			}
			int index = 0;
			for (int j = 0; j < bucketList.size(); j++) {
				for (int k = 0; k < bucketList.get(j).size(); k++)
					array[index++] = bucketList.get(j).get(k);
				bucketList.get(j).clear();
			}
		}
		return array;
	}

在这里插入图片描述


基数排序 vs 计数排序 vs 桶排序

这三种排序算法都利用了桶的概念,但对桶的使用方法上有明显差异:

基数排序: 根据键值的每位数字来分配桶
计数排序: 每个桶只存储单一键值
桶排序: 每个桶存储一定范围的数值

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/455680.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java线程池从入门到精通(线程池实战)

参考 java常用线程池及它们的适用场景&#xff08;JDK1.8&#xff09; Java线程与线程池实战 线程池的拒绝策略_线程池 RejectedExecutionHandler 拒绝策略 ThreadPoolExecutor原理解析-关闭线程池 代码经验—java获取cpu个数-docker 一、概念 Java 中的线程池核心实现类是 …

鉴源论坛 · 观模丨面向界面的图形化测试技术

作者 | 熊一衡 华东师范大学软件工程学院博士 苏亭 华东师范大学软件工程学院教授 版块 | 鉴源论坛 观模 01 什么是面向界面的图形化测试&#xff08;GUI Testing&#xff09; 图形用户界面(GUI) 是一种通过图形化方式呈现信息、数据、功能和操作的用户界面&#xff0c;旨在…

一起学 WebGL:三角形加上渐变色

大家好&#xff0c;我是前端西瓜哥。之前教大家绘制一个红色的三角形&#xff0c;这次我们来画个有渐变的三角形。 本文为系列文章&#xff0c;请先阅读如何绘制红色三角形的文章&#xff1a; 《一起学 WebGL&#xff1a;绘制三角形》 原来的写法&#xff0c;颜色是在片元着色器…

移动边缘计算意味着真正的5G时代已经来临

5G的承诺尚未实现&#xff0c;但现在宣布其失败还为时过早。DataBank首席执行官劳尔k马丁尼克(Raul K. Martynek)表示 &#xff0c;真正的5G正在通过移动边缘计算实现&#xff0c;而数据中心将成为其中的核心。 在美国所有主要的移动运营商都在大力宣传他们在全美范围内提供无…

STM32-移植RTT

目录 Cubemx引入RTT资源新建工程生成工程 时钟选择选单片机引脚引脚搜索快速选中取消引脚选中引脚命名IO普通模式设置 串口串口基本配置串口DMA ADC采集ADC基本应用ADC_DMA RTT-shell指令定义RTTCOM调试串口J-Link RTT调试 教程shell指令RTT外设驱动使用1--串口添加 STM32_pwm …

玩元宇宙血亏后 蓝色光标梭哈AI也挺悬

蓝色光标2022年年度报告出炉&#xff0c;巨亏21.75 亿元&#xff0c;其中20.38亿亏损因商誉、无形资产及其他资产减值造成&#xff0c;而在实际亏损业务中&#xff0c;元宇宙占比不小。 蓝色光标在元宇宙领域的布局&#xff0c;主要通过三家子公司实施&#xff0c;分别为蓝色宇…

分布式文件系统HDFS的多问多答

分布式文件系统HDFS 简述HDFS的优缺点简述HDFS的体系结构请论述HDFS中SecondaryNameNode的作用和工作原理请论述HDFS写数据原理 简述HDFS的优缺点 HDFS的优良特性&#xff1a; ①兼容廉价的硬件设备。在成百上千台廉价服务器中存储数据&#xff0c;常会出现节点失效的情况&…

从浏览器输入url到页面加载(四)协议栈和套接字以及三次握手确认对于通信的作用

前言 上一节我们说到了域名对用户记忆的优点&#xff0c;但是IP对于路由器的优点&#xff0c;所以需要有DNS服务器提供域名与IP地址的转换&#xff0c;还说到了在前端开发中dns-prefetch域名预解析的好处。 本小节呢&#xff0c;我们会说一些不常用的知识点&#xff0c;如协议…

【社区图书馆】读《悲惨世界》有感

文章目录 故事简介经典重现价值取向我的思想 故事简介 《悲惨世界》是一部充满了悲剧的小说&#xff0c;故事首先由教堂展开&#xff0c;然后主要围绕着主人公冉阿让进行一系列的生动形象的描写&#xff0c;讲述了冉阿让悲惨的一生。 主人公冉阿让是一个诚实、善良的工人&…

100天涨薪4k,从功能测试到自动化测试,我整理的3000字超全学习指南

去年6月份&#xff0c;由于经济压力让我下定决心进阶自动化测试&#xff0c;已经24的我做了3年功能测试&#xff0c;坐标广州薪资定格在8k&#xff0c;可能是生活过的太安逸&#xff0c;觉得8000的工资也够了&#xff0c;但是生活总是多变的&#xff0c;女朋友的突然怀孕&#…

SpringBoot 整合WebService详解

1. 概述 WebService服务端是以远程接口为主的&#xff0c;在Java实现的WebService技术里主要依靠CXF开发框架&#xff0c;而这个CXF开发框架可以直接将接口发布成WebService。 CXF又分为JAX-WS和JAX-RS&#xff0c;JAX-WS是基于xml协议&#xff0c;而JAX-RS是基于Restful风格&…

OCR卡证识别

文章目录 前言一、DBNet多分类二、步骤1.训练、训练模型推理、模型转换2.通过推理模型进行推理 三、解决思路1、查看模型2、tools/infer/predict_det.py修改3、utility.py修改 总结 前言 最近涉及到了身份证识别&#xff0c;为了便于匹配识别结果的属性&#xff0c;如姓名、身…

(二) AIGC—Stable Difussion (1)

1. 前置知识 目前通用的图像生成模型一般包含三个组件&#xff1a; Text Encoder 根据文字生成向量生成模型 根据向量和Noise 生成 缩小版本的图像Image Decoder 根据小分辨率图像生成大分辨率图像 2. Text Encoder 文字的Encoder对于结果的影响很大&#xff0c;增大Diffusio…

华为p60系列超级快充 Turbo技术,轻松搞定充电困扰!

随着手机的功能越来越丰富&#xff0c;电量消耗也越来越快&#xff0c;当手机电量剩余20%时&#xff0c;是否有电量焦虑。为了满足大家快速充电的需求&#xff0c;华为P60系列配备了超级快充Turbo充电技术&#xff0c;让我们手机充电更快&#xff0c;用的更久&#xff0c;从此告…

Python爬虫解读

爬虫&#xff1a; Python爬虫是指利用计算机程序或者脚本自动抓取网站数据的一种行为&#xff0c;通常是为了提取网站数据或者进行数据分析等目的。 Python 爬虫可以分为手动爬虫和自动爬虫两种。手动爬虫是指完全由人工编写代码来实现的爬虫&#xff0c;这种方式需要编写大量的…

ES使用小结

ES使用总结 1.查询es全部索2.根据es索引查询文档3.查看指定索引mapping文件4.默认查询总数10000条5.删除指定索引文档6.删除所有数据包括索引7.設置窗口值8. logstash简单配置Logstash配置&#xff1a;logstash 控制台输出 9. filebenat配置 1.查询es全部索 localhost:9200/_c…

为什么说网络安全行业是IT行业最后的红利?

前言 2023年网络安全行业的前景看起来非常乐观。根据当前的趋势和发展&#xff0c;一些趋势和发展可能对2023年网络安全行业产生影响&#xff1a; 5G技术的广泛应用&#xff1a;5G技术的普及将会使互联网的速度更快&#xff0c;同时也将带来更多的网络威胁和安全挑战。网络安全…

DHCP 给内网客户端分配ip地址

~ 为 InsideCli 客户端网络分配地址&#xff0c;地址池范围&#xff1a; 192.168.0.110-192.168.0.190/24&#xff1b; ~ 域名解析服务器&#xff1a;按照实际需求配置 DNS 服务器地址选项&#xff1b; ~ 网关&#xff1a;按照实际需求配置网关地址选项&#xff1b; ~ 为…

JAVAWeb08-手动实现 Tomcat 底层机制+ 自己设计 Servlet

1. 前言 先看一个小案例&#xff0c; 引出对 Tomcat 底层实现思考 1.1 完成小案例 ● 快速给小伙伴完成这个小案例 0. 我们准备使用 Maven 来创建一个 WEB 项目, 老师先简单给小伙伴介绍一下 Maven 是什么, 更加详细的使用&#xff0c;我们还会细讲, 现在先使用一把 先创建…

【MySQL】带你了解MySQL 如何学习MySQL以及MySQL的用途以及意义

目录 1 MySQL的起源和发展 1.0.1 数据库管理系统 1.1 MySQL的起源 命名由来&#xff1a; 1.2 MySQL的发展历程 2 什么是MySQL&#xff1f; 2.1 数据库 2.1.1 我们之前存储数据的格式&#xff1a; 2.1.2 使用数据库的目的&#xff1a; 2.1.3 数据库分类 2.2 SQL语句 2…