您的位置:首页 > 其它

不改变正负数相对顺序,重排数组,使负数在正数之前

2016-03-16 15:24 459 查看
最近看到了来自 v_JULY_v的博客一个关于这个问题的解决(原文链接 :nhttp://blog.csdn.net/v_JULY_v/article/details/7329314),其中第一句话引起了我的兴趣,原话(一直未曾看到令人满意的答案,为何呢?),我不信邪,非要想一想,于是有了下面的解法,不过我最后还是信邪了。确实我想不出时间复杂度O(N),空间复杂度O(1)的方法,如果是链表的话,倒是可以做到。不过倒是有时间复杂度O(N),
空间复杂度O(m)(m为负数个数)的方法。还有O(n+m^2)(不知道这个时间复杂度算的对不对。数学不好 ==!),空间复杂度O(1)的方法。。m可以控制在n/2以内。当m大于n/2的时候,可以调整方法,反过来对正数进行操作。。

我的两种解法是要预知负数个数的情况下,所以需要先遍历一遍数组。其实我们知道负数的个数的情况下,是不是可以确定好了负数在数组前面的多少个位置,正数在数组的后多少个位置。然后我们再顺着数组遍历一遍,遇到负数,则放到数组前面的负数的区间,正数则放到正数应该在的区间内。遍历玩数组。就排好顺序了。如图:


但是还有一个问题要解决。。我们遇到一个负数的时候把他放在负数的地盘里,但是原来负数的地盘里可能有正数。这个正数怎么办呢?

所以我想到两个解决办法。
先看第二个方法的流程:



第一个方法:分配一个与负数个数相同大小的数组,用来存放这个负数区间里面的正数。
代码如下:
inline void swap(int& a, int& b)
{
	if (a != b)
	{
		a = a^b;
		b = a^b;
		a = a^b;
	}
}

//其实两个算法都可以调整。如果是负数超过一半。那就应该是先排正数,这样可以保证辅助空间小于n/2
//第二个算法的实际执行时间降低,也就是有递归的那个地方,因为那个地方跟负数的个数有关,如果负数多,则倒过来
//从后面往前面排,移动正数, 
int ReSort(int a[], int n)
{
	assert(a);
	int nNeg = 0;	//记录负数的总个数
	for (int i = 0; n > 0 && i < n; i++)
	{
		if (a[i] < 0)
		{
			nNeg++;	//统计负数的个数
		}
	}
	if (n == nNeg || nNeg == 0)//全部是负数,或者没有负数。就返回
	{
		return nNeg;
	}
	int* pa = new int[nNeg] {0};// (int*)malloc(nNeg * sizeof(int));
	if (pa == NULL)
	{
		return 0;
	}
	int nFind = 0;	//目前为止找到的负数的个数
	int nStart = 0;	//每个子区间的开始位置
	int nPos = 0;
	int nTrip = 0;	
	memcpy(pa, a, nNeg* sizeof(int));
	for (int i = 0; i < n  ; i++)
	{
		if ((i - nStart) == nNeg)
		{
			nNeg -= nTrip;
			nStart = i;
			nPos = 0;
			nTrip = 0;
		}
		if (a[i] < 0)
		{
			a[nFind++] = a[i];
			a[i] = pa[i - nStart];
			nTrip++;
		}
		else
		{
			if (nPos == 0 && nTrip == 0 && nNeg != 0)
			{
				swap(pa[nPos++], a[i]);
			}
			else
			{
				pa[nPos++] = a[i];
				a[i] = pa[i - nStart];
			}
		}
	}
	return nFind;		//返回一共调整了多少个负数
}


另外一种方式是,始终保持负数区间内的负数都是按顺序排好的。所以。这里需要浪费一点时间复杂度,如果能不要移动数组元素的话。那就好了。可惜我想不到了。。
代码如下
int ReSort_1(int a[], int n)
{
	assert(a);
	int nNeg = 0;	//记录负数的总个数
	for (int i = 0; n > 0 && i < n; i++)
	{
		if (a[i] < 0)
		{
			nNeg++;	//统计负数的个数
		}
	}
	if (n == nNeg || nNeg == 0)//全部是负数,或者没有负数。就返回
	{
		return nNeg;
	}
	int nFind = 0;	//目前为止找到的负数的个数
	int nStart = 0;	//每个子区间的开始位置
	int nTrip = 0;	//一个区间找出的负数个数
	int nPos = 0;
	for (int i = 0; i < n ; i++)
	{
		if (i - nStart == nNeg)
		{
			nStart = i;
			nNeg -= nTrip;
			nFind += nTrip;
			nTrip = 0;
		}
		nPos = i - nStart;
		swap(a[i], a[nFind + nPos]);
		if (a[nFind + nPos] < 0)
		{
			//ReSort_1(a + nFind + nTrip, nPos - nTrip + 1);	
			//相当于下面的循环。因为只有一个负数。。移动到他到合适位置即可
			for (int j = nPos; j > nTrip; j--) 
			{ 
				swap(a[nFind + j], a[nFind + j - 1]);
			}
			nTrip++;
		}
	}
	return nFind;		//返回一共调整了多少个负数
}
然后就是调用的部分代码:
void Print(int a[], int n)
{
	assert(a);
	for (int i = 0; i < n; i++)
	{
		printf("%4d ", a[i]);
	}
}
int main()
{
	int a[] = { -1,-7,-59,-12,15, 33 ,8,9,-999 };
#define len (sizeof(a)/sizeof(int))
	int b[len] ;
	memcpy(b, a, sizeof(a));

	printf("排序前: ");
	Print(a, len);
	printf("\n");

	ReSort(a, len);

	ReSort_1(b, len);

	printf("\n第一种排序后: ");
	Print(a,len);
	printf("\n第二种排序后: ");
	Print(b, len);

	return _getch();
}




如果你发现有什么不对。

请斧正,感激不尽。

谢谢!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: