数据结构学习记录 (向量:五(有序向量的排序算法))

有序向量的排序

起泡排序

void bubblesort (int A[], int n)
{
	bool sorted = false;  //整体排序标志,假设其尚未排序
	while (!sorted)     //只要全局排序没有完成,就逐趟进行扫描
	{
		sorted = true;     //假定已经排序
		for(int i = 1; 1 < n, i++)  //自左向右逐一检查各相临元素
		{
			if(A[i - 1] < A[i])  //如果逆序
			{	
				swap(A[i - 1], A[i])//进行交换
				sorted = false; //整体排序不能保证,需清除排序标志
			}
	    }
	    n--; // 经过一趟扫描,末元素必然就位,故可缩短排序的有效长度
	}

起泡排序的原理是依据事实:一个有序向量中任意一对相邻的元素都是顺序,即所有相邻的元素对都是顺序;向量中只要有一对相邻元素为逆序,则该向量就不是有序向量。
所以惊醒一趟趟的扫描交换,每次扫描必然使该趟有效长度内的最大元素就位,当某次扫描发现没有逆序对时,则循环终止,排序完成
复杂度:O(n^2)

我们可以将其封装在verctor向量中去:

template <typename T> void Vertor<T>::bubbleSort(Rank lo, Rank hi)
{
	while (!bubble(lo, hi--));  //逐趟进行扫描,直至全序
}

template <typename T> bool Verctor<T>::bubble (Rank lo, Rank hi)
{
	bool sorted = true;  //整体有序标志
	while(++lo < hi)     //自左向右,逐一检查各对相邻元素
		if (_elem[lo - 1] > _elem[lo]) //若逆序
		{
			sorted = false;             //意味着尚未整体有序
			swap(_elem[lo - 1], _elem[lo]); //进行交换使局部有序
		}
	return sorted;  // 返回有序标志
}

起泡排序算法改进

以上的起泡排序算法已经进行了一定的改进:当发现前缀中没有逆序对时就及时终止扫描,而不是继续一趟一趟的进行扫描直至有效区间长度缩减为1.
但是以上算法没有考虑一种情况:后半部分是顺序的,前半部分有逆序对。这种情况下,每次扫描后半部分不动,前半句部分进行扫描交换。但以上算法还是从头到尾进行扫描,做了很多无用功。所以要及时的改变hi值使扫描区间控制在前半部分,改进代码如下:

template <typename T> void Vertor<T>::bubbleSort(Rank lo, Rank hi)
{
	while (lo < (hi = bubble(lo, hi)));  //逐趟进行扫描,直至全序
}

template <typename T> bool Verctor<T>::bubble (Rank lo, Rank hi)
{
	Rank last = lo;  //最右侧的逆序对假设为[lo - 1, lo]
	while(++lo < hi)     //自左向右,逐一检查各对相邻元素
		if (_elem[lo - 1] > _elem[lo]) //若逆序
		{
			last = lo;             //若逆序,则及时更细最右侧逆序对初始化位置
			swap(_elem[lo - 1], _elem[lo]); //进行交换使局部有序
		}
	return last;  // 返回最右侧逆序对位置
}

这样改进之后,可以更加及时的更新有效区间长度。
复杂度:O(n^2)

归并排序

归并排序是一个更高级、复杂度更低的排序算法。与起泡排序反复调用单糖扫描函数类似,归并排序则是通过反复调用二路归并算法实现排序。
所谓的二路归并算法,就是将两个有序的向量合并成一个有序序列。其算法中有一个循环,每次循环中,比较两个有序序列的首元素,将小者取出并追加到输出向量的末尾,该元素在原向量中的后继则称为新的首元素。
归并算法的策略是分治策略:将原问题分成两个小问题,之后再将这两个小问题分成更小的问题。所以有一个向量对其进行排序,将其分成两部分,之后再分直到每一个元素为一个部分,一个元素自然有序,之后再不断调用二路归并算法,一和二、二和四、四和八。。。。直到合并成原来规模的向量,此时向量完成排序:

template <typename T> void Vector<T>::mergeSort(Rank lo, Rank hi)
{
	if (hi - lo < 2)  //单元素区间自然有序,否则继续分而治之
		return;
	int mi = (lo + hi)/2;  //以中点为界
	mergSort(lo, mi);   //分别进行排序
	mergSort(mi, hi);
	merge(lo, mi, hi);   //调用二路归并算法进行归并

可以看到以上算法中,一直递归直到规模缩减至1从而达到递归基时,便开始进行归并。所以二路归并算法是核心所在:

template <typename T> void Vector<T>::merge(Rank lo, Rank mi, Rank hi) //各自有序的子向量[lo, mi), (mi, hi)
{
	T* A = _elem + lo; //声明一个向量A用来存最后归并后的向量:A[0, hi - lo) = _elem[lo, hi]
	int lb = mi - lo;
	T* B = new T[lb];  //前子向量B[0, lb) = _elem[lo, mi)
	for (Rank i = 0; i < lb; B[i] = A[i++]); //复制前子向量
	int lc = hi - mi; T* C = _elem + mi;  //后子向量C[0, lc) =  _elem[mi, hi)
	for (Rank i = 0, j = 0, k = 0; (j < lb) || (k < lc);)//B[j]和C[k]中的小者续至A末尾
	{
		if((j < lb) && (!(k < lc) || (B[j] <= C[k])))
			A[i++] = B[j++];
		if((k < lc) && (!(j < lb) || (C[k] < B[j])))
			A[i++] = C[k++];
	}
	delete [] B; //释放B空间
} 

复杂度:递归的时间复杂度为O(logn),迭代的时间复杂度为O(n),所以总的时间复杂度为O(nlogn)。比起泡排序更高效。

注意,前子向量我们分配了一个空间给B向量,而后子向量C则是直接在A向量上,所以存在一种情况:B向量已经全部添加在A向量上了,此时C向量仍然需要一次次进行归并,直到C向量完成归并。
而我们不需要进行这些步骤,因为C向量本来就是在A向量上的。所以我们无需考虑C向量提前耗尽的情况,可以对上述代码进行简化处理:

template void Vector::merge(Rank lo, Rank mi, Rank hi) //各自有序的子向量[lo, mi), (mi, hi)
{
T* A = _elem + lo;
int lb = mi - lo;
T* B = new T[lb];
for (Rank i = 0; i < lb; B[i] = A[i++]);
int lc = hi - mi; T* C = _elem + mi;
for (Rank i = 0, j = 0, k = 0; ( j < lb) || (k < lc)
{
if((j < lb) &&(!(k < lc) || (B[j] <= C[k])))
A[i++] = B[j++];
if((k < lc) && (!(j < lb) || (C[k] < B[j])))
A[i++] = C[k++];
}
delete [] B;
}

你可能感兴趣的:(算法,数据结构,排序算法)