?前言?
定义: 排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。平时的上下文中,如果提到排序,通常指的是排升序(非降序)。通常意义上的排序,都是指的原地排序
稳定性: 两个相等的数据,如果经过排序后,排序算法能保证其相对位置不发生变化,则我们称该算法是具备稳定性的排序算法
🍓 一,插入排序
1,直接插入排序
(1)原理
首先分为有序区间(0,i),即(0,1),无序区间(i,n),默认第一个元素是有序的,每次选择无序区间的第一个元素,插入在有序区间的合适位置
(2)实现`
public static void insertionSortBase(int[] arr) {
for (int i = 0; i < arr.length; i++) {
for (int j = i; j > 0 && arr[j-1]>arr[j]; j--) {
swap(arr,j,j-1);
}
}
}
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
(3)稳定性-时间复杂度
?稳定性 根据查入的位置,插入排序在排序的过程中,始终保持元素的在整体元素中的相对位置不变,所以是稳定的排序算法。
?时间复杂度 通过分析可以知道,在最好的情况下,即是数组本身有序的情况下,只需要遍历一遍即可,这时候的时间复杂度取决于元素个数,为:O(n)。
在最坏的情况下,内外都要循环一次,所以这时候时间复杂度为O(n2)。
平均时间复杂度为:O(n2)
2,希尔排序
(1)原理
不断地进行gap分组,组内排序,直到gap=1时,整个数组插入排序。其中插入排序最外层是从gap走到数组的末尾,最内层是从gap索引向前看,看距离gap长度的元素,j为当前元素,j-gap是距离gap长的元素,两者进行比,当j-gap>=0,说明还有元素,交换元素的位置
(2)具体实现
public static void shellSort(int[] arr) {
int gap = arr.length/2;
while(gap > 1){
insertionSortGap(arr,gap);
gap/=2;
}
insertionSortGap(arr,1);
}
private static void insertionSortGap(int[] arr, int gap) {
for (int i = gap; i < arr.length; i++) {
for (int j = i; j-gap>=0 && arr[j]<arr[j-gap]; j=j-gap) {
swap(arr,j,j-gap);
}
}
}
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
(3)稳定性-时间复杂度
🍌稳定性 在排序完成后,两个相同的元素位置(上面图示的7*和7)位置发生了改变,所以是不稳定的
🍌时间复杂度 介于O( n ^2)和O(nlogn)之间,视它的最好最坏以及平均复杂度具体分析 稳定性:
🍒二,选择排序
1,直接选择排序
(1)原理
每次将无序区间最小值放在有序区间的第一个,开始默认第一个元素最小,有序区间[0,i),无序区间[i+1,n), 类似于冒泡,循环到最后一次有序
(2)具体实现
public static void selectionSort(int[] arr) {
for (int i = 0; i < arr.length-1; i++) {
int min = i;
for (int j = i+1; j <arr.length ; j++) {
if(arr[j] < arr[min]){
min = j;
}
}
swap(arr,i,min);
}
}
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
(3)稳定性-时间复杂度
🍓 稳定性 假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。
🍓时间复杂度 第一次内循环比较N - 1次,然后是N-2次,N-3次,……,最后一次内循环比较1次。 共比较的次数是 (N - 1) + (N - 2) + … + 1,求等差数列和,得 (N - 1 + 1)* N / 2 = N^2 / 2。 舍去最高项系数,其时间复杂度为 O(N^2)。 虽然选择排序和冒泡排序的时间复杂度一样,但实际上,选择排序进行的交换操作很少,最多会发生 N - 1次交换。 而冒泡排序最坏的情况下要发生N^2 /2交换操作。从这个意义上讲,交换排序的性能略优于冒泡排序。
(4)优化-双向选择排序
每一次从无序区间选出最小 + 最大的元素,存放在无序区间的最前和最后,直到全部待排序的数据元素排完 。
public static void selectSortOP(int[] array) {
int low = 0;
int high = array.length - 1;
while (low <= high) {
int min = low;
int max = low;
for (int i = low + 1; i <= max; i++) {
if (array[i] < array[min]) {
min = i;
}
if (array[i] > array[max]) {
max = i;
}
}
swap(array, min, low);
if (max == low) {
max = min;
}
swap(array, max, high);
}
}
array = { 9, 5, 2, 7, 3, 6, 8 }; // 交换之前 // low = 0; high = 6 // max = 0; min = 2 array = { 2, 5, 9, 7, 3, 6, 8 }; // 将最小的交换到无序区间的最开始后 // max = 0,但实际上最大的数已经不在 0 位置,而是被交换到 min 即 2 位置了 // 所以需要让 max = min 即 max = 2 array = { 2, 5, 8, 7, 3, 6, 9 }; // 将最大的交换到无序区间的最结尾后
2,堆排序
(1)原理
(1)将无需序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆 (2)将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端 (3)重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序
(2)实现-时间复杂度
? 博客链接:? https://blog.csdn.net/qq_55660421/article/details/122380669
🍓文章目录 🍌一,堆排序 1,什么是堆排序 2,堆排序思想 3,代码实现 🍇二,时间复杂度分析 1,初始化建堆 2,排序重建堆 3,总结
🌸三,交换排序
1,冒泡排序
(1)原理
每次冒泡排序操作都会将相邻的两个元素进行比较,看是否满足大小关系要求,如果不满足,就交换这两个相邻元素的次序,一次冒泡至少让一个元素移动到它应该排列的位置,重复N次,就完成了冒泡排序。
(2)具体实现
public void bubbleSort(int[] arr, int n){
if(n<=1){
return;
}
for(int i=0;i < n;i++){
boolean flag = false;
for (int j = 0; j < n-i-1; j++) {
if (arr[i - 1] > arr[i]) {
swap(arr, i - 1, i);
flag = true;
}
}
if(!flag) break;
}
}
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
🌸🌸时间复杂度:
原操作(基本操作)为交换操作,当数组按从小到大有序排列时,基本操作执行次数为0,当自大到小有序排列时,基本操作次数为n(n-1)/2,一般情况下讨论算法在最坏的情况下时间复杂度(个别取平均),所以时间复杂度为O(n^2).
2,快速排序
(1)原理思路
快速排序主要采用分治的基本思想,每次将一个位置上的数据归位,此时该数左边的所有数据都比该数小,右边所有的数据都比该数大,然后递归将已归位的数据左右两边再次进行快排,从而实现所有数据的归位。
(2)具体分析
private static void quickSortInternal(int[] arr, int l, int r) {
if (r - l <= 15) {
insertBase(arr,l,r);
return;
}
int p = partition(arr,l,r);
quickSortInternal(arr,l,p-1);
quickSortInternal(arr,p+1,r);
}
private static int partition(int[] arr, int l, int r) {
int randomIndex = random.nextInt(l,r);
swap(arr,l,randomIndex);
int v = arr[l];
int j = l;
for (int i = l+1; i <= r; i++) {
if(arr[i] < v){
swap(arr,j+1,i);
j++;
}
}
swap(arr,l,j);
return j;
}
(3)稳定性-时间复杂度
🍒稳定性🍒
快速排序有两个方向,左边的i下标一直往右走(当条件a[i] <= a[center_index]时),其中center_index是中枢元素的数组下标,一般取为数组第0个元素。 在中枢元素和a[j]交换的时候,很有可能把前面的元素的稳定性打乱,比如序列为 5 3 3 4 3 8 9 10 11 现在中枢元素5和3(第5个元素,下标从1开始计)交换就会把元素3的稳定性打乱。 所以快速排序是一个不稳定的排序算法,不稳定发生在中枢元素和a[j]交换的时刻。
🍎时间复杂度🍎 最坏情况, 需要进行n‐1递归调用,其空间复杂度为O(n), 平均情况为O(logn)。
🍌四,归并排序
(1)原理
归并排序是一种概念上最简单的排序算法,与快速排序一样,归并排序也是基于分治法的。归并排序将待排序的元素序列分成两个长度相等的子序列,为每一个子序列排序,然后再将他们合并成一个子序列。合并两个子序列的过程也就是两路归并。
(2)具体实现
private static void mergeSortInternal(int[] arr, int l, int r) {
if (r - l<= 15) {
insertBase(arr,l,r);
return;
}
int mid = l + ((r - l) >> 1);
mergeSortInternal(arr,l,mid);
mergeSortInternal(arr,mid + 1,r);
if (arr[mid] > arr[mid + 1]) {
merge(arr,l,mid,r);
}
}
private static void merge(int[] arr, int l, int mid, int r) {
int[] temp = new int[r - l + 1];
for (int i = l; i <= r; i++) {
temp[i - l] = arr[i];
}
int i = l;
int j = mid + 1;
for (int k = l; k <= r; k++) {
if (i > mid) {
arr[k] = temp[j - l];
j ++;
}else if (j > r) {
arr[k] = temp[i - l];
i ++;
}else if (temp[i - l] <= temp[j - l]) {
arr[k] = temp[i - l];
i ++;
}else {
arr[k] = temp[j - l];
j ++;
}
}
}
(3)稳定性-时间复杂度
🍓(1)可以说合并排序是比较复杂的排序,特别是对于不了解分治法基本思想的同学来说可能难以理解。总时间=分解时间+解决问题时间+合并时间。分解时间就是把一个待排序序列分解成两序列,时间为一常数,时间复杂度o(1).解决问题时间是两个递归式,把一个规模为n的问题分成两个规模分别为n/2的子问题,时间为2T(n/2).合并时间复杂度为o(n)。总时间T(n)=2T(n/2)+o(n).这个递归式可以用递归树来解,其解是o(nlogn).此外在最坏、最佳、平均情况下归并排序时间复杂度均为o(nlogn).从合并过程中可以看出合并排序稳定。
🍓(2)从这个递归树可以看出,第一层时间代价为cn,第二层时间代价为cn/2+cn/2=cn…每一层代价都是cn,总共有logn+1层。所以总的时间代价为cn*(logn+1).时间复杂度是o(nlogn).
|