总括
排序算法在日常工作中应用较多,在此针对几种比较常用的排序算法进行java实现;算法默认都是以升序排序需求为前提.
1、冒泡排序
每次循环遍历,通过比较相邻数字大小并交换,将待排序序列最大数传递到序列末端;如此多次遍历剩余序列直至整个序列有序;算法的整体平均时间复杂度为O(n2).
算法实现:
public class Bubble {
public static void main(String[] args) {
int [] source = {2,3,5,7,25,3,9,8,4,11,12,14};
for (int i = source.length-2; i>1; i--) {
for (int j = 0; j <=i; j++) {
if(source[j] > source[j+1]){
int temp = source[j];
source[j] = source[j+1];
source[j+1] = temp;
}
}
}
System.out.println(Arrays.toString(source));
}
}
2、选择排序
多次循环遍历,每次循环遍历找到并记录最小数字位置,通过将最小数字和待排序列首位数字对换位置将最小数置首,依次对剩余待排序子序列同样操作直至整个序列有序;选择排序平均时间复杂度为O(n2). 选择排序的逻辑和冒泡排序逻辑类似,区别在于选择排序每次遍历过程只会记录最小值的位置信息,遍历完成将最小数和待排序序列首位数字交换位置以此达到最小数置首目的,而冒泡是通过不断交换位置实现;
算法实现:
public class Choose {
public static void main(String[] args) {
int [] source = {2,3,5,7,25,3,9,8,4,11,12,14};
for (int i = 0; i < source.length; i++) {
int minIndex = i;
for (int j = i+1; j <source.length; j++) {
if(source[j] < source[minIndex]){
minIndex = j;
}
}
if(minIndex != i){
int temp = source[i];
source[i] = source[minIndex];
source[minIndex] = temp;
}
}
System.out.println(Arrays.toString(source));
}
}
3、插入排序
依次遍历序列,将遍历的每个数插入到此数前方序列中正确的排序位置,遍历完成,序列排序完成.平均时间复杂度O(n2).
4、希尔排序
希尔排序是插入排序的升级版本,先将待排序序列按照步长分解为几个子序列,针对每个子序列进行插入排序,再缩短步长,重复排序操作,直至步长缩短至1,对整个序列进行一次插入排序.一般步长按照序列长度1/2依次缩短.时间平均复杂度小于O(n2),属于非稳定算法.
算法实现:
public class Insert {
public static void main(String[] args) {
int [] source = {2,3,5,7,25,3,9,8,4,11,12,14};
insertSort(source);
int [] source = {2,3,5,7,25,3,9,8,4,11,12,14};
shellSort(source);
}
//插入排序
static void insertSort(int [] source){
for (int i = 0; i < source.length; i++) {
int candidate = source[i];
for (int j = 0; j < i; j++) {
if (source[j] > candidate) {
System.arraycopy(source,j,source,j+1,i-j);
source[j] = candidate;
break;
}
}
}
System.out.println(Arrays.toString(source));
}
//希尔排序
static void shellSort(int [] source){
int span = source.length / 2;
while(span >= 1){
for (int r = 0; r < span; r++) {
for (int i = r; i < source.length ; i+=span) {
int candidate = source[i];
for (int j = r; j < i; j+=span) {
if(source[j] > candidate){
for (int k = i; k >j ; k-=span) {
source[k] = source[k-span];
}
source[j] = candidate;
break;
}
}
}
}
span /=2;
}
System.out.println(Arrays.toString(source));
}
}
5、归并排序
分而治之的排序思想,每一层将整个序列分割成1/2长度两个子序列,向下层展开依次将子序列再分割成两个子序列,直至子序列长度为1,再从最底层向上将每层两个子序列归并成有序序列,依次向上对每层归并后的两个子序列归并合一,直至合并至第一层序列.算法时间复杂度为O(nlogn),属于稳定算法.
算法实现:
public class Merge {
public static void main(String[] args) {
int [] source = {2,3,5,7,25,3,9,8,4,11,12,14};
int[] result = mergeSort(source);
System.out.println(Arrays.toString(result));
}
static int[] mergeSort(int [] source){
if(source.length == 1){
return source;
}else{
int [] left = Arrays.copyOfRange(source,0,source.length/2);
int [] right = Arrays.copyOfRange(source, source.length/2,source.length);
return merge(mergeSort(left),mergeSort(right));
}
}
static int [] merge(int [] left, int [] right){
int [] result = new int [left.length+right.length];
int leftCur = 0;
int rightCur = 0;
int resultCur = 0;
while(leftCur < left.length && rightCur < right.length){
int leftValue = left[leftCur];
int rightValue = right[rightCur];
if(leftValue < rightValue){
result[resultCur] = leftValue;
leftCur++;
resultCur++;
}else if(leftValue > rightValue){
result[resultCur] = rightValue;
rightCur++;
resultCur++;
}else {
result[resultCur] = leftValue;
resultCur++;
result[resultCur] = rightValue;
resultCur++;
leftCur++;
rightCur++;
}
}
if(leftCur != left.length){
System.arraycopy(left,leftCur,result,resultCur,left.length-leftCur);
}
if(rightCur != right.length){
System.arraycopy(right,rightCur,result,resultCur,right.length-rightCur);
}
return result;
}
}
6、快速排序
快速排序基本的思路,在待排序数列中选择一个基数(一般取第一个数字),将序列中所有小于基数的数字放置于基数左边,将序列中所有大于基数的数字放置于基数右边,至此带排序数列以基数为界分成左右两个字子序列.对子序列重复以上操作,直至所有待排序数列子序列长度为1. 平均时间复杂度为O(nlogn),属于非稳定算法.
实现基数左边数列均小于基数,基数右边数列均大于基数的具体方法往往采取,左右两个哨兵分别指向待排序数列首位元素,右边哨兵从右至左移动,找到第一小于基数的数字,然后停下;左边哨兵从左向右移动找到第一个大于基数的数字,然后停下,交换两哨兵指向的数字位置;然后右、左哨兵继续上面规则移动直至两哨兵相遇,然后将基数与哨兵相遇位置的数字交换位置,至此基数左边的数字均小于基数,基数右边的数字均大于基数;
算法实现:
public class QuickSort {
public static void main(String[] args) {
int [] source = {2,3,5,16,5,7,25,3,10,9,8,4,11,12,14};
sort(source,0, source.length -1);
System.out.println(Arrays.toString(source));
}
static void sort(int [] source, int topIndex, int endIndex){
if(topIndex != endIndex){
int guardI = topIndex;
int guardJ = endIndex;
int baseNum = source[topIndex];
while (guardI != guardJ){
while(source[guardJ] >= baseNum && guardJ != guardI){
guardJ--;
}
while(source[guardI] <= baseNum && guardI != guardJ ){
guardI++;
}
if(guardI != guardJ){
int temp = source[guardI];
source[guardI] = source[guardJ];
source[guardJ] = temp;
}
}
source[topIndex] = source[guardI];
source[guardI] = baseNum;
if(guardI!=topIndex){
sort(source,topIndex,guardI);
}
if(guardJ != endIndex ){
sort(source,guardJ+1,endIndex);
}
}
}
}
7、计数排序
计数排序属于非对比交换类排序,时间复杂度为O(n+k),n为序列数个数,k为序列数取值范围,适用于有一定确定范围的序列排序.
确定待排序序列最大值max,定义一个长度为max的辅助排序数组,遍历待排序序列,将待排序序列中元素ele出现的次数赋值给辅助排序数组索引号为ele的元素.最后遍历辅助排序数组,将元素值个下标数字回放至原待排序列中.
代码实现:
public class CountSort {
public static void main(String[] args) {
int [] source = {2,3,5,16,5,7,25,3,10,9,8,4,11,12,14,14,13};
count(source);
System.out.println(Arrays.toString(source));
}
static void count(int [] source){
int min= source[0];
int max = source[0];
//确定待排序范围
for (int i = 1; i < source.length; i++) {
if(source[i] < min){
min = source[i];
}else if(source[i] > max){
max = source[i];
}
}
//放入辅助数组
int corr = min;
int [] c = new int [max-min+1];
for (int j : source) {
int cur = j - corr;
c[cur] += 1;
}
//回放原待排序列
int i = 0;
for (int j = 0; j < c.length; j++) {
while(c[j] != 0){
source[i] = j+corr;
i++;
c[j]-=1;
}
}
}
}
8、桶排序
桶排序的思想是将序列中的元素通过一种函数映射分别放入一系列桶中,再通过一种排序方法将每个桶内的元素进行排序,最后遍历桶,将桶内的元素依次回放原待排序列.
定义的函数要求映射后的桶与桶之间有序,即右边的桶内元素整体大于左边桶内元素.
代码实现:
public class BucketSort {
public static void main(String[] args) {
int [] source = {2,3,5,16,5,7,25,3,10,9,8,4,11,12,14,14,13};
sort(source);
}
static void sort(int [] source){
//初始定义6个桶
int [][] buckets = new int [6][0];
//遍历序列,将数字放入桶内
for (int k : source) {
int indexBuck = k / 5 ;
int[] arrayAppend = arrayAppend(buckets[indexBuck], k);
buckets[indexBuck] = arrayAppend;
}
//桶内插入排序
for (int i = 0; i < 6; i++) {
Insert.insertSort(buckets[i]);
}
//回放至原序列
int j = 0;
for (int[] bucket : buckets) {
for (int i : bucket) {
source[j] = i;
j++;
}
}
System.out.println(Arrays.toString(source));
}
static int [] arrayAppend(int [] original ,int newNum){
int[] array = Arrays.copyOf(original, original.length + 1);
array[original.length] = newNum;
return array;
}
}
9、基数排序
基本思想将元素分别按照位进行排序,最后序列整体有序.
例:{3,5,23,15,35,8},先按个位进行排序后为:{03,23,05,15,35,08},再按照十位排序后:{03,05,08,15,23,35},整体变为有序;
注意点:
1、从低位向高位排序,因为位数越高,对数值大小影响越大;
2、进行位内排序,选择稳定排序方法例(如 56,255 个位排序后位56,258,如果十位排序不稳定,将变成258,56;百位继续排序,又将调换位置,对比稳定排序,多了一次位置交换).
代码实现:
public class RadixSort {
public static void main(String[] args) {
int [] source = {2,3,5,16,5,7,25,3,10,9,8,4,11,12,14,64,45,134,167};
sort(source);
System.out.println(Arrays.toString(source));
}
static void sort(int [] source){
//计算数组序列最大值
int max = max(source);
//计算几次桶遍历
int round = radixCount(max);
for (int i = 1; i <= round; i++) {
//定义桶
int [][] buckets = new int [10][0];
//遍历放入桶
for (int j : source) {
int bucketIndex = radixByPos(j, i);
int[] bucket = arrayAppend(buckets[bucketIndex], j);
buckets[bucketIndex] = bucket;
}
//回放序列
int index = 0;
for(int [] bucket: buckets){
for(int ele :bucket){
source[index] = ele;
index++;
}
}
}
}
//数字取位
static int radixByPos(int origin, int pos){
if(pos == 1){
return origin % 10;
}else {
return origin / (int)(Math.pow(10,(pos-1))) % 10;
}
}
//数组追加元素
static int [] arrayAppend(int [] original ,int newNum){
int[] array = Arrays.copyOf(original, original.length + 1);
array[original.length] = newNum;
return array;
}
//计算最大值
static int max(int [] source){
int max = source[0];
for(int ele : source){
max = Math.max(ele, max);
}
return max;
}
//计算数字位数
static int radixCount(int num){
int count = 0;
do{
num /= 10;
count++;
}while(num > 0);
return count;
}
}
以上算法代码仅供学习交流,一定不是最优雅、最简洁实现.如有错误、疏漏,望指正.