八大排序的基本思想

一.直接插入排序

核心思想:

  • 将数组中的所有元素依次跟前面已经排好的元素比较,如果选择的元素比已排序的元素小,则交换,直到全部元素都比较过。 直接插入排序可以用两个循环完成:
  • 1.第一层循环:遍历待比较的所有数组元素。(第一个不在数组之中)
  • 2.第二层循环:将本轮选择的元素(selected)与已经排好序的元素(ordered)相比较。如果selected>ordered,那么二者交换。
int[] arr = {21, 12, 4, 32, 12, 56, 0, 5, 34};
for( int i = 1; i < arr.length; i++ ) {
	int temp = arr[i];    // 取出当前元素,在已排序的元素中从后往前扫描
	for( int j = i; j >= 0; j-- ) {
		// 如果循环比较的元素比temp大,则往后移一位
		if( j > 0 && arr[j-1] > temp ) {
			arr[j] = arr[j-1];    // 如果该元素(已排序)大于取出的元素temp,将该元素移到下一位置
		} else {
			// 将新元素插入到该位置后
			arr[j] = temp;
			break;
		}
	}
}
System.out.println(Arrays.toString(arr));

二.希尔排序

核心思想:

  • 将待排序数组按照步长gap进行分组,然后将每组的元素利用直接插入排序的方法进行排序,每次讲gap折半减小,循环上述操作;当gap=1时,利用直接插入,完成排序。
  • 希尔排序可以由三个循环完成:
  • 1.第一层循环:将gap依次折半,对序列进行分组,直到gap=1
  • 2.第二、三层循环:也即直接插入排序所需要的两次循环(具体见一)
int[] array = {21, 12, 4, 32, 12, 56, 0, 5, 34};
int number = array.length / 2;
int i;
int j;
int temp;
while (number >= 1) {
	for (i = number; i < array.length; i++) {
		temp = array[i];
		j = i - number;
		while (j >= 0 && array[j] < temp) { //需要注意的是,這裡array[j] < temp將會使數组從大到小排序。
			array[j + number] = array[j];
			j = j - number;
		}
		array[j + number] = temp;
	}
	number = number / 2;
}
System.out.println(Arrays.toString(array));

三.选择排序

核心思想(比较+交换):

  • 1.从待排序序列中,找到关键字最小的元素
  • 2.如果最小元素不是待排序序列的第一个元素,将其和第一个元素互换
  • 3.从余下的n-1个元素中,找出关键字最小的元素,重复1.2,直至排序结束。
  • 选择排序由两个循环完成:
  • 第一层循环:依次循环序列中的每一个元素
  • 第二层循环:将遍历得到的当前元素依次与余下的元素进行比较,符合最小元素的条件,则交换
int[] array = {21, 12, 4, 32, 12, 56, 0, 5, 34};
	    
for (int i = 0; i < array.length; i++) {
	int min = i;
	// 找出最小的
	for (int j = i + 1; j < array.length; j++) {
		if (array[j] < array[min]) {
			min = j;
		}
	}

	// 交换位置
	if (min != i) {
		int temp = array[min];
		array[min] = array[i];
		array[i] = temp;
	}
}
System.out.println(Arrays.toString(array));

四.堆排序

  • 堆的概念:本质是一种数组对象。
  • 堆的性质:任意的叶子节点小于(或大于)他所有的父节点。对此,又分大顶堆和小顶堆,大顶堆要求节点的元素都要大于其孩子,小顶堆要求其节点元素都要小于其孩子。
  • 核心思想:
  • 1.首先将序列构建成为大顶堆(这样满足了大顶堆那条性质:位于根节点的元素一定是当前序列的最大值)
  • 2.取出当前大顶堆的根节点,将其与序列末尾元素进行交换(此时,序列末尾的元素为已排序的最大值,由于交换了元素,当前位于根节点的堆并不一定满足大顶堆的性质)
  • 3.对交换后的n-1个序列元素进行调整,使其满足大顶堆的性质。
  • 4.重复2.3步骤,直至堆中只有一个元素为止。
public static void heapSort(int[] arr) {
	for (int i = 0; i < arr.length; i++) {
		max_heapify(arr, i);
		int temp = arr[0];
		arr[0] = arr[i - 1];
		arr[i -1] = temp;
	}
}

private static void max_heapify(int[] arr, int limit) {
	if (arr.length <= 0 || arr.length < limit) return;
	int parentIdx = limit / 2;

	for (; parentIdx >= 0; parentIdx--) {
		if (parentIdx * 2 >= limit) continue;
		int left = parentIdx * 2;
		int right = (left + 1) >= limit ? left : (left + 1);
		int maxChildId = arr[left] >= arr[right] ? left : right;

		if (arr[maxChildId] > arr[parentIdx]) {
			int temp = arr[maxChildId];
			arr[parentIdx] = arr[maxChildId];
			arr[maxChildId] = temp;
		}
	}
}

五.冒泡排序

  • 核心思想:
  • 1.将序列当中的左右元素,依次比较,保证右边的元素始终大于左边的元素(第一轮结束后,序列最后一个元素一定是当前序列的最大值)
  • 2.对序列中剩余的n-1个元素再次执行步骤一
  • 3.对于长度为n的序列,一共需要执行n-1次比较(利用while循环可以减少执行次数)
for (int i = 0; i < list.length - 1; i++) {
	for (int j = 0; j < list.length - 1 - i; j++) {
		if (list[j] < list[j + 1]) {
			int temp = list[j];
			list[j] = list[j+1];
			list[j+1] = temp;
		}
	}
}

六.快速排序

  • 核心思想(挖坑填数+分治法)
  • 1.从序列中选择一个基数,在这里我们选择序列中第一个数为基准数。
  • 2.将序列中的所有数依次遍历,比基准数大的位于其右侧,比基准数小的位于左侧。
  • 3.重复1.2,直到素有自己当中只有一个元素为止。

七.归并排序

  • 核心思想:
  • 归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个典型的应用。它的基本操作是:将已有的子序列的合并,达到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。
  • 归并排序其实要做两件事:分解--将序列每次折半拆分,合并--将划分后的序列段凉凉排序合并。
如何分解:
  • 可以采用递归的方法,首先将待排序分成A.B两组,然后重复对A.B序列分组,直到分组后组内只有一个元素,此时我们认为组内所有元素有序,则分组结束。
如何合并
  • L[first...mid]为第一段,L[mid+1..last]为第二段,并且两端已有序,现在我们要将两端合成达到L[first...last]并且也有序。
  • 首先,依次从第一段与第二段中取出元素比较,将较小的元素赋值给temp[]
  • 重复执行上一步,当某一段赋值结束,则将另一段剩下的元素赋值给temp[]
  • 此时将temp[]中的元素赋值给L[],则得到的L[first...last]有序

八.基数排序

  • 核心思想:
  • 通过序列中各个元素的值,对排序的N个元素进行若干趟的分配与手机来实现排序。
  • 分配:我们将L[i]中的元素取出,首先确定其个位上的数字,根据该数字分配到与之序号相同的桶中
  • 收集:当序列中所有的元素都分配到对应的桶中,再按照顺序依次将桶中的元素收集形成新的一个待排序列L[ ]
  • 对新形成的序列L[]重复执行分配和收集元素中的十位、百位...直到分配完该序列中的最高位,则排序结束

时间复杂度计算

时间复杂度的计算
  • 计算一个算法的时间复杂度,不可能把所有的算法都编写出实际的程序出来让计算机跑,这样会做很多无用功,效率太低。实际采用的方法是估算算法的时间复杂度。

  • 在学习C语言的时候讲过,程序由三种结构构成:顺序结构、分支结构和循环结构。顺序结构和分支结构中的每段代码只运行一次;循环结构中的代码的运行时间要看循环的次数。

  • 由于是估算算法的时间复杂度,相比而言,循环结构对算法的执行时间影响更大。所以,算法的时间复杂度,主要看算法中使用到的循环结构中代码循环的次数(称为“频度”)。次数越少,算法的时间复杂度越低。

例如: a) ++x; s=0; b) for (int i=1; i<=n; i++) { ++x; s+=x; } c) for (int i=1; i<=n; i++) { for (int j=1; i<=n; j++) { ++x; s+=x; } }

  • 上边这个例子中,a 代码的运行了 1 次,b 代码的运行了 n 次,c 代码运行了 n*n 次。
时间复杂度的表示
  • 算法的时间复杂度的表示方式为:

  • O(频度)

  • 这种表示方式称为大“O”记法。

  • 注意,是大写的字母O,不是数字0。

  • 对于上边的例子而言,a 的时间复杂度为O(1),b 的时间复杂度为O(n),c 的时间复杂度为为O(n2)。

  • 如果a、b、c组成一段程序,那么算法的时间复杂度为O(n2+n+1)。但这么表示是不对的,还需要对n2+n+1进行简化。

简化的过程总结为3步:
  • 1.去掉运行时间中的所有加法常数。(例如 n2+n+1,直接变为 n2+n)
  • 2.只保留最高项。(n2+n 变成 n2)
  • 3.如果最高项存在但是系数不是1,去掉系数。(n2 系数为 1)
  • 所以,最终a、b和c合并而成的代码的时间复杂度为O(n2)。

常用的时间复杂度的排序

  • 列举了几种常见的算法时间复杂度的比较(又小到大):
  • O(1)常数阶 < O(logn)对数阶 < O(n)线性阶 < O(n2)平方阶 < O(n3)(立方阶) < O(2n) (指数阶)

转载于:https://my.oschina.net/chinahufei/blog/3030237

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值