天天看点

数据结构与算法 --排序 上(七)一、如何分析一个排序算法?二、冒泡排序三、插入排序四、选择排序

目录

一、如何分析一个排序算法?

1、算法的执行效率

2、排序算法的内存消耗

3、排序算法的稳定性

二、冒泡排序

一、冒泡排序原理

二、性能分析

三 、代码实现

三、插入排序

一、排序原理

二、性能分析

三、代码实现

四、选择排序

一、排序原理

二、性能分析

三、代码实现

问题:插入和冒泡复杂度都是O(n2),为什么实际开发中更倾向与插入算法呢?

一、如何分析一个排序算法?

1、算法的执行效率

  •  最好情况,最坏情况,平均时间复杂度
  •  时间复杂度的系数,常数,低阶。在实际开发中有能数据规模很小,所以在对同一阶时间复杂度的排序算法分析时,就要考虑到 系数,常数,低阶 
  •  比较次数和交换次数

2、排序算法的内存消耗

原地排序算法:特指空间复杂度是O(1)的排序算法。如,冒泡,插入,选择

3、排序算法的稳定性

稳定性:如果待排序的序列中存在值相等的元素,经过排序之后,相等元素之间的先后位置顺序不变

相同值元素,哪个在前在后什么关系,为什么考虑算法的稳定性?

实际开发中,要排序的往往是一组对象,需要按照对象的key来排序

例如:

先按照下单时间给订单排序,排序完成后用稳定排序算法按照订单金额重新排序。保证了相同值的数据按照下单时间排序

数据结构与算法 --排序 上(七)一、如何分析一个排序算法?二、冒泡排序三、插入排序四、选择排序

二、冒泡排序

一、冒泡排序原理

1、冒泡排序只操作相邻两个数据

2、对相邻数据进行比较,看关系是否满足要求,不满足则交换

3、一次冒泡排序至少让一个元素到它应在的位置,重复n次,就完成了排序

4、排序优化,如果某次冒泡不存在数据交换,则说明已经达到完全有序,所以终止冒泡

如果按照从小到大排序,每一次冒泡都比较相邻的数据,如果前者大于后者就相互交换,一次冒泡结束,最大的值就到了最后。第二次冒泡将第二大的值移动到最后倒数第二位,重复数组长度-1次,就可以达到有序

数据结构与算法 --排序 上(七)一、如何分析一个排序算法?二、冒泡排序三、插入排序四、选择排序

二、性能分析

冒泡排序是原地排序算法:冒泡的过程只涉及到数据的交换,只需要常量级的临时空间,所以空间复杂度为O(1)

冒泡排序是稳定的排序算法:相同大小的数据不进行交换,不改变顺序

冒泡排序的时间复杂度:

  •   最好时间复杂度 :O(n)    1次冒泡
  •   最坏时间复杂度:O(n2)   6次冒泡

平均时间复杂度O(n2)分析:

1、有序度:有序元素对:a[i] <= a[j], 如果 i < j。

倒序的数组有序度为0。完全有序的数组 1,2,3,4,5,6,有序度为n*(n-1)/2,就是15 。这种完全有序的数组的有序度叫满有序度

2、逆序度:逆序元素对:a[i] > a[j], 如果 i < j。

公式:逆序度 = 满有序度 - 有序度 排序的过程就是增加有序度,减少逆序度的过程,最好达到满有序度,说明排序完成

例如:

4,5,6,3,2,1  满有序度为15,初始有序度为3.每交换一次数据有序度+1.需要交换的次数一定的,为逆序度  15-3=12 进行12次交换数据

数据结构与算法 --排序 上(七)一、如何分析一个排序算法?二、冒泡排序三、插入排序四、选择排序

对于n个数据进行冒泡排序,平均交换次数?

最好情况,初始有序度为n*(n-1)/2,不需要交换数据,最坏情况,初始有序度为0,需要交换n*(n-1)/2次数据。我们取平均值为n*(n-1)/4

比较操作肯定要比交换操作多,而复杂度的上限是 O(n2),所以平均时间复杂度为O(n2)

三 、代码实现

/**
 * 冒泡排序
 * @author chao
 */
public class Bubble_Sort {
	public static void main(String[] args) {
		int[] arrays = {3,5,4,1,2,6};
		Bubble_Sort.sort(arrays, arrays.length);
		System.out.println(Arrays.toString(arrays));
	}
	public static void sort(int[] arrays,int n) {
		for (int i = 0; i < n-1 ; i++) {
			boolean flag= false;  //用来判断有木有交换数据
			for (int j = 0; j < n-i-1; j++) {
				if(arrays[j+1] < arrays[j]) {
					int temp = arrays[j];
					arrays[j]=arrays[j+1];
					arrays[j+1]=temp;
					flag=true;
				}
			}
			
			//如果没有交换数据就跳出循环
			if(!flag){
				break;
			}
		}
//严蔚民书上的伪代码实现
//		boolean flag = true;
//		for (int i = n-1; i >0  && flag; i--) {
//			flag=false;
//			for (int j = 0; j < i; j++) {
//				if(arrays[j+1] < arrays[j]) {
//					int temp = arrays[j];
//					arrays[j]=arrays[j+1];
//					arrays[j+1]=temp;
//					flag=true;
//				}
//			}
//			
//		}
	}
}
           
/**
 * 向下冒泡排序
 * @author chao
 */
public class Bubble_Sort2 {
	public static void main(String[] args) {
		int[] arrays = { 3, 2, 6, 4, 5, 1 };
		Bubble_Sort2.sort(arrays, arrays.length);
		System.out.println(Arrays.toString(arrays));
	}
	 /**
	   * 向下冒泡。可能比冒泡更易懂?
	   * 
	   * 算法概要:
	   * 从0开始,用这个元素去跟后面的所有元素比较,如果发现这个元素大于后面的某个元素,则交换。
	   * 3 2 6 4 5 1
	   * 第一趟是从 index=0 也就是 3, 开始跟index=1及其后面的数字比较
	   *  3 大于 2,交换,变为 2 3 6 4 5 1,此时index=0的位置变为了2
	   *    接下来将用2跟index=2比较
	   *  2 不大于 6 不交换
	   *  2 不大于 4 不交换
	   *  2 不大于 5 不交换
	   *  2 大于 1,交换,变为 1 3 6 4 5 2,第一趟排序完成。
	   * 
	   * 第二趟是从 index=1 也就是 3,开始跟index=2及其后面的数字比较
	   *  3 不大于 6 不交换
	   *  3 不大于 4 不交换
	   *  3 不大于 5 不交换
	   *  3 大于 2,交换,变为 1 2 6 4 5 3,第二趟排序完成。
	   * 
	   * 第三趟是从 index=2 也就是 6,开始跟index=3及其后面的数字比较
	   *  6 大于 4,交换,变为 1 2 4 6 5 3, 此时 index = 2 的位置变为了4
	   *     接下来将用4跟index=4比较
	   *  4 不大于 5 不交换
	   *  4 大于 3,交换,变为 1 2 3 6 5 4,第三趟排序完成。
	   * 
	   * 第四趟是从 index=3 也就是 6,开始跟index=4及其后面的数字比较
	   *  6 大于 5,交换,变为 1 2 3 5 6 4, 此时 index = 3 的位置变为了5
	   *     接下来将用5跟index=5比较
	   *  5 大于 4,交换,变为 1 2 3 4 6 5, 第四趟排序完成。
	   *
	   * 第五趟是从 index=4 也就是 6,开始跟index=5及其后面的数字比较
	   *  6 大于 5,交换,变为 1 2 3 4 5 6, 此时 index = 4 的位置变为了5
	   *     接下来将用5跟index=6比较
	   *  index = 6 已经不满足 index < length 的条件,整个排序完成。
	   */
	public static void sort(int[] arrays, int n) {
		if (arrays.length == 1)
			return;
		for (int i = 0; i < arrays.length - 1; i++) {
			for (int j = i + 1; j < arrays.length; j++) {
				if (arrays[i] > arrays[j]) {
					int temp = arrays[i];
					arrays[i] = arrays[j];
					arrays[j] = temp;
				}
			}
		}
	}
}
           

三、插入排序

一、排序原理

首先,我们将数组中的数据分为2个区间,即已排序区间和未排序区间。初始已排序区间只有一个元素,就是数组的第一个元素。插入算法的核心思想就是取未排序区间中的元素,在已排序区间中找到合适的插入位置将其插入,并保证已排序区间中的元素一直有序。重复这个过程,直到未排序中元素为空,算法结束

数据结构与算法 --排序 上(七)一、如何分析一个排序算法?二、冒泡排序三、插入排序四、选择排序

 如上图,左侧为已排序区间,右边为未排序区间。每次取未排序区间的元素,如取图中1,和左侧进行一一比较寻找合适的位置,将前面的元素依次后移给 1 腾出空间。

移动次数等于逆序度:  例如上图 初始有序度为5,满有序度为15,逆有序度为10=3+3+4

二、性能分析

原地排序算法:不需要额外的存储空间

稳定排序算法:值相同的元素,将后面出现的元素,插入到前面出现元素的后面,保持原有前后顺序不变

插入排序的时间复杂度:

  • 最好时间复杂度 :O(n)    从头到尾遍历已有序数据
  • 最坏时间复杂度:O(n2)    每次 插入相等于在数组的第一位插入新数据,所以要移动大量数据
  • 平均时间复杂度: O(n2)

三、代码实现

/**
 * 插入排序
 * @author chao
 */
public class Insert_Sort {
	public static void main(String[] args) {
		int[] arrs = { 5, 4, 2, 7, 8 };
		Insert_Sort.InsertSort(arrs, arrs.length);
		System.out.println(Arrays.toString(arrs));
	}

	public static void InsertSort(int[] arrays, int n) {
		for (int i = 1; i < n; i++) {
			int value = arrays[i];
			int j = i - 1;
			for (; j >= 0; --j) {
				// 找到自己的插入位置
				if (arrays[j] > value) {
					// 如果前面的元素比自己大就移动元素
					arrays[j + 1] = arrays[j];
				} else {
					// 否则跳出循环
					break;
				}
			}
			// 在需要插入的位置插入数据
			arrays[j + 1] = value;
		}
	}
}
           

四、选择排序

一、排序原理

类似插入排序,也分已排序区间和未排序区间。但是选择排序每次会从未排序区间找到最小的元素,将其放到已排序区间的末尾

数据结构与算法 --排序 上(七)一、如何分析一个排序算法?二、冒泡排序三、插入排序四、选择排序

二、性能分析

空间复杂度:选择排序是原地排序算法。

稳定性:选择排序不是稳定的排序算法。比如5,8,5,2,9  找到最小元素是2和5交换就不稳定了

时间复杂度:都是O(n^2)

1. 最好情况:O(n^2)。

2. 最坏情况:O(n^2)。

3. 平均情况:O(n^2)。

三、代码实现

/*
 * 选择排序
 */
public class Selection_Sort {

	public static void main(String[] args) {
		int[] arrs = { 5, 4, 2, 7, 8, 1 };
		Selection_Sort.selectSort(arrs, arrs.length);
		System.out.println(Arrays.toString(arrs));
	}

	public static void selectSort(int arrs[], int n) {
		for (int i = 0; i < n; i++) {
			int minIndex = i;
			for (int j = i + 1; j < n; j++) {
				// 寻找到最小数的下标
				if (arrs[minIndex] > arrs[j]) {
					minIndex = j;
				}
			}

			// 交换数据
			int minValue = arrs[i];
			arrs[i] = arrs[minIndex];
			arrs[minIndex] = minValue;
		}
	}
}
           

问题:插入和冒泡复杂度都是O(n2),为什么实际开发中更倾向与插入算法呢?

我们前面分析冒泡排序和插入排序的时候讲到,冒泡排序不管怎么优化,元素交换的次数是一个固定值,是原始数据的逆序度。插入排序是同样的,不管怎么优化,元素移动的次数也等于原始数据的逆序度。

但是,从代码实现上来看,冒泡排序的数据交换要比插入排序的数据移动要复杂,冒泡排序需要 3 个赋值操作,而插入排序只需要 一个

//冒泡排序中数据的交换操作:
if (a[j] > a[j+1]) { // 交换
   int tmp = a[j];
   a[j] = a[j+1];
   a[j+1] = tmp;
   flag = true;
}

//插入排序中数据的移动操作:
if (a[j] > value) {
  a[j+1] = a[j];  // 数据移动
} else {
  break;
}
           
数据结构与算法 --排序 上(七)一、如何分析一个排序算法?二、冒泡排序三、插入排序四、选择排序

继续阅读