七大排序算法介绍及其稳定性分析

参考:几种排序算法的稳定性分析

稳定排序是指原来相等的两个元素前后相对位置在排序后依然不变
比较时平局,排序算法保持数字/记录的相对顺序,则该排序算法被称为稳定

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);  
        }  
    } 

堆排序不稳定

总结:
稳定:冒泡,插入,归并
不稳定:选择,希尔,快速,堆排序

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值