数据结构与算法---排序---插入排序、二分搜索

插入排序(Insertion Sort)

插入排序非常类似于扑克牌的排序
在这里插入图片描述

执行流程:

1 在执行过程中,插入排序会将序列分为两部分:头部是已经排好序的,尾部是待排序的。

2 从头开始扫描每一个元素
每当扫描到一个元素,就将它插入到头部合适的位置,使得头部数组仍然保持有序。

在这里插入图片描述

不难写出代码:

public class sort {
	public static void main(String[] args)
	{
		int[] array = {19, 18, 67, 199, 6, 56};
		insertSort(array);
	}
	
	/**插入排序*/
	public static void insertSort(int[] array)
	{
		//prepareInsert准备插入的元素下标。从1开始,遍历到末尾
		for (int prepareInsert = 1; prepareInsert < array.length; prepareInsert++) {			
			//遍历已经排序好的部分,找到合适的位置,将其插入
			//需要做对比的元素,从0到prepareInsert - 1,一共prepareInsert个元素
			//遍历从后面开始,sortedInsert从prepareInsert - 1到0
			for (int sortedInsert = prepareInsert; sortedInsert > 0; sortedInsert--) {
				if (array[sortedInsert - 1] > array[sortedInsert]) {
					int temp = array[sortedInsert];
					array[sortedInsert] = array[sortedInsert - 1];
					array[sortedInsert - 1] = temp;
				}else {
					break;
				}
			}
		}
		
		for (int i = 0; i < array.length; i++) {
			System.out.print(array[i] + " ");
		}
		
	}
}

外面的循环,prepareInsert指的是准备插入的数据索引
里面的循环,是已经排序好的部分

MJ大神是这样写的:

/**插入排序2*/
	public static void insertSort2(int[] array)
	{
		for (int prepareInsert = 1; prepareInsert < array.length; prepareInsert++) {
			int cur = prepareInsert;
			while (cur > 0 && (array[cur - 1] > array[cur])) {
				int temp = array[cur];
				array[cur] = array[cur - 1];
				array[cur - 1] = temp;
				cur--;
			}
		
		for (int i = 0; i < array.length; i++) {
			System.out.print(array[i] + " ");
		}
		
	}

其实是跟上面的写法一样的,只是for循环变为了while循环。


逆序对(Inversion)

什么是逆序对?

数组[2, 3, 8 ,6, 1]的逆序对为:
<2, 1> < 3, 1> <8, 1> <6, 1> <8, 6>一共5个逆序对

插入排序的时间复杂度与逆序对的数量成正比关系
逆序对的数量越多,插入排序的时间复杂度越高

最坏、平均时间复杂度:O(n2)
最好时间复杂度:O(n)
空间复杂度:O(1)
属于稳定排序

当逆序对的数量极少时,插入排序的效率特别高
甚至速度比O(nlogn)级别的快速排序还要快

数据量不是特别大的时候,插入排序的效率也是非常好的。


优化方案一:

交换转为挪动

首先,我们观察到,上面的代码是每次都要比较大小,就跟冒泡排序一样,两两比较。
有一种优化方案,将待插入的元素备份,然后比较大小:
如果前大后小,则将前面元素往后移动,直接覆盖后一个元素,继续将前前元素与备份元素做比较,以此类推。直至找到合适的位置(前小后大),将备份元素插入到合适位置。
如果前小后大,则不动,结束循环。

这种虽然还是要一个一个移动,但是,少了过程中的交换,直接将找到位置做一次交换即可。

官方点的语言:

1 先将待插入的元素备份
2 头部有序数据中比待插入元素大的,都朝尾部方向挪动一个位置
3 将待插入元素放到最终的合适位置

在这里插入图片描述

不难写出优化代码:

/**插入排序*/
	public static void insertSort(int[] array)
	{
		for (int begin = 1; begin < array.length; begin++) {
			
			int beginValue = array[begin];
			
			for (int middle = begin; middle > 0; middle--) {
				if (array[middle - 1] > beginValue) {
					array[middle] = array[middle - 1];
				}else {
					//如果能走到这,说明找到了合适的位置,然后插入元素
					array[middle] = beginValue;
					break;
				}
				
				//如果走到了这,说明没有找到合适的插入位置,也就是,所有的元素都是大于要插入的元素的。
				//那么,需要将待插入的元素插入到最前面
				if (middle == 1) {
					array[0] = beginValue;
				}
			}
			
		}
	}

当然,也可以使用while循环

/**插入排序*/
	public static void insertSort2(int[] array)
	{
		for (int begin = 1; begin < array.length; begin++) {
			
			int current = begin;
			int beginValue = array[begin];
			
			while (current > 0 && (array[current - 1] > beginValue)) {
				array[current] = array[current - 1];
				current--;
			}
			
			array[current] = beginValue;
		}
	}

好像,使用for、while写的代码更简便些。


对于如何找出合适的位置,由于前面一部分已经是有序排序,对于有序排序,有一种方法查找元素效率比一个一个比较的查找效率要高,这就是二分搜索

二分搜索(Binary Search)

二分搜索又称为二分查找

如何确定一个元素在数组中的位置?(假设数组里面全都是整数)

如果是无序的数组,从第0个位置开始遍历搜索,平均时间复杂度为O(n)

如果是有序的数组,使用二分搜索,最坏的时间复杂度为O(logn)
每次去除一半,相当于一个二叉树,很容易得出是高度最大是logn

二分搜索思路

假设在[begin, end)范围内搜索某个元素v,mid == (begin + end)/2
或者mid == begin + (end - begin)/2

m = array[mid];

如果 v<m,去[begin, mid)范围内二分搜索
如果 v>m,去[mid + 1, end)范围内二分搜索
如果 v=m,直接返回mid,就是要搜索的值所在位置

在这里插入图片描述

不难写出二分查找的代码:

/**二分查找,并返回index*/
	public static int binarySearch(int[] array, int value)
	{
		if (array == null || array.length == 0) return -1;
		int begin = 0;
		int end = array.length;
		while (begin < end) {
			int middleIndex = begin + (end - begin)/2;
			int middleValue = array[middleIndex];
			if (value > middleValue) {
				begin = middleIndex + 1;
			}else if (value < middleValue) {
				end = middleIndex;
			}else {
				return middleIndex;
			}
		}
		
		return -1;
	}

当然,你也可以使用递归来写:

public static int binarySearch2(int[] array, int value, int begin, int end)
	{
		if (array == null || array.length == 0) return -1;
		int middleIndex = begin + (end - begin)/2;
		int middleValue = array[middleIndex];
		
		if (begin < end) {
			if (value > middleValue) {
				return binarySearch2(array, value, middleIndex + 1, end);
			}else if (value < middleValue) {
				return binarySearch2(array, value, begin, middleIndex);
			}else {
				return middleIndex;
			}
		}
		
		return -1;
	}
	

有一个问题需要注意的是,如果在数组里面有相同元素,二分查找找到的下标是不确定的。
比如[1, 1, 1, 1, 1, 1]


优化方案二:

利用二分查找,找出待插入的位置,然后将提前备份的元素插入合适的位置。

比如:

index 0 1 2 3 4 5  6  7
value 2 4 8 8 8 12 14 0

如果value = 5, 则插入位置为2
如果vaue = 1, 则插入位置为0
如果value = 15, 则插入位置为7
如果vaue = 8, 则插入位置为5
要求二分搜索返回的插入位置:第一个大于value的元素位置

明显,前面两种的二分查找并不能满足我们的要求,我们需要对二分查找进行改造。

public class test {

	public static void main(String[] args) {
		
		int[] array = {2, 4, 8, 8, 8, 12, 14};
		insertSort(array);
		for (int i = 0; i < array.length; i++) {
			System.out.print(array[i] + " ");
		}
	}

	
	public static void insertSort(int[] array)
	{
		for (int begin = 1; begin < array.length; begin++) {
			//保存需要插入的元素值
			int beginValue = array[begin];
			//需要插入的位置
			int insertIndex = binarySearch(array, begin);
			
			//从右往左依次后移
			for (int i = begin; i > insertIndex; i--) {
				array[i] = array[i - 1];
			}
			
			//将待插入值放在待插入位置
			array[insertIndex] = beginValue;
		}
	}
		
		
	/**输入一个index,寻找index的值需要插入的位置index*/
	public static int binarySearch(int[] array, int index)
	{
		int value = array[index];
		int begin = 0;
		int end = index;
		while (begin < end) {
			int middleIndex = begin + (end - begin)/2;
			int middleValue = array[middleIndex];
			if (value >= middleValue) {
				begin = middleIndex + 1;
			}else {
				end = middleIndex;
			}
		}
		
		return begin;
		
	}
}

需要注意的是,使用了二分搜索后,只是减少了比较次数,但插入排序的平均时间复杂度依然是O(n^2)。

return end;也可以,因为,while(begin < end){},能走到后面,则begin >= end

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值