排序算法
冒泡排序:每次比较相邻两个数的大小,调整顺序。
先比较第一个数和第二个数,调整完顺序再比较第二个和第三个,再比较第三个和第四个,依次比较,比较完第一轮将会把最大的数排序到最后。每一轮比较完就会把一个大的数冒泡到最后,冒泡好的数据将不用再排序比较。
public static void bubbleSort(int[] arr) {
for(int i = 0; i < arr.length - 1; i++) {
for(int j = 1; j < arr.length - i; j++) {
if(arr[j - 1] > arr[j]) {
int temp = arr[j - 1];
arr[j - 1] = arr[j];
arr[j] = temp;
}
}
}
}
平均时间复杂度 | 最好情况 | 最坏情况 | 空间复杂度 |
---|---|---|---|
O(n^2) | O(n) | O(n^2) | O(1) |
冒泡排序是稳定的排序,只有当相邻元素的大小不符合要求的时候才调换他们的位置
选择排序
首先在待排列序列中找到最小(大)元素,存放在排序序列的起始位置,然后,再从剩余未排序的序列中继续寻找最小(大)元素,放到已排序序列末尾。以此类推,直到所有元素均排序完毕。
选择排序的主要特点与数据移动有关。如果某个元素位于正确的最终位置上,则它不会被移动。选择排序每次交换一对元素,他们当中至少有一个将被移到其最终的位置上,因此对n个元素的序列进行排序总共进行n - 1次交换。在所有的完全依靠交换去移动元素的排序方法中,选择排序属于非常好的一种。
public static void selectionSort(int[] arr) {
for(int i = 0; i < arr.length; i++) {
int min = Integer.MAX_VALUE;
int index = 0;
for(int j = i; j < arr.length; j++) {
if(min > arr[j]){
min = arr[j];
index = j;
}
}
int temp = arr[i];
arr[i] = arr[index];
arr[index] = temp;
}
}
平均时间复杂度 | 最好情况 | 最坏情况 | 空间复杂度 |
---|---|---|---|
O(n^2) | O(n^2) | O(n^2) | O(1) |
选择排序是不稳定排序,并且无论哪种情况,哪怕原数组已经排序完成,它也会花费将近n^2/2次遍历来确认一遍。
直接插入排序
每一步将一个待排序的元素,按其排序码的大小,插入到前面已经排好序的一组元素的适当位置上去,直到元素全部插入为止。
public static void insertSort(int[] arr) {
int index = 0;//待插入得位置
for(int i = 1; i < arr.length; i++) {
int n = arr[i];//待插入的元素
for(int j = i - 1; j >= 0; j--) {
if(n < arr[j]) {
arr[j + 1] = arr[j];
}else{
index = j;
break;
}
index = j - 1;
}
arr[index + 1] = n;
}
}
平均时间复杂度 | 最好情况 | 最坏情况 | 空间复杂度 |
---|---|---|---|
O(n^2) | O(n^2) | O(n^2) | O(1) |
插入排序是稳定的排序
插入排序所需的时间取决于输入元素的初始顺序。例如,对一个很大且其中的元素已经有序(或者接近有序)的数组进行排序将会比随机顺序的数组或者逆序数组进行排序要块得多。
希尔排序
递减增量排序算法,是插入排序的一种更高效的改进版本。希尔排序是将整个待排序的记录序列分割成若干个子序列分别进行直接插入排序,待整个序列中的记录“基本有序时”,再对全体记录进行依次直接插入排序。
public static void shellSort(int[] nums) {
int i = nums.length / 2;
for(; i >= 1; i = i / 2) {
for(int j = 0; j < i; j++) {
for(int k = j + i; k < nums.length; k += i) {
for(int m = k; m - i >= j; m -= i) {
if(nums[m] < nums[m - i]) {
int temp = nums[m];
nums[m] = nums[m - i];
nums[m - i] = temp;
}
}
}
}
}
}
平均时间复杂度 | 最好情况 | 最坏情况 | 空间复杂度 |
---|---|---|---|
O(nlogn) | O(nlogn) | O(nlogn) | O(1) |
希尔排序是不稳定的
希尔排序更高效的原因是它权衡了子数组的规模和有序性。排序之初,各个子数组都很短,排序之后子数组都是部分有序的,这两种情况都很适合插入排序。
快速排序
采用了一种分治策略—挖坑填数+分治法
①先从序列中取出一个数作为基准数
②分区,将比这个数大的数全部放在它的右边,小于或者等于它的放在它的左边
③再对左右区间重复第二步,直到各个区间只有一个数
public static void quickSort (int[] arr,int l,int r) {
if(l < r) {
int low = l, high = r;
int temp = arr[l];
while(low < high) {
while(low < high && arr[high] > temp) {
high--;
}
if(low < high) {
arr[low] = arr[high];
low++;
}
while(low < high && arr[low] <= temp) {
low++;
}
if(low < high) {
arr[high] = arr[low];
high--;
}
}
arr[low] = temp;
quickSort(arr,l,low - 1);
quickSort(arr,low + 1, r);
}
}
快速排序是不稳定的
平均时间复杂度 | 最好情况 | 最坏情况 | 空间复杂度 |
---|---|---|---|
O(nlogn) | O(nlogn) | O(nlogn) | O(1) |
归并排序
归并排序是将两个(或两个以上)有序列表合并成一个新的有序表,即把待排序的序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。(先分再和的思想)
public static void mergeSort(int[] arr, int left, int right) {
if(left >= right) rerurn;
int mid = left + (right - left) / 2;
mergeSort(arr,left,mid);
mergeSort(arr,mid + 1,right);
combine(arr,left,right,mid);
}
public static void combine(int[] arr, int left, int right, int mid) {
int[] nums = new int[arr.length];
int l = left;
int r = mid + 1;
int index = 0;
while(l <= mid && r <= right) {
if(arr[l] < arr[r]) {
nums[index++] = arr[l++];
}else {
nums[index++] = arr[r++];
}
}
if(l <= mid) {//将左边剩余的元素填充进nums
while(l <= mid) {
mums[index++] = arr[l++];
}
}
if(r <= right) {
while(r <= right) {//将右边剩余的元素填充进nums
nums[index++] = arr[r++];
}
}
index = 0;
//将nums中的元素全部拷贝到原数组中
whlile(left <= right) {
arr[left++] = nums[index++];
}
}
平均时间复杂度 | 最好情况 | 最坏情况 | 空间复杂度 |
---|---|---|---|
O(nlogn) | O(nlogn) | O(nlogn) | O(n) |
归并排序是稳定的
基数排序
将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低为开始,依次进行依次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。
基数排序是稳定的排序
堆排序
将待排序序列构成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾进行交换,此时末尾就为最大值。然后将n - 1个元素重新构造成一个堆,这样会得到n的元素的次大值。如此反复执行,变能得到一个有序序列。
①构造初始堆。将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)
②将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素进行交换,得到第二大元素,如此反复进行交换,重建,交换。
因此堆排序的思路是:
①将无序序列构建成一个堆,根据升序和降序的需求选择大顶堆还是小顶堆。
②将堆顶元素与末尾元素交换,将最大元素“沉”到数组末端。
③重新调整堆结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整,交换,直到整个序列有序。
public static void heapSort(int[] nums,int n) {
int index = n / 2;//从最后一个非叶子节点开始
for(int i = index; i >= 0; i--) {
int child = 2 * i + 1;//左子节点
if(child + 1 <= n && nums[child] < nums[child + 1])
child = child + 1;
if(child <= n && nums[child] > nums[i]){
int tmp = nums[i];
nums[i] = nums[child];
nums[child] = tmp;
}
}
int t = nums[0];
nums[0] = nums[n];
nums[n] = t;
}
public static void main(String[] args) {
int[] nums = new int[] {1,3,2,1,7,8,4,5,6,2,9,8,-1};
for(int i = nums.length - 1; i >= 0; i--) {
heapSort(nums,i);
}
System.out.println(Arrays.toString(nums));
}
平均时间复杂度 | 最好情况 | 最坏情况 | 空间复杂度 |
---|---|---|---|
O(nlogn) | O(nlogn) | O(nlogn) | O(1) |
堆排序不是稳定的排序