您的位置:首页 > 编程语言 > C语言/C++

基础排序:单元,冒泡,分治排序的C++实现与讨论

2011-04-14 11:14 471 查看
一.为什么需要学习排序?

上学的时候学习排序是为了考试,是为了不挂科,在实际问题中,排序就显得不是那么重要了。特别是当使用c++ STL时,排序就是非常简单的事情了。

但为什么还需要学习排序呢?我个人感觉是为了理解细想,培养能力的一个必不可少的过程,像标题所提示的3个排序算法一样,特别是其中的分治算法对解决一切其他很多问题有深远的影响。

二.排序的用处.

排序是为了什么,在我接触的问题中,第一是为了显示,在游戏中必不可少,第二是为了多次查找。

比如说一个散列的数组需要查找一次,那么就直接遍历吧。最悲剧的情况,复杂度为O(n).

如果需要查找多次的话,那么排序后查找肯定要好的多。比如我们下面提到的分治排序,算法复杂度为O(n*ln(n)),如果再使用2分查找的话,复杂度为
O(ln(n)).如果有n个数,要使用K次查找的话,那么总的复杂度为O(n*ln(n) +
K*ln(n))。使用直接遍历的话,那么复杂度为O(K * n).

从上面可以看出,当n或者K大于一个数的时候,排序后查找肯定速度要快得多。

三.实现排序

1.单元排序

int SelectionSort(int* arr, const int length)
{
assert(arr != NULL);
assert(length > 0);

int Cnt = 0;	// 计算交换次数

int len = length;
int MinIndex = 0;
int MinValue = 0;
for (int i = 0; i < len; ++i)
{
MinIndex = i;
MinValue = arr[i];
for (int j = i+1; j < len; ++j)	// 从第一个数的后面开始循环
{
if (arr[j] < MinValue)		// 比最下的值小
{
MinValue = arr[j];
MinIndex = j;
}
}
if ( MinValue != arr[i])
{
swap(&arr[i], &arr[MinIndex]);
++Cnt;
}
}
return Cnt;
}


讨论:

(1).每次排序完都把最小的排在前面,越到后面需要循环比较的次数越少;

(2).返回交换的次数,为了方便跟冒泡排序法比较;

(3).算法复杂度 O(n^2)

2.冒泡排序

int BubbleSort(int* arr, const int length)
{
int Cnt = 0;	// 计算交换次数
//for (int i = 0; i < length; ++i)		// 最直接版本
//{
//	for (int j = i; j < length; ++j)
//	{
//		if (arr[i] > arr[j])
//		{
//			swap(&arr[i], &arr[j]);
//			++Cnt;
//		}
//	}
//}
for (int i = 0, j = i; j < length; j++)	// 稍为改进版本,效果一样,
{
if (arr[i] > arr[j])
{
swap(&arr[i], &arr[j]);
++Cnt;
}
if (j == length-1)	// 到最后,重置
{
++i;
j = i;
}
}
return Cnt;
}


讨论:

(1).每次完都把最小的放在前面

(2).返回交换的次数,为了方便跟选择排序法比较

(3).算法复杂度O(n^2)

3.分治排序

int Merge(int* arr, const int start, const int mid, const int end)
{
int len = end-start+1;				// 计算长度
int* newArr = new int[len];
for (int x = start; x <= end; ++x)	// 把原数组[start, end] 赋值到新数组
{
newArr[x-start] = arr[x];
}
int _mid = mid - start;		// 新数组中间的值
int len1 = _mid+1;			// 前半段的长度
int i, j;
i = 0;						// 前半段的索引
j = _mid+1;					// 后半段的索引
int index = start;		// arr数组的索引
while ((index-start+1) <= len)		// 赋值没完 ,进行 len 次循环
{
if (i < len1 && j < len)		// 合并2个数组
{
if (newArr[i] < newArr[j])
{
arr[index++] = newArr[i++];
}
else
{
arr[index++] = newArr[j++];
}
}
else if (i == len1 && j <= len )		// 如果有一个比较完了,把另一个数组元素依次放入后面
{
arr[index++] = newArr[j++];
}
else if (j == len && i <= len1)
{
arr[index++] = newArr[i++];
}
}
delete[] newArr;
return index;
}
void DiviedMergeSort(int *arr, const int start, const int end)
{
if ((end-start) <= 1)					// 如果只有2个或者更少的元素
{
if (arr[start] > arr[end])			// 比较大小
{
swap(&arr[start], &arr[end]);	// 交换
}
}
else if (start < end)					// 如果有2个以上的元素,并且数组下标合法
{
int mid = (start + end) / 2;		// 计算中间的元素,如果是3个元素的话,前2个为一组,后2个为一组
DiviedMergeSort(arr, start, mid);	// 递归前面的一组
DiviedMergeSort(arr, mid+1, end);	// 递归后面的一组

Merge(arr, start, mid, end);		// 把分开的合并
}
}


讨论:

(1).把一个大的问题分成相同类型小的问题处理后,在组合起来

(2).大部分的交换都变成2个元素之间的交换,大大减少的循环的次数

(3).算法复杂度 O(n*ln(n))

附加上交换的代码:

void swap(int* p1, int* p2)
{
int temp;
temp = *p1;
*p1 = *p2;
*p2 = temp;
}


四.总结:

1.单元排序和冒泡排序的比较:

(1).算法复杂度都一样。

(2).冒泡排序的交换次数远远大于选择排序,所以不建议使用冒泡排序,我也很少看到有人使用冒泡排序。除了教科书上,因为最直接。

2.分治排序提高了很高的效率,但是实现起来也相对困难,不是那么直接。这也许就是对我这种执着的人有很大的魅力。有时候为了提高一点效率,硬是付出了很多的时间。哈哈~~~

3.关于分治排序的合并算法,当我写出后,去网上看看其他人写的,发现就我用了一个循环实现,有点感慨,也许N人都不把代码放出来,或者搜索引擎没搜到,反正至少满足了我一点荣誉感,对于程序原来说,也许就够了。

4.我不赞成使用自己写的排序,包括我自己写的我也不会使用,只是为了研究一个问题,得到一种思维,甚至可以说得到一点满足感。如果需要在问题中使用到算法,我绝不犹豫的把现有的问题映射到已经写好的算法中
,比如说使用c++ STL。既安全,有省事,何乐而不为呢?

PS:因水平有限,不免有什么不妥之处,请各大看官不吝赐教,谢谢~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: