前言
说实话还是为了准备(可能有的)面试,哈哈,先把常用的总结一下。我想的是这样:排序算法应该要经常手撕,所以可以在markdown编辑状态下经常写,然后和文章里的对一下,这样效果可能比较好。
1.复杂度,稳定性总结
注意:一般来说最好和最快问的少
二.代码实现
冒泡排序:
- 介绍:一种简单的排序算法,其方法是:首先将第一个关键字与第二个关键字进行比较,若逆序,则交换位置;然后比较第二个与第三个关键字,依次进行比较,直到第n-1个关键字和第n的关键字完成比较为止。上述过程是第一趟冒泡,结果就是值最大的关键字排在了最后面。然后对前n-1个关键字进行第二趟冒泡,如此往复,直到整个序列有序为止。
- 优化:很明显,若果某一趟冒泡过程中没有发生元素位置交换,那么此时整个序列已经是有序的,无需继续下面的操作。
最好情况已经排好了时间复杂度遍历一下就是O(n),最坏每次都交换O(n2)
public static void bubbleSort(int[] array) {
int len = array.length;
for(int i=0;i<len;i++){
for(int j=0;j<len-i-1;j++){
if(array[j] > array[j+1])swap(array,j,j+1);
}
}
}
public static void swap(int[] array,int l,int r){
int temp = array[l];
array[l] = array[r];
array[r] = temp;
return;
}
快速排序:
- 介绍:快速排序是对冒泡排序的一种改进,它的基本思想如下:通过一趟排序将序列分割成两部分,其中一部分的关键字均大于另一部分,则可以分别对两部分进行排序,从而使整个序列有序。
- 步骤:首先选择一个关键字作为枢纽(通常为第一个元素的关键字),然后让所有关键字比枢纽小元素放在枢纽前面,比枢纽大的元素放在枢纽后面。这样就根据枢纽最后落在的位置将序列分割成为了两部分。然后进行递归运算,就可以对数列进行排序。
时间复杂度:nlogn;空间复杂度:logn,最坏n
public static void quickSort(int[] array, int left, int right) {
if (left < right) {
int pivot = array[left];
int l = left;
int r = right;
while (l < r) {
while (l < r && array[r] >= pivot) {
r--;
}
array[l] = array[r];
while (l < r && array[l] <= pivot) {
l++;
}
array[r] = array[l];
}
array[l] = pivot;
quickSort(array, left, l - 1);
quickSort(array, l + 1, right);
}
}
归并排序:
- 介绍:将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
- 步骤:
- 把长度为n的输入序列分成两个长度为n/2的子序列。
- 对这两个子序列分别采用归并排序。
- 将两个排序好的子序列合并成一个最终的排序序列。
时间复杂度nlogn, 空间复杂度n:对于n个数据,需要分成logn层,而合并每一层的数据,都需要O(n)的时间,所以时间复杂度稳定为O(nlogn)。
//注意这是闭区间,左右都能取到的
public static void sort(int[] data,int left,int right) {
if(left >= right) return;
//找出中间索引
int center = (left + right)/2;
//对左边数组进行递归
sort(data, left, center);
//对右边数组进行递归
sort(data, center+1, right);
//合并
merge(data,left,center,right);
}
public static void merge(int[] data,int left, int center,int right) {
//临时数组
int[] tmpArr = new int[data.length];
//右数组第一个元素索引
int mid = center + 1;
//third 记录临时数组的索引
int newIndex = left;
//缓存左数组第一个元素的索引
int tmp = left;
while (left <= center && mid <=right) {
//从两个数组中取出最小的放入临时数组
if (data[left] <= data[mid]) {
tmpArr[newIndex++] = data[left++];
} else {
tmpArr[newIndex++] = data[mid++];
}
}
//剩余部分依次放入临时数组(实际上两个while只会执行其中一个)
while(mid <= right) {
tmpArr[newIndex++] = data[mid++];
}
while(left <= center) {
tmpArr[newIndex++] = data[left++];
}
//将临时数组中的内容拷贝回原数组中
while (tmp <= right) {
data[tmp] = tmpArr[tmp];
tmp++;
}
}
堆排序:
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
时间复杂度:
因为选择是没有顺序的,所以选择排序不能提前退出,不管是什么序列,都需要执行完整的排序过程。
- 最优:O(nlogn)
- 最差:O(nlogn)
- 平均:O(nlogn)
空间复杂度:
不需要额外的空间占用,所以为O(1)。
public class HeapSort {
public static void main(String[] args) {
// int[] arr = {5, 1, 7, 3, 1, 6, 9, 4};
int[] arr = {16, 7, 3, 20, 17, 8};
heapSort(arr);
for (int i : arr) {
System.out.print(i + " ");
}
}
/**
* 创建堆,
* @param arr 待排序列
*/
private static void heapSort(int[] arr) {
//创建堆
for (int i = (arr.length - 1) / 2; i >= 0; i--) {
//从第一个非叶子结点从下至上,从右至左调整结构
adjustHeap(arr, i, arr.length);
}
//调整堆结构+交换堆顶元素与末尾元素
for (int i = arr.length - 1; i > 0; i--) {
//将堆顶元素与末尾元素进行交换
int temp = arr[i];
arr[i] = arr[0];
arr[0] = temp;
//重新对堆进行调整
adjustHeap(arr, 0, i);
}
}
/**
* 调整堆
* @param arr 待排序列
* @param parent 父节点
* @param length 待排序列尾元素索引
*/
private static void adjustHeap(int[] arr, int parent, int length) {
//将temp作为父节点
int temp = arr[parent];
//左孩子
int lChild = 2 * parent + 1;
while (lChild < length) {
//右孩子
int rChild = lChild + 1;
// 如果有右孩子结点,并且右孩子结点的值大于左孩子结点,则选取右孩子结点
if (rChild < length && arr[lChild] < arr[rChild]) {
lChild++;
}
// 如果父结点的值已经大于孩子结点的值,则直接结束
if (temp >= arr[lChild]) {
break;
}
// 把孩子结点的值赋给父结点
arr[parent] = arr[lChild];
//选取孩子结点的左孩子结点,继续向下筛选
parent = lChild;
lChild = 2 * lChild + 1;
}
arr[parent] = temp;
}
}
选择排序:
- 步骤:找出序列中的最小关键字,然后将这个元素与序列首端元素交换位置。例如,序列前i个元素已经有序,从第i+1到第n个元素中选择关键字最小的元素,假设第j个元素为最小元素,则交换第j个元素与第i+1个元素的位置。依次执行此操作,直到第n-1个元素也被确定
- 复杂度:简单选择排序不论是否序列已经有序都需要进行n-1次最小数选择,所以它的最好、最坏以及平均时间复杂度都是O(n2)
public static void selectSort(int[] array) {
int n = array.length;
for (int i = 0; i < n; i++) {//每一轮都会找到最小的元素放到i这个位置上
int minIndex = i;
for (int j = i + 1; j < n; j++) {
if (array[minIndex] > array[j]) {
minIndex = j;
}
}
if (i != minIndex) {
int temp = array[i];
array[i] = array[minIndex];
array[minIndex] = temp;
}
}
}
插入排序
- 步骤:将一个记录插入到已经排序号好的有序表中。假如一个序列的前四个元素经过排序后已经有序,为38,49,65,97,再插入76。假设从右往左进行对比,因为65<76<97,所以76被插入到65与97之间。依次对每个元素进行这样的操作,直到所有的元素都被插入到有序表中。
- 复杂度:在最好的情况下,序列已经是有序的,每次插入元素只需要与有序表中最后一个元素进行比较,时间复杂度为O(n)。在最坏的情况下,每次插入元素需要与前面所有的元素进行比较,时间复杂度为O(n2)
public static void insertSort(int[] array) {
int len = array.length;
for (int i = 1,; i < len; i++) {
if (array[i] < array[i - 1]) {
int temp = array[i];
int j;
for (j = i - 1; j >= 0 && temp < array[j]; j--) {
array[j + 1] = array[j];
}
array[j + 1] = temp;
}
}
}
shell排序
1、简介:
希尔排序是插入排序改良的算法,希尔排序步长从大到小调整,第一次循环后面元素逐个和前面元素按间隔步长进行比较并交换,直至步长为1,步长选择是关键。
2、步骤:
先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,具体算法描述:
选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
按增量序列个数k,对序列进行k 趟排序;
每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
时间复杂度:
最坏的情况下是O(n2),一般情况下是O(nlogn)~O(n2)之间。【不太确定】
空间复杂度:
不需要额外的空间占用,所以为O(1)。
稳定性:
插入排序是稳定的,但是分组之后再插入排序的希尔排序是不稳定。
/**
* 希尔排序
*/
public static void order6(int[] a) {
int len = a.length;//单独把数组长度拿出来,提高效率。
while(len != 0) {
len = len/2;
for (int i = 0; i < len; i++) {//分组
for (int j = i + 1; j < a.length; j+=len) {//元素从第二个开始
int k = j - len;//k为有序序列最后一位的位数
int temp = a[j];//要插入的元素
while (k >= 0 && temp < a[k]) {//从后往前遍历
a[k + len] = a[k];
k -= len;//向后移动len位
}
a[k + len] = temp;
}
}
}
}