算法 排序 【详解基于二分查找的折半插入排序】*稳定性仍需改进

3 篇文章 0 订阅

基于二分查找的折半插入排序

时间复杂度:O(n²),但减少了比较元素的次数。

稳定性:稳定,无发生相对位置改变。

这个算法的核心思想还是插入排序思想,

唯一不同的一点是:

直接插入排序是比较一次,挪一次位置,直到找到目标位置,

并且挪的过程是逐个的;

而折半插入排序在找位置的时候,

先不比较,而是采用二分查找先把要插入的位置提前找出来,

然后统一地移动插入位置之后的所有元素。

 

 于是开始了今夜的DEBUG旅程

首先,我先写了一个二分查找算法:

int myBinarySearch(T array[], int len, int x)
{
	T low = 0;
	T high = len - 1;

	while (low <= high)
	{
		T mid = (low + high) / 2;
		if (x < array[mid])
		{
			high = mid - 1;
		}
		else if (x > array[mid])
		{
			low = mid + 1;
		}
		else
		{
			return mid;
		}
	}
	return -1;
}

这个二分查找是基于升序的。

测试后,无误:
 

	int b[] = {1,2,3,4,5,6,7,8,9,10};
	int len = sizeof(b) / sizeof(b[0]);
	cout << len << endl;
	cout << myBinarySearch<int>(b, len, 2);

 

接下来,我准备应用这个算法到折半插入排序中:

我举了一个例子:

一个整型数组,4,3,2,1。

i还是从1开始,代表数组下标为1的元素即将进行排序。

为了不影响i,我们定义一个j = i,

接下来的操作都是基于j的。

首先创建一个临时变量temp存放待排元素,

然后通过二分查找算法,

查找数组长度为j+1的子数组,

查找元素为temp。

然后定义一个下标down = myBinarySearch(array, j + 1, temp);

比如第一趟,

j = 1;

down = 0;

然后开始排序:

这里使用的是while循环,

当j>down时,进行循环体。

		while (j > down)
		{
			array[j] = array[j - 1];
			j--;
		}

开始挪位置;

挪好后,

将array[down] = temp;

然后进行下一次循环即可。

当前代码:

template<typename T>
void myBinaryInsertSort(T array[], int len)
{
	for (int i = 1; i < len; i++)
	{
		int j = i;
		T temp = array[j];
		int num = 0;
		int down = myBinarySearch(array, j + 1, temp);
		cout << "元素" << temp << "应该放在本待插入序列的第" << down + 1 << "个位置" << endl;
		while (j > down)
		{
			array[j] = array[j - 1];
			j--;
		}
		array[down] = temp;
		cout << "这是第" << i << "趟排序" << endl;
	}
}

运行程序:

居然报了一个异常。

我又仔细地看了我的代码,

逻辑上没有问题,

于是按F11进入函数体逐个分析。

终于发现了我的问题:

由于待排序数组里的空格子放了一个数,

即便他是无意义的,

但如果他比他左边的数字要小的话,

子序列就不再是一个升序的序列,

也就不满足二分查找算法了。

所以现在需要解决的问题是:

如何将待排序列变成一个有序数组。

很明显,造成乱序的元素就是起始空格子里的元素,

他左边的序列已经是升序。

比如第一趟排序中:

我们要在{4,3}中找3,

但是{4,3}是一个逆序。

不过,如果我们单独看{4}的话,

4是一个升序,

所以我决定以此为突破口,

写一个新的二分查找算法。

这个新二分查找算法的功能如下:

对一个升序数组查找指定元素:

如果找到了,则返回其下标;

如果没找到,则返回若其在数组里,升序后的下标。

比如{1,2,4,5},找3,

函数会return 2。

开始实现:

int a[] = {1,2,4,5}

第一个例子:查找数在中间

x = 3

第一趟:

a[low] = 1,

a[high] = 5,

a[mid] = 2;

x>mid;

low = 2;

第二趟:

a[low] = 4,

a[high] = 5;

a[mid] = 4;

x<mid;

high = 2;

第三趟:

a[high] = 4,

a[low] = 4,

a[mid] = 4;

x<mid;

high = 1;

跳出循环。

看一下此时low,high,mid的下标:

low:2,

high:1。

也就是说,此时low&high会错位挨在一起,

而中间正好差一个值为x的数。

再举一个例子:查找数在最左侧

x = 0

第一趟:

a[low] = 1,

a[high] = 5,

a[mid] = 2,

x<mid;

high = 0;

第二趟:

a[high] = 1,

a[low] = 1,

a[mid] = 1,

x<mid;

high = -1;

跳出循环,

此时low = 0,

high = -1。

第三个例子:查找数在最右侧

x = 6

第一趟:

a[low] = 1,

a[high] = 5,

a[mid] = 2,

x>mid;

low = 2;

第二趟:

a[high] = 5,

a[low] = 4,

a[mid] = 4,

x>mid;

low = 5;

第三趟:

a[high] = 5,

a[low] = 5,

a[mid] = 5,

x>mid;

low = 4;

跳出循环,

此时low = 4,

high = 3。

第四种情况:要找的数在序列里。

这种情况return mid即可。

通过以上现象,我们得出结论:

low的值即为将要插入的值。

int myBinarySearch(T array[], int len, int x)
{
	T low = 0;
	T high = len - 1;

	while (low <= high)
	{
		T mid = (low + high) / 2;
		if (x < array[mid])
		{
			high = mid - 1;
		}
		else if (x > array[mid])
		{
			low = mid + 1;
		}
		else
		{
			return mid;
		}
	}
	return low;
}

排序代码:

template<typename T>
void myBinaryInsertSort(T array[], int len)
{
	for (int i = 1; i < len; i++)
	{
		int j = i;
		T temp = array[j];
		int num = 0;
		int down = myBinarySearch(array, j, temp);
		cout << "元素" << temp << "应该放在本待插入序列的第" << down + 1 << "个位置" << endl;
		while (j > down)
		{
			array[j] = array[j - 1];
			j--;
		}
		array[down] = temp;
		cout << "这是第" << i << "趟排序" << endl;
	}
}

测试代码:

	int b[] = {5,21,6,8,4,3,2,1};
	int len = sizeof(b) / sizeof(b[0]);
	cout << len << endl;
	//cout << myBinarySearch<int>(b, len, 2);
	myBinaryInsertSort<int>(b, len);
	for (int i = 0; i < len; i++)
	{
		cout << b[i] << endl;
	}

运行截图:

大功告成!

仍需改进的地方

虽然成功排好了顺序,

但是和直接插入不同的是,

如果第一次就找到了该元素,

但该元素后面跟着同样值的元素,

这样新插入的元素就会把先进来的同值元素挤到后面去,

造成了不稳定。

这个问题暂时还没想好解决办法,

想出来了会回来解决。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值