排序算法可以分为两大类:
排序算法 | 时间复杂度(平均) | 时间复杂度(最差) | 时间复杂度(最好) | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|
冒泡排序 | O(n2) | O(n2) | O(n) | O(1) | 稳定 |
快速排序 | O(n㏒2n) | O(n2) | O(n㏒2n) | O(n㏒2n) | 不稳定 |
简单插入排序 | O(n2) | O(n2) | O(n) | O(1) | 稳定 |
希尔排序 | O(n1.3) | O(n2) | O(n) | O(1) | 不稳定 |
简单选择排序 | O(n2) | O(n2) | O(n2) | O(1) | 不稳定 |
堆排序 | O(n㏒2n) | O(n㏒2n) | O(n㏒2n) | O(1) | 不稳定 |
归并排序 | O(n㏒2n) | O(n㏒2n) | O(n㏒2n) | O(n) | 稳定 |
计数排序 | O(n+k) | O(n+k) | O(n+k) | O(n+k) | 稳定 |
桶排序 | O(n+k) | O(n2) | O(n) | O(n+k) | 稳定 |
基数排序 | O(n2k) | O(n2k) | O(n2k) | O(n+k) | 稳定 |
稳定性: 排序过程中,相等的两个值对比时是否会发生前后变化。
时间复杂度: 对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律。
空间复杂度: 是指算法在计算机内执行时所需存储空间的度量,它也是数据规模n的函数。
描述:
冒泡排序 是一种简单的排序算法。重复遍历需要排序的数列,每次比较两个元素,按照升序或降序的方式交换位置,直到没有再需要交换的值,即该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。
使用双重循环遍历该数组,外层循环控制遍历次数,内层循环每个位置相互比较,按照升序或降序的规则,对比时小的或大的数值将被放在前面或后面。
性能:
代码实现:
/**
* 交换排序:冒泡排序
* @param array
* @return
*/
public static int[] bubbleSort(int[] array){
for (int i = 0; i < array.length; i++) {//外层循环控制遍历的位置数量
for (int j = 0; j < array.length - i - 1; j++) {//内层循环对比是否交换位置
if(array[j] > array[j+1]){
int tmp = array[j];
array[j] = array[j+1];
array[j+1] = tmp;
}
}
}
return array;
}
描述:
快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。
设计思想(分治法):
性能::
代码实现:
/**
* 交换排序:快速排序
* 时间复杂度:最坏时间:O(n^2); 最好和平均:O(n*log n) 辅助空间:O(log n) 稳定性:不稳定
* @param array 待排序数组
* @return 排序后的数组
*/
public static int[] quickSort(int[] array) {
if(array!=null&&array.length>0){
quickSorts(array,0,array.length-1);
}
return array;
}
private static void quickSorts(int[] array, int low, int high){
//递归结束条件
if(low >= high){
return ;
}
//定义基准值
int key = array[low];
//从前往后遍历的索引坐标
int i = low;
//从后往前遍历的索引坐标
int j = high;
//从基准开始出发,i、j两个值从前和从后两个方向向中间遍历,直到相遇(交叉)即停止遍历,代表此次找到新的基准和分段排序
while(i < j){
//循环从后往前遍历获取比基准key小的值,记录下标j
while(i < j && array[j] > key){
j--;
}
//循环从前往后遍历获取比基准key大的值,记录下标i
while(i < j && array[i] <= key){
i++;
}
/**
* 找到基准key进行上述i和j的循环需要交换的值
* i为找到大于等于基准的下标索引
* j为找到小于基准的下标索引
* i大于等于j则表明不需要交换,不满足交换i、j条件
*/
if(i < j){
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
// 调整key的基准即
int temp = array[low];
array[low] = array[i];
array[i] = temp;
//排序i左边
quickSorts(array,low,i-1);
//排序i右边
quickSorts(array,i+1,high);
}
描述:
选择排序就是每次找未排序数组中的最小(最大)的记录并固定其位置。
设计思想:
性能::
代码实现:
/**
* 选择排序:直接选择排序
* 时间复杂度:最好和最坏平均时间均为O(n^2) 辅助空间:O(1) 稳定性:不稳定
* @param array
* @return
*/
public static int[] chooseSort(int[] array){
//控制外层循环即N次交换值,将剩余后的最小值与i位置交换
for(int i = 0; i < array.length - 1; i++){
//记录每次循环的最小值索引
int min = i;
//遍历找到每次循环的最小值
for(int j = i + 1; j < array.length; j++){
if(array[j] < array[min]){
//记录本次外层循环的最小值
min = j;
}
}
//将最小值和第i个位置进行对换
int temp = array[i];
array[i] = array[min];
array[min] = temp;
}
return array;
}
描述:
将一个记录插入到已经排好序的有序表中,从而一个新的、记录数增1的有序表。在其实现过程使用双层循环,外层循环对除了第一个元素之外的所有元素,内层循环对当前元素前面有序表进行待插入位置查找并进行移动。
设计思想:
性能::
代码实现:
/**
* 插入排序:直接插入排序
* @param array
* @return
*/
public static int[] insertSort(int[] array){
//待插入的节点
int insertNode = 0;
//遍历索引
int leftIndex = 0;
//循环遍历,从数组中第二个节点开始,默认第一个节点是有序的即一个数字本身就是有序的
for(int i = 1; i < array.length; i++){
//循环设置插入值
insertNode = array[i];
//获取有序列表中的最右下标索引
leftIndex = i - 1;
/**
* 从有序列表中的最右下标索引开始与插入值对比
* 仅当下标索引不为负数才使用下标索引值对比插入值即索引仍在有序列表中
* 索引值大于插入值,则说明继续往左边遍历
*/
while(leftIndex >= 0 && array[leftIndex] > insertNode){
//移动有序列表中的值
array[leftIndex+1] = array[leftIndex];
//索引自减
leftIndex--;
}
/**
* 将插入值放入索引位置,因遍历初进行i-1,说明本身leftIndex是在i的左侧
* 若while循环未进入,也能保证leftIndex+1 等于i,相当于自身重复赋值
* 若满足while条件,则说明leftIndex又被减少过,即说明插入点索引也是需要加1
*/
array[leftIndex+1] = insertNode;
}
return array;
}
描述:
希尔排序也叫“缩小增量排序”,是插入排序的改进版
图形效果:
设计思想:
性能::
代码实现:
/**
* 插入排序:希尔排序
* @param a 排序的数组
* @param n 排序的长度,用以确定步长序列{t1=n/2,t2=t1/2,...}
*/
public static void shellSort(int[] array,int n){
//获取步长
for (int gap = n/2; gap > 0 ; gap = gap/2) {
//对每一个步长,进行遍历,按步长对应的索引下标对比
for (int i = gap; i < n; i++) {
// 索引对应值对比
if(array[i] < array[i-gap]){
//存储步长目标值
int temp = array[i];
//获取步长的上一个索引下标
int k = i - gap;
/**
* 循环对比步长索引值即关键遍历步长对比
* (1)每次对比必须步长前索引值大于目标索引值才进行赋值
* (2)每次进行赋值后,需要对步长对比索引向前推进步长单位,然后再次进行对比
* (3)直到对比索引越界后结束或对比索引值与目标索引小,则结束while循环
*/
while (k >= 0 && array[k] > temp){
//将目标值小于对比索引值的进行赋值即将小值放在前一个步长
array[k+gap] = array[k];
//继续按步长进行推进,再次对比,检查是否需要继续向前对比
k -= gap;
}
//将步长目标值设置到原位置或上述while变更的位置
array[k+gap] = temp;
}
}
}
}
描述:
将待排序列使用递归进行折半分组,使用辅助数组按照从小到大进行两两合并;
图形效果:
设计思想:
利用递归与分治技术将数据序列划分为越来越小的半子表,再对半子表排序,最后用递归将排好序的半子表合并为越来越大的有序序列。
性能:
实现代码:
/**
* 2.二路归并排序,递归实现
* @param array 待排序数组
* @param first 起点
* @param last 终点
* @param temp 辅助数组
*/
public static void mergeSort(int[] array,int first,int last,int[] temp){
//二路排序递归终止条件
if(first >= last){
return ;
}
//获取本次需要排序的起点和终点的中间点
int mid = (first + last)/2;
//开启中间点左边排序即一路
mergeSort(array,first,mid,temp);
//开启中间点右边排序即另一路
mergeSort(array,mid+1,last,temp);
//对上述两路排序进行合并排序
mergeArray(array,first,mid,last,temp);
}
/**
* 1.将两个有序数组进行合并,a[first,mid] 和 a[mid+1,last]
* @param array 待排序数组
* @param first 起点
* @param mid 中间点
* @param last 终点
* @param temp 辅助数组
*/
public static void mergeArray(int[] array,int first,int mid,int last,int[] temp){
//定义第一路即中间点左边数组起始点
int i = first;
//定义第二路即中间点右边数组起始点
int j = mid + 1;
//定义辅助数组起始点
int k = 0;
//循环遍历两路,将小值陆续放入辅助数组
while (i <= mid && j <= last){
//左路与右路遍历对比
if(array[i] <= array[j]){
//放入辅助数组
temp[k++] = array[i++];
}else {
//放入辅助数组
temp[k++] = array[j++];
}
}
//检查上面while不满足条件时是否存在左路未遍历完
while (i <= mid){
temp[k++] = array[i++];
}
//检查上面while不满足条件时是否存在右路未遍历完
while (j <= last){
temp[k++] = array[j++];
}
for (int s = 0;s < k;s++) {
array[first+s] = temp[s];
}
}