排序算法总结

本文总结一下冒泡排序,选择排序,插入排序,快速排序,归并排序,堆排序,主要从原理,代码实现,稳定性,时间复杂度,空间复杂度这几方面来介绍。

冒泡排序

原理

每轮排序,从第一个数开始,依次和后一个位置的数比较,如果左边的数大于右边的,交换两个数。这样,第i轮排序,就会挑选出一个第i大的值。

代码

	public static void bubbleSort(int[] a, int n) {
		for (int i = 0; i < n - 1; i++) {
			for (int j = 0; j < n - 1 - i; j++) {
				if (a[j] > a[j + 1]) {
					int temp = a[j];
					a[j] = a[j + 1];
					a[j + 1] = temp;
				}
			}
		}
	}

稳定性

属于稳定排序,因为只有前面一个数大于后面时,才会交换,所以相等的两个数的相对位置不会改变。

时间复杂度

最好:O(n),原因是当第一次遍历时没有发生任何交换时,可以认为数组本身有序,不再执行后面的操作。代码稍作修改如下:

	public static void bubbleSort(int[] a, int n) {
		for (int i = 0; i < n - 1; i++) {
			boolean swapped = false;
			for (int j = 0; j < n - 1 - i; j++) {
				if (a[j] > a[j + 1]) {
					int temp = a[j];
					a[j] = a[j + 1];
					a[j + 1] = temp;
					swapped = true;
				}
			}
			if (!swapped) {
				break;
			}
		}
	}

最坏:O(n2)
平均:O(n2)

空间复杂度

O(1)

选择排序

原理

每次选择无序序列中最小的,然后将这个数和位置i的数进行交换。

代码

	public static void selectSort(int[] a, int n) {
		for (int i = 0; i < n - 1; i++) {
			int index = i;
			for (int j = i + 1; j < n; j++) {
				if (a[j] < a[index]) {
					index = j;
				}
			}
			if (i != index) {
				int temp = a[i];
				a[i] = a[index];
				a[index] = temp;
			}
		}
	}

稳定性

不稳定,交换的时候可能改变两个相等元素的相对位置
例如
9 2 3 9 4 7 1
第一轮排序之后变为
1 2 3 9 4 7 9

时间复杂度

最好,最坏,平均都是O(n2)

空间复杂度

O(1)

插入排序

原理

每次将无序数组中的第一个元素插入到有序数组的合适的位置,这里定位合适的位置时既可以从前往后,也可以从后往前。如果当前元素比较大的话,从后往前定位合适,反之,从前往后合适。这里,两种写法都给出来。

代码

从前往后

	public static void insertSort(int[] a, int n) {
		for (int i = 1; i < n; i++) {
			for (int j = 0; j < i; j++) {
				if (a[i] < a[j]) {//找到要插入的位置,即为j
					int temp = a[i];
					for (int k = i - 1; k >= j; k--) {//把位置处于[j,i-1]的元素后移一位
						a[k + 1] = a[k];
					}
					a[j] = temp;//插入当前元素
					break;
				}
			}
		}
	}

从后往前:

    public static void insertSort(int[] a, int n) {
		for (int i = 1; i < n; i++) {
			int j = i - 1;
			for (; j >= 0; j--) {
				if (a[i] >= a[j]) {//找到要插入的位置,即为j+1
					break;
				}
			}
			int temp = a[i];
			for (int k = i - 1; k > j; k--) {//把位置处于[j+1,i-1]的元素后移一位
				a[k + 1] = a[k];
			}
			a[j + 1] = temp;//插入当前元素
		}
	}

可以看出从前往后的代码比较好理解,而从后往前稍微复杂一些。从前往后:第二个for循环中没有找到位置,其实就相当于当前元素最大,就什么也不做就可以了。从后往前,第二个for循环中没有找到位置,说明当前元素最小,需要后移所有元素,所以比较麻烦,需要把移动元素的这个操作单独出来,不能写在for循环之内。本人更喜欢写从前往后定位的方法。

稳定性

属于稳定排序,从前往后时,当元素相等时,会继续往后走,保证,相等的元素的相对位置不变。从后往前时,如果元素相等,直接插入。

时间复杂度

最好,最坏,平均都是O(n2)

快速排序

原理

快速排序,每次以待排序数组的第一个元素为切分点,把数组分为两部分,大于等于这个元素的在后面,反之在前面,依次递归,直至整个数组有序。

代码

	public static void quickSort(int[] a, int begin, int end) {
		if (begin >= end) {
			return;
		}
		int temp = a[begin];
		int i = begin;
		int j = end;
		while (i < j) {
			while (i < j && a[j] >= temp)
				j--;
			a[i] = a[j];
			while (i < j && a[i] <= temp)
				i++;
			a[j] = a[i];
		}
		a[i] = temp;
		quickSort(a, begin, i - 1);
		quickSort(a, i + 1, end);
	}

稳定性

属于不稳定排序,因为基于交换,且不同于冒泡,它不是相邻元素的交换,所以会改变相等元素的相对位置。
举个例子:
例如:
6 1 2 3 8 5 9 2
经过第一轮快排之后变为:
2 1 2 3 5 6 9 8

时间复杂度

最好:O(nlogn) 最坏:O(n2) 平均:O(nlogn)
最坏的情况是数组本身有序的情况,这样一轮快排并不能把数组分为元素数量基本相等的两部分

空间复杂度

最好:O(logn) 最坏:O(n) 平均:O(logn)
最坏的情况同上

归并排序

原理

使用递归,把两个有序数组合并为一个有序数组,注意部分有序的时候要记得复制回原数组。

代码

	public static void mergeSort(int[] a, int begin, int end) {
		if (begin >= end) {
			return;
		}
		int mid = begin + (end - begin) / 2;//从中间将数组一分为二,递归调用归并排序使得左右两个数组有序
		mergeSort(a, begin, mid);
		mergeSort(a, mid + 1, end);
		merge(a, begin, mid, end);//合并两个有序数组

	}

	public static void merge(int[] a, int begin, int mid, int end) {
		int[] b = new int[a.length];
		int k = begin;
		int i = begin;
		int j = mid + 1;
		while (i <= mid && j <= end) {
			if (a[i] <= a[j]) {
				b[k++] = a[i++];
			} else {
				b[k++] = a[j++];
			}
		}

		while (i <= mid) {
			b[k++] = a[i++];
		}
		while (j <= end) {
			b[k++] = a[j++];
		}

		for (k = begin; k <= end; k++) {//注意一定要复制回原数组
			a[k] = b[k];
		}
	}

稳定性

属于稳定排序,在两个数组合并时,如果两个元素相等,取左边数组中的元素,保证相等元素的相对位置不会改变。

时间复杂度

最好,最坏,平均都是O(nlogn)

空间复杂度

因为需要一个辅助数组b,所以空间复杂度为O(n)。

堆排序

原理

如果要从小到大排序,那么使用大顶堆,堆用数组来存储。
构造大顶堆的方式是自底向上,从第一个非叶子节点开始,调整元素位置,使得每一个非叶子节点都大于等于它的左右子节点。所以,建堆的时间复杂度为O(n),因为自底向上,只需往下走一层即可,即使有元素交换,也不会打乱下面的关系,因为即使有元素交换,也是把大的换到下面,当然满足大顶堆的性质了。
下面开始真正的排序,排序就是把位置为0的元素和位置为i的元素交换,然后调整堆(0到i-1),使其重新满足大顶堆的性质。这样,每执行一轮,就会把当前最大的元素放到后面。这个过程的时间复杂度为O(nlogn)。

代码

	public static void heapSort(int[] a) {
		int len = a.length;
		for (int i = (len - 2) / 2; i >= 0; i--) {//构造大顶堆
			adjustHeap(a, i, len - 1);
		}
		for (int i = len - 1; i > 0; i--) {
		    //交换第一个和最后一个元素的位置
			int temp = a[i];
			a[i] = a[0];
			a[0] = temp;
			adjustHeap(a, 0, i - 1);//调整堆
		}

	}

	public static void adjustHeap(int[] a, int parent, int end) {
		int temp = a[parent];//目的就是把temp放到合适的位置
		for (int i = parent * 2 + 1; i <= end; i = i * 2 + 1) {//i是左节点
			if (i < end && a[i] < a[i + 1]) {//取左右节点的最大值
				i++;
			}
			if (temp >= a[i]) {//如果temp大于等于最大值,满足大顶堆,找到位置,即parent,跳出循环
				break;
			}
			a[parent] = a[i];//反之,把最大值放到parent的位置
			parent = i;//parent指向i,继续向下遍历
		}
		a[parent] = temp;//找到位置或者遍历到最后,那么把temp放到parent的位置

	}

稳定性

属于不稳定排序,举例,左面的是构造大顶堆之后的状态,右面的是经过第一轮排序之后的状态,两个2的相对位置改变了:
在这里插入图片描述

时间复杂度

最好,最坏,平均都是O(nlogn)

空间复杂度

O(1)

总结

最后,总结一下上面6种排序算法。

排序算法稳定性最好时间复杂度最坏时间复杂度平均时间复杂度空间复杂度
冒泡O(n)O(n2)O(n2)O(1)
选择O(n2)O(n2)O(n2)O(1)
插入O(n2)O(n2)O(n2)O(1)
快排O(nlogn)O(n2)O(nlogn)O(logn)
归并O(nlogn)O(nlogn)O(nlogn)O(n)
堆排O(nlogn)O(nlogn)O(nlogn)O(1)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值