一、排序算法实现
1).冒泡排序
1.1算法描述:
冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。
这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。
1.2代码实现(Java)
public static int[] bubbleSort(int[] array) {
if (array.length == 0)
return array;
for (int i = 0; i < array.length; i++)
for (int j = 0; j < array.length - 1 - i; j++)
if (array[j + 1] < array[j]) {
int temp = array[j + 1];
array[j + 1] = array[j];
array[j] = temp;
}
return array;
}
2) 选择排序
2.1算法描述
在n个数据的序列中,需要经过n-1趟的排序。选择排序的原理就是每次在序列中找到最小(大)值,首先每一次找最值需要定一个标准,把未排序列的第一个当作最小(大)值这么一标准,然后在剩下的序列中再找比这个最值小或者大的值来代替它,最终和未排序列的第一个交换(如果找不到那就是自己和自己交换),重复以上步骤,直到排序完成
2.2代码实现(Java)
public static int[] selectionSort(int[] array) {
if (array.length == 0)
return array;
for (int i = 0; i < array.length; i++) {
int minIndex = i;
for (int j = i; j < array.length; j++) {
if (array[j] < array[minIndex]) //找到最小的数
minIndex = j; //将最小数的索引保存
}
int temp = array[minIndex];
array[minIndex] = array[i];
array[i] = temp;
}
return array;
}
3)插入排序
3.1算法描述
插入排序(Insertion-Sort)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。
3.2代码实现(Java)
public static int[] insertionSort(int[] array) {
if (array.length == 0)
return array;
int current;
for (int i = 0; i < array.length - 1; i++) {
current = array[i + 1];
int preIndex = i;
while (preIndex >= 0 && current < array[preIndex]) {
array[preIndex + 1] = array[preIndex];
preIndex--;
}
array[preIndex + 1] = current;
}
return array;
}
4) 希尔排序
4.1算法描述
希尔排序是希尔(Donald Shell)于1959年提出的一种排序算法。希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序,同时该算法是冲破O(n2)的第一批算法之一。它与插入排序的不同之处在于,它会优先比较距离较远的元素。希尔排序又叫缩小增量排序
希尔排序是把记录按下表的一定增量分组,对每组使用直接插入排序算法排序;******随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止
4.2代码实现(Java)
public static int[] ShellSort(int[] array) {
int len = array.length;
int temp, gap = len / 2;
while (gap > 0) {
for (int i = gap; i < len; i++) {
temp = array[i];
int preIndex = i - gap;
while (preIndex >= 0 && array[preIndex] > temp) {
array[preIndex + gap] = array[preIndex];
preIndex -= gap;
}
array[preIndex + gap] = temp;
}
gap /= 2;
}
return array;
}
5)归并排序
5.1算法描述
归并排序是将一个数组分成左右两部分,然后进行比较排序,最终再合并成一个完全有序的一种算法。归并排序在进行分组的过程中,需把序列分到不能再分的情况下(也就是说只剩1个元素的时候),依次进行左右两部分元素比较并合并(使子序列变得有序),最终得到整个序列有序。
5.2代码实现(Java)
public static int[] MergeSort(int[] array) {
if (array.length < 2) return array;
int mid = array.length / 2;
int[] left = Arrays.copyOfRange(array, 0, mid);
int[] right = Arrays.copyOfRange(array, mid, array.length);
return merge(MergeSort(left), MergeSort(right));
}
public static int[] merge(int[] left, int[] right) {
int[] result = new int[left.length + right.length];
for (int index = 0, i = 0, j = 0; index < result.length; index++) {
if (i >= left.length)
result[index] = right[j++];
else if (j >= right.length)
result[index] = left[i++];
else if (left[i] > right[j])
result[index] = right[j++];
else
result[index] = left[i++];
}
return result;
}
6) 快速排序
6.1算法描述
快速排序的基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序
6.2代码实现(Java)
public static void quicksort(int[] a, int start, int end) {
int i, j;
if(start > end) {
return;
}
i = start;
j = end;
int jz = a[start]; //先设定序列第一个元素为基准
while(i < j) {
while(jz <= a[j] && i < j) { //这里必须先从右向左找小于基准的第一个元素
j--; //否则会报索引越界异常
}
while(jz >= a[i] && i < j) {
i++;
}
if(i < j) { //把小于基准的和大于基准的元素交换
int tem = a[i];
a[i] = a[j];
a[j] = tem;
}
}
a[start]=a[j]; //到这里证明这一趟排完
a[j]=jz; //更换基准的位置,使得左边都是比基准小的,右边都是比基准大的
quicksort(a, start, j-1); //然后再对这左右两部分进行快速排序(不包含基准)
quicksort(a, j+1, end);
}
7)堆排序
7.1算法描述
堆排序就是把未排序列看成是一个二叉堆,每次通过堆顶和最后一个元素进行交换,再进行堆顶的调整操作,重复操作来达到有序数列
7.2代码实现(Java)
public static void heapsort(int[] a) {
//构建二叉堆(大顶堆)
for(int i = (a.length - 2) / 2 ; i >= 0 ; i--) {
downAdjust(a, i, a.length);
}
//堆排序
for(int i = a.length - 1; i >= 1; i--) {
//把堆顶的元素与最后一个元素交换
int tem = a[i];
a[i] = a[0];
a[0] = tem;
//为了保持有序,交换后还行进行下沉操作
downAdjust(a, 0, i);
}
}
//调整操作(父节点下沉,子节点上浮)
public static void downAdjust(int[] a, int parent, int n) {
//临时保存要下沉的元素
int tem = a[parent];
//定位左孩子节点的位置
int child = 2 * parent + 1;
//开始下沉
while(child < n) {
//如果右孩子大于左孩子,则定位到右孩子
if(child + 1 < n && a[child + 1] > a[child]) {
child = child + 1;
}
//如果孩子节点小于或等于父节点,则下沉结束
if(a[child] <= tem) {
break;
}
//父节点进行下沉
a[parent] = a[child];
parent = child;
child = 2 * parent + 1;
}
a[parent] = tem;
}
8) 计数排序
8.1算法描述
计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。
计数排序(Counting sort)是一种稳定的排序算法。计数排序使用一个额外的数组C,其中第i个元素是待排序数组A中值等于i的元素的个数。然后根据数组C来将A中的元素排到正确的位置。它只能对整数进行排序。
8.2代码实现(Java)
public static void countingsort(int[] a) {
if(a == null || a.length < 2)
return;
int max = a[0];
int min = a[0];
//寻找数组中的最大值和最小值
for(int i = 0 ; i < a.length ; i++) {
if(max < a[i]) {
max = a[i];
}
if(min > a[i]) {
min = a[i];
}
}
//创建一个临时的数组,为了防止创建多余的空间,
//需要定义一个区间的大小,这个区间大小由最大和最小值的差值决定
int[] tem = new int[max - min + 1];
//统计元素出现的次数,元素的值当做tem数组的下标
for(int i = 0 ; i < a.length; i++) {
tem[a[i] - min]++;
}
int index = 0;
//把统计好的数据还原到原来数组中
for(int i = 0 ; i < max - min + 1; i++) {
for(int j = tem[i] ; j > 0; j--) {
a[index++] = i + min;
}
}
}
9)桶排序
9.1算法描述
桶排序是计数排序的一个更高级版本,桶排序和计数排序的不同就是桶排序把最大值和最小值之间的这些数据进行瓜分,放到不同的桶中,对每个桶进行排序(这里可以使用其他排序,比如快速排序、插入排序等看情况而定),最终再把每个桶里面的序列拼接起来使整个序列有序
9.2代码实现(Java)
public static void bucketsort(int[] a) {
if(a == null || a.length < 2)
return;
int max = a[0];
int min = a[0];
//寻找数组中的最大值和最小值
for(int i = 0 ; i < a.length ; i++) {
if(max < a[i]) {
max = a[i];
}
if(min > a[i]) {
min = a[i];
}
}
//计算差值
int d = max - min;
//设置桶存放元素的范围,这里5就是0~4,5~9,10~14...
int bucketSize = 5;
//桶的数量
int bucketNum = d/5 + 1;
//创建桶
ArrayList<ArrayList<Integer>> bucketList = new ArrayList<>();
//初始化桶
for(int i = 0 ; i < bucketNum; i++) {
bucketList.add(new ArrayList<Integer>());
}
//遍历原数组,将每个元素放入桶中
for(int i = 0 ; i < a.length ; i++) {
//计算应该存放在哪个桶
Integer I = (a[i] - min) / bucketSize;
bucketList.get(I).add(a[i] - min);
}
//对每个桶进行排序,这里使用插入排序
for(int i = 0 ; i < bucketNum ; i++) {
insertionsort(bucketList.get(i));
}
//把每个排序好的数据进行合并汇总放回原数组
int index = 0;
for(int i = 0 ; i < bucketNum; i++) {
for(Integer j : bucketList.get(i)) {
a[index++] = j + min;
}
}
}
public static void insertionsort(ArrayList<Integer> a) {
for(int i = 1 ; i < a.size(); i++ ) {
int insert = a.get(i); //取出要插入的元素
int ageIndex = i-1;
for(; ageIndex >= 0 && a.get(ageIndex) > insert ; ageIndex--) {//这里需把ageIndex >= 0写在前面,不然会报错
a.set(ageIndex + 1, a.get(ageIndex)); //把大于或小于insert的元素向后移
}
a.set(ageIndex+1, insert); //插入ageIndex+1个位置上,因为上面的循环中最后多ageIndex多减了一次
}
}
}
10) 基数排序
10.1算法描述
基数排序是把元素先按个位数(低位)放进十个桶里(0-9),把具有相同位数的放在同一个桶里,按顺序0~9取出;接着按十位数又放进十个桶里,按顺序取出;接着按百位数来.....以此类推。最终达到整个序列有序。反之,也可以从高位进行
10.2代码实现(Java)
public static void rediosort(int[] a) {
if(a == null || a.length < 2)
return;
int max = a[0];
//找出最大值
for(int i = 1 ; i < a.length; i++) {
if(max < a[i]) {
max = a[i];
}
}
//计算最大值是几位数
int num = 1;
while(max / 10 > 0) {
num++;
max = max / 10;
}
//创建10个桶
ArrayList<ArrayList<Integer>> bucketList = new ArrayList<>();
//初始化桶
for(int i = 0 ; i < 10 ; i++) {
bucketList.add(new ArrayList<Integer>());
}
//进行排序,从个位数开始排序
for(int i = 1 ; i <= num; i++) {
for(int j = 0 ; j < a.length ; j++) {
//获取每个数第i个位数的数字
int I = (a[j] / (int)Math.pow(10, i-1)) % 10;
//放入对应的桶里
bucketList.get(I).add(a[j]);
}
//合并放回原数组
int index = 0;
for(int j = 0 ; j < 10; j++) {
for(Integer k : bucketList.get(j)) {
a[index++] = k;
}
//放回原数组后把桶里数据清空,因为每排完一个位后还需要用到这10个桶
bucketList.get(j).clear();
}
}
}
二、排序算法横向总结
关键词解析
稳定性
(1) 稳定排序:在序列中如果a在b的前面,并且a==b,经过排序交换后,a还在b的前面,则称为稳定排序。
(2) 非稳定排序:在序列中如果a在b的前面,并且a==b,经过排序交换后,a有可能不在b前面,则称为非稳定排序。
时间复杂度
一个算法执行需要消耗的时间。
空间复杂度
运行完一个程序所需内存的大小。
排序方式
内部排序:是指在排序过程中不占用额外的存储空间,只利用原来存储待排数据的存储空间进行比较和交换的数据排序。
外部排序:因为数据量大,需要占用额外的存储空间的数据排序。