Java排序算法汇总

在这里插入图片描述

  • 稳定的定义:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,ri=rj,且ri在rj之前,而在排序后的序列中,ri仍在rj之前,则称这种排序算法是稳定的;否则称为不稳定的。

  • 稳定的意义:排序的内容是一个复杂对象的数字属性,且其原本的初始顺序存在意义,那么在二次排序的基础上保持原有排序,就是稳定的。比如商品销量相同,价格不同,原本已经按价格排好,现在按销量排,就要尽量避免相同销量的商品相对位置改变,要使它们价格顺序不变。

  • 快速排序、希尔排序、选择排序、堆排序是不稳定的排序算法

  • 而基数排序、冒泡排序、插入排序、折半插入排序、归并排序是稳定的排序算法
    (快些选一堆美女一起玩儿——不稳定)

【面试题】在某种情况下,哪种排序算法最快?

  • 如果不在乎浪费空间,应该是桶排序最快
  • 如果整体基本有序**(升序),插入排序最快**,如果降序,则为最慢
  • 如果考虑综合情况,快速排序更加实用常见(希尔排序、堆排序等各种排序也各有优劣)
  • 数据量大,数据分布比较随机,用快速排序,要求稳定则用归并
  • 冒泡排序:数据量不大,对稳定性有要求,且数据基本有序;
  • 选择排序:数据量不大,且对稳定性没有要求。

一、冒泡排序算法

步骤:
(1)比较前后两个数据,若前面的大于后面的,则交换;
(2)遍历后,最大的数据“沉到了数组N-1位置
(3)最大的数据搞定后,对其它的N-1个数据进行处理:N=N-1,只要N>0,则重复前面。

最坏时间复杂度:O(n ^2),最好时间复杂度:O(n),平均时间复杂度:O(n ^2)
空间复杂度:O(1)

public class 冒泡排序 
{
	public static void bubbleSort(int[] a,int N)
	{
		int i,j;
		for(i=0;i<N;i++) //全部遍历一遍
		{
			for(j=1;j<N-i;j++) //比较前后两个数据,自1始,到n-i结束
		    {
			    if(a[j-1]>a[j])
				{
					int temp;
					temp = a[j-1];
					a[j-1] = a[j];
					a[j] = temp;
				}
		    }
		}
	}

	public static void main(String[] args)
	{
		int[] a={3,6,8,9,1,4,5};
		System.out.println("按从小到大顺序排列为:");
		bubbleSort(a,a.length);
		for(int i=0;i<a.length;i++)
		{
			System.out.print(a[i] + " ");
		}
	}
}

在这里插入图片描述

二、插入排序算法

原理:将要插入的牌从右到左地比较,若原位置的牌更大,则与更前面的牌对比(原位置的后移一个),直到遇到比要插入的数小的,则要插入的数放在该位置的后面一个。
最坏时间复杂度:O(n^2),最好时间复杂度:O(n)
在这里插入图片描述
在这里插入图片描述

//插入排序法,从小到大排列
public class insertSort {

	public static void main(String[] args) {
		int[] a = {2,7,9,4,1,6,5}; 
		insert(a);
		for (int i : a) {
			System.out.print(i+" ");;
		}
	}
	
	public static int[] insert(int[] a) {
		for(int i=1;i<a.length;i++) {//选中i位置的值,将其与i之前的数挨个比较
			for(int j=i;j>0;j--) {
				if(a[j]<a[j-1]) {
					int temp;
					temp = a[j-1];
					a[j-1] = a[j];
					a[j] = temp;
				}
			}
		}
		return a;
	}
}

最佳情况:T(n) = O(n) 最坏情况:T(n) = O(n2) 平均情况:T(n) = O(n2)

三、选择排序

原理:从数组中选择最小的元素,与第一个元素交换位置;再从剩余数组中选择最小的,与第二个元素交换位置,直到实现从小到大排列。

public class SelectionSort {

	public static void main(String[] args) {
		int[] a = {2,6,8,4,1,5,3};
		Selection(a);
		for (int i : a) {
			System.out.print(i+" ");
		}
		
	}
	
	public static void Selection(int[] a) {
		//寻找最小值
		for(int i=0;i<7-1;i++) {
			int min = i;
			for(int j=i+1;j<7;j++) {//寻找当前数组的最小元素下标
				if(a[j]<a[min]) {
					min=j;
				}
			}
			swap(a,min, i);
		}
	}
	//——Java对普通类型的变量是不支持引用传递的,这里需借用数组来实现交换!
	public static void swap(int[] a, int i,int j) {
		int temp = a[j];
		a[j] = a[i];
		a[i] = temp;
	}
}

四、快速排序算法(*)

原理:
1、先从数列中取出一个数作为基准数;
2、分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边;——二分
3、再对左右区间重复第二步,直到各区间只有一个数,此时数组有序。

1)设置两个变量i、j,排序开始的时候:i=0,j=n-1;
2)第一个数组值作为比较值,首先保存到temp中,即temp=A[0];
3)然后j-- ,向前搜索,找到小于temp的数后,停下来;
4)然后i++,向后搜索,找到大于temp的数后,停下来, 交换:s[j]=s[i]
5)继续重复第3、4步,直到i=j,最后使得s[i]=temp
6) 然后采用“二分”的思想,以i为分界线,拆分成两个数组 s[0,i-1]、s[i+1,n-1]又开始进行一遍上述排序

具体步骤:
图片来源于:https://blog.csdn.net/qq_40941722/article/details/94396010
(1)以i=0,j=n-1分别指向序列两端,以A[0]=temp作为基准值;
在这里插入图片描述
(2)j–从后往前搜索,找到比基准值小的后停下来,i++从前向后搜索,找到比基准值大的停下来
在这里插入图片描述
(3)交换A[i]=A[j]
在这里插入图片描述
(4)j和i继续移动(j先移动),则交换9和4,得到如下:
在这里插入图片描述
(5)直到i=j后,使A[i]=A[j]=基准值,此时基准值左边的数都比它小,右边的数都比它大,对于基准值来说,左右两边就有序了。
在这里插入图片描述
(6)采用“二分”的思想,重新选取基准值,使用递归的方法,对左右两边的序列重复上述排序:
在这里插入图片描述
(7)得到:
在这里插入图片描述
最终得到有序序列:
在这里插入图片描述
其完全具体步骤如图:
在这里插入图片描述
最坏时间复杂度:O(n^2),平均时间复杂度:O(nlogn)
空间复杂度:O(n
logn)

import java.util.*;

public class Main {
    public static void main(String[] args) {
		int[] a = {6,1,2,7,9,3,4,5,10,8};

		quickSort(a, 0, a.length - 1);
		for(int i=0;i<a.length;i++){
			System.out.print(a[i]+" ");
		}
		System.out.println();
		//System.out.print(Arrays.toString(a));
    }

	public static void quickSort(int[] a,int low,int high){
		if(low > high){
			return;
		}
		int i = low;
		int j = high;
		int key = a[low];//基准值
		while(i < j){//循环至i=j
			//(1)向前搜索,找到比基准值小的,停下来
			while(a[j] >= key && i < j){
				j--;
			}
			//(2)向后搜索,找到比基准值大的,停下来
			while(a[i] <= key && i < j){
				i++;
			}
			//(3)此时交换两者
			if(i < j){
				int temp = a[i];
				a[i] = a[j];
				a[j] = temp;
			}
		}
		//(4)此时i=j,将此处的数a[i]=a[j]和基准值key交换
		a[low] = a[i];
		a[i] = key;
		//(5)二分,左右分别递归
		quickSort(a, low, i - 1);
		quickSort(a, j + 1, high);	
	}
}

4.1 利用快排思想,找出第K大的数

import java.util.*;

public class Finder {
    public int findKth(int[] a, int n, int K) {
        // write code here
        return findK(a, 0, n-1, K);
    }

    public static int partition(int[] arr, int left, int right) {
        int pivot = arr[left];

        while (left < right) {
            while (left < right && arr[right] <= pivot) {
                right--;
            }
            arr[left] = arr[right];
            while (left < right && arr[left] >= pivot) {
                left++;
            }
            arr[right] = arr[left];
        }
        arr[left] = pivot;
        return left;
    }
    public static int findK(int[] arr, int left, int right, int k) {
        if (left <= right) {
            int pivot = partition(arr, left, right);

            if (pivot == k - 1) {
                return arr[pivot];
            } else if (pivot < k - 1) {
                return findK(arr, pivot + 1, right, k);
            } else {
                return findK(arr, left, pivot - 1, k);
            }
        }
        return -1;
    }
}

五、归并排序(*)

原理:归并(递归、层层合并)排序法(Merge)即把待排序序列分为若干个子序列每个子序列是有序的。然后再把有序子序列合并为整体有序序列
——分治法,各层分治递归

【时间复杂度】:归并排序最好、最差和平均时间复杂度都是O(n*logn),因为每次划分时两个子序列的长度基本一样。

//归并排序法,从小到大排列.
//把待排序序列分为若干个子序列,每个子序列是有序的。
//然后再把有序子序列合并为整体有序序列。
public class 练习题1_1 
{
	public static void mergeSort(int[] a)
	{
		sort(a,0,a.length-1);
	}

	public static void sort(int[] a, int left, int right)
	{
		// left:左数组第一个元素索引;right:右数组最后一个元素索引
		if(left>=right) return;//递归边界条件
		int center = (left + right)/2;//中间索引,划分子序列
		sort(a,left,center);//左边递归
		sort(a,center+1,right);//右边递归
		merge(a,left,center,right);//合并
	}
	//对两个已经有序的数组进行归并
	public static void merge(int[] a,int left,int center,int right)
	{
		int[] tempArr = new int[a.length];//临时数组
		int mid = center +1;//右边第一个元素索引
		int third = left;//记录临时数组的索引
		int temp = left;//缓存左边第一个元素的索引
		while(left<=center && mid <= right)
		{
			//从两个数组中取出最小的放入临时数组中
			if(a[left]<=a[mid])
			{
				tempArr[third++] = a[left++];
			}
			else
			{
				tempArr[third++] = a[mid++];
			}
		}
        //剩余部分依次放入临时数组
		while(mid<=right)
		{
			tempArr[third++] = a[mid++];
		}
		while(left <= center)
		{
			tempArr[third++] = a[left++];
		}
		//将临时数组中的内容拷贝回原数组中
		while(temp <= right)
		{
			a[temp]=tempArr[temp++];
		}
	}
}

六、堆排序

大顶堆与小顶堆

大顶堆:每个结点都大于其左右孩子结点
小顶堆:每个结点都小于其左右孩子结点

升序:大顶堆(因为顶部大的先出去)
降序:小顶堆

堆排序基本步骤

步骤一:构造初始堆。将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆);
步骤二:最大值为根节点,将堆顶与末尾元素进行交换,使末尾元素最大,然后继续调整堆(将剩下的n-1个元素重新构建成大顶堆),再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换,直到整个序列有序。

具体步骤

步骤一:构造初始堆:

  1. 初始元素为:[4,6,8,5,9],
    在这里插入图片描述

  2. 从最后一个非叶子结点开始:6,从左至右,从上至下进行调整(与其叶子结点进行对比,交换);
    在这里插入图片描述
    3)找到第二个非叶子结点:4,进行调整:
    在这里插入图片描述

4)再次调整最后一个非叶子结点
在这里插入图片描述
此时,无序序列就被构造成一个大顶堆了。

步骤二:最大值为根节点,将堆顶与末尾元素进行交换,使末尾元素最大
非叶子结点的索引:arr.length/2-1

package erchashu;
import java.util.Arrays;

public class HeapSort {

	public static void main(String[] args) {
		
		int[] arr = {4,6,8,5,9,1,23,15,7};//要求将该数组升序排列
		heapSort(arr);
	} 
	
	//堆排序方法
	public static void heapSort(int[] arr) {
		System.out.println("堆排序:");
		
		//构建第一个大顶堆
		for(int i = arr.length/2-1;i>=0;i--) {//i=arr.length/2-1
			adjustHeap(arr,i,arr.length);
		}
		System.out.println("最初调整得到大顶堆数组="+Arrays.toString(arr));
		
		//头尾交换
		for(int j=arr.length-1; j>0 ;j--) {
			//交换大顶堆顶部和末尾元素
			int temp = arr[j];
			arr[j]=arr[0];
			arr[0] = temp;
			//再调成大顶堆
			adjustHeap(arr, 0, j);
		}
		System.out.println("最终结果="+Arrays.toString(arr));
	}
	
	//将二叉树调整成大顶堆:将以 i 对应非叶子结点的树调整为大顶堆
	//i:非叶子结点在数组中的索引,length:对多少个元素进行调整(逐渐减少)
	public static void adjustHeap(int[] arr,int i,int length) {
		int temp = arr[i];//先取出当前非叶子结点元素的值,保存在临时变量中
		//i为要调的非叶子结点,k为其左子结点
		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;
			}
		}
		//for循环结束后,此时已经将以i为父结点的局部树的最大值调整到了顶部
		arr[i]=temp;//将父结点放到调整后的位置
	}
}

时间复杂度全是:O(nlogn)

6.1 利用大顶堆找出第K大的数

class Solution {
    public  int findKthLargest(int[] nums, int k) {
        return  heapSort(nums, k);
    }
    private  int heapSort(int[] nums, int k) {
        //将无序数组构成一个大顶堆
        for (int i = nums.length / 2 - 1; i >= 0; i--) {
            adjustHeap(nums, i, nums.length);
        }
        int count = 0;
        for (int j = nums.length - 1; j >= 0; j--) {
            count++;
            int temp = nums[j];
            nums[j] = nums[0];
            if (count == k) {
                return nums[0];
            }
            nums[0] = temp;
            adjustHeap(nums, 0, j);
        }
        return 0;
    }
    private  void adjustHeap(int[] nums, int i, int length) {
        int temp = nums[i];
        for (int k = i * 2 + 1; k < length; k = k * 2 + 1) {
            if (k + 1 < length && nums[k + 1] > nums[k]) {
                k++;
            }
            if (nums[k] > temp) {
                nums[i] = nums[k];
                i = k;
            } else {
                break;
            }
        }
        nums[i] = temp;
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值