参考:几种排序算法的稳定性分析
稳定排序是指原来相等的两个元素前后相对位置在排序后依然不变。
比较时平局,排序算法保持数字/记录的相对顺序,则该排序算法被称为稳定
1. 选择排序
保持 i 位置值最小
遍历后面,选出最小值与 i 值比较
交换使 i 位置最小
public void selectSort(int[] arr){
if(arr == null || arr.length < 2){
return;
}
//arr有可能为空
int N = arr.length;
for(int i = 0; i < N; i++){
int minValueIndex = i;
for(int j = i+1; j < N; j++ ){
minValueIndex = arr[j] < arr[minValueIndex] ? j : minValueIndex;
}
swap(arr,i,minValueIndex);
}
}
public void swap(int[] arr, int i, int j){
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
选择排序不稳定
比如 [5,8,3,5,2],第一次交换把第一个 5 换到最后去了,第二个 5 此时在第一个5位置之前了
2. 冒泡排序
两者位置相比较,保证值大的在后面
值大的再跟后面的比
保证n-1-i 位置的值最大
public static void bubbleSort(int[] arr){
int n = arr.length;
for(int i = 0; i < n-1; i++){ // 外层循环控制排序轮数
boolean flag = false; // 标记本轮是否发生交换
for(int j = 0; j < n-i-1; j++){ // 内层循环控制每轮排序的次数
if(arr[j] > arr[j+1]){
swap(arr, j, j+1);
flag = true;
}
}
if(!flag){ // 如果本轮没有发生交换操作,说明已经是有序的,直接退出循环
break;
}
}
}
public static void swap(int[] arr, int i, int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
冒泡排序可以是稳定的
冒泡排序主要是左右相邻相比较,如相等时不发生交换,相对位置将不变
3. 插入排序
i
处的值若比前面的值小
则交换位置,直到最小的值在0位置
保证i
前面的顺序是有序的
即每移动一次就要排一次序
public static void insertionSort(int[] arr) {
int n = arr.length;
for (int i = 1; i < n; i++) {
int key = arr[i]; // 保存当前值
int j = i - 1; // 从前一个起
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key;
}
}
同冒泡排序,插入排序可以稳定
4. 希尔排序
希尔排序(Shell Sort)是插入排序的一种,实质上是一种分组插入方法。
把原数组分成几批进行插入
以设置gap步长进行分批
里面一个插入排序
public static void shellSort(int[] arr) {
int n = arr.length;
// gap 分批,每次除2
for (int gap = n / 2; gap > 0; gap /= 2) {
// 里面一个插入排序
for (int i = gap; i < n; i++) {
int temp = arr[i];
int j;
for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) {
arr[j] = arr[j - gap];
}
arr[j] = temp;
}
}
}
时间复杂度与增量 (步长gap) 有关。使用最简单的增量序列(即每次将上一次的增量除以2),则希尔排序的平均时间复杂度大约是O(n^2)
希尔排序是不稳定的
对于相同的两个数,可能由于分在不同的组中而导致它们的顺序发生变化
5. 归并排序
归并排序(Merge Sort),主要思想:分治,递归。速度仅次于快速排序
递归:
左子数组排序
右子数组排序
合并两个有序数组
public static void mergeSort(int[] arr, int left, int right) {
if (left < right) {
int mid = (left + right) / 2;
mergeSort(arr, left, mid); // 左子数组排序
mergeSort(arr, mid + 1, right); // 右子数组排序
merge(arr, left, mid, right); // 合并两个有序数组
}
}
public static void merge(int[] arr, int left, int mid, int right) {
int[] temp = new int[arr.length];
int i = left;
int j = mid + 1;
int k = left;
while (i <= mid && j <= right) {
if (arr[i] <= arr[j]) {
temp[k++] = arr[i++];
} else {
temp[k++] = arr[j++];
}
}
while (i <= mid) {
temp[k++] = arr[i++];
}
while (j <= right) {
temp[k++] = arr[j++];
}
for (int l = left; l <= right; l++) {
arr[l] = temp[l];
}
}
时间复杂度O(nlogn)
归并排序稳定
归并排序算法中,归并最后到底都是相邻元素之间的比较交换,并不会发生相同元素的相对位置发生变化
参考:排序算法之归并排序
6. 快速排序
也是分治,递归,显然不稳定。平均时间复杂度为O(n log n)
选择基准
定义左右指针
找出当前的正确下标
分治
public void quickSort(int[] arr, int start, int end){
int key = arr[start];
int left = start, right = end;
while(left < right){
while(left < right && arr[right] > key) right--;
arr[left] = arr[right];
while(left < right && arr[left] < key) left++;
arr[right] = arr[left];
}
arr[left] = key;
quickSort(arr,start,left-1);
quickSort(arr,left+1,end);
}
7. 堆排序
堆是特殊完全二叉树(层序遍历的数组形式)
完全二叉树特性:
下标为 i 节点的左孩子下标:2i + 1
下标为 i 节点的左孩子下标:2i + 2
根据完全二叉树不同的排序规则:
大顶堆:父节点 >= 左右孩子,最大值为根节点,一般用于升序
小顶堆:父节点 <= 左右孩子,最小值为根节点,一般用于降序
堆排序(大顶堆):
建堆,将无序表转化为堆
取出堆顶元素放到对应位置(交换)
重新整理堆
重复2,3
public void sort(int arr[]) {
int n = arr.length;
// Build heap
for (int i = n / 2 - 1; i >= 0; i--) {
heapify(arr, n, i);
}
// One by one extract an element from heap
for (int i = n - 1; i >= 0; i--) {
// Move current root to end
int temp = arr[0];
arr[0] = arr[i];
arr[i] = temp;
// call max heapify on the reduced heap
heapify(arr, i, 0);
}
}
// 整理堆
void heapify(int arr[], int n, int i) {
int largest = i; // Initialize largest as root
int left = 2 * i + 1; // left = 2*i + 1
int right = 2 * i + 2; // right = 2*i + 2
if (left < n && arr[left] > arr[largest]) {
largest = left;
}
if (right < n && arr[right] > arr[largest]) {
largest = right;
}
// If largest is not root
if (largest != i) {
int swap = arr[i];
arr[i] = arr[largest];
arr[largest] = swap;
// Recursively heapify the affected sub-tree
heapify(arr, n, largest);
}
}
堆排序不稳定
总结:
稳定:冒泡,插入,归并
不稳定:选择,希尔,快速,堆排序