一 前言
排序:就是使一串记录按照某个关键字的大小进行递增或递减的排列操作。
应用举例:
分类:
复杂度以及稳定性分析:
二 七大排序
1.插入排序
❤️①直接插入排序
基本思想:将待排序的元素按照大小逐个插入到已经排好序的有序序列,直到所有元素插入完为止。
/*插入排序:
* 1、时间复杂度:
* ①最好情况O(N):数据完全有序12345
* ②最坏情况O(N^2):数据完全逆序54321
*
* 2、空间复杂度:O(1)
* 3、稳定性:稳定的排序
* (一个本身稳定的排序可以实现为不稳定的排序,
* 相反一个本身不稳定的排序不可能实现稳定排序)
* */
public static void insertSort(int[] array){
for (int i = 1; i < array.length; i++) {
int tmp = array[i];
int j = i - 1;
for (; j >= 0 ; j--) {
if(array[j] > tmp){
array[j+1] = array[j];
}else{
break;
}
}
array[j+1] = tmp;
}
}
❤️②希尔排序
基本思想:是对直接插入排序的优化,又称缩小增量法。先给定一个gap,将待排序数组中所有元素分成距离为gap的多个组,并对每一组进行排序。当gap为1的时候,再对整个数组进行排序。
/*希尔排序:
* 时间复杂度:n^1.3-n^1.5
* 空间复杂度: O(1)
* 稳定性:不稳定
* */
public static void shellSort(int[] array){
int gap = array.length;
while(gap > 1){
gap /= 2;
shell(array,gap);
}
}
private static void shell(int[] array, int gap){
for (int i = gap; i < array.length ; i++) {
int tmp = array[i];
int j = i - gap;
for (; j >= 0 ; j-=gap) {
if(array[j] > tmp){
array[j+gap] = array[j];
}else{
break;
}
}
array[j+gap] = tmp;
}
}
2.选择排序
基本思想:每一次从数组中选择最小(最大)元素放在起始位置,直到排序完成。
❤️①直接选择排序
基本思想:
1.在元素集合中选择最小的元素(这里以升序举例)
2.如果这个最小元素不是第一个元素,那么它就要和第一个元素交换位置
3.剩余的元素重复这个步骤
第一种方法:
/*直接选择排序: * 时间复杂度:不管最好还是最坏情况,都是O(n^2) * 空间复杂度:O(1) * 稳定性:不稳定的排序*/ public static void selectSort1(int[] array){ for (int i = 0; i < array.length; i++) { int minIndex = i; for (int j = i+1; j < array.length ; j++) { if(array[j] < array[minIndex]){ minIndex = j; } } swap(array, minIndex, i); } } private static void swap(int[] array, int i, int j){ int tmp = array[i]; array[i] = array[j]; array[j] = tmp; }
第二种方法:
public static void selectSort2(int[] array){ int left = 0; int right = array.length-1; while(left < right){ int minIndex = left; int maxIndex = left; for (int i = left+1; i <= right; i++) { if(array[i] < array[minIndex]){ minIndex = i; } if(array[i] < array[maxIndex]){ maxIndex = i; } } swap(array, left, minIndex); //最大值刚好在最小值的位置 if(maxIndex == left){ maxIndex = minIndex; } swap(array, right, maxIndex); left++; right--; } }
❤️②堆排序
基本思想:将堆顶与堆底交换后调整为大根堆,调整之后堆底那个元素就不变了,堆底之前的元素就变成了新的堆底,这时候重复上面步骤。如果是升序建大根堆,降序建小根堆。
/*堆排序:
* 时间复杂度:O(nlogN)
* 空间复杂度:O(1)
* 稳定性:不稳定
* 数据量非常大的时候,堆排序一定比希尔排序快
* */
public static void heapSort(int[] array){
createBigHeap(array);
int end = array.length-1;
while(end > 0){
swap(array, 0, end);
shiftDown(array, 0, end);
end--;
}
}
private static void createBigHeap(int[] array){
for (int parent = (array.length-1-1)/2; parent >= 0 ; parent--) {
shiftDown(array, parent, array.length);
}
}
private static void shiftDown(int[] array, int parent, int end){
int child = 2*parent+1;
while(child < end){
if(child+1 < end && array[child] < array[child+1]){
child++;
}
if(array[child] > array[parent]){
swap(array, child, parent);
parent = child;
child = 2*parent+1;
}else{
break;
}
}
}
3.交换排序
基本思想:键值值大的元素向尾部移动,键值小的元素向前部移动
❤️①冒泡排序
/*冒泡排序:
* 时间复杂度:O(n^2) 加了优化最好情况是O(N)
* 空间复杂度:O(1)
* 稳定性:稳定
* */
public static void bubbleSort(int[] array){
for (int i = 0; i < array.length-1; i++) {
boolean flg = false;
for (int j = 0; j < array.length-1-i; j++) {
if(array[j] > array[j+1]){
swap(array, j, j+1);
flg = true;
}
}
if(!flg){
return;
}
}
}
❤️②快速排序
基本思想:是一种二叉树结构的交换排序方法。取第一个元素为基准值,将待排序的集合分为两个序列,左子序列的所有元素小于基准元素,右子序列的所有元素都大于基准元素,然后左右子序列重复刚才的过程直到排序完成。
递归实现:
/*快速排序: * 1、时间复杂度: * ①最好情况O(N*logN):满二叉树/完全二叉树 * ②最坏情况O(N^2):单分支的树 * * 2、空间复杂度: * ①最好情况O(logN):满二叉树/完全二叉树 * ②最坏情况O(N):单分支的树 * * 3、稳定性:不稳定的排序 * */ public static void quickSort(int[] array){ quick(array, 0, array.length-1); } private static void quick(int[] array, int start, int end){ if(start >= end) return;//左边只有一个节点或者一个都没有 /*优化 * 1、三数取中:降低空间复杂度*/ int index = midIndexThree(array, start, end); swap(array, index, start); //2、插入排序:减少递归次数 if(end - start + 1 <= 7) { //插入排序 insertSortRange(array,start,end); return; } int pivot = partition1(array, start, end); quick(array, start, pivot-1); quick(array, pivot+1, end); } private static int midIndexThree(int[] array, int left, int right){ int mid = (left+right) / 2; if(array[left] < array[right]) { if(array[mid] < array[left]) { return left; }else if(array[mid] > array[right]) { return right; }else { return mid; } }else { if(array[mid] > array[left]) { return left; }else if(array[mid] < array[right]) { return right; }else { return mid; } } } private static void insertSortRange(int[] array,int begin,int end) { for (int i = begin+1; i <= end; i++) { int tmp = array[i]; int j = i-1; for (; j >= begin ; j--) { if(array[j] > tmp) { array[j+1] = array[j]; }else { break; } } array[j+1] = tmp; } } //1、Hoare法 private static int partition1(int[] array,int left, int right){ int key = array[left]; int i = left; while(left < right){ while(left < right && array[right] >= key){ right--; } //right下标一定是比key小的数据 while(left < right && array[left] <= key){ left++; } //left下标一定是比key大的数据 } //相遇的位置与i交换 swap(array, left, i); return left; } //2、挖坑法 private static int partition2(int[] array,int left, int right){ int key = array[left]; while(left < right){ while(left < right && array[right] >= key){ right--; } //right下标一定是比key小的数据 array[left] = array[right]; while(left < right && array[left] <= key){ left++; } //left下标一定是比key大的数据 array[right] = array[left]; } array[left] = key; return left; }
非递归实现:
//非递归实现快速排序 public static void quickSortNor(int[] array){ Stack<Integer> stack = new Stack<>(); int left = 0; int right = array.length-1; int pivot = partition1(array, left, right); if(pivot - 1 > left){ stack.push(left); stack.push(pivot-1); } if(pivot + 1 < right){ stack.push(pivot+1); stack.push(right); } while(!stack.isEmpty()){ right = stack.pop(); left = stack.pop(); pivot = partition1(array, left, right); if(pivot - 1 > left){ stack.push(left); stack.push(pivot-1); } if(pivot + 1 < right){ stack.push(pivot+1); stack.push(right); } } }
4.归并排序
基本思想:采用分治法,先让每个子序列有序,再使子序列段间有序。
merge方法:
递归实现:
/*归并排序: * 时间复杂度:O(N*logN) * 空间复杂度:O(n) * 稳定性:稳定*/ public static void mergeSort(int[] array){ mergeSortFunc(array, 0, array.length-1); } private static void mergeSortFunc(int[] array, int left, int right){ if(left >= right) return; int mid = (left + right)/2; mergeSortFunc(array, left, mid); mergeSortFunc(array, mid+1, right); merge(array, left, right, mid); } private static void merge(int[] array, int left, int right, int mid){ int s1 = left; int s2 = mid+1; int[] tmpArr = new int[right - left +1]; int k = 0; //证明两个区间都同时有数据 while(s1 <= mid && s2 <= right){ if(array[s2] <= array[s1]){ tmpArr[k++] = array[s2++]; }else{ tmpArr[k++] = array[s1++]; } } while(s1 <= mid){ tmpArr[k++] = array[s1++]; } while(s2 <= right){ tmpArr[k++] = array[s2++]; } //这时候临时数组tmpArr里面一定是有序数组了 for (int i = 0; i < tmpArr.length; i++) { array[i+left] = tmpArr[i]; } }
非递归实现:
//非递归实现归并排序 public static void mergeSortNor(int[] array){ int gap = 1; while(gap < array.length){ for (int i = 0; i < array.length-1; i += 2*gap) { int left = i; int mid = left+gap-1; int right = mid+gap; if(mid >= array.length){ mid = array.length-1; } if(right >= array.length){ right = array.length-1; } merge(array,left,right,mid); } gap *= 2; } }
三 其他排序
1.基数排序
2.计数排序
/*计数排序:
* 时间复杂度:O(N+范围)
* 空间复杂度:O(范围)
* 稳定性:和给定的范围有关*/
public static void countSort(int[] array){
int minVal = array[0];
int maxVal = array[0];
//1.求当前数组的最大值和最小值
for (int i = 1; i < array.length; i++) {
if(array[i] > maxVal){
maxVal = array[i];
}
if(array[i] < minVal){
minVal = array[i];
}
}
//2.根据最大值和最小值来确定数组大小
int[] count = new int[maxVal - minVal + 1];
//3.遍历原来的数组并计数
for (int i = 0; i < array.length; i++) {
count[array[i] - minVal]++;
}
//4.遍历计数count把当前元素写回array
int index = 0;//重新表示数组下标
for (int i = 0; i < count.length; i++) {
while(count[i] > 0){
array[index] = i+minVal;
index++;
count[i]--;
}
}
}