每坚持一天,offer就会离我更近一步🌹
文章目录
- 冒泡排序
- 选择排序
- 插入排序
- 希尔排序
- 快速排序
- 计数排序
- 堆排序
- 归并排序
冒泡排序
算法描述:从第一个元素开始,两两比较,如果前者比后者大,那么就将两者进行交换,这样每经过一次排序,都能找到一个最大的元素并且把它放在数组的最后。
稳定性:稳定
时间复杂度:O(N^2)
代码:
public static int[] bubbleSort(int[] arr){
for (int i = 0; i < arr.length - 1; i++) {
// 比较次数要减去i,每比较一次都会把最大的排到最后
for (int j = 0; j < arr.length - 1 - i; j++) {
// 如果前边元素比后边的元素大就交换
if(arr[j+1] < arr[j]){
int temp = arr[j+1];
arr[j+1] = arr[j];
arr[j] = temp;
}
}
}
return arr;
}
选择排序
算法描述:在每一次排序时,先假设第一个元素是最小的,然后从第二个元素一直遍历到最后一个元素,如果找到比第一个小的元素就进行交换,否则不交换。第一次排序时,有序区间中只有一个元素;第二次排序,有序区间中有两个元素,依次递增,直到所有元素都有序
时间复杂度:O(N^2)
稳定性:不稳定
代码:
public static void selectSort(int[] arr){
for(int i = 0;i < arr.length - 1;i++){
// 假设第一个元素是最小的
int minIndex = i;
for (int j = i+1; j < arr.length; j++) {
// 再从无序的元素中找到比arr[minIndex]小的元素,进行交换
if (arr[j]<arr[minIndex]){
int temp = arr[j];
arr[j] = arr[minIndex];
arr[minIndex] = temp;
}
}
}
}
插入排序
算法描述:,初始时,先把第一个元素看成是有序的,然后把未排序的元素一个一个插入到有序序列中。
时间复杂度:O(N^2)
稳定性:稳定
public static void insertSort(int[] arr){
// 将arr[0]看作是有序的,所以从arr[1]开始遍历
for (int i = 1; i < arr.length; i++){
// 暂存待插入元素
int inserted = arr[i];
int j = i - 1;
// 从后往前开始比较,如果待插入元素更小就将有序元素后移
for (; j >= 0 && inserted < arr[j]; j--) {
arr[j+1] = arr[j];
}
// 将待插入元素插入
arr[j+1] = inserted;
}
}
希尔排序
改进了插入排序
public static void shellSort(int[] arr){
int n = arr.length;
for(int gap = n/2;gap > 0; gap /= 2){
for (int i = gap; i < n; i++) {
int inserted = arr[i];
int j=i-gap;
for (; j >= 0&&arr[j]>inserted; j-=gap) {
arr[j+gap] = arr[j];
}
arr[j+gap] = inserted;
}
}
}
快速排序
算法描述:选择第一个元素当哨兵,一个左指针一个右指针,左指针从左开始扫描,遇到比哨兵大的元素就停;右指针从右开始扫描,遇到比哨兵小的元素就停,然后交换两个元素,直到左指针和右指针相遇,并交换相遇处的元素和哨兵。然后分别进行左半部分和右半部分的排序,同样的过程。
时间复杂度:O(NlogN)
稳定性:不稳定
代码:
public static void quickSort(int[] arr,int begin,int end){
if (begin > end) return;
// 第一个元素当作哨兵
int pivot = arr[begin];
int i = begin;
int j = end;
while (true){
// 从后往前找比哨兵小的
while (arr[j] >= pivot && i < j)
j--;
// 从前往后找比哨兵大的
while (arr[i] <= pivot && i < j)
i++;
// 两个指针相遇就暂停
if (i >= j){
break;
}
// 指针相遇后交换元素
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
// 交换哨兵和指针相遇处的元素
arr[begin] = arr[i];
arr[i] = pivot;
// 对左半部分元素进行排序
quickSort(arr,begin,i-1);
// 对右半部分元素进行排序
quickSort(arr,i+1,end);
}
计数排序
算法描述:从初始数组中选取一个最大的元素,用它来做辅助数组的长度,然后遍历初始数组,统计每一个元素出现的次数并保存到辅助数组中,然后遍历辅助数组,取出元素并覆盖初始数组即可。
时间复杂度:O(N)
空间复杂度:O(N)
适用于元素范围较小的数组
代码:
public static void countSort(int[] arr){
int max = Integer.MIN_VALUE;
// 找出数组中最大的数
for(int num:arr){
if(num > max) max = num;
}
// 初始化计数数组,统计每个元素所出现的次数
int[] temp = new int[max+1];
for(int i=0;i<arr.length;i++){
temp[arr[i]]++;
}
// 取出计数数组中的元素,覆盖原来的数组
int k = 0;
for(int i=0;i<temp.length;i++){
for(int j = temp[i];j > 0;j--){
arr[k++] = i;
}
}
}
堆排序
算法描述:先根据给定数组建一个大根堆(小根堆也可以),然后每次取堆顶元素和堆尾元素进行交换,此时取下来的堆顶元素就是当前数组中最大的,然后再次维护堆的性质,执行n次之后数组即为有序。
时间复杂度:O(NlogN)
(建堆的时间复杂度是O(N)
,维护堆性质的时间复杂度是O(logN)
)
稳定性:不稳定
代码:
// 堆排序入口
public static void heapSort(int[]arr){
int n = arr.length;
//建立堆,大根堆
// 注意,建完堆之后是局部有序的,建堆的复杂度是O(n)
// 从n/2-1开始的原因:下标为i的节点的父节点下标是(i-1)/2,当i=n-1时,代入得i=n/2-1
for( int i = n/2-1;i>=0;i--){
heapify(arr,n,i);
}
// 排序
//每次取堆中最大元素并维护堆的性质
for( int i=n-1;i>0;i--){
int temp = arr[i];
arr[i] = arr[0];
arr[0] = temp;
heapify(arr,i,0);
}
}
// 比较的具体过程
public static void heapify(int[] arr,int n,int i){
// 父节点下标
int largest = i;
// 左孩子下标
int lson = 2 * i + 1;
// 右孩子下标
int rson = 2 * i + 2;
// 比较父节点和左孩子,左孩子下标必须小于n,如果左孩子大,则替换父节点
if(lson<n&&arr[largest]<arr[lson]){
largest = lson;
}
// 比较父节点和右孩子,右孩子下标必须小于n,如果右孩子大,则替换父节点
if(rson<n&&arr[largest]<arr[rson]){
largest = rson;
}
// 如果不满足大根堆的性质,则进行交换
// 将父节点和左右孩子中较大的元素交换
if(largest!=i){
int temp = arr[largest];
arr[largest] = arr[i];
arr[i] = temp;
// 向下继续比较
heapify(arr,n,largest);
}
}
归并排序
算法描述:采用递归与分治的思想,先把数组按照两两划分的原则对数组分割,直到每个分组都是一个元素,然后再按照之前分割的顺序比较大小之后进行两两合并。
时间复杂度:O(NlogN)
稳定性:稳定
代码:
public static void merge(int[] arr,int[] tempArr,int left,int mid,int right){
// 标记左半区第一个未排序的元素
int l_pos = left;
// 标记右半区第一个未排序的元素
int r_pos = mid + 1;
// 临时数组元素的下标
int pos = left;
// 开始合并
while (l_pos <= mid&&r_pos <= right){
if(arr[l_pos] < arr[r_pos])
tempArr[pos++] = arr[l_pos++];
else
tempArr[pos++] = arr[r_pos++];
}
// 可能左半区还有剩余元素
while (l_pos <= mid)
tempArr[pos++] = arr[l_pos++];
// 可能右半区还有剩余元素
while (r_pos <= right)
tempArr[pos++] = arr[r_pos++];
// 把临时数组的元素复制回主数组
while (left <= right){
arr[left] = tempArr[left];
left++;
}
}
public static void msort(int[] arr,int[] tempArr,int left,int right){
// 如果只有一个元素则不进行划分
if(left < right){
int mid = (left+right)/2;
//递归划分左半部分
msort(arr,tempArr,left,mid);
//递归划分右半部分
msort(arr,tempArr,mid+1,right);
//合并
merge(arr,tempArr,left,mid,right);
}
}
public static void mergeSort(int[] arr){
int n = arr.length;
// 分配一个辅助数组
int[] tempArr = new int[n];
// 开始归并排序,传入左边界和右边界
msort(arr,tempArr,0,n-1);
}
整理也挺费时间的,如果觉得对你有帮助,点个赞再走吧!感谢收看!