堆排序
构建最大堆(根节点大于左右子节点)或最小堆(根节点小于左右子节点),每次获取堆的根节点(将根接点和最后一个叶子节点交换),然后忽略原根节点,把剩余节点重新构建堆。重复上述步骤。
public void sort(int[] array) {
int len = array.length;
//找到最后一个非叶子节点len/2-1
for (int i=len/2-1; i>=0; i--){
//从下往上构建最小堆
minHeap(array, i, len-1);
}
//每次获取最小堆的根节点,与最后一个节点交换,然后重新构建最小堆
for (int i=len-1; i>=0; i--) {
int tmp = array[0];
array[0] = array[i];
array[i] = tmp;
minHeap(array, 0, i-1);
}
}
//构建最小堆
public void minHeap(int[] array, int start,int end) {
int tmp = array[start];
//如果非叶子节点为i,它的左右子节点分别为2i+1,2i+2
//从start开始,如果子节点比父节点大,则交换
for (int i=start*2+1; i<=end; i=i*2+1) {
//找到左右子节点中较小的一个
if (i<end && array[i]>array[i+1]) {
i++;
}
if (tmp<=array[i]) {
break;
}else {
array[start] = array[i];
start = i;
}
}
array[start] = tmp;
}
快速排序
public void sort(int[] nums) {
quickSort(nums, 0, nums.length - 1);
}
public void quickSort(int[] nums, int left, int right){
//递归停止条件:需要排序的数组长度为1
if (left>=right){
return;
}
//定义左右指针
int i = left, j = right;
//定义基准数
int base = nums[left];
while (i < j){
//由于基准数为第一个数,所以右指针先移动,左移找到第一个小于等于基准的数或者直接走到左指针位置
while (j>i && nums[j]>base){
j--;
}
//左指针右移找到第一个大于基准的数或者与右指针重合
while (i<j && nums[i]<=base){
i++;
}
//交换 左右指针位置对应的数,使小于基准的数在左边,大于基准的数在右边
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
//将基准数与左右指针重合的数交换,交换完成后
//基准数左边的数都小于基准数,基准数右边的数都大于基准数
nums[left] = nums[i];
nums[i] = base;
//递归的对左子数组和右子数组进行排序
quickSort(nums, left, i - 1);
quickSort(nums, i + 1, right);
}
冒泡排序
//时间复杂度:O(n^2)
//空间复杂度:O(1)
//稳定性:稳定
//特点:相邻元素两两比较,大的元素往后放,一次遍历后最大的元素就到了最后
//缺点:效率低,时间复杂度为O(n^2)
//优点:算法简单,容易理解
//适用场景:数据量较小,数据基本有序
//冒泡排序的优化:
//1.设置一标志性变量t,用于记录每趟排序中是否发生了交换,如果某趟排序中没有发生交换,则说明数据已经有序,不用再进行后续的排序,直接结束排序。
public void bubbleSort(int[]arr){
int temp;
//第一个for循环控制排序趟数,
for(int i=0;i<arr.length-1;i++){
//第二个for循环控制需要排序的数字个数
//因为每趟排序都会将一个最大或最小值放到最后,所以需要排序的数字个数-i
for(int j=0;j<arr.length-i-1;j++){
if(arr[j]>arr[j+1]){
temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
}
}
选择排序
//时间复杂度:O(n^2)
//空间复杂度:O(1)
//稳定性:不稳定。举例5 8 5 2 9,第一遍选择第1个元素5会和2交换,那么原序列中两个5的相对前后顺序就被破坏了。
//特点:第i趟排序从第i个数字开始,找出最小的元素,与第i个元素交换位置
public void selectionSort(int[]arr){
int min;
//第一个循环控制次数
for(int i=0;i<arr.length-1;i++){
min=i;
//第二个循环找到剩余数中的最小值
for(int j=i+1;j<arr.length;j++){
if(arr[j]<arr[min]){
min=j;
}
}
//将最小值放到剩余数中的第一个
if(min!=i){
int temp=arr[i];
arr[i]=arr[min];
arr[min]=temp;
}
}
}
插入排序
//时间复杂度:O(n^2)
//空间复杂度:O(1)
//稳定性:稳定
public void insertionSort(int[]arr){
int temp;
//第一个循环控制需要插入的次数,第一个数视为有序
for(int i=1;i<arr.length;i++){
temp=arr[i];
int j=i-1;
//第二个循环找到当前数在有序数组中的位置
while(j>=0&&temp<arr[j]){
//将大于temp的数向后移动一位
arr[j+1]=arr[j];
j--;
}
//将temp放到正确的位置
arr[j+1]=temp;
}
}
希尔排序
//时间复杂度:O(n^1.3)
//空间复杂度:O(1)
//稳定性:不稳定,单词插入排序是稳定的,但是多次的分成子序列后多次的插入排序,会改变相同元素的相对关系。
//希尔排序在插入排序的基础上进行了改进,它的基本思路是先将整个数据序列分割成若干子序列分别进行直接插入排序,待整个序列中的记录基本有序时,再对全部数据进行依次直接插入排序。
public void shellSort(int[]arr){
int temp;
//第一个循环控制分割子序列的次数,如果数组长度为10,那么第一次gap=5,分成5组,分别进行排序
for(int gap=arr.length/2;gap>0;gap/=2){
//第二三个循环控制对每个子序列进行直接插入排序
//length=10,gap=5
//那么这次循环会分成0,5;1,6;2,7;3,8;4,9五个子序列
//下一次循环时,gap:5/2=2。
//分成0,2,4,6,8;1,3,5,7,9两个序列
//由于插入排序是从前往后插入,所以前面的序列已经有序。
//例如在对第4个数做插入的时候,第0和第2个数已经有序。
for(int i=gap;i<arr.length;i++){
temp=arr[i];
int j=i-gap;
//第三个循环找到当前数在有序数组中的位置
while(j>=0&&temp<arr[j]){
//将大于temp的数向后移动一位
arr[j+gap]=arr[j];
j-=gap;
}
//将temp放到正确的位置
arr[j+gap]=temp;
}
}
}
归并排序
//时间复杂度:O(nlogn)
//空间复杂度:O(n),最终会把数组分割成n个长度为1的子数组
//稳定性:稳定
//二分法,将数组不断分割,直到只剩一个元素,然后重新合并。
public void mergeSort(int[]arr,int left,int right){
if(left<right){
int mid=(left+right)/2;
//左边归并排序,使得左子序列有序
mergeSort(arr,left,mid);
//右边归并排序,使得右子序列有序
mergeSort(arr,mid+1,right);
//将两个有序子数组合并操作
merge(arr,left,mid,right);
}
}
public void merge(int[]arr,int left,int mid,int right){
int[]temp=new int[right-left+1];
int i=left;
int j=mid+1;
int k=0;
//比较左右两部分的元素,哪个小,就将这个数放入temp中,然后移动指针继续比较,
//直到左右两部分的元素全部放入temp中,再将temp中的元素拷贝给原数组
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++];
}
System.arraycopy(temp, 0, arr, left, temp.length);
}
计数排序
// 原理:
// 1. 找出待排序的数组中最大和最小的元素
// 2. 统计数组中每个值为i的元素出现的次数,存入数组
// 3. 对所有的计数累加(从1开始)
// 4. 反向填充目标数组:将每个元素i放在新数组的第Ci个位置上
// 空间复杂度O(n+k),时间复杂度O(n+k),其中k为待排序数组的最大值。
// 稳定性:稳定
public static int[] countSort(int[] arr) {
int maxVal = 0;
int minVal = 1000;
for (int num : arr) {
maxVal = Math.max(maxVal, num);
minVal = Math.min(minVal, num);
}
//创建一个统计数组,数组长度为(maxVal - minVal + 1)
// 数组index为排序数组的值相对于最小值的位置,
// 数组value为该值出现的次数
int bucketCount = maxVal - minVal + 1;
int[] count = new int[bucketCount];
for (int num : arr) {
count[num - minVal]++;
}
//计算在排序数组中,每个元素应该放置的位置
for (int i = 1; i < bucketCount; i++) {
count[i] += count[i - 1];
}
int[] sortedArr = new int[arr.length];
//反向填充排序数组。否则如果出现两个相同的数,后一个会被放在前面的位置,导致错误
for (int i = arr.length - 1; i >= 0; i--) {
sortedArr[count[arr[i] - minVal] - 1] = arr[i];
count[arr[i] - minVal]--;
}
return sortedArr;
}
基数排序
// 基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。
//稳定
//时间复杂度O(d(n+r)),d为位数,r为基数。在基数为10时,d = log10(max) + 1。所以时间复杂度也可看成
//O(nLOGr(max))
//空间复杂度O(n+r)
private void radixSort(int[] arr) {
//获取数组最大值
int maxVal = arr[0];
for (int num : arr) {
if (num > maxVal) {
maxVal = num;
}
}
// 计算最大位数
int maxDigit = 0;
while (maxVal > 0) {
maxVal /= 10;
maxDigit++;
}
// 进行d次排序
for (int i = 0; i < maxDigit; i++) {
count_sort(arr, arr.length, (int) Math.pow(10, i));
}
}
public void count_sort(int[] a, int n, int exp)
{
// 存储"被排序数据"的临时数组
int[] output = new int[n];
int i;
int[] buckets = new int[10];
Arrays.fill(buckets, 0);
// 将数据出现的次数存储在buckets[]中
for (i = 0; i < n; i++)
//(a[i]/exp)%10 获取第i位的数字
buckets[ (a[i]/exp)%10 ]++;
// 计算数据在output[]中的位置。
for (i = 1; i < 10; i++)
buckets[i] += buckets[i - 1];
// 将数据存储到临时数组output[]中,从后往前
for (i = n - 1; i >= 0; i--)
{
output[buckets[(a[i]/exp)%10]-1] = a[i];
buckets[ (a[i]/exp)%10 ]--;
}
// 将排序好的数据赋值给a[]
for (i = 0; i < n; i++)
a[i] = output[i];
}
桶排序
//时间复杂度分析
//假如排序数组的元素个数为n,均匀分布在个数为m的桶中,那么每个桶中的元素个数为k=n/m
// 假设桶内使用的是归并排序,那么每个桶快速排序的时间复杂度为klogk即n/mlog(n/m)
// 那么m个桶一起就是nlog(n/m),假如桶的个数m跟元素个数n十分接近,那么最终的时间复杂度为O(n).
//如果极端情况下,所有的元素n个都分在一个桶里,这种情况下时间复杂度就退化成O(nlogn)了
//空间复杂度:O(n+k)
//稳定性:稳定,主要看桶内部排序的算法
public void bucketSort(int[] nums) {
int n = nums.length;
int mn = nums[0], mx = nums[0];
// 找出数组中的最大最小值
for (int i = 1; i < n; i++) {
mn = Math.min(mn, nums[i]);
mx = Math.max(mx, nums[i]);
}
// 计算平均差值,作为每个桶存储数的范围大小,使得数尽量均匀地分布在各个桶中,保证最少存储一个
int size = (mx - mn) / n + 1;
// 桶的个数,保证桶的个数至少为1
int cnt = (mx - mn) / size + 1;
// 创建桶
List<List<Integer>> buckets = new ArrayList<>();
for (int i = 0; i < cnt; i++) {
buckets.add(new ArrayList<>());
}
// 扫描一遍数组,将数放进桶里
for (int i = 0; i < n; i++) {
int idx = (nums[i] - mn) / size;
buckets.get(idx).add(nums[i]);
}
// 对各个桶中的数进行排序,这里用库函数,数量小于7时,库函数排序算法为插入排序,否则为归并排序
//这里也可以自己实现快速排序等其他排序算法
for (int i = 0; i < cnt; i++) {
buckets.get(i).sort(null);
}
// 依次将各个桶中的数据放入返回数组中
int index = 0;
for (int i = 0; i < cnt; i++) {
for (int j = 0; j < buckets.get(i).size(); j++) {
nums[index++] = buckets.get(i).get(j);
}
}
}