排序是什么?
排序是对随机一组数字进行从大到小(或从小到大)的排列
排序算法的稳定性
对于两个相邻且相同的元素,排序过后其元素位置不会改变
冒泡排序
依次比较相邻两个元素,若前者大于后者(从小到大排序),则交换两者位置,即将大的依次往后移
public static void BubbleSort(int[] array) {
for (int i = 0; i < array.length; i++) {
for (int j = i + 1; j < array.length; j++) {
if (array[i] > array[j]) {
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
}
}
比较次数(与数组数量n有关,与数组初始排列情况无关)
- 固定情况——第零轮比较n-1次,第一轮比较n-2次,第二轮比较n-3次…第n-1轮比较1次,即(n-1)+(n-2)+…+1 = n2/2,[0,n-1]共n个数前后相加等于n,再除2算出多少个n
交换次数(与数组数量n及数组初始排列情况有关)
- 最好情况——数据刚好从小到大排序,则无需交换
- 最坏情况——数据刚好从大到小排序,则每次比较都需要交换,也为n2/2
时间复杂度(比较次数+最坏情况的交换次数)
- O( [ n2/2 ] ) + O( [ n2/2 ] ) = O(n2)
空间复杂度
- 在数组内部操作,故为n
稳定性
- array[i] > array[j] 表示大于才会交换,故冒泡排序是稳定的
- 若改为array[i] >= array[j] 则不稳定
选择排序
从数组中依次选择最小的放到前面,即将最小的往前移(从小到大排序,先假定第一个是最小的,然后依次和后面的比较,若有更小的则交换)
public static void SelectSort(int[] array) {
for (int i = 0; i < array.length; i++) {
int min = i;
for (int j = i + 1; j < array.length; j++) {
if (array[j] < array[i]) {
min = j;
}
}
if (min != i) {
int temp = array[i];
array[i] = array[min];
array[min] = temp;
}
}
}
比较次数(与数组数量n有关,与数组初始排列情况无关)
- 固定情况——第零轮比较n-1次,第一轮比较n-2次,第二轮比较n-3次…第n-1轮比较1次,即(n-1)+(n-2)+…+1 = n2/2,[0,n-1]共n个数前后相加等于n,再除2算出多少个n
交换次数(与数组数量n及数组初始排列情况有关)
- 最好情况——数据刚好从小到大排序,则无需交换
- 最坏情况——数据刚好从大到小排序,则前后互换,共n个元素则交换n/2次
时间复杂度(比较次数+最坏情况的交换次数)
- O( [n2/2] ) + O( n/2 ) = O(n2)
空间复杂度
- 在数组内部操作,故为n
稳定性
- array[j] < array[i] 表示小于才会交换,故选择排序是稳定的
- 若改为array[j] <= array[i] 则不稳定
插入排序
将元素依次和其前面的元素进行比较(也可从下标1开始),若小于则将元素往前移(从小到大排序),即将元素插入到合适的位置(元素左边是有序的,右边是无序的)
public static void InsertSort(int[] array) {
for (int i = 0; i < array.length; i++) {
for (int j = i; j > 0; j--) {
if (array[j] < array[j - 1]) {
int temp = array[j - 1];
array[j - 1] = array[j];
array[j] = temp;
}
}
}
}
比较次数(与数组数量n及数组初始排列情况有关)
- 最好情况——数据刚好从小到大排序,则每轮比较一次即可,共n次
- 最坏情况——数据刚好从大到小排序,则第零轮比较0次,第一轮比较1次,第二轮比较2次…第n-1轮比较n-1次,即0+1+…+(n-1) = n(n-1)/2,[0,n-1]共n个数前后相加等于n-1,再除2算出多少个n-1
交换次数(与数组数量n及数组初始排列情况有关)
- 最好情况——数据刚好从小到大排序,则无需交换
- 最坏情况——数据刚好从大到小排序,则每次比较都需要交换,也为n(n-1)/2
时间复杂度(最坏情况比较次数+交换次数)
- O( [n(n-1)/2] ) + O( [n(n-1)/2] ) = O(n2)
空间复杂度
- 在数组内部操作,故为n
稳定性
- array[j] < array[j - 1] 表示小于才会交换,故选择排序是稳定的
- 若改为 array[j] <= array[j - 1] 则不稳定
归并排序
利用递归将数组平分为子数组,直到不能再分时,对子数组进行两两合并,对比两个子数组,依次将较小的元素取出归位,从而完成排序
public static void MergeSort(int[] array) {
Sort(array, 0, array.length - 1);
}
private static void Sort(int[] array, int form, int to) {
if (form >= to) {
return;
}
int middle = form + (to - form) / 2;
Sort(array, form, middle);
Sort(array, middle + 1, to);
Merge(array, form, middle, to);
}
private static void Merge(int[] array, int form, int middle, int to) {
int[] extArray = new int[array.length];
int i = form;
int j = middle + 1;
for (int k = form; k <= to; k++) {
extArray[k] = array[k];
}
for (int k = form; k <= to; k++) {
if (i > middle) {
array[k] = extArray[j++];
} else if (j > to) {
array[k] = extArray[i++];
} else if (extArray[j] < extArray[i]) {
array[k] = extArray[j++];
} else {
array[k] = extArray[i++];
}
}
}
分割次数(与数组数量n及数组初始排列情况无关)
- 分割不需要花费时间,可当作数组本就是分割好的
比较次数(与数组数量n有关,与数组初始排列情况无关)
- 在每层合并时,依次比较子数组的首位数据大小,故比较次数和子数组长度有关,数组共n个,每行子数组总数也是n个,故每行的比较次数为O(n),n个元素可平分为log2n行
时间复杂度(分割次数+比较次数)
- O( [ n(log2n) ] ) = O(nlogn)
空间复杂度
- 使用了辅助数组,在辅助数组和内部数组操作,故为2n
稳定性
- extArray[j] < extArray[i] 表示小于才会交换,故归并排序是稳定的
- 若改为 extArray[j] <= extArray[i] 则不稳定
快速排序
选择第一个为基准数base,j从右往左找到小于base的数,i从左往右找到大于base的数,交换它们,若i和j相遇,说明已经找完,则将base换到中间,即左边全部小于base,右边全部大于base,然后递归处理左右两边
public static void QuickSort(int[] array) {
Sort(array, 0, array.length - 1);
}
private static void Sort(int[] array, int from, int to) {
if (from > to) {
return;
}
int i = from;
int j = to;
int base = array[from];
while (i < j) {
while (i < j) {
if (array[j] < base) {
break;
}
j--;
}
while (i < j) {
if (array[i] > base) {
break;
}
i++;
}
int temp = array[j];
array[j] = array[i];
array[i] = temp;
}
array[from] = array[i];
array[i] = base;
Sort(array, from, i - 1);
Sort(array, i + 1, to);
}
比较次数(与数组数量n有关,与数组初始排列情况无关)
- 最好情况——如果每次选择的基准数都能平分子序列,则比较次数和子数组长度有关,数组共n个,每行子数组总数也是n个,故每层的比较次数为O(n),n个元素可平分为log2n层
- 最坏情况——如果数据刚好从大到小排序,则第一个元素比较n-1次,第二个元素比较n-2次,第n个元素比较1次,即(n-1)+(n-2)+…+1 = n(n-1)/2,n-1个数前后相加等于n,再除2算出多少个n
交换次数(与数组数量n及数组初始排列情况有关)
- 最好情况——数据刚好从小到大排序,则无需交换
- 最坏情况——数据刚好从大到小排序,需要将每个元素定位,共n个元素交换n次
时间复杂度(比较次数+交换次数)
- 最好情况——O(nlogn)
- 最坏情况——O( [n(n-1)/2] ) + O(n) = O(n2)
空间复杂度
- 在数组内部操作,故为n
稳定性
- array[j] < base 表示找到小于的才会左右替换,故快速排序是稳定的
- 若改为 array[j] <= base 则不稳定
堆排序
堆排序分为两步(分别对应2个循环)
1.构建最大堆(用于从小到大排序)
- 从右往左从下往上调整所有父节点(非叶节点),数组从0开始,最后一个节点为arr.length - 1,除以2即是最后一个父节点
- 对比父节点和它的左右节点,从左右节点选择大的与父节点比较,若大于则交换
- 交换后递归子树,因为交换后可能导致子树不符合最大堆
2.排序
- 依次将最后一个节点(叶节点)和根节点交换(即将最大的放在最后)
- 从根节点往下调整节点(交换后不符合最大堆,需重新构建),此次构建不包括最后节点(传入参数为arr.length - 1)
public static void heapSort(int[] arr) {
for (int i = (arr.length - 1) / 2; i >= 0; i--) {
buildMaxHeap(arr, arr.length, i);
}
for (int i = arr.length - 1; i > 0; i--) {
int temp = arr[0];
arr[0] = arr[i];
arr[i] = temp;
buildMaxHeap(arr, i, 0);
}
}
private static void buildMaxHeap(int[] arr, int size, int parent) {
int leftChild = 2 * parent + 1;
int rightChild = 2 * parent + 2;
int max = parent;
if (leftChild < size && arr[leftChild] > arr[max]) {
max = leftChild;
}
if (rightChild < size && arr[rightChild] > arr[max]) {
max = rightChild;
}
if (max != parent) {
int temp = arr[parent];
arr[parent] = arr[max];
arr[max] = temp;
buildMaxHeap(arr, size, max);
}
}
构建堆时的比较次数(与数组数量n及数组初始排列情况有关)
- 最好情况——已是最大堆,[0, (n - 1) / 2] 共 (n - 1) / 2 + 1个父节点,要跟其左右节点比较,故乘2等于n+1次
- 最坏情况——每层比较都会触发交换并调用递归,倒数第一层(叶节点)比较0次,倒数第二层比较2次(与左右比较),倒数第三层比较4次(自己2次+倒数第二层2次)…倒数第log2n-1层(顺数第二层)比较n次,倒数第log2n层(根节点)比较2n次,故总比较次数为0+2+4+…n+2n = log2n/2*2n = nlog2n(n个数平分共log2n层,前后两两相加为2n,除以2计算多少个2n)
构建堆时的交换次数(与数组数量n及数组初始排列情况有关)
- 最好情况——已是最大堆,每次比较无需交换
- 最坏情况——每层比较都会触发交换并调用递归,交换次数是比较次数的一半,即nlog2n/2
排序堆时的比较次数(与数组数量n及数组初始排列情况有关)
- 最好情况——交换头尾元素后不可能还是最大堆,故没有最好情况
- 最坏情况——交换头尾元素后,每层比较都会触发交换并调用递归,比较次数同上即nlog2n
排序堆时的交换次数(与数组数量n及数组初始排列情况有关)
- 最好情况——交换头尾元素后不可能还是最大堆,故没有最好情况
- 最坏情况——交换头尾元素后,每层比较都会触发交换并调用递归,头尾交换+父子交换次数等于比较次数,即nlog2n,
时间复杂度(最坏情况比较次数+交换次数)
- 最坏情况——O( [nlog2n] ) + O( [nlog2n/2] ) + O( [nlog2n] ) + O( [nlog2n] )= O(nlogn)
空间复杂度
- 在数组内部操作,故为n
稳定性
- arr[leftChild] > arr[max] 表示找到大于的才会父子交换,故堆排序是稳定的
- 若改为 arr[leftChild] >= arr[max] 则不稳定
Tips:上面的循环已是优化过的,若看不出复杂度,可看下面的,这样是遍历所有节点
public static void heapSort(int[] arr) {
for (int i = arr.length - 1; i >= 0; i--) {
buildMaxHeap(arr, arr.length, i);
}
for (int i = arr.length - 1; i >= 0; i--) {
int temp = arr[0];
arr[0] = arr[i];
arr[i] = temp;
buildMaxHeap(arr, i, 0);
}
}