本篇主要总结一些常用的排序方法。
排序:即将一个集合中的元素以大小进行递增或递减的形式排列的操作。
排序存在稳定与不稳定之说。给定一个集合如{5 8 9 6 3 9 85}, 将其以一定形式进行排序后可见,由于该数组集合种存在两个相同大小的元素,因此排序后的这两个元素肯定是紧挨着的。如果排序前后二者的相位置未发生改变,则为稳定的排序,反之则为不稳定排序。
如果一个排序是稳定的,那它必定能实现不稳定排序;反之一个排序是不稳定的,那它无论如何都无法实现稳定的排序。
1.直接插入排序。
其思路是将待排序的元素逐个插入到一个已经排好序的有序队列种,直到插完为止:
//直接插入排序
public static void insertSort(int[] array){
for (int i = 1; i < array.length; i++) {
int j = i - 1;
int tmp = array[i];
for (; j >= 0; j--) {
if (array[j] > tmp) {
array[j + 1] = array[j];
} else {
break;
}
}
array[j + 1] = tmp;
}
}
时间复杂度:O(N^2)
空间复杂度:O(1)
不稳定
2.希尔排序
其思想是将缩小增量。将一个集合中的元素间隔等分成若干个组,再对每个组中的元素进行插入排序,直到组数为一,由于依次排序,整个数组会越来越有序。插入排序又是越有序效率越高,因此实际上希尔排序是直接插入排序的一种优化:
//希尔排序
public static void shellSort(int[] array){
int gap = array.length;
while (gap > 1){
insertSort1(array, gap);
gap = gap / 2;
}
insertSort1(array, gap);
}
public static void insertSort1(int[] array, int gap){
for (int i = gap; i < array.length; i++) {
int j = i - gap;
int tmp = array[i];
for (; j >= 0; j -= gap) {
if (array[j] > tmp) {
array[j + gap] = array[j];
} else {
break;
}
}
array[j + gap] = tmp;
}
}
其时间复杂度并不好计算。因为i对于不同数量的元素,其gap组数是不定的,没有一个稳定确定的增量来实现普遍性使用。但它是不稳定的排序。
3.直接选择排序
其思想是从待排序的元素中选出一个最大或最小的,存放与首位,直到排序完全部元素:
//选择排序
public static void selectSort(int[] array){
for (int i = 0; i < array.length; i++){
int index = i;
int j = i + 1;
for (; j < array.length; j++){
if (array[j] < array[index]){
index = j;
}
}
swap(array, i, index);
}
}
public static void swap(int[] array, int i, int j){
int tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
时间复杂度:O(N^2)
空间复杂度:O(1)
不稳定
4.快速排序
其核心是想是,在一组待排序的元素中,取一个元素作为基准。比其小的元素全置于左边,比其大的元素全置于右边。然后以此元素作为界限划分为两个小的集合,再次执行该过程。直到划分至每个元素都在其相应位置为止,可以使用挖坑法。
public static void quickSort(int[] array){
quick(array, 0, array.length - 1);
}
public static void quick(int[] array, int start, int end){
if (start >= end){
return;
}
int index = midThree(array, start, end);
swap(array, start, index);
//使用该优化为了减少递归次数
if (end - start + 1 <= 14){
insertSort1(array, start, end);
}
int pivot = partition(array, start, end);
quick(array, start, pivot - 1);
quick(array, pivot + 1, end);
}
private static void insertSort1(int[] array, int left, int right){
for (int i = left + 1; i <= right; i++){
int tmp = array[i];
int j = i - 1;
for (; j >= left; j--){
if (array[j] > tmp){
array[j + 1] = array[j];
}
else {
break;
}
}
array[j + 1] = tmp;
}
}
public static int midThree(int[] array, int left, int right){
int mid = array.length / 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;
}
}
}
//挖坑法(快排)
public static int partition(int[] array,int left, int right){
int tmp = array[left];
while (left < right){
//二次判断left < right是为了避免极端情况下的越界,如数组本身就是1 2 3 4
while (left < right && array[right] >= tmp){
right--;
}
array[left] = array[right];
while (left < right && array[left] <= tmp){
left++;
}
array[right] = array[left];
}
array[left] = tmp;
return left;
}
如上面所示,该快排存在部分可优化的地方。如,在进行多次排序后由于越来越趋于有序,因此在最后几次递归时可以使用直接插入排序。
其次,对于基准的值,若随意取,可能会出现分割后一边特别长一边几乎没有元素的情况。因此取一个中间值,这个中间值从集合数组头部尾部和中部中选择其中间大小。也可以使用与挖坑法类似的Hoare法寻找基准:
public static int getPoint1(int[] array, int left, int right){
int tmp = array[left];
int i = left;
while (left < right){
while (left < right && array[left] <= tmp){
left++;
}
while (left < right && array[right] >= tmp){
right--;
}
swap(array, left, right);
}
swap(array, i, left);
return left;
}
时间复杂度:O(N*logN)
空间复杂度:O(logN)
不稳定
5.归并排序
其思想是将一个待排序的集合进行划分,划分至不可分割为止,再将每个分组中的元素进行排序,使其在该序列中有序,再将两个有序的序列合并,以此类推。
public static void merge(int[] array){
merge1(array, 0, array.length - 1);
}
public static void merge1(int[] array, int start, int end){
if (start == end){
return;
}
int mid = (end + start) / 2;
merge1(array, start, mid);
merge1(array, mid + 1, end);
mergeFunc(array, start, end, mid);
}
public static void mergeFunc(int[] array, int left, int right, int mid){
int s1 = left;
int s2 = mid + 1;
int[] tmp = new int[right - left + 1];
int k = 0;
while (s1 <= mid && s2 <= right){
if (array[s1] <= array[s2]){
tmp[k++] = array[s1++];
}
else {
tmp[k++] = array[s2++];
}
}
while (s1 <= mid){
tmp[k++] = array[s1++];
}
while (s2 <= right){
tmp[k++] = array[s2++];
}
for (int i = 0; i < tmp.length; i++){
array[i + left] = tmp[i];
}
}
时间复杂度:O(N*logN)
空间复杂度:O(N)
稳定
---------------------------最后编辑于2023.5.16 上午十一点左右