排序算法
总览:
选择排序
代码思路
不断选择最值将其放在最终的位置上
特点
数据移动最少,运行时间与数据输入时的顺序无关
复杂度
- 时间复杂度:O(n²)
使用环境
数据量少
算法代码
private static void sort(int[] nums){
int n=nums.length;
int minIndex,temp;
for(int i=0;i<n-1;i++){
minIndex=i;
for(int j=i+1;j<n;j++){
if(nums[j]<nums[minIndex]){
minIndex=j;
}
}
temp=nums[i];
nums[i]=nums[minIndex];
nums[minIndex]=temp;
}
}
插入排序
代码思路
像抓牌一样,将牌暂时放入现在的适当的位置。当到最后一个元素时就排序完成了
特点
运行时间与输入情况有关,对于一个部分有序(数组中元素离最终位置都不远,或者一个有序的大数组加一个小数组)来说速度比较快
复杂度
- 时间复杂度:O(n²)
使用环境
待排序数组基本有序
算法代码
private static void sort(int[] nums) {
int n=nums.length;
int preIndex,current;
for(int i=1;i<n;i++){
preIndex=i-1;
current=nums[i];
while (preIndex>=0&&nums[preIndex]>current){
nums[preIndex+1]=nums[preIndex];
preIndex--;
}
nums[preIndex+1]=current;
}
}
冒泡排序
代码思路
十分简单,重复访问,依次比较进行交换
- 比较相邻元素,大就交换
- 从第一对开始,到最后一对,一次排序后保证最大的位于末尾
- 重复n次
特点
思路简单
复杂度
- 时间复杂度:
使用环境
无
算法代码
private static void sort(int[] nums) {
int n=nums.length;
for(int i=0;i<n-1;i++){
for(int j=0;j<n-i-1;j++){
if(nums[j]>nums[j+1]){
int temp=nums[j];
nums[j]=nums[j+1];
nums[j+1]=temp;
}
}
}
}
希尔排序(缩小增量排序)
代码思路
使数组中任意间隔为h的元素是有序的。最后对于一个以1结尾的的h序列我们都能够将其排序。
- 先将整个待排序的记录序列分隔成若干子序列,分别进行直接插入排序
- 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
- 按增量序列个数k,对序列进行k 趟排序;
- 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
特点
基于插入排序的快速排序
复杂度
- 时间复杂度:
使用环境
大型数组,大多数情况下希尔排序都是比较高效且简单的算法
算法代码
private static void sort(int[] nums) {
int n=nums.length;
int gap=n/2;
while (gap>0){
for(int j=gap;j<n;j++){
int i=j;
while ((i>=gap&&nums[i-gap]>nums[i])){
int temp=nums[i];
nums[i]=nums[i-gap];
nums[i-gap]=temp;
i-=gap;
}
}
gap=gap/2;
}
}
归并排序
代码思路
建立在归并操作上的一种算法,使用分治法的思想将小的有序的数组归并为一个大的数组
- 把长度为n的输入序列分成两个长度为n/2的子序列;
- 对这两个子序列分别采用归并排序;
- 将两个排序好的子序列合并成一个最终的排序序列。
特点
速度较快,不受输入数据的影响,所需额外空间与N成正比
复杂度
- 时间复杂度:O()
- 空间复杂度:O(n)
使用环境
算法代码
private static void sort(int[] nums,int start,int end) {
if(start<end){
int mid=(start+end)/2;
sort(nums,start,mid);
sort(nums,mid+1,end);
merge(nums,start,mid,end);
}
}
private static void merge(int[] nums,int left,int mid,int right){
int []tmp=new int[nums.length];
int p1=left,p2=mid+1,k=left;
while (p1<=mid && p2<=right){
if(nums[p1]<=nums[p2])
tmp[k++]=nums[p1++];
else
tmp[k++]=nums[p2++];
}
while (p1<=mid)
tmp[k++]=nums[p1++];
while (p2<=right)
tmp[k++]=nums[p2++];
for (int i=left;i<=right;i++)
nums[i]=tmp[i];
}
快排(三取样切分)
代码思路
通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
- 从数列中挑出一个元素,称为 “基准”(pivot);
- 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
- 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
特点
应用广泛,原地排序,几乎不需要额外的空间
复杂度
- 时间复杂度:
使用环境
广泛
算法代码
private static void sort(int[] nums,int start,int end) {
if(nums.length<0)
return;
if(start>=end)
return;
int left=start;
int right=end;
int temp=nums[left];
while (left<right){
while (left<right && nums[right]>=temp)
right--;
nums[left]=nums[right];
while (left<right && nums[left]<=temp)
left++;
nums[right]=nums[left];
}
nums[left]=temp;
sort(nums,start,left-1);
sort(nums,left+1,end);
}
堆排序
代码思路
利用堆这种数据结构,堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
- 将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的无序区;
- 将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n];
- 由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。
特点
复杂度
- 时间复杂度:
使用环境
算法代码
public static void sort(int[] list) {
//构造初始堆,从第一个非叶子节点开始调整,左右孩子节点中较大的交换到父节点中
for (int i = (list.length) / 2 - 1; i >= 0; i--) {
headAdjust(list, list.length, i);
}
//排序,将最大的节点放在堆尾,然后从根节点重新调整
for (int i = list.length - 1; i >= 1; i--) {
int temp = list[0];
list[0] = list[i];
list[i] = temp;
headAdjust(list, i, 0);
}
}
private static void headAdjust(int[] list, int len, int i) {
int k = i, temp = list[i], index = 2 * k + 1;
while (index < len) {
if (index + 1 < len) {
if (list[index] < list[index + 1]) {
index = index + 1;
}
}
if (list[index] > temp) {
list[k] = list[index];
k = index;
index = 2 * k + 1;
} else {
break;
}
}
list[k] = temp;
}
计数排序
代码思路
计数排序不是基于比较的排序算法,其核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。 作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。
- 找出待排序的数组中最大和最小的元素;
- 统计数组中每个值为i的元素出现的次数,存入数组C的第i项;
- 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加);
- 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1。
特点
复杂度
- 时间复杂度:O(n+k)
- 空间复杂度:O(n+k)
使用环境
其排序速度快于任何比较排序算法。当k不是很大并且序列比较集中时,计数排序是一个很有效的排序算法。
算法代码
public static void sortCount(int[] arr, int min, int max) {
int len = arr.length;
int[] tem = new int[max - min + 1];
for(int i = 0; i < len; i++) {
tem[arr[i] - min] += 1;
}
for(int i = 0, index = 0; i < tem.length; i++) {
int item = tem[i];
while(item-- != 0) {
arr[index++] = i + min;
}
}
}
桶排序
代码思路
桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。桶排序 (Bucket sort)的工作的原理:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排)。
- 设置一个定量的数组当作空桶;
- 遍历输入数据,并且把数据一个一个放到对应的桶里去;
- 对每个不是空的桶进行排序;
- 从不是空的桶里把排好序的数据拼接起来。
特点
复杂度
- 桶排序最好情况下使用线性时间O(n),桶排序的时间复杂度,取决与对各个桶之间数据进行排序的时间复杂度,因为其它部分的时间复杂度都为O(n)。很显然,桶划分的越小,各个桶之间的数据越少,排序所用的时间也会越少。但相应的空间消耗就会增大
使用环境
算法代码
public static void main(String[] args) {
// 输入元素均在 [0, 10) 这个区间内
float[] arr = new float[] { 0.12f, 2.6f, 8.8f, 7.6f, 7.2f, 6.3f, 9.0f, 1.6f, 5.6f, 2.4f };
bucketSort(arr);
System.out.println(Arrays.toString(arr));
}
public static void bucketSort(float[] arr) {
// 新建一个桶的集合
ArrayList<LinkedList<Float>> buckets = new ArrayList<LinkedList<Float>>();
for (int i = 0; i < 10; i++) {
// 新建一个桶,并将其添加到桶的集合中去。
// 由于桶内元素会频繁的插入,所以选择 LinkedList 作为桶的数据结构
buckets.add(new LinkedList<Float>());
}
// 将输入数据全部放入桶中并完成排序
for (float data : arr) {
int index = getBucketIndex(data);
insertSort(buckets.get(index), data);
}
// 将桶中元素全部取出来并放入 arr 中输出
int index = 0;
for (LinkedList<Float> bucket : buckets) {
for (Float data : bucket) {
arr[index++] = data;
}
}
}
/**
* 计算得到输入元素应该放到哪个桶内
*/
public static int getBucketIndex(float data) {
// 这里例子写的比较简单,仅使用浮点数的整数部分作为其桶的索引值
// 实际开发中需要根据场景具体设计
return (int) data;
}
/**
* 我们选择插入排序作为桶内元素排序的方法 每当有一个新元素到来时,我们都调用该方法将其插入到恰当的位置
*/
public static void insertSort(List<Float> bucket, float data) {
ListIterator<Float> it = bucket.listIterator();
boolean insertFlag = true;
while (it.hasNext()) {
if (data <= it.next()) {
it.previous(); // 把迭代器的位置偏移回上一个位置
it.add(data); // 把数据插入到迭代器的当前位置
insertFlag = false;
break;
}
}
if (insertFlag) {
bucket.add(data); // 否则把数据插入到链表末端
}
}
基数排序
代码思路
基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。
- 取得数组中的最大数,并取得位数;
- arr为原始数组,从最低位开始取每个位组成radix数组;
- 对radix进行计数排序(利用计数排序适用于小范围数的特点);
特点
复杂度
- 基数排序基于分别排序,分别收集,所以是稳定的。但基数排序的性能比桶排序要略差,每一次关键字的桶分配都需要O(n)的时间复杂度,而且分配之后得到新的关键字序列又需要O(n)的时间复杂度。假如待排数据可以分为d个关键字,则基数排序的时间复杂度将是O(d*2n) ,当然d要远远小于n,因此基本上还是线性级别的。
- 基数排序的空间复杂度为O(n+k),其中k为桶的数量。一般来说n>>k,因此额外空间需要大概n个左右。
使用环境
算法代码
public static void main(String[] args) {
int[] arr = new int[] { 5,789,2138,456,3,1,9,1,13,4984,3 };
radixSort(arr,10000);
System.out.println(Arrays.toString(arr));
}
private static void radixSort(int[] array,int d)
{
int n=1;//代表位数对应的数:1,10,100...
int k=0;//保存每一位排序后的结果用于下一位的排序输入
int length=array.length;
int[][] bucket=new int[10][length];//排序桶用于保存每次排序后的结果,这一位上排序结果相同的数字放在同一个桶里
int[] order=new int[length];//用于保存每个桶里有多少个数字
while(n<d)
{
for(int num:array) //将数组array里的每个数字放在相应的桶里
{
int digit=(num/n)%10;
bucket[digit][order[digit]]=num;
order[digit]++;
}
for(int i=0;i<length;i++)//将前一个循环生成的桶里的数据覆盖到原数组中用于保存这一位的排序结果
{
if(order[i]!=0)//这个桶里有数据,从上到下遍历这个桶并将数据保存到原数组中
{
for(int j=0;j<order[i];j++)
{
array[k]=bucket[i][j];
k++;
}
}
order[i]=0;//将桶里计数器置0,用于下一次位排序
}
n*=10;
k=0;//将k置0,用于下一轮保存位排序结果
}
}
睡眠排序
代码思路
代码思路倒是很简单,就是利用线程睡眠来进行排序,让线程睡眠、
特点
没啥特点就是好玩
复杂度
- 时间复杂度:
使用环境
无
算法代码
public static void sleepSort(int[] array) {
for (int i : array) {
new Thread(()->{
try {
Thread.sleep(i);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(i);
}).start();
}
}
public static void main(String[] args) {
int[] array = { 10, 30, 50, 60, 100, 40, 150, 200, 70 };
sleepSort(array);
}
随机排序排序
代码思路
就是让系统随机排序,然后验证是否有序
特点
巨复杂
复杂度
- 时间复杂度:
使用环境
无
算法代码
public static void randSortX(int [] array){
List<Integer> list=new ArrayList<>();
for (Integer integer : array) {
list.add(integer);
}
int pre=0;
int index=0;
while(true){
pre=0;
for (index = 1; index < list.size(); index++) {
if(list.get(index)>list.get(pre)){
pre++;
}else{
break;
}
}
if(pre+1==list.size()){
break;
}
Collections.shuffle(list);
}
System.out.println(list.toString());
}
public static void main(String[] args) {
int[] array = { 10, 30, 50, 60, 100, 40, 150, 200, 70 };
randSortX(array);
}
XX排序
代码思路
特点
复杂度
- 时间复杂度:
使用环境
算法代码