有一个数组,3,2,3,4,1
我们把第一个3标记为黑色
第二个3标记为红色
如果排序之后,黑色的3仍在红色3前边,我们就认为这个排序是稳定的
如果红色3在黑色3前面,我们就认为这个排序是不稳定的
想象斗地主时咋摸牌的,保证有序
import java.util.Arrays;
public class InlineSorting {
public static void main(String[] args) {
int[] arr = {1,5,8,10,6};
System.out.println("排序前的数组:");
System.out.println(Arrays.toString(arr));
InlineSorting(arr);
System.out.println("排序后的数组:");
System.out.println(Arrays.toString(arr));
}
static void InlineSorting(int[] arr){
sort(arr);
}
static void sort(int[] arr){
int len = arr.length;
for(int i=1; i<len; i++){
int tmp = arr[i];
int j=i-1;
for(; j>=0; j--){
if (arr[j] > tmp){
arr[j+1] = arr[j];
}else {
break;
}
}
arr[j+1] = tmp;
}
}
}
排序前的数组:
[1, 5, 8, 10, 6]
排序后的数组:
[1, 5, 6, 8, 10]
最好: O(N)
最坏: O(N^2)
O(1)
稳定排序
当原始数据有序的时候,直接就continue了,所以越有序的数据越快
也叫缩小增量算法,核心就是分组和组内直接插入排序
希尔排序为啥要跳着分组,不是相邻分组?
可以把较大的元素,尽量放到后面去
package InsertSorting;
import java.util.Arrays;
public class ShellSort {
public static void main(String[] args) {
int[] arr = {1,5,8,10,6,9};
System.out.println("排序前的数组:");
System.out.println(Arrays.toString(arr));
ShellSort(arr);
System.out.println("排序后的数组:");
System.out.println(Arrays.toString(arr));
}
static void ShellSort(int[] arr){
int[] drr = {5,2,1};
for (int i = 0; i < drr.length; i++) {
sortByShell(arr,drr[i]);
}
}
static void sortByShell(int[] arr,int gap){
int len = arr.length;
for(int i=gap; i<len; i++){
int tmp = arr[i];
int j=i-gap;
if (tmp > arr[j]){
//说明有序的,直接continue
continue;
}
for(; j>=0; j-=gap){
if (arr[j] > tmp){
arr[j+gap] = arr[j];
}else {
break;
}
}
arr[j+gap] = tmp;
}
}
}
排序前的数组:
[1, 5, 8, 10, 6, 9]
排序后的数组:
[1, 5, 6, 8, 9, 10]
希尔排序的代码,就是直接插入排序的代码-1,+1的部分,替换为-gap,+gap
O(N^1.3 ~ N^1.5)
O(1)
不稳定
希尔排序是一种基于插入排序的排序算法,在对大规模数据进行排序时具有比较高的效率。它适合于以下场景:
总体而言,希尔排序适合处理大规模且分布较为均匀的数据集,且内存资源有限的场景。
package SelectSort;
import java.util.Arrays;
public class SelectSort {
public static void main(String[] args) {
int[] arr = {2,3,10,8,7};
System.out.println("排序前的数组:");
System.out.println(Arrays.toString(arr));
SelectSort(arr);
System.out.println("排序后的数组:");
System.out.println(Arrays.toString(arr));
}
static void SelectSort(int[] arr){
sort(arr);
}
static void sort(int[] arr){
int length = arr.length;
for (int i = 0; i < length-1; i++) {
int minIndex = i;
for (int j = i+1; j < length ; j++) {
if (arr[j] < arr[minIndex]){
minIndex = j;
}
}
swap(arr,i,minIndex);
}
}
static void swap(int[] arr,int i,int j){
int tmp = arr[j];
arr[j] = arr[i];
arr[i] = tmp;
}
}
排序前的数组:
[2, 3, 10, 8, 7]
排序后的数组:
[2, 3, 7, 8, 10]
时间复杂度:
O(N^2)
空间复杂度:
O(1)
稳定性:
适用场景:
选择排序是一种简单直观的排序算法,适用于小规模数据的排序。由于它的时间复杂度为O(n^2),因此不适合处理大量数据的排序任务。在实际应用场景中,选择排序可能被用于以下情况:
总体来说,选择排序虽然简单,但是其性能在大规模数据排序中不佳。因此,在实际应用中,更多地使用高效的排序算法,例如快速排序、归并排序和堆排序等
算法启蒙,没啥说的
package SwapSort;
import java.util.Arrays;
public class BubbleSort {
public static void main(String[] args) {
int[] arr = {2,3,10,8,7};
System.out.println("排序前的数组:");
System.out.println(Arrays.toString(arr));
bubbleSort(arr);
System.out.println("排序后的数组:");
System.out.println(Arrays.toString(arr));
}
static void bubbleSort(int[] arr){
for(int i=0; i< arr.length-1; i++){
boolean flag = false;
for(int j=0; j< arr.length-1-i; j++){
if (arr[j] > arr[j+1]){
swap(arr,j,j+1);
flag = true;
}
}
if (!flag){
break;
}
}
}
static void swap(int[] arr,int i,int j){
int tmp = arr[j];
arr[j] = arr[i];
arr[i] = tmp;
}
}
排序前的数组:
[2, 3, 10, 8, 7]
排序后的数组:
[2, 3, 7, 8, 10]
快排是Hoare于1962年提出的一种二叉树结构的交换排序方法
其基本思想为: 任取待排序元素序列中的某元素作为基准值
按照该排序码将待排序集合分割成两子序列
左子序列中所有元素均小于基准值
右子序列中所有元素均大于基准值
让后最左右子序列重复该过程
直到所有元素都排列在相应位置上为止
//递归形式
package SwapSort;
import java.util.Arrays;
public class QuickSort {
public static void main(String[] args) {
int[] arr = {6,8,3,9,11,33,22};
System.out.println("排序前的数组:");
System.out.println(Arrays.toString(arr));
quickSort(arr);
System.out.println("排序后的数组:");
System.out.println(Arrays.toString(arr));
}
static void quickSort(int[] arr){
quick(arr,0, arr.length-1);
}
private static void quick(int[] arr,int left, int right){
if (left >= right){
return;
}
int pivot = partition(arr,left,right);
quick(arr,0,pivot-1);
quick(arr,pivot+1,right);
}
private static int partition(int[] arr, int start, int end){
int i = start;
int key = arr[start];
while (start < end){
while (start < end && arr[end] >= key){
end--;
}
while (start < end && arr[start] <= key){
start++;
}
swap(arr,start,end);
}
swap(arr,start,i);
return start;
}
static void swap(int[] arr,int i,int j){
int tmp = arr[j];
arr[j] = arr[i];
arr[i] = tmp;
}
}
排序前的数组:
[6, 8, 3, 9, 11, 33, 22]
排序后的数组:
[3, 6, 8, 9, 11, 22, 33]
//循环
时间复杂度:
O(N * logN)
空间复杂度:
O(1)
稳定性:
不稳定
适用场景:
快速排序是一种高效的排序算法,它的时间复杂度为O(N*logN),在大多数情况下都能够快速地将数据排序。由于其实现简单,同时可扩展性强,因此被广泛应用于各种场景。
快速排序适用于以下场景:
总之,快速排序适用于大数据量、内部排序、不需要稳定性排序的场景,并且数据随机分布时效果最佳。
//快速排序非递归实现
static void quickSortNot(int[] arr){
Stack<Integer> stack = new Stack<>();
int left = 0;
int right = arr.length-1;
int pivot = partition(arr,left,right);
if (pivot > left+1){
//pivot左边元素大于一个
stack.push(left);
stack.push(pivot-1);
}
if (pivot < right -1){
//pivot右边大于一个
stack.push(pivot+1);
stack.push(right);
}
while (!stack.isEmpty()){
right = stack.pop();
left = stack.pop();
pivot = partition(arr,left,right);//找基准的函数
if (pivot > left+1){
//pivot左边元素大于一个
stack.push(left);
stack.push(pivot-1);
}
if (pivot < right -1){
//pivot右边大于一个
stack.push(pivot+1);
stack.push(right);
}
}
}
排序前的数组:
[6, 11, 55, 77, 88, 22, 33, 99, 10]
排序后的数组:
[6, 10, 11, 22, 33, 55, 77, 88, 99]
归并排序是一种常见的基于分治策略的排序算法,其主要思想是将待排序序列拆分为若干子序列,对每个子序列进行排序,然后再将已经排好序的子序列合并成一个有序序列。具体步骤如下:
归并排序的时间复杂度为 O(nlogn),其中 n 为待排序序列的长度。空间复杂度为 O(n),因为在排序过程中需要申请额外的数组存储有序序列,但是这些数组会随着归并过程不断被释放。归并排序是一种稳定的排序算法,它保证相等的元素在排序前后的相对位置不变。
归并排序适用于任何情况下的排序,尤其是在对数据量较大的序列进行排序时,其效率较高。同时,由于归并排序是一种稳定的排序算法,因此在需要保留相等元素顺序的场合也很常用。
package MergeSort;
import java.util.Arrays;
public class MergeSort {
public static void main(String[] args) {
int[] arr = {2,3,10,8,7};
System.out.println("排序前的数组:");
System.out.println(Arrays.toString(arr));
mergeSort(arr);
System.out.println("排序后的数组:");
System.out.println(Arrays.toString(arr));
}
static void mergeSort(int[] arr){
mergeSortFunc(arr,0,arr.length-1);
}
static void mergeSortFunc(int[] arr, int left, int right){
if (left >= right){
return;
}
int mid = (left + right)/2;
//1. 分解左边
mergeSortFunc(arr,left,mid);
//2. 分解右边
mergeSortFunc(arr,mid+1,right);
//3. 进行合并
merge(arr,left,right,mid);
}
private static void merge(int[] arr, int start, int end,int midIndex){
int[] tmpArr = new int[end-start+1];
int k = 0;//tmpArr数组的下标
int s1 = start;
int e1 = midIndex;
int s2 = midIndex+1;
int e2 = end;
while (s1 <= e1 && s2 <= e2){
//两个归并段都有数据
if (arr[s1] <= arr[s2]){
tmpArr[k++] = arr[s1++];
}else {
tmpArr[k++] = arr[s2++];
}
}
//把剩余的数据复制到数组
while (s1 <= e1){
tmpArr[k++] = arr[s1++];
}
while (s2 <= e2){
tmpArr[k++] = arr[s2++];
}
//把排好序的数字拷贝回原数组
for (int i=0; i<k; i++){
arr[i+start] = tmpArr[i];
}
}
}
排序前的数组:
[2, 3, 10, 8, 7]
排序后的数组:
[2, 3, 7, 8, 10]
//非递归实现
public class MergeSort {
public void sort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
int n = arr.length;
for (int i = 1; i < n; i = i << 1) {
for (int j = 0; j < n - i; j += (i << 1)) {
merge(arr, j, j + i - 1, Math.min(j + (i << 1) - 1, n - 1));
}
}
}
private void merge(int[] arr, int left, int mid, int right) {
int[] temp = new int[right - left + 1];
int i = left, j = mid + 1, k = 0;
while (i <= mid && j <= right) {
if (arr[i] <= arr[j]) {
temp[k++] = arr[i++];
} else {
temp[k++] = arr[j++];
}
}
while (i <= mid) {
temp[k++] = arr[i++];
}
while (j <= right) {
temp[k++] = arr[j++];
}
for (int p = 0; p < temp.length; p++) {
arr[left + p] = temp[p];
}
}
}
在这个示例中, sort 方法使用循环而不是递归来对数组进行排序。 它首先从每个元素(单独形成一组)开始,并通过合并较小的有序子序列来构建更大的有序子序列。 在每次迭代中, i 递增一个倍数,并且 j 每次递增 i << 1(等于 i * 2)来计算待合并的子序列的范围。 然后,将两个子数组合并为一个有序数组的过程与递归实现相同。
这种非递归的归并排序方法在实际应用中效率较高,尤其是在处理大型数据集时。
在这个示例中, sort
方法使用循环而不是递归来对数组进行排序。 它首先从每个元素(单独形成一组)开始,并通过合并较小的有序子序列来构建更大的有序子序列。 在每次迭代中, i
递增一个倍数,并且 j
每次递增 i << 1
(等于 i * 2
)来计算待合并的子序列的范围。 然后,将两个子数组合并为一个有序数组的过程与递归实现相同。
这种非递归的归并排序方法在实际应用中效率较高,尤其是在处理大型数据集时。
public class CountingSort {
public void sort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
int max = Integer.MIN_VALUE, min = Integer.MAX_VALUE;
for (int num : arr) {
max = Math.max(max, num);
min = Math.min(min, num);
}
int[] count = new int[max - min + 1];
for (int num : arr) {
count[num - min]++;
}
for (int i = 1; i < count.length; i++) {
count[i] += count[i - 1];
}
int[] sorted = new int[arr.length];
for (int i = arr.length - 1; i >= 0; i--) {
sorted[--count[arr[i] - min]] = arr[i];
}
System.arraycopy(sorted, 0, arr, 0, arr.length);
}
}
在这个示例中, sort 方法接收一个整数数组作为参数,并执行计数排序算法以对其进行排序。 它首先找出最大值和最小值,创建并初始化长度为 max - min + 1 的计数数组。之后遍历原始数组,将每个元素减去 min 作为下标,在计数数组对应的位置上计数。然后通过累加计数数组来确定有序序列中每个元素应该出现的位置。 最后,反向遍历原始数组,并根据其在计数数组中的值从有序序列的末尾开始将其放置到相应的位置上。
桶排序是一种线性排序算法,适用于待排序元素在一个比较小的区间内。它将区间划分为若干个桶(bucket),对于每个桶收集区间内符合特定范围的元素,再对每个桶中的元素进行排序,最后遍历每个桶,按照顺序把所有元素依次放回原来的序列中。
具体实现过程如下:
max
和最小值 min
;由于排序过程涉及到创建和维护多个桶以及对每个桶中的元素进行排序,因此桶排序的时间复杂度通常为 O(n+k),其中 n 为待排序序列的长度,k 是桶的数量。空间复杂度也很高,为 O(n+k)。
以下是Java代码实现桶排序的示例:
public class BucketSort {
public void sort(double[] arr) {
if (arr == null || arr.length < 2) {
return;
}
double max = Double.NEGATIVE_INFINITY, min = Double.POSITIVE_INFINITY;
for (double num : arr) {
max = Math.max(max, num);
min = Math.min(min, num);
}
int bucketNum = (int) Math.ceil((max - min) / arr.length) + 1;
List<List<Double>> buckets = new ArrayList<>(bucketNum);
for (int i = 0; i < bucketNum; i++) {
buckets.add(new ArrayList<>());
}
for (double num : arr) {
int index = (int) ((num - min) / arr.length * (bucketNum - 1));
buckets.get(index).add(num);
}
for (List<Double> bucket : buckets) {
Collections.sort(bucket);
}
int index = 0;
for (List<Double> bucket : buckets) {
for (double num : bucket) {
arr[index++] = num;
}
}
}
}
在这个示例中, sort 方法接收一个双精度浮点数组作为参数,并执行桶排序算法以对其进行排序。 它首先找出最大值和最小值,然后根据 arr.length 计算桶数量,并创建相应数量的桶。然后遍历待排序序列,将每个元素映射到相应的桶中。 每个桶都可以使用标准库中的任何排序算法进行排序,但在这个示例中使用了 Collections.sort() 方法。 最后,按照顺序遍历所有桶并将其中的元素复制回原始序列中。
桶排序最适用的情况是待排序元素在一个确定的区间内且分布越均匀越好,其时间复杂度主要受到桶的数量影响。
这种算法通常用于解决一些计数问题,如成绩统计、年龄分布等。例如,如果有一份成绩单,里面包含了许多学生的分数,而分数的范围是 0 到 100 分。现在要计算每种分数出现的次数,可以使用桶排序算法:将 0 到 100 分的范围划分为 101 个区间,每个区间作为一个桶,并将分数根据其所属的区间放入相应的桶中,然后遍历每个桶,统计其中元素的数量即可。
桶排序还可以与其他排序算法结合使用以提高效率。例如,在一个整数数组中,如果待排序元素在一个较小的范围内(例如 0-65535),可以使用桶排序按照元素值进行排序; 如果待排序元素的范围比较大,则可以使用基数排序(radix sort)先对元素的各个位数进行排序,然后再使用桶排序。
基数排序(Radix Sort)是一种非比较排序算法,适用于待排序元素的位数固定的情况。
基数排序的主要思想是:将待排序元素按照其位数从低到高依次进行排序。具体实现过程如下:
d
;在每次排序时,可以使用任何一种稳定的排序算法,如计数排序、桶排序等。对于位数小于 d
的数字,可以在前面填充零以与其他数字位数保持一致。当然,也可以从高位到低位排序,这取决于具体实现。
基数排序的时间复杂度为 O(d*(n+k)),其中 d 是待排序元素的最大位数,n 是待排序序列长度,k 是基数,即数据范围。空间复杂度为 O(n+k)。
以下是Java代码实现基数排序的示例:
public class RadixSort {
public void sort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
int max = Integer.MIN_VALUE;
for (int num : arr) {
max = Math.max(max, num);
}
int maxLength = String.valueOf(max).length();
int mod = 10, div = 1;
List<List<Integer>> buckets = new ArrayList<>(mod);
for (int i = 0; i < mod; i++) {
buckets.add(new ArrayList<>());
}
for (int i = 0; i < maxLength; i++, mod *= 10, div *= 10) {
for (int num : arr) {
buckets.get((num % mod) / div).add(num);
}
int index = 0;
for (List<Integer> bucket : buckets) {
for (int num : bucket) {
arr[index++] = num;
}
bucket.clear();
}
}
}
}
在这个示例中, sort 方法接收一个整数数组作为参数,并执行基数排序算法以对其进行排序。 它首先找出最大值,并确定位数,然后创建相应数量的桶。然后通过循环按照从低到高的顺序依次比较每个数字的各个位上的数值,将其分配到相应的桶中,从桶中取出结果并复制回原数组中。循环结束后,原始数组已经按照指定的列数排序完成。
在互联网领域,使用最广泛的排序算法如下:
总之,对于互联网领域的排序需求,选用不同的排序算法主要考虑数据规模、排序稳定性、算法效率等方面的因素。