本篇介绍常用排序算法的java实现
借用网上一幅图,排序算法分类如下:
对于各个算法的时间复杂度和空间复杂度,借用网上的图如下:
其中:选择排序算法本身是稳定的,用顺序结构实现时是不稳定的,用链表实现时是稳定的。
快速排序是平均速度最快的排序算法。
归并排序需要的辅助空间最多。堆排序需要的辅助空间最少。
所谓不稳定,简单举个例子就是,如果待排序的数组里,如果出现两个相同的数,比如5(1),5(2),排好序后如果顺序变成5(2)、5(1),那么就是不稳定的。
直接插入排序
假设已经有一组排好序的数组(可以是1个),然后有待加入的一堆数n个,现在把这n个数有序的插入到已经排好序的数组里,一个一个进行,与前面排好序的数进行比较,找到合适的位置插入,如此反复循环,最终形成的数组就是排好序的。
如:23,45,67,12,97,4,32,55,初始选择23作为已经排好序的,而后面剩余的数就是待插入的,接着是43(假设我们升序排序,下面的算法也都是),与23比较,大于23,所以不需要改变位置,接着67,比45大,由于前面的是排好序的,所以一样不需要改变位置,此时:23 45 67 12 97 4 32 55前面3个已经排好序了。接着是12,与67比较,比67小,67位置后移,或者说12往前移动位置,再继续与45比较,仍然比45小,继续前移,再与23比较,还是比23小,继续前移,已经是最后一个数了,所以12位置就定下来了,就是首位。接着是97...如此循环反复,最终形成有序数组。
比较过程:
23
45 67 12 97 4 32 55
23 45
67 12 97 4 32 55
23 45 67
12 97 4 32 55
12 23 45 67
97 4 32 55
12 23 45 67 97
4 32 55
4 12 23 45 67 97
32 55
4 12 23 32 45 67 97
55
4 12 23 32 45 55 67 97
代码示例如下:
/**
* 直接插入排序
* @param nums
*/
public static void insertSort(int[] nums){
for(int i = 1;i<nums.length;i++){
int tmp = nums[i];
int j = i - 1;
for(;j >= 0 && tmp < nums[j];j--){
nums[j+1] = nums[j];
}
nums[j+1] = tmp;
}
}
希尔排序(最小增量排序)
先将待排序数组按某个增量(一般为n/2,n是数组个数)分成若干组,每组中记录的下标相差d.对每组中全部元素进行直接插入排序,然后再用一个较小的增量(d/2)对它进行分组,在每组中再进行直接插入排序。当增量减到1时,进行直接插入排序后,排序完成。
比如对于23,45,67,12,97,4,32,55,n=8 d=8/2=4,具体步骤如下:
23
45
67
12 97
4
32
55 (同颜色为一组,同组的进行比较并排序)
23 4 32 12 97 45 67 55 (这时d = d/2 = 2)
23
4
32
12
97
45
67
55 (同颜色为一组,同组的进行比较并排序)
23 4 32 12 67 45 97 55 (这时d = d/2 = 1)
4 23 12 32 45 67 55 97 (此时只需要按正常排序进行,但是由于先前的排序已经排好了大部分,基本就是左右进行比较就可以了)
代码示例如下:
/**
* 希尔排序
* @param nums
*/
public static void shellSort(int[] nums){
int d = nums.length;
while(d > 1){
d = (d+1)/2;
for(int z = 0;z < d;z++){
for(int i = z + d;i < nums.length; i+=d ){
int tmp = nums[i];
int j = i -d;
for(; j >= 0 && tmp < nums[j];j-=d){
nums[j+d] = nums[j];
}
nums[j+d] = tmp;
}
}
}
}
选择排序
待排序的数组里,选择第一个数,之后依次与后面的数进行比较,如果比这个数小,就与其交换,当与后面的数比较完后,第一个数就会是整个数组最小的。接着选择数组第二个数,依次与后面进行比较,比它小的就继续交换位置,最后第二个数会是数组第二小的数。如此循环反复,最后形成有序数组。
比如对于23,45,67,12,97,4,32,55,排序过程如下:
23 45 67 12 97 4 32 55
4
45 67 23 97 12 32 55 (23 先与12交换数值,得到12,再与4交换数值 得到目前这个排序 下面类似)
4 12
67 45 97 23 32 55
4 12 23
45 97 67 32 55
4 12 23 45
32 67 97 55
4 12 23 45 32
55 97 55
4 12 23 45 32 55
67 97
4 12 23 45 32 55 67 97 (最后剩一个数就不用比较了)
代码示例如下:
/**
* 选择排序(升序)
* @param nums
*/
public static void selectSort(int[] nums){
for(int i = 0;i < nums.length;i++){
for(int j = i+1; j < nums.length;j++){
if(nums[i] > nums[j]){
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
}
}
}
堆排序
堆排序是基于树形结构的选择排序算法。是对直接选择排序的有效改进 。下面这段摘自网络:
堆的定义如下:具有n个元素的序列(h1,h2,…,hn),当且仅当满足(hi>=h2i,hi>=2i+1)或(hi<=h2i,hi<=2i+1)(i=1,2,…,n/2)时称之为堆。在这里只讨论满足前者条件的堆。由堆的定义可以看出,堆顶元素(即第一个元素)必为最大项(大顶堆)。完全二叉树可以很直观地表示堆的结构。堆顶为根,其它为左子树、右子树。初始时把要排序的数的序列看作是一棵顺序存储的二叉树,调整它们的存储序,使之成为一个堆,这时堆的根节点的数最大。然后将根节点与堆的最后一个节点交换。然后对前面(n-1)个数重新调整使之成为堆。依此类推,直到只有两个节点的堆,并对它们作交换,最后得到有n个节点的有序序列。从算法描述来看,堆排序需要两个过程,一是建立堆,二是堆顶与堆的最后一个元素交换位置。所以堆排序有两个函数组成。一是建堆的渗透函数,二是反复调用渗透函数实现排序的函数。
例如对于数组23,45,67,12,97,4,32,55,可以构建的树如下:
按照大顶堆进行排序后如下:
排序的思路就是:将最大的数(这里指升序)与堆最后一个节点交换位置,然后最后一个节点就不再参与下次的排序,相当于不在堆里。剩余的数经过调整重新成为大顶堆。
如此循环反复,可以得到升序排序的数组。
示例代码如下(该算法应该还可以优化,有待改进):
/**
* 堆排序(升序)
* 下面三个方法
* @param nums
*/
public static void heapSort(int[] nums){
int size = nums.length;
for(int i = 0;i < size-1;i++){
buildMaxHeap(nums,size-1-i); //建立大顶堆
Swap(nums, 0, size-i-1); //交换第一个即最大的和堆的最后一个数
}
}
public static void buildMaxHeap(int[] nums,int last){
//注意从最后一个节点的父节点开始建立堆
for(int i = (last-1)/2;i >= 0;i--){
int k = i;
//while(k*2+1 <= last){
//数组 起始点为0 这里bigger为子节点的左节点
int bigger = 2*k+1;
if (bigger <= last) { //左节点存在
if (bigger+1 <= last) { //右节点存在
if (nums[bigger] < nums[bigger+1]) {
bigger++; //bigger指向子节点较大的那个索引
}
}
}
//当前节点即上面说的子节点的父节点与子节点中最大的进行比较
if (nums[k] < nums[bigger]) {
//如果小 就交换
Swap(nums,k,bigger);
}
//break; //while其实这里可不写 相应的break可以去掉
//}
}
}
冒泡排序
对未排好序的数组中的数,相邻两个依次进行比较,较大的数后移,如此每执行一轮比较,最大的(就参与比较的而言)数就移到了最后。这样经过n-1轮比较后,数组就排好序了。
比如对于23,45,67,12,97,4,32,55,排序过程如下:
23 45 67 12 97 4 32 55 第一次:23与45比较,不改变,45与67比较,67与12比较,由于12比较小,交换,67与97比较,97与4比较,交换,97与32比 较,交换,97与55比较,交换。最后97移到了最后一个位置。
23 45 67 12 4 32 55
97 第二次97不参与排序。依次类推
23 45 12 4 32 55
67 97
23 45 12 4 32
55 67 97
23 12 4 32
45 55 67 97
23 12 4
32 45 55 67 97
12 4
23 32 45 55 67 97
4
12 23 32 45 55 67 97
4 12 23 32 45 55 67 97
代码示例如下:
/**
* 冒泡排序(升序)
* @param nums
*/
public static void bubbleSort(int[] nums){
for(int i = 0;i<nums.length;i++)
for(int j = 0;j<nums.length-1;j++){
if(nums[j]>nums[j+1]){
int tmp = nums[j];
nums[j] = nums[j+1];
nums[j+1] = tmp;
}
}
}
快速排序
从待排序的数组里选取一个作为基准数,一般选第1个或者最后一个(当然也有选中间一个的)。然后扫描数组,对数组进行第一轮排序,将比基准数小的移到左边,比基准数大的移到右边。然后数组就按基准数分成了两个数组,分别对左右数组递归的进行相同的处理,最后得到的就是排好序的了。
比如对于23,45,67,12,97,4,32,55,选取23做基准数,45比23大,移到23后面,67比23小,移到后面(实际代码可以保持原位,索引加1即可),接着12比23小,移到23前面,,,如此下去,示意图如下(递归部分比较麻烦,就不一步一步分析了,加粗的为下一个待比较的数):
23
45 67 12 97 4 32 55
23 45
67 12 97 4 32 55
23 45 67
12 97 4 32 55
12 23 45 67
97 4 32 55
12 23 45 67 97
4 32 55
12 4 23 45 67 97
32 55
12 4 23 45 67 97 32
55
12 4 23 45 67 97 32 55
代码示例如下:
/**
* 以下三个方法用于快速排序
* @param nums
*/
public static void quickSort(int[] nums){
_quickSort(nums, 0, nums.length-1);
}
public static void _quickSort(int[] nums,int low,int high){
//这步判断必须加
if(low < high){
int middle = getMiddle(nums,low,high);
_quickSort(nums, low, middle-1);
_quickSort(nums, middle+1, high);
}
}
public static int getMiddle(int[] nums,int low,int high){
int tmp = nums[low];
while(low < high){
if (nums[high] >= tmp && low < high) {
high--;
}
nums[low] = nums[high];
if (nums[low] <= tmp && low < high) {
low++;
}
nums[high] = nums[low];
}
nums[low] = tmp;
return low;
}
归并排序
先将待排序数组分成若干个数组,直到不可再分(剩2个就可以开始排序了),再将分成的数组内进行排序,然后将排好序的数组再两两进行合并成有序数组,最后形成排好序的数组。归并也用到了递归的思想。
比如对于23,45,67,12,97,4,32,55,排序过程如下:
分组阶段: 23 45 67 12 97 4 32 55
23 45 67 12 97 4 32 55
23 45 67 12 97 4 32 55
合并阶段: 23 45 12 67 4 97 32 55
12 23 45 67 4 32 55 97
45 12 4 23 67 97 32 55
代码示例如下:
/**
* 归并排序(升序)
* @param nums 数组
* @param low
* @param high
*/
public static void mergeSort(int[] nums,int low,int high){
if(low < high){
int middle = (low + high)/2; //中间点
mergeSort(nums, low, middle); //对左边数组递归
mergeSort(nums, middle + 1, high); //对右边数组递归
Merge(nums, low, middle, high); //将左右已排好序的数组合并成有序数组
}
}
public static void Merge(int[] nums,int low,int middle,int high){
int[] temp = new int[nums.length];
int mid = middle + 1; //右边数组第一个数的索引
int k = low;
int t = low; //用于最后复制临时数组的值覆盖原数组的索引
while(low <= middle && mid <= high){ //有序合并两个数组
if(nums[low] <= nums[mid]){
temp[k++] = nums[low++];
}else{
temp[k++] = nums[mid++]; //注意这里是mid不是high
}
}
//剩余部分依次进入临时数组
while(low <= middle){
temp[k++] = nums[low++];
}
while(mid <= high){
temp[k++] = nums[mid++];
}
//复制回原数组 覆盖原来的顺序
while(t <= high){
nums[t] = temp[t++];
}
}
基数排序
将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。
比如对于23,45,67,12,97,4,32,55,排序过程如下:
23
|
45
|
67
|
12
|
97
|
4
|
32
|
55
|
按个位:
0
|
1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
9
|
|
|
12
|
23
|
4
|
45
|
|
67
|
|
|
|
|
32
|
|
|
55
|
|
97
|
|
|
按顺序收集:12 32 23 4 45 55 67 97
(其实这里已经排好序了 但是有些情况没这么顺利,还要继续按十位 百位等的收集)
按十位:
0
|
1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
9
|
|
|
12
|
23
|
4
|
45
|
|
67
|
|
|
|
|
32
|
|
|
55
|
|
97
|
|
|
按顺序收集:12 32 23 4 45 55 67 97
代码示例如下:
/**
* 基数排序
* @param nums
*/
public static void radixSort(int[] nums){
//获取数组中最大的数
int max = nums[0];
for(int i = 0;i < nums.length;i++){
if(max < nums[i]){
max = nums[i];
}
}
//通过最大的数得到最大位数,确定排序次数
int t = 0;
while(max > 0){
max/=10;
t++;
}
//初始化链表 每一个数又可能有多个值 所以list里的元素仍然为链表这里即数组集合
List<ArrayList<Integer>> list = new ArrayList<>();
for(int i = 0; i < 10;i++){
ArrayList<Integer> alist = new ArrayList<>();
list.add(alist);
}
//开始排序
for(int i = 0; i < t;i++){
for(int j = 0;j < nums.length;j++){
//得到第i位数
int n = nums[j]%(int)Math.pow(10, i+1)/(int)Math.pow(10, i);
ArrayList<Integer> arrayList = list.get(n);
arrayList.add(nums[j]);
list.set(n, arrayList);
}
//按第i位数的大小顺序收集元素 如果有相同的 按在数组里的排序
int count = 0; //数组索引
for(int y = 0; y < 10;y++){
while(list.get(y).size() > 0){
ArrayList<Integer> alist = list.get(y);
nums[count] = alist.get(0);
//为保证取出数的顺序以及判断是否取完 这里remove掉取出的数据 同时为下一趟排序清空数据
alist.remove(0);
count++;
}
}
}
}