排序算法是个基本的算法,在大部分的算法入门书籍中都会提到。排序很重要—-1.实际用处很多(对查询到的数据进行排序);2.简单到深入,从很简单的实现到复杂实现都有,对于学习和理解算法的知识点有很大帮助。
排序的算法很多,我这里总共找到了12种,分别是:冒泡排序、选择排序、插入排序、快速排序、堆排序、希尔排序、二分插入排序、鸡尾酒排序(双向冒泡排序)、桶排序、计数排序、基数排序、归并排序。
注意:下面代码需要用到两个公共的方法,我抽取出来放到对应的类中,代码如下:
package com.sort;
//用于输出序列内容
public class SortPrint {
//输出
public static void print(int[] text) {
for(int i:text) {
System.out.println("-->"+i);
}
}
}
package com.sort;
import java.util.Random;
//随机产生一个序列
public class SortRandom {
public static int[] randomSerial(int num) {
int[] serail = new int[num];
Random ran = new Random();
for(int i=0;i<num;i++) {
serail[i] = ran.nextInt(num);
}
return serail;
}
}
1. 冒泡排序
概述:冒泡排序就是遍历需要排序的序列,比较序列中相邻两个子元素,如果排序方式错误,则按照所需要排列的方式排好,如果排序方式正确,则继续往后;然后重复遍历需要排序的序列,一直到序列中所有子元素都按照正确的排序方式排序为止。
步骤:以升序排序为例–
1. 比较相邻元素,如果第一个比第二个元素大,则交换两个元素
2. 按照步骤1的方法比较序列中的所有相邻元素,完成一次排序后,末尾的元素就是最大的。
3. 重复上述步骤,一直到序列中所有的元素都排序完成。
图示说明:
代码:
package com.sort;
import java.util.Random;
//冒泡排序代码
public class BubbleSort {
//测试
public static void main(String[] args) {
int[] test = SortRandom.randomSerial(100);
int[] out = sort(test);
SortPrint.print(out);
}
//排序
public static int[] sort(int[] in) {
int len = in.length;
int temp;
//循环对比相邻,确保所有的元素都排序好,
for(int i=0;i<len-1;i++) {
//比较序列中的所有相邻元素,
for(int j=0;j<len-i-1;j++) {
if(in[j]<in[j+1]) {
temp = in[j];
in[j] = in[j+1];
in[j+1] = temp;
}
}
}
return in;
}
}
2. 选择排序
概述:选择排序就是每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完
步骤:以升序排序为例–
1. 将第一个元素取出,然后依次跟序列中的其他元素比较,如果比被比较的元素大,则将被比较的元素放到起始位置上,然后一直比较到序列最末;
2. 然后取第二个元素。。。一次类推,重复上述步骤,一直到序列中所有的元素都排序完成。
图示说明:
代码:
package com.sort;
//选择排序
public class SelectedSort {
public static void main(String[] args) {
int[] test = SortRandom.randomSerial(100);
SortPrint.print(sort(test));
}
//排序过程
public static int[] sort(int[] in) {
int min;
int temp;
int len = in.length;
for(int i=0;i<len;i++) {
//选中需要对比的元素的下标
min = i;
//循环比较
for(int j=i;j<len;j++) {
if(in[min]<in[j]) {
min = j;
}
}
//元素交换
if(min!=i) {
temp = in[min];
in[min] = in[i];
in[i] = temp;
}
}
return in;
}
}
3. 插入排序
概述:将待排序的数组中的元素依次插入到已经排好序序列中的对应位置,
步骤:以升序排序为例–
1. 把序列中的第一个元素当作一个已经排好序的序列,然后将第二个元素与第一个元素做比较,比第一个元素小则将第二个元素插入到第一个元素的前面。
2. 再取第三个元素跟前面两个已经排好序的元素比较,如果比第一个元素小,则插入到第一个元素前面;如果比第二个元素小,则插入到第二个元素前面。
3. 依次类推,一直到序列中所有的元素都排序完成。
图示说明:
代码:
package com.sort;
public class InsertSort {
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] test = SortRandom.randomSerial(10);
SortPrint.print(sort(test));
}
//升序
public static int[] sort(int[] in) {
int j=0;
int temp;
int next;
for(int i=1;i<in.length;i++) {
j=i-1;
next = i;
while(next>0&&in[next]<in[j]) {
temp = in[next];
in[next] = in[j];
in[j] = temp;
next--;
j--;
}
}
return in;
}
}
4. 快速排序
概述:将一个未排序的序列分为两个序列,其中一个序列中的所有元素都比另外一个序列中的所有元素大(小);再将已经分好的子序列再安装同样的方法拆分,知道序列为已排好序列。
步骤:以升序排序为例–
1. 取一个基准数,然后将序列中的所有元素跟基准数做比较,小的放在基准数的左边,大的放在基准数的右边
2. 再将小于基准数的序列和大于基准数的序列分别按照步骤1的方法处理。
3. 依次类推,直到序列排序完成。
图示说明:
代码:
package com.sort;
public class QuikySort {
public static void main(String[] args){
//-->7-->8-->1-->3-->9-->7-->4-->8-->6-->2
// int[] test = {7,8,1,3,9,7,4,8,6,2};
// int[] test = {3,4,6,2};
int[] test = SortRandom.randomSerial(100);
SortPrint.print(test);
System.out.println();
test = sort(test,0,test.length);
SortPrint.print(test);
}
private static int[] sort(int[] in,int index,int length) {
int mid;
int lowIndex=index,higherIndex=0;
int lowLen = 0,higherLen = 0;
mid = index;
int temp;
higherIndex = mid+1;
for(int n=index+1;n<length+index;n++) {
if(in[n]<in[mid]) {
//插入到左边
if(higherLen!=0) {
temp = in[mid+1];
in[mid+1] = in[mid];
in[mid] = temp;
}
temp = in[n];
in[n] = in[mid];
in[mid] = temp;
//mid = n;
mid++;
higherIndex++;
lowLen++;
}else if(in[n]>=in[mid]) {
higherLen++;
}
}
if(lowLen>1)
sort(in, lowIndex, lowLen);
if(higherLen>1)sort(in, higherIndex, higherLen);
return in;
}
}
5. 桶排序
概述:是将数组分到有限数量的桶子里。每个桶子再个别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序)
步骤:以未排序序列中值的范围为0–100为例–
1. 定义一个新的数组,数组大小为101(数组大小取决于序列中的数值的取值范围,如果序列中的值的取值范围是0–1000,则数值大小应为1001),数值中的值为0;
2. 遍历序列,增加序列元素对应的数组位置的值(比如序列元素为1,则将数组中第1个位置的值增加1)。
3. 遍历数组,顺序输出值不是0的所有数值位置。
图示说明:
代码:
package com.sort;
public class BucketSort {
public static void main(String[] args) {
int[] test = SortRandom.randomSerial(100);
SortPrint.print(test);
System.out.println();
SortPrint.print(sort(test));
}
public static int[] sort(int[] in) {
int[] out = new int[in.length];
for(int i=0;i<in.length;i++) {
out[i] = 0;
}
for(int i=0;i<in.length;i++) {
out[in[i]]++;
}
int index = 0;
for(int i=0;i<in.length;i++) {
if(out[i]!=0) {
for(int j=0;j<out[i];j++) {
in[index] = i;
index++;
}
}
}
return in;
}
}
6. 归并排序
概述:归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
步骤:以升序排序为例–
1. 将未排序的序列分成两个子序列,
2. 将子序列在分成两个子序列;
3. 重复上述步骤,当所有的子序列都已经排序完成,就将所有的子序列合并成一个已经排好序的序列。
图示说明:
代码:
import org.junit.Test;
public class MergeSort {
public static void main(String args[]){
int[] test = SortRandom.randomSerial(99);
SortPrint.print(test);
//SortPrint.print(merge(test,0,2,2));
SortPrint.print(sort(test,0,0));
}
/**
* 1.把序列拆分成若干小的序列
* 2.把小的序列排序
* 3.归并
* 我们把系列的单个元素看出是一个已经排好序的最小系列,然后进行归并
* @param in
* @param count
* @param preLen
* @return
*/
public static int[] sort(int[] in,int count,int preLen){
int remainLen;
if(count!=0) {
remainLen = in.length % count;
if (remainLen != 0) {
if (preLen == 0) {
preLen = remainLen;
} else {
if (remainLen != preLen) {
//不等说明前面没有排序,等于,就说明已经排序了
merge(in, in.length - 1 - remainLen, remainLen - preLen, preLen);
}
}
}
}
if(count>in.length/2){
merge(in,0,in.length-preLen,preLen);
return in;
}
count = count==0?1:count*2;
for (int i=0;i<in.length/(count*2);i++){
merge(in,i*(count*2),count,count);
}
sort(in,count,preLen);
return in;
}
/**
*
* @param in 需要归并的序列
* @param index 归并子序列的起始位置
* @param firstLen 归并的待插入的子序列的长度
* @param secondLen 归并的插入的子序列的长度
* @return
*/
public static int[] merge(int[] in,int index,int firstLen,int secondLen){
//将数组中第一个元素取出,然后插入到已经排好序的前一个数组
//外层循环--遍历需要插入的数值中的元素
int temp;
int startIndex=0;
for(int i=0;i<secondLen;i++){
//遍历待插入的数组,确认插入数组
for(int j=startIndex;j<(firstLen+startIndex);j++){
if(in[i+index+firstLen]<=in[j+index]){
temp = in[i+index+firstLen];
//插入元素
for(int z=(i+index+firstLen);z>(j+index);){
in[z] = in[z-1];
z = z-1;
}
in[j+index] = temp;
startIndex++;
break;
}
}
}
return in;
}
}
7. 计数排序
概述:将整数按位数切割成不同的数字,然后按每个位数分别比较
步骤:以升序排序为例–
1. 遍历数组获取到数组中的最小值和最大值
2. 根据最小值和最大值创建一个大小为(最大值-最小值)新的数组
3. 用桶排序的思想,排序。
图示说明:
代码:
import org.junit.Test;
/**
* 计数排序
*/
public class CountingSort {
@Test
public void test(){
int[] test = SortRandom.randomSerial(10);
sort(test);
}
//计数排序
public void sort(int[] in){
/**
* 1.遍历数组获取到数组中的最小值和最大值
* 2.根据最小值和最大值创建一个大小为(最大值-最小值)新的数组
* 3.用桶排序的思想,排序
*/
int len = 0;
int min = in[0],max = 0;
//遍历数组获取到数组中的最小值和最大值
for(int i:in){
if(i>max){
max = i;
}
if(i<min){
min = i;
}
}
//根据最小值和最大值创建一个大小为(最大值-最小值)新的数组
int[] out = new int[max-min+1];
//用桶排序的思想,排序
//把数组放入到桶中
for(int i=0;i<in.length;i++){
out[in[i]-min]++;
}
int index=0;
//把桶排序
for(int i=0;i<out.length;i++){
if(out[i]>0){
for(int j=0;j<out[i];j++){
in[index] = i+min;
index++;
}
}
}
}
}
8. 基数排序
概述:将整数按位数切割成不同的数字,然后按每个位数分别比较
步骤:以升序排序为例–
1. 先找出最大的元素,然后确定最大元素有多少位数
2. 从低位开始,依次进行排序。
3. 一直到序列中所有的元素都排序完成。
图示说明:
代码:
package com.yk;
import org.junit.Test;
/**
* Created by jimago on 2017/12/27.
* 基数排序
*/
public class CardinalSort {
@Test
public void test(){
int[] test = {101,99,33,8,8888,7771,31351};
SortPrint.print(test);
sort(test);
SortPrint.print(test);
}
public static int[] sort(int[] in){
int[][] result = new int[10][in.length];
int index[] = new int[10];
int maxCount = 0;
//获取到最大的元素,
for(int i=0;i<in.length;i++){
int temp = (in[i]+"").length();
if(temp>maxCount){
maxCount = temp;
}
}
for(int j=0;j<maxCount;j++){
//先将序列中的元素按照基数比较拆分到数组中
//每次都需要将数组重置
for(int i=0;i<index.length;i++){
index[i] = 0;
}
//将数组重置
if(result == null){
result = new int[10][in.length];
}
for(int i=0;i<in.length;i++){
result[getIndexInt(in[i],j)][index[getIndexInt(in[i],j)]] = in[i];
index[getIndexInt(in[i],j)]++;
}
// System.out.print("len="+result.length+"=="+result[0].length);
//将拆分的数组仿到序列中
int inNum = 0;
int resultFirst = 0;
int resultSecond = 0;
while(inNum<in.length&&resultFirst<10){
resultSecond = 0;
while(resultSecond<result[resultFirst].length&&result[resultFirst][resultSecond]!=0){
in[inNum] = result[resultFirst][resultSecond];
inNum++;
resultSecond++;
}
resultFirst++;
}
result = null;
}
return in;
}
private static int getIndexInt(int in,int index){
int pow = (int) Math.pow(10,index);
return (in/pow)%10;
}
}
9. 堆排序
概述:二叉堆,它满足二个特性:父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值;每个结点的左子树和右子树都是一个二叉堆(都是最大堆或最小堆).
二叉最小堆—对应的降序排序
二叉最大堆—对应的升序排序
步骤:以降序排序为例–
1. 开始建一个最小堆,然后取出最小堆中顶点的元素,放到系列的最末尾;
2. 继续对剩余的元素建最小堆,然后将顶点的元素放到序列中倒数第二的位置;
3. 依次类推,完成排序
图示说明:
代码:
import org.junit.Test;
class HeapSort {
@Test
public void test(){
int[] test = SortRandom.randomSerial(10);
SortPrint.print(test);
SortPrint.print(sort(test));
}
public int[] sort(int[] in){
for(int j=0;j<in.length;j++) {
for (int i = 0; i < (in.length-j); i++) {
buildHeap(in, 0,in.length-j);
}
swap(0,in.length-j-1,in);
}
return in;
}
//获取堆节点左边的位置
private int getLeft(int i){
int num;
num = i*2+1;
return num;
}
//获取堆节点右边的位置
private int getRight(int i){
int num;
num = i*2+2;
return num;
}
/**
* 二叉堆,它满足二个特性:
1---父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值。
2---每个结点的左子树和右子树都是一个二叉堆(都是最大堆或最小堆)
二叉最小堆---对应的降序排序
二叉最大堆---对应的升序排序
* @param in
*/
private void buildHeap(int[] in,int start,int len) {
for (int i=start;i<len;i++){
int left = getLeft(i);
int right = getRight(i);
if(left<len&&in[i]>in[left]){
swap(i,left,in);
}
if(right<len&&in[i]>in[right]){
swap(i,right,in);
}
if(left<len&&right<len&&in[left]>in[right]){
swap(left,right,in);
}
}
}
//交换数组中两个位置的元素
private void swap(int i, int left,int[] in) {
int temp = in[i];
in[i] = in[left];
in[left]= temp;
}
}
10. 二分插入排序
概述:
步骤:以升序排序为例–
1. 假设前三个元素是已经排好序的,此时取第4个元素,跟已经排好序的序列中的中间元素进行比较(跟第二个元素进行比较),如果比第二个元素大,我们再将第4个元素跟第二个元素后面的元素做比较,否则,则跟前面的元素做比较
2. 依次类推
图示说明:
代码:
import org.junit.Test;
/**
* Created by jimago on 2018/2/11.
* 二分插入排序:升级的插入排序,将需要插入的元素与已经排好序的序列的中间元素进行对比,如果比元素大(小)则在次元素的前面的序列中进行插入排序,否则则在后面插入排序
* 1.假设前三个元素是已经排好序的,此时取第4个元素,跟已经排好序的序列中的中间元素进行比较(跟第二个元素进行比较),
* 如果比第二个元素大,我们再将第4个元素跟第二个元素后面的元素做比较,否则,则跟前面的元素做比较
* 2.依次类推
*/
public class MiddleInsertSort {
@Test
public void test(){
int[] test = SortRandom.randomSerial(10);
SortPrint.print(test);
System.out.println("***********************");
SortPrint.print(sort(test));
}
public int[] sort(int[] in){
for (int i=1;i<in.length;i++){
int middle = i/2;
int key = in[i];
// mInsertSort(0,i,in);
int start=0 ,end=i-1;
while(start<=end){
middle = (end-start)/2+start;
if(in[middle]>in[i]){
//取中间数左边的
end = middle -1;
}else{
//取中间数右边的
start = middle +1;
}
}
//将大于目标值的所有值都往右移
for(int j=i-1;j>=start;j--){
in[j+1] = in[j];
}
//将目标值复制到指定位置
in[start] = key;
}
return in;
}
}
11. 希尔排序
概述:
步骤:以升序排序为例–
1. 取一个小于序列长度的正整数做为第一个增量,根据增量对序列进行分组,然后对每组进行排序;
2. 再取一个小于第一个增量的正整数做为第二个增量,根据增量对序列进行分组,然后对每组进行排序;
3. 以此类推,直到所有排序完成;
图示说明:
代码:
import org.junit.Test;
public class HillSort {
@Test
public void test(){
int[] test = SortRandom.randomSerial(200);
SortPrint.print(test);
System.out.println("****************************");
SortPrint.print(sort(test));
}
public static int[] sort(int[] in){
//从序列的1/2步长开始;
int x = in.length/2;
int temp = 0;
boolean lastFlag = false;
while(x>0){
//获取每次步长需要排序的元素
int index = in.length/x;
//开始排序
for(int i=0;i<x;i++){
for(int j=1;j<index;j++){
//排序由步长长度所区分的元素序列
int num = j;
temp = in[num*x+i];
while(num>0&&temp<in[(num-1)*x+i]){
in[num*x+i] = in[(num-1)*x+i];
num--;
}
in[num*x+i] = temp;
}
}
//最后一次计算步数时,会出现1/2=1会是一个死循环,
// 但是步数为1的情况也必须进行排序,所以这里增加一个变量用于结束最后一次排序
if (lastFlag){
x=0;
}
//每次循环步长为上次的1/2
x = x/2;
if(x==1){
lastFlag = true;
}
}
return in;
}
}
12. 鸡尾酒排序
概述:鸡尾酒排序又可以叫做双向冒泡排序,
步骤:以升序排序为例–
1. 从序列的最前端比较相邻元素,如果第一个比第二个元素大,则交换两个元素
2. 按照步骤1的方法比较序列中的所有相邻元素,完成一次排序后,末尾的元素就是最大的。
3.从序列的最末端比较相邻元素,如果最末一个元素比倒数第二个元素小,则交换两个元素
4. 按照步骤1的方法比较序列中的所有相邻元素,完成一次排序后,最前面的元素就是最小的。
5. 重复上述步骤1–4,一直到序列中所有的元素都排序完成。
图示说明:
代码:
public class CocktaiSort {
public static void main(String arg[]){
int[] test = SortRandom.randomSerial(7);
SortPrint.print(test);
SortPrint.print(sort(test));
}
private static int[] sort(int[] in){
// Double d = new BigDecimal();
int temp = 0;
for(int i=0;i<in.length/2;i++){
//从左到右,循环结束后,右边为大的元素
for(int j=i;j<in.length-1;j++){
if(in[j]>in[j+1]){
temp = in[j];
in[j] = in[j+1];
in[j+1] = temp;
}
}
//从右到左,循环结束后,左边为小的元素
for(int j=in.length-1-i;j>i+1;j--){
if(in[j-1]>in[j]){
temp = in[j-1];
in[j-1] = in[j];
in[j] = temp;
}
}
}
return in;
}
}
算法书籍的推荐:首先看入门的《java数据结构和算法》或者《啊哈,算法》(这是基于C语言的),然后再去看《算法导论》,《算法导论》更偏重于算法的分析。