常用的排序算法包括:
(1)插入排序√
(2)折半排序√
(3)希尔排序√
(4)选择排序√
(5)堆排序
(6)快速排序√
(7)归并排序√
(8)基数排序
(9)冒泡排序√
一、插入排序
基本思路:从前往后,每个数组插入到应该在的位置。如:第2个数字与第一个数字比较,根据二者大小判断是否应该转换位置;第3个数字在排序时由于前两个数字已经排好顺序,只要根据与前两个数字的大小关系判断,找到适合的位置插入进去,并将其他数字依次后移即可。以此类推。
代码:
static void sort(int[] nums){
int len = nums.length;
for(int i = 1; i < len; i++){
int tmp = nums[i];//保存被排元素的数值
for(int j = i-1; j >= 0; j--){
//依次遍历被排元素之前的元素,找到一个位置,该位置前面的数值小于它,后面大于它
if(nums[j] > tmp){
nums[j+1] = nums[j];
}else{
nums[j+1] = tmp;
break;
}
}
}
时间复杂度:O(n^2),对于每一个数字,都必须向前依次遍历寻找合适的位置
空间复杂度:O(1)
排序算法是稳定的(两个相同大小的数字,在排序后的相对位置不会改变,则称为稳定的排序算法)
二、折半排序
基本思路:在插入排序的基础上,当插入排序中比较操作的代价比交换操作大时,可采用二分查找的方法。由于在每一轮中,被排数字之前的数字都是已经排序好的,因此我们可以通过二分查找的方法来找到正确的插入位置。
代码:
static void sortB(int[] nums){
int len = nums.length;
for(int i = 1; i < len; i++){
//用二分查找法找到应当插入的位置
int tmp = nums[i];
int left = 0, right = i-1;
while(left <= right){//注意是<=
int mid = left + (right-left)/2;
if(nums[mid] < tmp){
left = mid+1;
}
if(nums[mid] > tmp){
right = mid-1;
}
}//停止在left>right,right<tmp,left>tmp
for(int j = i-1; j>=right ; j--){
nums[j+1] = nums[j];
}
nums[right+1] = tmp;
}
}
时间复杂度:O(n^2)
空间复杂度:O(1)
算法稳定
三、希尔排序
基本思路:由于插入排序对几乎已经有序的数据进行操作时,可以达到几乎线性的效率,故希尔排序先将整个待排序序列分为若干个子序列进行插入排序,再对整个序列进行直接排序。首先会给定一个步长gap,每隔gap的数字分为一组进行插入排序;排序完成后gao减小一半,继续进行插入排序,直到gap=1.
代码:
static void shellSort(int[] nums) {
//先分组,距离为gap的为一组,然后gap不断缩小直到插入排序
int len = nums.length;
int gap = len/2;//一般初始距离为len/2
while(gap > 0){
for(int i = gap; i <len ; i++){
//事实上,gap后的每一个数字都拥有依一次被排序的机会,因此没必要按照组进行两次for循环来确保按组排序
int tmp = nums[i];
for(int j = i-gap; j>=0; j -= gap){
if(nums[j] > tmp){
nums[j+gap] = nums[j];
}else{
nums[j+gap] = tmp;//放到空的位置
break;
}
}
}
gap = gap/2;
}
}
其时间复杂度介于O(nlogn) 到 O(n^2) 之间
空间复杂度为O(1)
希尔排序不是稳定的,因为希尔排序在插入元素的时候是跳跃性插入,有可能破坏稳定性。
四、选择排序
基本思路:每一轮选择其中最大的数字放在数组的尾端,进行n轮后全部排好
代码:
static void chooseSort(int[] nums){
int len = nums.length;
for(int i = 0; i< len ; i++){
int min = i;
for(int j = i+1; j<len; j++){
if(nums[min] > nums[j]){
min = j;
}
}//找出最小值
if(min != i){
//放在最前端
int tmp = nums[min];
nums[min] = nums[i];
nums[i] = tmp;
}
}
}
最好情况时间复杂度、最坏情况和平均情况时间复杂度都为O(n^2)。
空间复杂度为O(1)
不稳定
五、堆排序
六、快速排序
基本思想:每一轮都选择一个元素(一般是选择左侧元素)作为基准,遍历整个数组,将比基准小的数字放在左侧,比基准大的数字放在右侧。一轮结束后,分别对左侧和右侧的数组递归采用快速排序。
代码:
static void quickSort(int[] nums,int left,int right){
//快速排序
if(left >= right){
return ;
}
int base = nums[left];//每次都选择最左侧的数作为基准
int i = left+1, j = right;
while(i != j){
while(nums[i] <= base && i<j){
i++;//找到第一个比base大的数
}
while(nums[j] >= base && i<j){
j--;//找到第一个比base小的数
}
//交换i和j
if(i < j){
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}//此时i的值是比base小的数,j是比base大的数,因此将i与base交换,仍不改变前面都是小于base的数
}
if(base>nums[i]){//只有当nums[i]<base时才移动
nums[left] = nums[i];
nums[i] = base;
}
quickSort(nums,left,i-1);
quickSort(nums,i,right);
}
最优的情况下空间复杂度为:O(logN)
最差的情况下空间复杂度为:O(N^2)
快速排序的平均时间复杂度是:O(N*logN)
因为在快速排序的时候,即使待排序元素可基数相等也需要移动待排序元素的位置使得有序,所以快速排序是不稳定的。
七、归并排序
基本思路:对于序列分为两个组,对两个组分别进行排序后再合并到一起排序,依次递归。合并的方法为:两个指针分别指向两组数据的第一个值,比较两个的大小,每次都取出最小的一个作为新数组的头部。
代码:
static void merge(int[]nums,int low,int mid, int high){
int[] nums1 = Arrays.copyOfRange(nums,low,mid+1);//最后一位不包含
int[] nums2 = Arrays.copyOfRange(nums,mid+1,high+1);
int i = 0, j = 0;
for(int m = low; m <= high; m++){
if(i < nums1.length && j < nums2.length){
if(nums1[i] <= nums2[j]){
nums[m] = nums1[i++];
}else{
nums[m] = nums2[j++];
}
}else if(i < nums1.length && j >= nums2.length){
nums[m] = nums1[i++];
}else if(i >= nums1.length && j <nums2.length){
nums[m] = nums2[j++];
}else{
break;
}
}
}
static void mergeSort(int[] nums,int low,int high){
System.out.println("low:"+low);
System.out.println("high:"+high);
int len = high-low+1;
System.out.println("len:"+len);
int mid = (high+low)/2;
System.out.println("mid:"+mid);
if(low >= high)
return ;
mergeSort(nums,low,mid);
mergeSort(nums,mid+1,high);
merge(nums,low,mid,high);
for(int i = 0; i<len; i++){
System.out.print(nums[i]+" ");
}
}
每次合并操作的时间复杂度是O(N),而二叉树的深度是log2N,所以总的时间复杂度是O(N*lgN)。
算法是稳定的
八、冒泡排序
基本思想:进行n轮比较,每一轮中比较相邻的两个数字,直到把最大的数字移到末端
代码:
static void bubbleSort(int[] nums){
int len= nums.length;
for(int i = 0; i < len; i++){
for(int j = 1; j < len-i-1; j++){
if(nums[j] < nums[j-1]){
int tmp = nums[j];
nums[j] = nums[j+1];
nums[j+1] = nums[j];
}
}
}
}
时间复杂度是O(N^2)
算法是稳定的