几种常见排序算法的总结(Java版上)

排序操作在计算机程序设计中分为外部排序和内部排序。我们一般所说的排序算法指的就是内部排序,即数据记录在计算机内存中进行排序。

上图为内部排序的脑图。下来九种排序算法我们一一介绍。

1、冒泡排序

(1)基本思想:

  • 依次比较相邻两个数,小的放前边,大的放后边,依次比较,直到比较到最后两个数。得到最后位置的数为最大数。
  • 所有数重复上边的操作,除了最后一个。以此类推,比较完所有的数。

假设 n 个数进行排序,则需要比较 n - 1 轮,第一轮比较 n - 1 次,之后每轮比较减少 1 次。

(2)代码实现:

第一个版本:

   
 public static void bubbleSort(int[] a) {
        int temp;// 临时变量
        for (int i = 0; i < a.length - 1; i++) {// 需要比较 n - 1 轮
		for (int j = 0; j < a.length - i - 1; j++) {// 每轮需要比较的次数减 1,第一轮比较 n - 1 次
			if (a[j] > a[j + 1]) {
				temp = a[j];
				a[j] = a[j + 1];
				a[j + 1] = temp;
			}
		}
	}
}

第一版本存在这样的问题:假设一数组长度为 6 ,故需要比较 5 轮,但会发现在第 4 轮就已完成,第 5 轮比较就没有意义了,所以还能继续优化。设置一个Boolean 变量 flag ,判断某轮比较中是否发生数据交换。若没有发生则表示排序完成,就可以立即停止。

第二个版本:

 public static void bubbleSort(int[] a) {
        boolean flag;//表示是否交换
        int temp;// 临时变量
        for (int i = 0; i < a.length - 1; i++) {// 需要比较 n - 1 轮
                flag = false;
                for (int j = 0; j < a.length - i - 1; j++) {// 每轮需要比较的次数减 1,第一轮比较 n - 1 次
			if (a[j] > a[j + 1]) {
				temp = a[j];
				a[j] = a[j + 1];
				a[j + 1] = temp;
                                flag = true;
                        }
                }
                if(!flag) break;//如果为false则排序完成,跳出外层循环
        }
}

(3)算法分析:

最坏时间复杂度:O(n^2)    最好时间复杂度:O(n)    故平均复杂度为:O(n^2)

稳定性:算法是比较相邻两个元数,交换也发生在这两个元数上,两个相邻元数值相等不会交换,排序前排序后相等的两个元数的相对顺序不会发生改变,故算法具有稳定性。

2、快速排序

(1)基本思想:(利用了递归与分治策略的思想)

  • 从数组中取出一个数作为“基准”。
  • 划分:所有比基准小的数放在其左边,所有比基准大的数放其右边,与其相等可放任意一边。
  • 递归求解:将以上所划分的子序列通过递归继续执行快排算法,直到子序列不可分割为止。

一轮快排具体操作是:i 和 j 开始分别置于数组的最左端和最右端,设某个数为基准(一般取第 0 个数)。首先 j 先向左移动找到第一个小于“基准”的数并和基准相互交换,然后 i 向右移动找到第一个大于“基准”的数并和基准交换;重复这两步直到 i == j 。

其实实际排序中没有必要找到符合要求的数就和“基准”进行交换,因为只需要在 i == j 的位置才是最后基准的位置,所以现在现将 基准暂存在 a [0] 位上,j 和 i 各自移动,找到符合条件(比基准小 / 大)的数,直到一轮排序结束再将基准移动到正确位置。

(2)代码实现:

public static void quickSort(int [] a,int left,int right){
    if(left > right) return;//若不满足直接跳出该方法
    int i = left;//数组最左下标赋给 i
    int j = right;//最右下标赋给 j
    int pivotkey = a [left];//将最左端数值赋给 pivotkey 作为基准

    while(i < j){
        while(i < j && a [j] >= pivotkey)//j 从右向左找第一个小于基准的值
            j--;
            if(i < j){
                a [i] = a [j];
                i++;
            }
        while(i < j && a [i] < pivotkey)// i 从左向右找第一个大于基准的值
            i++;
            if(i < j){
                a [j] = a [i];
                j--;
            }
    }
    a [i] = pivotkey;// i== j 时将基准放置 i 处
    quickSort(a,left,i - 1);//递归调用
    quickSort(a,i + 1,right);
}

(3)算法分析:

最坏时间复杂度:O(n^2)    最好时间复杂度:O(n logn)    故平均复杂度为:O(n logn)

稳定性:数组 { 5, 2, 6, 3, 2, 7 } 基准元素为 5 ,和第四个元素 2 交换,两个 2 的相对顺序发生改变,算法不稳定。

3、简单选择排序

(1)基本思想:

  • 设长度为 n 的无序数组,需要排序 n - 1 轮,每轮需要比较 n - i 次,从 n - i + 1 个元素中找到最小的数值和第 i 个元素交换。
  • 第一轮需要比较 n - 1 次,从 n 个元素中找到最小数值和第一个元素交换。......

如图示例:数组长度为 8,需要比较 7 轮,第一轮需要比较 7 次,之后每轮比较次数减 1。

(2)代码实现:

public static void selectSort(int [] a) {		
    for (int i = 0; i < a.length-1; i++) {//第 i轮排序
        int minIndex = i;//记录最小数的位置
	for (int j = i+1; j < a.length; j++) {
	    if (a[j]<a[minIndex]) {
	        minIndex = j;//目前最小数的位置
	    }
	}
	if (minIndex != i) {//最小数和第 i 个元素交换
	    int temp = a[i];
	    a[i] = a[minIndex];
	    a[minIndex] = temp;
	 }
    }
}

(3)算法分析:

该算法无论数组的初始排列如何,元素所需比较的次数相同,均为 n(n - 1)/ 2,所以时间复杂度最坏/最好/平均都是 O(n ^ 2)

稳定性:数组{ 4,8,  3,4,  6} 一次选择的最小元素的值为 3 ,然后和第一个 4 交换,两个 4 的相对位置发生变化,故该算法不稳定。

4、堆排序

堆的定义:n 个元素的序列 {K1,K2,……,Kn} 当且仅当满足以下关系:

大顶堆完全二叉树:                                   小顶堆完全二叉树:

Ki <= K2i                                                 Ki >= K2i

Ki <= K2i+1                                            Ki >= K2i+1      (i = 1,2,3,……,n/2)

示例:

 


(1)基本思想:

  • 先将一个无序序列构建成一个堆。
  • 输出堆顶的最大值(最小值),将剩余 n - 1 个元素的序列重新构建一个堆,得到 n 个元素中的次大值(次小值)
  • 反复执行上述第二步,最终能得到一个有序序列。

(2)代码实现:

// 堆排序
    public static void heapSort(int[] a) {

        // 1.构建大顶堆
        for (int i = a.length / 2 - 1; i >= 0; i--) {
            // 从第一个非叶子结点从下至上,从右至左调整结构
            adjustHeap(arr, i, a.length);
        }
        // 2.调整堆结构+交换堆顶元素与末尾元素
        for (int j = a.length - 1; j > 0; j--) {
            // 将堆顶元素与末尾元素进行交换
            int temp = a[0];
            a[0] = a[j];
            a[j] = temp;
            adjustHeap(a, 0, j);// 重新对堆进行调整
        }
    }

    public static void adjustHeap(int[] a, int i, int length) {
        int temp = a[i];// 先取出当前元素i
        for (int k = i * 2 + 1; k < length; k = k * 2 + 1) {// 从i结点的左子结点开始,也就是2i+1处开始
            if (k + 1 < length && a[k] < a[k + 1]) {// 如果左子结点小于右子结点,k指向右子结点
                k++;
            }
            if (a[k] > temp) {// 如果子节点大于父节点,将子节点值赋给父节点(不用进行交换)
                a[i] = a[k];
                i = k;
            } else {
                break;
            }
        }
        a[i] = temp;// 将temp值放到最终的位置
    }

(3)算法分析:

初始化堆为 O(n) ,交换堆元素并重建堆为 O(n logn) ,其时间复杂度最坏/最好/平均都是 O(n logn)

空间复杂度为 O( 1 )

稳定性:数组{ 8, 4, 5, 4 } 堆顶元素为 8,堆排序下一步把 8 和第二个 4 交换,最终排序完成后两个 4的相对顺序发生变化,故算法不稳定。


参考文章:https://blog.csdn.net/lutianfeiml/article/details/51958962

猜你喜欢

转载自blog.csdn.net/qq_38190057/article/details/80552714