有序序列的二分查找、冒泡排序、归并排序算法实战解析

本节开始讲解一下几个简单的算法,原理都在那本书上,大家自己看吧,我就不做搬运工了,这里不同的是,我把vector接口函数单独拿出来进行测试了,深深的体会到算法的奥妙之处,等你深入理解了你会情不自禁拍案叫绝的,废话不多说,因为这都是前段时间自己的练习代码,有详细的注释,不理解的请好好思考和看书,这里就不细说了


#include<iostream>
#include"F.h"

using namespace std;

//int disorde(int *A, int lo, int hi)
//{
//	int cnt = 0;
//	
//	while (++lo < hi)
//	{
//		if (A[lo]-1 > A[lo])
//			cnt += 1;
//		cout << "lo = " << lo << endl;
//	}
//	//cout << "lo = " << lo << endl;
//	return (cnt == 0) ? -1 : 1;
//}

//检查是否已经排好序
int disordered(int *A, int n)
{
	int cnt = 0;
	for (int i = 1; i < n; i++)
		if (A[i - 1] > A[i])
			cnt += 1;
	return cnt;
}

//删除操作
void remove(int *A,int lo,int hi ,int n)
{
	//int i = 0;
	while (hi < n)
	{
		A[lo++] = A[hi++];
		
	}
}

//有序唯一化,清除重复数据
void uniquify(int *A, int n)
{
	//int i = 1;
	//
	//while (i < n)
	//{
	//	if (A[i - 1] != A[i])
	//		i++;
	//	remove(A, i,i+1, n);
	//	
	//}
	int i = 0, j = 0;
	while (j++ < n)
		if (A[i] != A[j])
			A[i++] = A[j];
	//n = ++i;

}

//1.有序化的二分查找,该算法看上去复杂度均衡,其实不均衡,因此需要改进,使用
//菲波那切数列可以很好的改进复杂度的均衡。
int search(int *A,  const int &e, int lo, int hi)
{
	//复杂度为log(n)
	
	while (lo < hi)
	{
		int mid = (lo + hi) >> 1;
		if (e < A[mid]) hi = mid;
		else if (A[mid] < e) lo = mid+1;
		else return mid;
	}
	return -1;
}
//2.有序的二分查找之斐波那契数列查找,可以均衡的改进复杂度
int searchfib(int *A, int const&e, int lo, int hi)
{
	Fib fib(hi - lo);
	while (lo < hi)
	{
		while (hi - lo < fib.get()) fib.preve();
		int mid = lo + fib.get() - 1;
		if (e < A[mid]) hi = mid;
		else if (A[mid] < e) lo = mid + 1;
		else return mid;
	}
	return -1;
}
 
//有序查找之继续优化
//改进均衡的方法还可以通过其他方法改进,即使的他们直接看上去很均衡
//该算法虽然可以均衡复杂度,但是无法及时推出,就是说即使mid命中了,也还要等到
//最后才能停止和返回,该版本整体性能稳定,但是不好的地方是如果查不到,只返回一个-1
//如果查不到这插入,那么这个算法就不是很好,无法得到合适的插入位置,因此需要进一步要求
//因此在此基础上可以继续改进
int searchPro(int *A, const int &e, int lo, int hi)
{
	while (1 < hi - lo)
	{
		int mid = (lo + hi) >> 1;
		(e < A[mid]) ? hi = mid : lo = mid;
	}

	return (e == A[lo]) ? lo : -1;
}
//有序查找的终极优化,该算法在循环结束之后,无论成功
//与否,只需返回lo - 1即可
int searchPro_(int *A, const int &e, int lo, int hi)
{
	while (lo < hi)
	{
		int mid = (lo + hi) >> 1;
		(e < A[mid]) ? hi = mid : lo = mid + 1;
	}
	return --lo;//在寻找到最后时,hi和lo重合了,也就是说他们指向一个地方
	//如果,存在这个元素,则返回对应的下标,如果不存在这个元素则返回最近这个数的下标,
	//以此可以使用插入算法,进行插入即可
}
int Search(int *A, const int &e, int n)
{
	//return search(A, e, 0, n); 
	//return searchfib(A, e, 0, n);
	//return searchPro(A, e, 0, n);
	return searchPro_(A, e, 0, n);
}

//通过上面我们发现如果排好序了,就可以更高效的进行处理元素了
//因此排序的重要性就不言而喻了,下面我们就开始排序算法实践

void swap(int &a, int &b)
{
	a = a + b;
	b = a - b;
	a = a - b;
}

//1.冒泡排序(最基本的冒泡排序),这个复杂度很高,达到O(n^2),即使已经排序完成
//还是不能及时停止推出,在此基础上需要改进算法,使其能够及时退出
void bubblesort(int *A, int n)
{
	while (0 < n)
	{
		for (int i = 1; i < n; i++)
		{
			if (A[i - 1] > A[i]) swap(A[i - 1], A[i]);
		}
		n--;
	}
	
}
//2.为了达到及时退出的目的,我们需要考虑每趟的遍历是否发生交换,
//如果发生交换,则继续排序,如果一趟比较后没发生交换,说明已经排好序了,此时可以
//终止程序,由此我们需要检测每趟是否排序,怎么实现呢?可以如下:
void bubblesort_pro(int *A, int n)
{
	//int flag = 0;//这里是为了输出遍历了几趟,来计数使用的

	bool sorted = false;//刚进函数,还没排序,假设没排好序
	while (!sorted)
	{		
		sorted = true;//假设已经排好序了,n每次更新说明,已经走完一趟了,这里设置
				      //为true,是假设走了一趟都没有发生交换,说明已经排好序,符合逻辑,如果发生交换我们
		              //置为false
		for (int i = 1; i < n; i++)
			if (A[i - 1] > A[i])
			{
				swap(A[i - 1], A[i]);
				sorted = false; //进来这里说明还是存在调换的,因此不能保证已排好序,需要把sorted改为false
			}
		n--;
		//flag++;
	}
	//cout << "遍历了:" << flag << endl;
}

//上面该进的冒泡排序算法,已经达到了我们的要求了,但是对于最糟糕的情况,复杂度还是O(n^2)
//一边情况可以到达O(n),这是在存在排好序的情况下,而且他是根据是否有逆序来判断,虽然有时候
//可以达到一定的效果,但是还有缺点,例如如果有一段序列,排好序的是已经分好几段了,那么根据上面
//这个算法将没有那么高效了,我们知道,若最后一部分已经排好序了,算法还是会进行比对的,
//如果现在我们希望算法能做到这样,就是排好序的我们不比对,只针对乱序的进行排序
//这样就可以很高效的进行排序了,那么这个思想如何实现呢?
//思想原理是可以通过记录最后一次发生交换的地方,这样下一次就可以直接改变待排序的长度
//也就是说,从后向前不断缩减待排序的内容,如下
int bubblesort_pro_pro(int *A, int n)
{
	int last = 0;//假设逆序对最先从最左端开始的
	int i = 0;
	while (++i <n)
	{		
			if (A[i-1] > A[i])
			{
				swap(A[i - 1], A[i]);				
				last = i;
			}			
			cout << "last = " << last << " " << endl;
	}
	return last;
}
void bubble_P(int *A, int n)
{
	while (0 < (n = bubblesort_pro_pro(A, n)));
}
//上面的算法可以很好的解决了前面提的要求,同时复杂度达到了O(n)的数量级,以上就是冒泡排序的各版本了,至于其他的版本,这些都差不多
//只是用处不引用,思想是一样的,大家需要深入理解其精髓。


//下面引入归并排序算法,归并排序和冒泡排序的不同之处在,有序序列的情况如何,
//其复杂的都是nlogn的复杂度;
void merge(int *ele, int lo, int mid, int hi)
{
	int *A = ele + lo;
	int lb = mid - lo;
	int *B = new int[lb];
	for (int i = 0; i < lb; B[i] = A[i++]);
	int lc = hi - mid;
	int *C = ele + mid;
	for (int i = 0, j = 0, k = 0; j < lb || k < lc;)
	{
		if (j < lb && (lc <= k || B[j] <= C[k])) A[i++] = B[j++];
		if (k < lc && (lb <= j || C[k] < B[j])) A[i++] = C[k++];
	}
	delete[] B;
}

//精简版版归并排序算法,精简其实就体现在两个if条件语句中
void merge_sim(int *arr, int lo, int mid, int hi)
{
	//if (hi - lo < 2) return;
	int *A = arr + lo;
	int lb = mid - lo;
	int *B = new int[lb];
	for (int i = 0; i < lb; B[i] = A[i++]);
	int *C = arr + mid;
	int lc = hi - mid;
	for (int 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 && C[k] < B[j]) A[i++] = C[k++];
		//if (k < lc && (j >= lb || C[k] < B[j])) A[i++] = C[k++];
		if (lc <= k || B[j] <= C[k]) A[i++] = B[j++];
	}
	delete[] B;

}

void mergesort(int*A, int lo,int hi)
{
	if (hi - lo < 2) return;
	int mid = (lo + hi) >> 1;
	
	mergesort(A, lo, mid);
	mergesort(A, mid, hi);
	merge_sim(A, lo, mid, hi);

}




int main()
{
	//int arr[] = { 2,1,3,0,5,4,8,7,6,9 };
	//int arr[10] = {3,4,5,6,7,8,9,10,11,12 };
	int arr[10] = { 3,5,4,6,8,7,9,11,10,12 };
	for (int i = 0; i < (int) size(arr); i++)
		cout << arr[i] << " ";
	cout << endl;
	//cout << disordered(arr, 10) << endl;
	//uniquify(arr,10);
	//for (int i = 0; i < size(arr); i++)
	//	cout << arr[i] << " ";
	//cout << Search(arr,100, 10) << endl;
	//Fib fib(0);
	//for (int i = 0; i < 40; i++)
	//	cout << fib.next() << " ";
	//cout << endl;


	//排序
	//bubblesort(arr, size(arr));
	//bubble_P(arr, 10);
	mergesort(arr, 0, 10);

	for (int i = 0; i <(int) size(arr); i++)
		cout << arr[i] << " ";
	cout << endl;
	//int(size) = int(size*);

	return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_42398658/article/details/87979368